Update: Js 4 Log.html 95% --next move the loglevel to settingserver
This commit is contained in:
@@ -5,4 +5,5 @@ esbuild ./frontend/js/main.js \
|
|||||||
--outdir=./web/static/js \
|
--outdir=./web/static/js \
|
||||||
--splitting \
|
--splitting \
|
||||||
--format=esm \
|
--format=esm \
|
||||||
|
--loader:.css=css \
|
||||||
--watch=forever
|
--watch=forever
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
.flatpickr-calendar {
|
.flatpickr-calendar {
|
||||||
/* --- 主题样式 --- */
|
/* --- 主题样式 --- */
|
||||||
@apply bg-background text-foreground rounded-lg shadow-lg border border-border border-zinc-500/30 w-auto font-sans;
|
@apply bg-background text-foreground rounded-lg shadow-lg border border-zinc-500/30 w-auto font-sans;
|
||||||
animation: var(--animation-panel-in);
|
animation: var(--animation-panel-in);
|
||||||
width: 200px;
|
width: 200px;
|
||||||
/* --- 核心结构样式 --- */
|
/* --- 核心结构样式 --- */
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
.flatpickr-current-month .cur-month { @apply font-semibold; }
|
.flatpickr-current-month .cur-month { @apply font-semibold; }
|
||||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||||
|
|
||||||
@apply w-[5.5rem] font-semibold bg-transparent border-0 p-0 text-sm text-foreground text-right;
|
@apply w-22 font-semibold bg-transparent border-0 p-0 text-sm text-foreground text-right;
|
||||||
|
|
||||||
@apply appearance-none focus:outline-none focus:ring-0;
|
@apply appearance-none focus:outline-none focus:ring-0;
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
focus-visible:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[(var(--ring))]
|
focus-visible:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[(var(--ring))]
|
||||||
disabled:pointer-events-none disabled:opacity-50
|
disabled:pointer-events-none disabled:opacity-50
|
||||||
hover:text-accent-foreground
|
hover:text-accent-foreground
|
||||||
h-7 w-7 flex-shrink-0;
|
h-7 w-7 shrink-0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.flatpickr-prev-month svg,
|
.flatpickr-prev-month svg,
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.flatpickr-day {
|
.flatpickr-day {
|
||||||
@apply w-4 h-6.5 flex items-center justify-center rounded-full border-0 text-foreground transition-colors flex-shrink-0; /* <--- 从 w-9 h-9 缩小 */
|
@apply w-4 h-6.5 flex items-center justify-center rounded-full border-0 text-foreground transition-colors shrink-0; /* <--- 从 w-9 h-9 缩小 */
|
||||||
flex-basis: 14.2857%;
|
flex-basis: 14.2857%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -363,8 +363,8 @@
|
|||||||
@apply w-[1.2rem] text-center;
|
@apply w-[1.2rem] text-center;
|
||||||
@apply transition-all duration-300 ease-in-out;
|
@apply transition-all duration-300 ease-in-out;
|
||||||
/* 悬停和激活状态 */
|
/* 悬停和激活状态 */
|
||||||
@apply group-hover:text-[#60a5fa] group-hover:[filter:drop-shadow(0_0_5px_rgba(59,130,246,0.5))];
|
@apply group-hover:text-[#60a5fa] group-hover:filter-[drop-shadow(0_0_5px_rgba(59,130,246,0.5))];
|
||||||
@apply group-data-[active='true']:text-[#60a5fa] group-data-[active='true']:[filter:drop-shadow(0_0_5px_rgba(59,130,246,0.7))];
|
@apply group-data-[active='true']:text-[#60a5fa] group-data-[active='true']:filter-[drop-shadow(0_0_5px_rgba(59,130,246,0.7))];
|
||||||
}
|
}
|
||||||
/* 4. 指示器 */
|
/* 4. 指示器 */
|
||||||
.nav-indicator {
|
.nav-indicator {
|
||||||
@@ -392,13 +392,13 @@
|
|||||||
@apply flex items-start p-3 w-full rounded-lg shadow-lg ring-1 ring-black/5 dark:ring-white/10 bg-white/80 dark:bg-zinc-800/80 backdrop-blur-md pointer-events-auto;
|
@apply flex items-start p-3 w-full rounded-lg shadow-lg ring-1 ring-black/5 dark:ring-white/10 bg-white/80 dark:bg-zinc-800/80 backdrop-blur-md pointer-events-auto;
|
||||||
}
|
}
|
||||||
.toast-icon {
|
.toast-icon {
|
||||||
@apply flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-white mr-3;
|
@apply shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-white mr-3;
|
||||||
}
|
}
|
||||||
.toast-icon-loading {@apply bg-blue-500;}
|
.toast-icon-loading {@apply bg-blue-500;}
|
||||||
.toast-icon-success {@apply bg-green-500;}
|
.toast-icon-success {@apply bg-green-500;}
|
||||||
.toast-icon-error {@apply bg-red-500;}
|
.toast-icon-error {@apply bg-red-500;}
|
||||||
.toast-content {
|
.toast-content {
|
||||||
@apply flex-grow;
|
@apply grow
|
||||||
}
|
}
|
||||||
.toast-title {
|
.toast-title {
|
||||||
@apply font-semibold text-sm text-zinc-800 dark:text-zinc-100;
|
@apply font-semibold text-sm text-zinc-800 dark:text-zinc-100;
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
|
|
||||||
/* --- 任务项主内容区 (左栏) --- */
|
/* --- 任务项主内容区 (左栏) --- */
|
||||||
.task-item-main {
|
.task-item-main {
|
||||||
@apply flex items-center justify-between flex-grow gap-1; /* flex-grow 使其占据所有可用空间 */
|
@apply flex items-center justify-between grow gap-1; /* flex-grow 使其占据所有可用空间 */
|
||||||
}
|
}
|
||||||
/* 2. 任务项头部: 包含标题和时间戳 */
|
/* 2. 任务项头部: 包含标题和时间戳 */
|
||||||
.task-item-header {
|
.task-item-header {
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
}
|
}
|
||||||
.task-item-timestamp {
|
.task-item-timestamp {
|
||||||
/* 融合了您原有的字体样式 */
|
/* 融合了您原有的字体样式 */
|
||||||
@apply text-xs self-start pt-1.5 pl-2 text-zinc-400 dark:text-zinc-500 flex-shrink-0;
|
@apply text-xs self-start pt-1.5 pl-2 text-zinc-400 dark:text-zinc-500 shrink-0
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. [新增] 阶段动画的核心容器 */
|
/* 3. [新增] 阶段动画的核心容器 */
|
||||||
@@ -447,13 +447,13 @@
|
|||||||
@apply flex items-center gap-2 p-1.5 rounded-md transition-all duration-300 ease-in-out relative;
|
@apply flex items-center gap-2 p-1.5 rounded-md transition-all duration-300 ease-in-out relative;
|
||||||
}
|
}
|
||||||
.task-stage-icon {
|
.task-stage-icon {
|
||||||
@apply w-4 h-4 relative flex-shrink-0 text-zinc-400;
|
@apply w-4 h-4 relative shrink-0 text-zinc-400;
|
||||||
}
|
}
|
||||||
.task-stage-icon i {
|
.task-stage-icon i {
|
||||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-200;
|
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-200;
|
||||||
}
|
}
|
||||||
.task-stage-content {
|
.task-stage-content {
|
||||||
@apply flex-grow flex justify-between items-baseline text-xs;
|
@apply grow justify-between items-baseline text-xs;
|
||||||
}
|
}
|
||||||
.task-stage-name {
|
.task-stage-name {
|
||||||
@apply text-zinc-600 dark:text-zinc-400;
|
@apply text-zinc-600 dark:text-zinc-400;
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
}
|
}
|
||||||
/* --- 4. 折叠/展开的雪佛兰图标 --- */
|
/* --- 4. 折叠/展开的雪佛兰图标 --- */
|
||||||
.task-toggle-icon {
|
.task-toggle-icon {
|
||||||
@apply transition-transform duration-300 ease-in-out text-zinc-400 flex-shrink-0 ml-2;
|
@apply transition-transform duration-300 ease-in-out text-zinc-400 shrink-0 ml-2;
|
||||||
}
|
}
|
||||||
/* --- 5. 展开状态下的图标旋转 --- */
|
/* --- 5. 展开状态下的图标旋转 --- */
|
||||||
/*
|
/*
|
||||||
@@ -619,7 +619,7 @@
|
|||||||
* 2. 【新增】移动端首屏的 "当前分组" 选择器样式
|
* 2. 【新增】移动端首屏的 "当前分组" 选择器样式
|
||||||
*/
|
*/
|
||||||
.mobile-group-selector {
|
.mobile-group-selector {
|
||||||
@apply flex-grow flex items-center justify-between p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg;
|
@apply grow flex items-center justify-between p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg;
|
||||||
}
|
}
|
||||||
/* 移动端群组下拉列表样式 */
|
/* 移动端群组下拉列表样式 */
|
||||||
.mobile-group-menu-active {
|
.mobile-group-menu-active {
|
||||||
@@ -770,7 +770,7 @@
|
|||||||
/* Tag Input Component */
|
/* Tag Input Component */
|
||||||
.tag-input-container {
|
.tag-input-container {
|
||||||
|
|
||||||
@apply flex flex-wrap items-center gap-2 mt-1 w-full rounded-md bg-white dark:bg-zinc-700 border border-zinc-300 dark:border-zinc-600 p-2 min-h-[40px] focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500;
|
@apply flex flex-wrap items-center gap-2 mt-1 w-full rounded-md bg-white dark:bg-zinc-700 border border-zinc-300 dark:border-zinc-600 p-2 min-h-10 focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500;
|
||||||
}
|
}
|
||||||
.tag-item {
|
.tag-item {
|
||||||
@apply flex items-center gap-x-1.5 bg-blue-100 dark:bg-blue-500/20 text-blue-800 dark:text-blue-200 text-sm font-medium rounded-full px-2.5 py-0.5;
|
@apply flex items-center gap-x-1.5 bg-blue-100 dark:bg-blue-500/20 text-blue-800 dark:text-blue-200 text-sm font-medium rounded-full px-2.5 py-0.5;
|
||||||
@@ -780,7 +780,7 @@
|
|||||||
}
|
}
|
||||||
.tag-input-new {
|
.tag-input-new {
|
||||||
/* 使其在容器内垂直居中,感觉更好 */
|
/* 使其在容器内垂直居中,感觉更好 */
|
||||||
@apply flex-grow bg-transparent focus:outline-none text-sm self-center;
|
@apply grow bg-transparent focus:outline-none text-sm self-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 为复制按钮提供基础样式 */
|
/* 为复制按钮提供基础样式 */
|
||||||
@@ -851,7 +851,7 @@
|
|||||||
}
|
}
|
||||||
/* .tooltip-text is now dynamically generated by JS */
|
/* .tooltip-text is now dynamically generated by JS */
|
||||||
.global-tooltip {
|
.global-tooltip {
|
||||||
@apply fixed z-[9999] w-max max-w-xs whitespace-normal rounded-lg bg-zinc-800 px-3 py-2 text-sm font-medium text-white shadow-lg transition-opacity duration-200;
|
@apply fixed z-9999 w-max max-w-xs whitespace-normal rounded-lg bg-zinc-800 px-3 py-2 text-sm font-medium text-white shadow-lg transition-opacity duration-200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Filename: frontend/js/main.js
|
// Filename: frontend/js/main.js
|
||||||
|
import Swal from './vendor/sweetalert2.esm.js';
|
||||||
|
import './vendor/sweetalert2.min.css';
|
||||||
|
import anime from './vendor/anime.esm.js';
|
||||||
// === 1. 导入通用组件 (这些是所有页面都可能用到的,保持静态导入) ===
|
// === 1. 导入通用组件 (这些是所有页面都可能用到的,保持静态导入) ===
|
||||||
import SlidingTabs from './components/slidingTabs.js';
|
import SlidingTabs from './components/slidingTabs.js';
|
||||||
import CustomSelect from './components/customSelect.js';
|
import CustomSelect from './components/customSelect.js';
|
||||||
@@ -48,3 +50,5 @@ window.modalManager = modalManager;
|
|||||||
window.taskCenterManager = taskCenterManager;
|
window.taskCenterManager = taskCenterManager;
|
||||||
window.toastManager = toastManager;
|
window.toastManager = toastManager;
|
||||||
window.uiPatterns = uiPatterns;
|
window.uiPatterns = uiPatterns;
|
||||||
|
window.Swal = Swal;
|
||||||
|
window.anime = anime;
|
||||||
@@ -427,7 +427,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">验证任务出错: ${maskedKey}</p>
|
<p class="task-item-title">验证任务出错: ${maskedKey}</p>
|
||||||
<p class="task-item-status text-red-500 truncate" title="${safeError}">${safeError}</p>
|
<p class="task-item-status text-red-500 truncate" title="${safeError}">${safeError}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -443,7 +443,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary"><i class="${iconClass}"></i></div>
|
<div class="task-item-icon-summary"><i class="${iconClass}"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">${title}: ${maskedKey}</p>
|
<p class="task-item-title">${title}: ${maskedKey}</p>
|
||||||
<p class="task-item-status truncate" title="${safeMessage}">${safeMessage}</p>
|
<p class="task-item-status truncate" title="${safeMessage}">${safeMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -455,7 +455,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main gap-3">
|
<div class="task-item-main gap-3">
|
||||||
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">正在验证: ${maskedKey}</p>
|
<p class="task-item-title">正在验证: ${maskedKey}</p>
|
||||||
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -495,7 +495,7 @@ class ApiKeyList {
|
|||||||
data-mapping-id="${mappingId}">
|
data-mapping-id="${mappingId}">
|
||||||
<input type="checkbox" class="api-key-checkbox h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 shrink-0">
|
<input type="checkbox" class="api-key-checkbox h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 shrink-0">
|
||||||
<span data-status-indicator class="w-2 h-2 rounded-full shrink-0"></span>
|
<span data-status-indicator class="w-2 h-2 rounded-full shrink-0"></span>
|
||||||
<div class="flex-grow min-w-0">
|
<div class="grow min-w-0">
|
||||||
<p class="font-mono text-xs font-semibold truncate">${maskedKey}</p>
|
<p class="font-mono text-xs font-semibold truncate">${maskedKey}</p>
|
||||||
<p class="text-xs text-zinc-400 mt-1">失败: ${errorCount} 次</p>
|
<p class="text-xs text-zinc-400 mt-1">失败: ${errorCount} 次</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -854,7 +854,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main gap-3">
|
<div class="task-item-main gap-3">
|
||||||
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">批量验证 ${data.total} 个Key</p>
|
<p class="task-item-title">批量验证 ${data.total} 个Key</p>
|
||||||
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -867,7 +867,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">批量验证任务出错</p>
|
<p class="task-item-title">批量验证任务出错</p>
|
||||||
<p class="task-item-status text-red-500 truncate" title="${data.error}">${data.error}</p>
|
<p class="task-item-status text-red-500 truncate" title="${data.error}">${data.error}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -893,7 +893,7 @@ class ApiKeyList {
|
|||||||
return `
|
return `
|
||||||
<div class="flex items-start text-xs">
|
<div class="flex items-start text-xs">
|
||||||
<i class="fas fa-check-circle text-green-500 mt-0.5 mr-2"></i>
|
<i class="fas fa-check-circle text-green-500 mt-0.5 mr-2"></i>
|
||||||
<div class="flex-grow">
|
<div class="grow">
|
||||||
<p class="font-mono">${maskedKey}</p>
|
<p class="font-mono">${maskedKey}</p>
|
||||||
<p class="text-zinc-400">${safeMessage}</p>
|
<p class="text-zinc-400">${safeMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -902,7 +902,7 @@ class ApiKeyList {
|
|||||||
return `
|
return `
|
||||||
<div class="flex items-start text-xs">
|
<div class="flex items-start text-xs">
|
||||||
<i class="fas fa-times-circle text-red-500 mt-0.5 mr-2"></i>
|
<i class="fas fa-times-circle text-red-500 mt-0.5 mr-2"></i>
|
||||||
<div class="flex-grow">
|
<div class="grow">
|
||||||
<p class="font-mono">${maskedKey}</p>
|
<p class="font-mono">${maskedKey}</p>
|
||||||
<p class="text-zinc-400">${safeMessage}</p>
|
<p class="text-zinc-400">${safeMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -913,7 +913,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary"><i class="${overallIconClass}"></i></div>
|
<div class="task-item-icon-summary"><i class="${overallIconClass}"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<div class="flex justify-between items-center cursor-pointer" data-task-toggle>
|
<div class="flex justify-between items-center cursor-pointer" data-task-toggle>
|
||||||
<p class="task-item-title">${summaryTitle}</p>
|
<p class="task-item-title">${summaryTitle}</p>
|
||||||
<i class="fas fa-chevron-down task-toggle-icon"></i>
|
<i class="fas fa-chevron-down task-toggle-icon"></i>
|
||||||
@@ -1248,7 +1248,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main gap-3">
|
<div class="task-item-main gap-3">
|
||||||
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
<div class="task-item-icon task-item-icon-running"><i class="fas fa-spinner animate-spin"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">${title}</p>
|
<p class="task-item-title">${title}</p>
|
||||||
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
<p class="task-item-status">运行中... (${data.processed}/${data.total})</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1258,7 +1258,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
<div class="task-item-icon-summary text-red-500"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">${title}任务出错</p>
|
<p class="task-item-title">${title}任务出错</p>
|
||||||
<p class="task-item-status text-red-500 truncate" title="${safeError}">${safeError}</p>
|
<p class="task-item-status text-red-500 truncate" title="${safeError}">${safeError}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1295,7 +1295,7 @@ class ApiKeyList {
|
|||||||
contentHtml = `
|
contentHtml = `
|
||||||
<div class="task-item-main">
|
<div class="task-item-main">
|
||||||
<div class="task-item-icon-summary"><i class="${iconClass}"></i></div>
|
<div class="task-item-icon-summary"><i class="${iconClass}"></i></div>
|
||||||
<div class="task-item-content flex-grow">
|
<div class="task-item-content grow">
|
||||||
<p class="task-item-title">${title}</p>
|
<p class="task-item-title">${title}</p>
|
||||||
<p class="task-item-status truncate" title="${safeSummary}">${safeSummary}</p>
|
<p class="task-item-status truncate" title="${safeSummary}">${safeSummary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Filename: frontend/js/pages/logs/index.js
|
||||||
import { apiFetchJson } from '../../services/api.js';
|
import { apiFetchJson } from '../../services/api.js';
|
||||||
import LogList from './logList.js';
|
import LogList from './logList.js';
|
||||||
import CustomSelectV2 from '../../components/customSelectV2.js';
|
import CustomSelectV2 from '../../components/customSelectV2.js';
|
||||||
@@ -145,6 +146,7 @@ class LogsPage {
|
|||||||
|
|
||||||
switchToView(viewName) {
|
switchToView(viewName) {
|
||||||
if (this.state.currentView === viewName && this.elements.contentContainer.innerHTML !== '') return;
|
if (this.state.currentView === viewName && this.elements.contentContainer.innerHTML !== '') return;
|
||||||
|
|
||||||
if (this.systemLogTerminal) {
|
if (this.systemLogTerminal) {
|
||||||
this.systemLogTerminal.disconnect();
|
this.systemLogTerminal.disconnect();
|
||||||
this.systemLogTerminal = null;
|
this.systemLogTerminal = null;
|
||||||
@@ -153,25 +155,22 @@ class LogsPage {
|
|||||||
this.fp.destroy();
|
this.fp.destroy();
|
||||||
this.fp = null;
|
this.fp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.themeObserver) {
|
if (this.themeObserver) {
|
||||||
this.themeObserver.disconnect();
|
this.themeObserver.disconnect();
|
||||||
this.themeObserver = null;
|
this.themeObserver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.currentView = viewName;
|
this.state.currentView = viewName;
|
||||||
this.elements.contentContainer.innerHTML = '';
|
this.elements.contentContainer.innerHTML = '';
|
||||||
if (viewName === 'error') {
|
const isErrorView = viewName === 'error';
|
||||||
this.elements.errorFilters.classList.remove('hidden');
|
this.elements.errorFilters.style.display = isErrorView ? 'flex' : 'none';
|
||||||
this.elements.systemControls.classList.add('hidden');
|
this.elements.systemControls.style.display = isErrorView ? 'none' : 'flex';
|
||||||
|
if (isErrorView) {
|
||||||
const template = this.elements.errorTemplate.content.cloneNode(true);
|
const template = this.elements.errorTemplate.content.cloneNode(true);
|
||||||
this.elements.contentContainer.appendChild(template);
|
this.elements.contentContainer.appendChild(template);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this._initErrorLogView();
|
this._initErrorLogView();
|
||||||
});
|
});
|
||||||
} else if (viewName === 'system') {
|
} else if (viewName === 'system') {
|
||||||
this.elements.errorFilters.classList.add('hidden');
|
|
||||||
this.elements.systemControls.classList.remove('hidden');
|
|
||||||
const template = this.elements.systemTemplate.content.cloneNode(true);
|
const template = this.elements.systemTemplate.content.cloneNode(true);
|
||||||
this.elements.contentContainer.appendChild(template);
|
this.elements.contentContainer.appendChild(template);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -328,7 +327,7 @@ class LogsPage {
|
|||||||
template.className = 'custom-select-panel-template';
|
template.className = 'custom-select-panel-template';
|
||||||
|
|
||||||
template.innerHTML = `
|
template.innerHTML = `
|
||||||
<div class="custom-select-panel absolute z-[1000] my-2 w-24 origin-top-right rounded-md bg-popover dark:bg-zinc-900 shadow-lg ring-1 ring-zinc-500/30 ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" tabindex="-1">
|
<div class="custom-select-panel absolute z-1000 my-2 w-24 origin-top-right rounded-md bg-popover dark:bg-zinc-900 shadow-lg ring-1 ring-zinc-500/30 ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" tabindex="-1">
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
nativeMonthSelect.classList.add('hidden');
|
nativeMonthSelect.classList.add('hidden');
|
||||||
@@ -359,8 +358,14 @@ class LogsPage {
|
|||||||
this.elements.selectAllCheckbox.addEventListener('change', (event) => this.handleSelectAllChange(event));
|
this.elements.selectAllCheckbox.addEventListener('change', (event) => this.handleSelectAllChange(event));
|
||||||
}
|
}
|
||||||
if (this.elements.tableBody) {
|
if (this.elements.tableBody) {
|
||||||
this.elements.tableBody.addEventListener('change', (event) => {
|
this.elements.tableBody.addEventListener('click', (event) => {
|
||||||
if (event.target.type === 'checkbox') this.handleSelectionChange(event.target);
|
const checkbox = event.target.closest('input[type="checkbox"]');
|
||||||
|
const actionButton = event.target.closest('button[data-action]');
|
||||||
|
if (checkbox) {
|
||||||
|
this.handleSelectionChange(checkbox);
|
||||||
|
} else if (actionButton) {
|
||||||
|
this._handleLogRowAction(actionButton);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.elements.searchInput) {
|
if (this.elements.searchInput) {
|
||||||
@@ -465,6 +470,118 @@ class LogsPage {
|
|||||||
deleteSelectedBtn.disabled = !hasSelection;
|
deleteSelectedBtn.disabled = !hasSelection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _handleLogRowAction(button) {
|
||||||
|
const action = button.dataset.action;
|
||||||
|
const row = button.closest('.table-row');
|
||||||
|
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||||
|
if (!row) return;
|
||||||
|
const logId = parseInt(row.dataset.logId, 10);
|
||||||
|
const log = this.state.logs.find(l => l.ID === logId);
|
||||||
|
if (!log) {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'error', title: '找不到日志数据', showConfirmButton: false, timer: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case 'view-log-details': {
|
||||||
|
const detailsHtml = `
|
||||||
|
<div class="space-y-3 text-left text-sm p-2">
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">状态码</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.StatusCode || 'N/A'}</p></div>
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">状态</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.Status || 'N/A'}</p></div>
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">模型</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.ModelName || 'N/A'}</p></div>
|
||||||
|
<div class="border-t border-zinc-200 dark:border-zinc-700 my-2"></div>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-zinc-500 mb-1">错误消息</p>
|
||||||
|
<div class="max-h-40 overflow-y-auto bg-zinc-100 dark:bg-zinc-800 p-2 rounded-md text-zinc-700 dark:text-zinc-300 wrap-break-word text-xs">
|
||||||
|
${log.ErrorMessage ? log.ErrorMessage.replace(/\n/g, '<br>') : '无错误消息。'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
Swal.fire({
|
||||||
|
target: '#main-content-wrapper',
|
||||||
|
width: '32rem',
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
|
customClass: {
|
||||||
|
popup: `swal2-custom-style rounded-xl ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}`,
|
||||||
|
title: 'text-lg font-bold',
|
||||||
|
htmlContainer: 'm-0 text-left',
|
||||||
|
},
|
||||||
|
title: '日志详情',
|
||||||
|
html: detailsHtml,
|
||||||
|
showCloseButton: false,
|
||||||
|
showConfirmButton: false,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'copy-api-key': {
|
||||||
|
const key = dataStore.keys.get(log.KeyID);
|
||||||
|
if (key && key.APIKey) {
|
||||||
|
navigator.clipboard.writeText(key.APIKey).then(() => {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}` }, icon: 'success', title: 'API Key 已复制', showConfirmButton: false, timer: 1500 });
|
||||||
|
}).catch(err => {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'error', title: '复制失败', text: err.message, showConfirmButton: false, timer: 2000 });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'warning', title: '未找到完整的API Key', showConfirmButton: false, timer: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(key.APIKey).then(() => {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'API Key 已复制', showConfirmButton: false, timer: 1500 });
|
||||||
|
}).catch(err => {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'error', title: '复制失败', text: err.message, showConfirmButton: false, timer: 2000 });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 如果不可用,则提供明确的错误提示
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: '复制失败',
|
||||||
|
text: '此功能需要安全连接 (HTTPS) 或在 localhost 环境下使用。',
|
||||||
|
target: '#main-content-wrapper',
|
||||||
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}` },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete-log': {
|
||||||
|
Swal.fire({
|
||||||
|
width: '20rem',
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}` },
|
||||||
|
title: '确认删除',
|
||||||
|
text: `您确定要删除这条日志吗?此操作不可撤销。`,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: '确认删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
reverseButtons: false,
|
||||||
|
confirmButtonColor: '#ef4444',
|
||||||
|
cancelButtonColor: '#6b7280',
|
||||||
|
focusCancel: true,
|
||||||
|
target: '#main-content-wrapper',
|
||||||
|
}).then(async (result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
try {
|
||||||
|
const url = `/admin/logs?ids=${logId}`;
|
||||||
|
const { success, message } = await apiFetchJson(url, { method: 'DELETE' });
|
||||||
|
if (success) {
|
||||||
|
Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: '删除成功', showConfirmButton: false, timer: 2000, timerProgressBar: true });
|
||||||
|
this.loadAndRenderLogs();
|
||||||
|
} else {
|
||||||
|
throw new Error(message || '删除失败,请稍后重试。');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({ icon: 'error', title: '操作失败', text: error.message, target: '#main-content-wrapper' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changePageSize(newSize) {
|
changePageSize(newSize) {
|
||||||
this.state.filters.page_size = newSize;
|
this.state.filters.page_size = newSize;
|
||||||
this.state.filters.page = 1;
|
this.state.filters.page = 1;
|
||||||
@@ -520,23 +637,33 @@ class LogsPage {
|
|||||||
finalParams[key] = filters[key];
|
finalParams[key] = filters[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const translatedErrorCodes = new Set();
|
// --- [MODIFIED] START: Combine all error-related filters into a single parameter for OR logic ---
|
||||||
const translatedStatusCodes = new Set(filters.status_codes);
|
const allErrorCodes = new Set();
|
||||||
|
const allStatusCodes = new Set(filters.status_codes);
|
||||||
if (filters.error_types.size > 0) {
|
if (filters.error_types.size > 0) {
|
||||||
filters.error_types.forEach(type => {
|
filters.error_types.forEach(type => {
|
||||||
for (const [code, obj] of Object.entries(STATUS_CODE_MAP)) {
|
// Find matching static error codes (e.g., 'API_KEY_INVALID')
|
||||||
if (obj.type === type) translatedStatusCodes.add(code);
|
|
||||||
}
|
|
||||||
for (const [code, obj] of Object.entries(STATIC_ERROR_MAP)) {
|
for (const [code, obj] of Object.entries(STATIC_ERROR_MAP)) {
|
||||||
if (obj.type === type) translatedErrorCodes.add(code);
|
if (obj.type === type) {
|
||||||
|
allErrorCodes.add(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find matching status codes (e.g., 400, 401)
|
||||||
|
for (const [code, obj] of Object.entries(STATUS_CODE_MAP)) {
|
||||||
|
if (obj.type === type) {
|
||||||
|
allStatusCodes.add(code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass the combined codes to the backend. The backend will handle the OR logic.
|
||||||
|
if (allErrorCodes.size > 0) finalParams.error_codes = [...allErrorCodes].join(',');
|
||||||
|
if (allStatusCodes.size > 0) finalParams.status_codes = [...allStatusCodes].join(',');
|
||||||
|
// --- [MODIFIED] END ---
|
||||||
|
|
||||||
if (filters.key_ids.size > 0) finalParams.key_ids = [...filters.key_ids].join(',');
|
if (filters.key_ids.size > 0) finalParams.key_ids = [...filters.key_ids].join(',');
|
||||||
if (filters.group_ids.size > 0) finalParams.group_ids = [...filters.group_ids].join(',');
|
if (filters.group_ids.size > 0) finalParams.group_ids = [...filters.group_ids].join(',');
|
||||||
if (translatedErrorCodes.size > 0) finalParams.error_codes = [...translatedErrorCodes].join(',');
|
|
||||||
if (translatedStatusCodes.size > 0) finalParams.status_codes = [...translatedStatusCodes].join(',');
|
|
||||||
|
|
||||||
Object.keys(finalParams).forEach(key => {
|
Object.keys(finalParams).forEach(key => {
|
||||||
if (finalParams[key] === '' || finalParams[key] === null || finalParams[key] === undefined) {
|
if (finalParams[key] === '' || finalParams[key] === null || finalParams[key] === undefined) {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class LogList {
|
|||||||
statusCodeHtml: `<span class="inline-flex items-center rounded-md bg-green-500/10 px-2 py-1 text-xs font-medium text-green-600">成功</span>`
|
statusCodeHtml: `<span class="inline-flex items-center rounded-md bg-green-500/10 px-2 py-1 text-xs font-medium text-green-600">成功</span>`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 2. [新增] 特殊场景优先判断 (结合ErrorCode和ErrorMessage)
|
// 2. 特殊场景优先判断 (结合ErrorCode和ErrorMessage)
|
||||||
const codeMatch = log.ErrorCode ? log.ErrorCode.match(errorCodeRegex) : null;
|
const codeMatch = log.ErrorCode ? log.ErrorCode.match(errorCodeRegex) : null;
|
||||||
if (codeMatch && codeMatch[1] && log.ErrorMessage) {
|
if (codeMatch && codeMatch[1] && log.ErrorMessage) {
|
||||||
const code = parseInt(codeMatch[1], 10);
|
const code = parseInt(codeMatch[1], 10);
|
||||||
@@ -146,7 +146,7 @@ class LogList {
|
|||||||
|
|
||||||
const checkedAttr = isChecked ? 'checked' : '';
|
const checkedAttr = isChecked ? 'checked' : '';
|
||||||
return `
|
return `
|
||||||
<tr class="table-row" data-log-id="${log.ID}" ${errorMessageAttr}>
|
<tr class="table-row group even:bg-zinc-200/30 dark:even:bg-black/10" data-log-id="${log.ID}" ${errorMessageAttr}>
|
||||||
<td class="table-cell">
|
<td class="table-cell">
|
||||||
<input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500" ${checkedAttr}>
|
<input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500" ${checkedAttr}>
|
||||||
</td>
|
</td>
|
||||||
@@ -157,10 +157,26 @@ class LogList {
|
|||||||
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
|
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
|
||||||
<td class="table-cell">${modelNameFormatted}</td>
|
<td class="table-cell">${modelNameFormatted}</td>
|
||||||
<td class="table-cell text-muted-foreground text-xs">${requestTime}</td>
|
<td class="table-cell text-muted-foreground text-xs">${requestTime}</td>
|
||||||
<td class="table-cell">
|
<td class="table-cell relative">
|
||||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="查看详情">
|
<!-- [MODIFIED] - 2. 替换原有按钮为悬浮操作菜单 -->
|
||||||
<i class="fas fa-ellipsis-h h-4 w-4"></i>
|
<div class="flex items-center justify-center">
|
||||||
</button>
|
<!-- 默认显示的图标 -->
|
||||||
|
<span class="text-zinc-400 group-hover:opacity-0 transition-opacity">
|
||||||
|
<i class="fas fa-ellipsis-h h-4 w-4"></i>
|
||||||
|
</span>
|
||||||
|
<!-- 悬浮时显示的操作按钮 -->
|
||||||
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center bg-zinc-100 dark:bg-zinc-700 rounded-full shadow-md opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10">
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-blue-500" data-action="view-log-details" title="查看详情">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-green-500" data-action="copy-api-key" title="复制APIKey">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-red-500" data-action="delete-log" title="删除日志">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|||||||
1311
frontend/js/vendor/anime.esm.js
vendored
Normal file
1311
frontend/js/vendor/anime.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4611
frontend/js/vendor/sweetalert2.esm.js
vendored
Normal file
4611
frontend/js/vendor/sweetalert2.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/js/vendor/sweetalert2.min.css
vendored
Normal file
1
frontend/js/vendor/sweetalert2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -195,7 +195,10 @@ func (h *ProxyHandler) serveTransparentProxy(c *gin.Context, requestBody []byte,
|
|||||||
|
|
||||||
finalRecorder, finalProxyErr, isSuccess = recorder, attemptErr, attemptSuccess
|
finalRecorder, finalProxyErr, isSuccess = recorder, attemptErr, attemptSuccess
|
||||||
// ✅ 修正 isSuccess
|
// ✅ 修正 isSuccess
|
||||||
if finalProxyErr != nil || (finalRecorder != nil && finalRecorder.Code >= 400) {
|
if attemptSuccess && (finalRecorder == nil || finalRecorder.Code < 400) {
|
||||||
|
isSuccess = true
|
||||||
|
finalProxyErr = nil // ✅ 清除错误
|
||||||
|
} else {
|
||||||
isSuccess = false
|
isSuccess = false
|
||||||
}
|
}
|
||||||
lastUsedResources = resources
|
lastUsedResources = resources
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Filename: internal/service/log_service.go
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -73,11 +74,13 @@ func (s *LogService) GetLogs(ctx context.Context, params LogQueryParams) ([]mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LogService) applyFilters(query *gorm.DB, params LogQueryParams) *gorm.DB {
|
func (s *LogService) applyFilters(query *gorm.DB, params LogQueryParams) *gorm.DB {
|
||||||
if params.ModelName != "" {
|
|
||||||
query = query.Where("model_name = ?", params.ModelName)
|
|
||||||
}
|
|
||||||
if params.IsSuccess != nil {
|
if params.IsSuccess != nil {
|
||||||
query = query.Where("is_success = ?", *params.IsSuccess)
|
query = query.Where("is_success = ?", *params.IsSuccess)
|
||||||
|
} else {
|
||||||
|
query = query.Where("is_success = ?", false)
|
||||||
|
}
|
||||||
|
if params.ModelName != "" {
|
||||||
|
query = query.Where("model_name = ?", params.ModelName)
|
||||||
}
|
}
|
||||||
if params.StatusCode != nil {
|
if params.StatusCode != nil {
|
||||||
query = query.Where("status_code = ?", *params.StatusCode)
|
query = query.Where("status_code = ?", *params.StatusCode)
|
||||||
@@ -88,10 +91,16 @@ func (s *LogService) applyFilters(query *gorm.DB, params LogQueryParams) *gorm.D
|
|||||||
if len(params.GroupIDs) > 0 {
|
if len(params.GroupIDs) > 0 {
|
||||||
query = query.Where("group_id IN (?)", params.GroupIDs)
|
query = query.Where("group_id IN (?)", params.GroupIDs)
|
||||||
}
|
}
|
||||||
if len(params.ErrorCodes) > 0 {
|
hasErrorCodes := len(params.ErrorCodes) > 0
|
||||||
|
hasStatusCodes := len(params.StatusCodes) > 0
|
||||||
|
if hasErrorCodes && hasStatusCodes {
|
||||||
|
query = query.Where(
|
||||||
|
s.db.Where("error_code IN (?)", params.ErrorCodes).
|
||||||
|
Or("status_code IN (?)", params.StatusCodes),
|
||||||
|
)
|
||||||
|
} else if hasErrorCodes {
|
||||||
query = query.Where("error_code IN (?)", params.ErrorCodes)
|
query = query.Where("error_code IN (?)", params.ErrorCodes)
|
||||||
}
|
} else if hasStatusCodes {
|
||||||
if len(params.StatusCodes) > 0 {
|
|
||||||
query = query.Where("status_code IN (?)", params.StatusCodes)
|
query = query.Where("status_code IN (?)", params.StatusCodes)
|
||||||
}
|
}
|
||||||
if params.Q != "" {
|
if params.Q != "" {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -449,6 +449,9 @@
|
|||||||
.z-50 {
|
.z-50 {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
.z-1000 {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
.z-\[100\] {
|
.z-\[100\] {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
@@ -503,6 +506,9 @@
|
|||||||
.my-2 {
|
.my-2 {
|
||||||
margin-block: calc(var(--spacing) * 2);
|
margin-block: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
.mt-0 {
|
||||||
|
margin-top: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.mt-0\.5 {
|
.mt-0\.5 {
|
||||||
margin-top: calc(var(--spacing) * 0.5);
|
margin-top: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -624,6 +630,9 @@
|
|||||||
width: calc(var(--spacing) * 6);
|
width: calc(var(--spacing) * 6);
|
||||||
height: calc(var(--spacing) * 6);
|
height: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
|
.h-0 {
|
||||||
|
height: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.h-0\.5 {
|
.h-0\.5 {
|
||||||
height: calc(var(--spacing) * 0.5);
|
height: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -708,6 +717,9 @@
|
|||||||
.w-0 {
|
.w-0 {
|
||||||
width: calc(var(--spacing) * 0);
|
width: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
|
.w-1 {
|
||||||
|
width: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.w-1\/4 {
|
.w-1\/4 {
|
||||||
width: calc(1/4 * 100%);
|
width: calc(1/4 * 100%);
|
||||||
}
|
}
|
||||||
@@ -822,6 +834,9 @@
|
|||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.flex-shrink {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
.shrink-0 {
|
.shrink-0 {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -834,6 +849,9 @@
|
|||||||
.caption-bottom {
|
.caption-bottom {
|
||||||
caption-side: bottom;
|
caption-side: bottom;
|
||||||
}
|
}
|
||||||
|
.border-collapse {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
.origin-center {
|
.origin-center {
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
@@ -860,6 +878,10 @@
|
|||||||
--tw-translate-x: 100%;
|
--tw-translate-x: 100%;
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
}
|
}
|
||||||
|
.-translate-y-1 {
|
||||||
|
--tw-translate-y: calc(var(--spacing) * -1);
|
||||||
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
|
}
|
||||||
.-translate-y-1\/2 {
|
.-translate-y-1\/2 {
|
||||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
@@ -1028,6 +1050,9 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-x-1 {
|
||||||
|
column-gap: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.gap-x-1\.5 {
|
.gap-x-1\.5 {
|
||||||
column-gap: calc(var(--spacing) * 1.5);
|
column-gap: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -1180,6 +1205,9 @@
|
|||||||
.\!border-primary {
|
.\!border-primary {
|
||||||
border-color: var(--color-primary) !important;
|
border-color: var(--color-primary) !important;
|
||||||
}
|
}
|
||||||
|
.border-black {
|
||||||
|
border-color: var(--color-black);
|
||||||
|
}
|
||||||
.border-black\/10 {
|
.border-black\/10 {
|
||||||
border-color: color-mix(in srgb, #000 10%, transparent);
|
border-color: color-mix(in srgb, #000 10%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1207,6 +1235,9 @@
|
|||||||
.border-green-200 {
|
.border-green-200 {
|
||||||
border-color: var(--color-green-200);
|
border-color: var(--color-green-200);
|
||||||
}
|
}
|
||||||
|
.border-primary {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
.border-primary\/20 {
|
.border-primary\/20 {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1243,12 +1274,18 @@
|
|||||||
.border-zinc-300 {
|
.border-zinc-300 {
|
||||||
border-color: var(--color-zinc-300);
|
border-color: var(--color-zinc-300);
|
||||||
}
|
}
|
||||||
|
.border-zinc-500 {
|
||||||
|
border-color: var(--color-zinc-500);
|
||||||
|
}
|
||||||
.border-zinc-500\/30 {
|
.border-zinc-500\/30 {
|
||||||
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.border-zinc-700 {
|
||||||
|
border-color: var(--color-zinc-700);
|
||||||
|
}
|
||||||
.border-zinc-700\/50 {
|
.border-zinc-700\/50 {
|
||||||
border-color: color-mix(in srgb, oklch(37% 0.013 285.805) 50%, transparent);
|
border-color: color-mix(in srgb, oklch(37% 0.013 285.805) 50%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1324,6 +1361,9 @@
|
|||||||
.bg-gray-500 {
|
.bg-gray-500 {
|
||||||
background-color: var(--color-gray-500);
|
background-color: var(--color-gray-500);
|
||||||
}
|
}
|
||||||
|
.bg-gray-950 {
|
||||||
|
background-color: var(--color-gray-950);
|
||||||
|
}
|
||||||
.bg-gray-950\/5 {
|
.bg-gray-950\/5 {
|
||||||
background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 5%, transparent);
|
background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 5%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1538,6 +1578,10 @@
|
|||||||
--tw-gradient-position: to right in oklab;
|
--tw-gradient-position: to right in oklab;
|
||||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||||
}
|
}
|
||||||
|
.from-blue-500 {
|
||||||
|
--tw-gradient-from: var(--color-blue-500);
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
.from-blue-500\/30 {
|
.from-blue-500\/30 {
|
||||||
--tw-gradient-from: color-mix(in srgb, oklch(62.3% 0.214 259.815) 30%, transparent);
|
--tw-gradient-from: color-mix(in srgb, oklch(62.3% 0.214 259.815) 30%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1608,6 +1652,9 @@
|
|||||||
.px-8 {
|
.px-8 {
|
||||||
padding-inline: calc(var(--spacing) * 8);
|
padding-inline: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
|
.py-0 {
|
||||||
|
padding-block: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.py-0\.5 {
|
.py-0\.5 {
|
||||||
padding-block: calc(var(--spacing) * 0.5);
|
padding-block: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -1656,6 +1703,9 @@
|
|||||||
.pr-20 {
|
.pr-20 {
|
||||||
padding-right: calc(var(--spacing) * 20);
|
padding-right: calc(var(--spacing) * 20);
|
||||||
}
|
}
|
||||||
|
.pb-1 {
|
||||||
|
padding-bottom: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.pb-1\.5 {
|
.pb-1\.5 {
|
||||||
padding-bottom: calc(var(--spacing) * 1.5);
|
padding-bottom: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -1765,6 +1815,9 @@
|
|||||||
.break-words {
|
.break-words {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
.wrap-break-word {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
.break-all {
|
.break-all {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
@@ -1949,6 +2002,9 @@
|
|||||||
--tw-ordinal: ordinal;
|
--tw-ordinal: ordinal;
|
||||||
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||||
}
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
@@ -2010,6 +2066,10 @@
|
|||||||
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, oklab(from rgb(0 0 0 / 0.05) l a b / 25%));
|
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, oklab(from rgb(0 0 0 / 0.05) l a b / 25%));
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
.inset-shadow-sm {
|
||||||
|
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, rgb(0 0 0 / 0.05));
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
.ring-black {
|
.ring-black {
|
||||||
--tw-ring-color: var(--color-black);
|
--tw-ring-color: var(--color-black);
|
||||||
}
|
}
|
||||||
@@ -2034,12 +2094,19 @@
|
|||||||
.ring-input {
|
.ring-input {
|
||||||
--tw-ring-color: var(--color-input);
|
--tw-ring-color: var(--color-input);
|
||||||
}
|
}
|
||||||
|
.ring-zinc-500 {
|
||||||
|
--tw-ring-color: var(--color-zinc-500);
|
||||||
|
}
|
||||||
.ring-zinc-500\/30 {
|
.ring-zinc-500\/30 {
|
||||||
--tw-ring-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
--tw-ring-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
--tw-ring-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
--tw-ring-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.outline {
|
||||||
|
outline-style: var(--tw-outline-style);
|
||||||
|
outline-width: 1px;
|
||||||
|
}
|
||||||
.blur {
|
.blur {
|
||||||
--tw-blur: blur(8px);
|
--tw-blur: blur(8px);
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
@@ -2145,6 +2212,13 @@
|
|||||||
.ring-inset {
|
.ring-inset {
|
||||||
--tw-ring-inset: inset;
|
--tw-ring-inset: inset;
|
||||||
}
|
}
|
||||||
|
.group-hover\:opacity-0 {
|
||||||
|
&:is(:where(.group):hover *) {
|
||||||
|
@media (hover: hover) {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.group-hover\:opacity-100 {
|
.group-hover\:opacity-100 {
|
||||||
&:is(:where(.group):hover *) {
|
&:is(:where(.group):hover *) {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -2152,6 +2226,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.even\:bg-zinc-50\/50 {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, oklch(98.5% 0 0) 50%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-zinc-50) 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.even\:bg-zinc-100\/50 {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, oklch(96.7% 0.001 286.375) 50%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-zinc-100) 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.even\:bg-zinc-200\/30 {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, oklch(92% 0.004 286.32) 30%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-zinc-200) 30%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.even\:bg-zinc-200\/50 {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, oklch(92% 0.004 286.32) 50%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-zinc-200) 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.focus-within\:border-blue-500 {
|
.focus-within\:border-blue-500 {
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
border-color: var(--color-blue-500);
|
border-color: var(--color-blue-500);
|
||||||
@@ -3129,6 +3235,106 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dark\:even\:bg-black\/5 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 5%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 5%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/10 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 10%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 10%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/20 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 20%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/30 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 30%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/50 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 50%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/60 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 60%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-black\/90 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #000 90%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-black) 90%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-white\/5 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #fff 5%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-white) 5%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-white\/15 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #fff 15%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-white) 15%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dark\:even\:bg-white\/50 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: color-mix(in srgb, #fff 50%, transparent);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
background-color: color-mix(in oklab, var(--color-white) 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.dark\:hover\:border-blue-400 {
|
.dark\:hover\:border-blue-400 {
|
||||||
&:where(.dark, .dark *) {
|
&:where(.dark, .dark *) {
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -3448,7 +3654,6 @@
|
|||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: var(--color-border);
|
|
||||||
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
border-color: color-mix(in srgb, oklch(55.2% 0.016 285.938) 30%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
border-color: color-mix(in oklab, var(--color-zinc-500) 30%, transparent);
|
||||||
@@ -3564,7 +3769,7 @@
|
|||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
}
|
}
|
||||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||||
width: 5.5rem;
|
width: calc(var(--spacing) * 22);
|
||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
@@ -4165,7 +4370,6 @@
|
|||||||
transition-duration: 200ms;
|
transition-duration: 200ms;
|
||||||
}
|
}
|
||||||
.task-stage-content {
|
.task-stage-content {
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -4916,7 +5120,7 @@
|
|||||||
.tag-input-container {
|
.tag-input-container {
|
||||||
margin-top: calc(var(--spacing) * 1);
|
margin-top: calc(var(--spacing) * 1);
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 40px;
|
min-height: calc(var(--spacing) * 10);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -5582,6 +5786,11 @@
|
|||||||
inherits: false;
|
inherits: false;
|
||||||
initial-value: 0 0 #0000;
|
initial-value: 0 0 #0000;
|
||||||
}
|
}
|
||||||
|
@property --tw-outline-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@property --tw-blur {
|
@property --tw-blur {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -5679,11 +5888,6 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
@property --tw-outline-style {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
initial-value: solid;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
@@ -5749,6 +5953,7 @@
|
|||||||
--tw-ring-offset-width: 0px;
|
--tw-ring-offset-width: 0px;
|
||||||
--tw-ring-offset-color: #fff;
|
--tw-ring-offset-color: #fff;
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-outline-style: solid;
|
||||||
--tw-blur: initial;
|
--tw-blur: initial;
|
||||||
--tw-brightness: initial;
|
--tw-brightness: initial;
|
||||||
--tw-contrast: initial;
|
--tw-contrast: initial;
|
||||||
@@ -5773,7 +5978,6 @@
|
|||||||
--tw-backdrop-sepia: initial;
|
--tw-backdrop-sepia: initial;
|
||||||
--tw-duration: initial;
|
--tw-duration: initial;
|
||||||
--tw-ease: initial;
|
--tw-ease: initial;
|
||||||
--tw-outline-style: solid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5364
web/static/js/keys-YEK3YJ77.js
Normal file
5364
web/static/js/keys-YEK3YJ77.js
Normal file
File diff suppressed because it is too large
Load Diff
2924
web/static/js/logs-SSK3L2XT.js
Normal file
2924
web/static/js/logs-SSK3L2XT.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1609,7 +1609,7 @@ var LogList = class {
|
|||||||
const requestTime = new Date(log.RequestTime).toLocaleString();
|
const requestTime = new Date(log.RequestTime).toLocaleString();
|
||||||
const checkedAttr = isChecked ? "checked" : "";
|
const checkedAttr = isChecked ? "checked" : "";
|
||||||
return `
|
return `
|
||||||
<tr class="table-row" data-log-id="${log.ID}" ${errorMessageAttr}>
|
<tr class="table-row group" data-log-id="${log.ID}" ${errorMessageAttr}>
|
||||||
<td class="table-cell">
|
<td class="table-cell">
|
||||||
<input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500" ${checkedAttr}>
|
<input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500" ${checkedAttr}>
|
||||||
</td>
|
</td>
|
||||||
@@ -1620,10 +1620,26 @@ var LogList = class {
|
|||||||
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
|
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
|
||||||
<td class="table-cell">${modelNameFormatted}</td>
|
<td class="table-cell">${modelNameFormatted}</td>
|
||||||
<td class="table-cell text-muted-foreground text-xs">${requestTime}</td>
|
<td class="table-cell text-muted-foreground text-xs">${requestTime}</td>
|
||||||
<td class="table-cell">
|
<td class="table-cell relative">
|
||||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="\u67E5\u770B\u8BE6\u60C5">
|
<!-- [MODIFIED] - 2. \u66FF\u6362\u539F\u6709\u6309\u94AE\u4E3A\u60AC\u6D6E\u64CD\u4F5C\u83DC\u5355 -->
|
||||||
<i class="fas fa-ellipsis-h h-4 w-4"></i>
|
<div class="flex items-center justify-center">
|
||||||
</button>
|
<!-- \u9ED8\u8BA4\u663E\u793A\u7684\u56FE\u6807 -->
|
||||||
|
<span class="text-zinc-400 group-hover:opacity-0 transition-opacity">
|
||||||
|
<i class="fas fa-ellipsis-h h-4 w-4"></i>
|
||||||
|
</span>
|
||||||
|
<!-- \u60AC\u6D6E\u65F6\u663E\u793A\u7684\u64CD\u4F5C\u6309\u94AE -->
|
||||||
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center bg-zinc-100 dark:bg-zinc-700 rounded-full shadow-md opacity-0 group-hover:opacity-100 transition-opacity duration-200 z-10">
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-blue-500" data-action="view-log-details" title="\u67E5\u770B\u8BE6\u60C5">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-green-500" data-action="copy-api-key" title="\u590D\u5236APIKey">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button class="px-2 py-1 text-zinc-500 hover:text-red-500" data-action="delete-log" title="\u5220\u9664\u65E5\u5FD7">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
@@ -2559,8 +2575,14 @@ var LogsPage = class {
|
|||||||
this.elements.selectAllCheckbox.addEventListener("change", (event) => this.handleSelectAllChange(event));
|
this.elements.selectAllCheckbox.addEventListener("change", (event) => this.handleSelectAllChange(event));
|
||||||
}
|
}
|
||||||
if (this.elements.tableBody) {
|
if (this.elements.tableBody) {
|
||||||
this.elements.tableBody.addEventListener("change", (event) => {
|
this.elements.tableBody.addEventListener("click", (event) => {
|
||||||
if (event.target.type === "checkbox") this.handleSelectionChange(event.target);
|
const checkbox = event.target.closest('input[type="checkbox"]');
|
||||||
|
const actionButton = event.target.closest("button[data-action]");
|
||||||
|
if (checkbox) {
|
||||||
|
this.handleSelectionChange(checkbox);
|
||||||
|
} else if (actionButton) {
|
||||||
|
this._handleLogRowAction(actionButton);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.elements.searchInput) {
|
if (this.elements.searchInput) {
|
||||||
@@ -2663,6 +2685,115 @@ var LogsPage = class {
|
|||||||
deleteSelectedBtn.disabled = !hasSelection;
|
deleteSelectedBtn.disabled = !hasSelection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async _handleLogRowAction(button) {
|
||||||
|
const action = button.dataset.action;
|
||||||
|
const row = button.closest(".table-row");
|
||||||
|
const isDarkMode = document.documentElement.classList.contains("dark");
|
||||||
|
if (!row) return;
|
||||||
|
const logId = parseInt(row.dataset.logId, 10);
|
||||||
|
const log = this.state.logs.find((l) => l.ID === logId);
|
||||||
|
if (!log) {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "error", title: "\u627E\u4E0D\u5230\u65E5\u5FD7\u6570\u636E", showConfirmButton: false, timer: 2e3 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case "view-log-details": {
|
||||||
|
const detailsHtml = `
|
||||||
|
<div class="space-y-3 text-left text-sm p-2">
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">\u72B6\u6001\u7801</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.StatusCode || "N/A"}</p></div>
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">\u72B6\u6001</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.Status || "N/A"}</p></div>
|
||||||
|
<div class="flex"><p class="w-24 font-semibold text-zinc-500 shrink-0">\u6A21\u578B</p><p class="font-mono text-zinc-800 dark:text-zinc-200">${log.ModelName || "N/A"}</p></div>
|
||||||
|
<div class="border-t border-zinc-200 dark:border-zinc-700 my-2"></div>
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-zinc-500 mb-1">\u9519\u8BEF\u6D88\u606F</p>
|
||||||
|
<div class="max-h-40 overflow-y-auto bg-zinc-100 dark:bg-zinc-800 p-2 rounded-md text-zinc-700 dark:text-zinc-300 break-words text-xs">
|
||||||
|
${log.ErrorMessage ? log.ErrorMessage.replace(/\n/g, "<br>") : "\u65E0\u9519\u8BEF\u6D88\u606F\u3002"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
Swal.fire({
|
||||||
|
target: "#main-content-wrapper",
|
||||||
|
width: "32rem",
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
|
customClass: {
|
||||||
|
popup: `swal2-custom-style rounded-xl ${document.documentElement.classList.contains("dark") ? "swal2-dark" : ""}`,
|
||||||
|
title: "text-lg font-bold",
|
||||||
|
htmlContainer: "m-0 text-left"
|
||||||
|
},
|
||||||
|
title: "\u65E5\u5FD7\u8BE6\u60C5",
|
||||||
|
html: detailsHtml,
|
||||||
|
showCloseButton: false,
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "copy-api-key": {
|
||||||
|
const key = dataStore.keys.get(log.KeyID);
|
||||||
|
if (key && key.APIKey) {
|
||||||
|
navigator.clipboard.writeText(key.APIKey).then(() => {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains("dark") ? "swal2-dark" : ""}` }, icon: "success", title: "API Key \u5DF2\u590D\u5236", showConfirmButton: false, timer: 1500 });
|
||||||
|
}).catch((err) => {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "error", title: "\u590D\u5236\u5931\u8D25", text: err.message, showConfirmButton: false, timer: 2e3 });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "warning", title: "\u672A\u627E\u5230\u5B8C\u6574\u7684API Key", showConfirmButton: false, timer: 2e3 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(key.APIKey).then(() => {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "success", title: "API Key \u5DF2\u590D\u5236", showConfirmButton: false, timer: 1500 });
|
||||||
|
}).catch((err) => {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "error", title: "\u590D\u5236\u5931\u8D25", text: err.message, showConfirmButton: false, timer: 2e3 });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "\u590D\u5236\u5931\u8D25",
|
||||||
|
text: "\u6B64\u529F\u80FD\u9700\u8981\u5B89\u5168\u8FDE\u63A5 (HTTPS) \u6216\u5728 localhost \u73AF\u5883\u4E0B\u4F7F\u7528\u3002",
|
||||||
|
target: "#main-content-wrapper",
|
||||||
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains("dark") ? "swal2-dark" : ""}` }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "delete-log": {
|
||||||
|
Swal.fire({
|
||||||
|
width: "20rem",
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
|
customClass: { popup: `swal2-custom-style ${document.documentElement.classList.contains("dark") ? "swal2-dark" : ""}` },
|
||||||
|
title: "\u786E\u8BA4\u5220\u9664",
|
||||||
|
text: `\u60A8\u786E\u5B9A\u8981\u5220\u9664\u8FD9\u6761\u65E5\u5FD7\u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002`,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "\u786E\u8BA4\u5220\u9664",
|
||||||
|
cancelButtonText: "\u53D6\u6D88",
|
||||||
|
reverseButtons: false,
|
||||||
|
confirmButtonColor: "#ef4444",
|
||||||
|
cancelButtonColor: "#6b7280",
|
||||||
|
focusCancel: true,
|
||||||
|
target: "#main-content-wrapper"
|
||||||
|
}).then(async (result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
try {
|
||||||
|
const url = `/admin/logs?ids=${logId}`;
|
||||||
|
const { success, message } = await apiFetchJson(url, { method: "DELETE" });
|
||||||
|
if (success) {
|
||||||
|
Swal.fire({ toast: true, position: "top-end", icon: "success", title: "\u5220\u9664\u6210\u529F", showConfirmButton: false, timer: 2e3, timerProgressBar: true });
|
||||||
|
this.loadAndRenderLogs();
|
||||||
|
} else {
|
||||||
|
throw new Error(message || "\u5220\u9664\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({ icon: "error", title: "\u64CD\u4F5C\u5931\u8D25", text: error.message, target: "#main-content-wrapper" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
changePageSize(newSize) {
|
changePageSize(newSize) {
|
||||||
this.state.filters.page_size = newSize;
|
this.state.filters.page_size = newSize;
|
||||||
this.state.filters.page = 1;
|
this.state.filters.page = 1;
|
||||||
1237
web/static/js/main.css
Normal file
1237
web/static/js/main.css
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
9
web/static/vendor/fontawesome/css/all.min.css
vendored
Normal file
9
web/static/vendor/fontawesome/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
web/static/vendor/fontawesome/webfonts/fa-brands-400.woff2
vendored
Normal file
BIN
web/static/vendor/fontawesome/webfonts/fa-brands-400.woff2
vendored
Normal file
Binary file not shown.
BIN
web/static/vendor/fontawesome/webfonts/fa-regular-400.woff2
vendored
Normal file
BIN
web/static/vendor/fontawesome/webfonts/fa-regular-400.woff2
vendored
Normal file
Binary file not shown.
BIN
web/static/vendor/fontawesome/webfonts/fa-solid-900.woff2
vendored
Normal file
BIN
web/static/vendor/fontawesome/webfonts/fa-solid-900.woff2
vendored
Normal file
Binary file not shown.
BIN
web/static/vendor/fontawesome/webfonts/fa-v4compatibility.woff2
vendored
Normal file
BIN
web/static/vendor/fontawesome/webfonts/fa-v4compatibility.woff2
vendored
Normal file
Binary file not shown.
1
web/static/vendor/sweetalert/sweetalert.min.js
vendored
Normal file
1
web/static/vendor/sweetalert/sweetalert.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -8,7 +8,13 @@
|
|||||||
<link href="/static/css/output.css" rel="stylesheet">
|
<link href="/static/css/output.css" rel="stylesheet">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.cn/css2?family=Pixelify+Sans:wght@400..700&family=QuinqueFive&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.cn/css2?family=Pixelify+Sans:wght@400..700&family=QuinqueFive&display=swap">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
<link rel="stylesheet" href="/static/vendor/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/js/main.css">
|
||||||
|
<style>
|
||||||
|
.fas, .fa-solid {
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<title>{% block title %}GEMINI BALANCER{% endblock %}</title>
|
<title>{% block title %}GEMINI BALANCER{% endblock %}</title>
|
||||||
|
|
||||||
{% block head_extra %}{% endblock %}
|
{% block head_extra %}{% endblock %}
|
||||||
@@ -122,8 +128,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% block core_scripts %}
|
{% block core_scripts %}
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.23.0/sweetalert2.all.min.js"></script>
|
|
||||||
<script src="/static/js/main.js" type="module" defer></script>
|
<script src="/static/js/main.js" type="module" defer></script>
|
||||||
{% endblock core_scripts %}
|
{% endblock core_scripts %}
|
||||||
<!-- [核心] Block 2: 留给子页面的脚本扩展插槽 -->
|
<!-- [核心] Block 2: 留给子页面的脚本扩展插槽 -->
|
||||||
|
|||||||
@@ -91,8 +91,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [新增] 3.2 系统日志的快捷操作栏 (默认隐藏) -->
|
<!--3.2 系统日志的快捷操作栏 (默认隐藏) -->
|
||||||
<div id="system-logs-controls" class="hidden flex items-center justify-end shrink-0 py-4 space-x-4">
|
<div id="system-logs-controls" class="flex items-center justify-end shrink-0 py-4 space-x-4">
|
||||||
<div id="terminal-status-indicator" class="flex items-center text-xs text-zinc-500 dark:text-zinc-400">
|
<div id="terminal-status-indicator" class="flex items-center text-xs text-zinc-500 dark:text-zinc-400">
|
||||||
<span class="relative flex h-2 w-2 mr-2">
|
<span class="relative flex h-2 w-2 mr-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-zinc-400 dark:bg-zinc-500 opacity-75"></span>
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-zinc-400 dark:bg-zinc-500 opacity-75"></span>
|
||||||
|
|||||||
Reference in New Issue
Block a user