diff --git a/web/static/css/c2.css b/web/static/css/c2.css
index d500fe27..8c8575fe 100644
--- a/web/static/css/c2.css
+++ b/web/static/css/c2.css
@@ -249,7 +249,8 @@ html[data-theme="dark"] .c2-checkbox-label {
height: 1px;
padding: 0;
margin: -1px;
- overflow: hidden;
+ overflow-x: auto;
+ overflow-y: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
@@ -2121,43 +2122,324 @@ html[data-theme="dark"] #page-c2-payloads .c2-form-select-option.is-selected {
}
/* ============================================================================
- Events
+ Events — audit log
============================================================================ */
.c2-events-page-wrap {
display: flex;
flex-direction: column;
- gap: 0;
+ gap: 16px;
+ min-height: 0;
+ flex: 1;
}
-.c2-events-toolbar {
+.c2-events-panel {
display: flex;
- align-items: center;
- gap: 12px;
- padding: 8px 4px 10px;
- border-bottom: 1px solid var(--c2-border);
- margin-bottom: 0;
+ flex-direction: column;
+ min-height: 0;
+ flex: 1;
+ background: var(--c2-surface);
+ border: 1px solid var(--c2-border);
+ border-radius: var(--c2-radius);
+ box-shadow: var(--c2-shadow-sm);
+ overflow: hidden;
}
-.c2-events-select-all-label {
+.c2-events-list-container {
+ flex: 1;
+ min-height: 0;
+ overflow-x: auto;
+ overflow-y: hidden;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+.c2-events-empty {
+ padding: 48px 24px;
+ text-align: center;
+ color: var(--c2-text-dim);
+ font-size: 14px;
+}
+
+.c2-events-table {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ table-layout: fixed;
+ min-width: 920px;
+}
+
+.c2-events-table thead {
+ display: block;
+ flex: 0 0 auto;
+}
+
+.c2-events-table tbody {
+ display: block;
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ scrollbar-width: thin;
+ scrollbar-color: color-mix(in srgb, var(--c2-text-dim) 70%, transparent) transparent;
+}
+
+.c2-events-table tr {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+
+.c2-events-table tbody::-webkit-scrollbar {
+ width: 8px;
+}
+
+.c2-events-table tbody::-webkit-scrollbar-track {
+ background: var(--c2-surface);
+}
+
+.c2-events-table tbody::-webkit-scrollbar-thumb {
+ border: 2px solid transparent;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--c2-text-dim) 70%, transparent);
+ background-clip: content-box;
+}
+
+.c2-events-table tbody::-webkit-scrollbar-thumb:hover {
+ background: color-mix(in srgb, var(--c2-text-muted) 78%, transparent);
+ background-clip: content-box;
+}
+
+.c2-events-table thead th {
+ z-index: 2;
+ text-align: left;
+ padding: 12px 16px;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--c2-text-dim);
+ background: var(--c2-surface-alt);
+ font-weight: 700;
+ border-bottom: 1px solid var(--c2-border);
+ white-space: nowrap;
+ box-shadow: 0 1px 0 var(--c2-border);
+}
+
+.c2-events-table th:nth-child(1),
+.c2-events-table td:nth-child(1) { width: 44px; }
+.c2-events-table th:nth-child(2),
+.c2-events-table td:nth-child(2) { width: 210px; }
+.c2-events-table th:nth-child(3),
+.c2-events-table td:nth-child(3) { width: 112px; }
+.c2-events-table th:nth-child(4),
+.c2-events-table td:nth-child(4) { width: 140px; }
+.c2-events-table th:nth-child(5),
+.c2-events-table td:nth-child(5) { width: auto; }
+.c2-events-table th:nth-child(6),
+.c2-events-table td:nth-child(6) { width: 150px; }
+.c2-events-table th:nth-child(7),
+.c2-events-table td:nth-child(7) { width: 150px; }
+.c2-events-table th:nth-child(8),
+.c2-events-table td:nth-child(8) { width: 72px; }
+
+.c2-events-table td {
+ padding: 12px 16px;
+ border-top: 1px solid var(--c2-border);
+ font-size: 13px;
+ vertical-align: middle;
+ background: var(--c2-surface);
+}
+
+.c2-events-table tbody tr:first-child td {
+ border-top: none;
+}
+
+.c2-events-summary {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+}
+
+.c2-events-stat {
display: inline-flex;
align-items: center;
gap: 8px;
- font-size: 13px;
+ padding: 6px 12px;
+ border-radius: var(--c2-radius-sm);
+ background: var(--c2-surface);
+ border: 1px solid var(--c2-border);
+ font-size: 12px;
color: var(--c2-text-muted);
- cursor: pointer;
- user-select: none;
}
-.c2-events-select-all-label input {
+.c2-events-stat strong {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--c2-text);
+ font-variant-numeric: tabular-nums;
+}
+
+.c2-events-stat--info strong { color: var(--c2-accent); }
+.c2-events-stat--warn strong { color: var(--c2-amber); }
+.c2-events-stat--critical strong { color: var(--c2-red); }
+
+.c2-events-filter-card {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 16px 18px;
+ background: var(--c2-surface);
+ border: 1px solid var(--c2-border);
+ border-radius: var(--c2-radius);
+ box-shadow: var(--c2-shadow-sm);
+ flex-shrink: 0;
+}
+
+.c2-events-time-presets {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+}
+
+.c2-events-time-presets-label {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--c2-text-dim);
+ margin-right: 4px;
+}
+
+.c2-events-time-preset-btn {
+ padding: 5px 12px;
+ border-radius: 999px;
+ border: 1px solid var(--c2-border);
+ background: var(--c2-surface-alt);
+ color: var(--c2-text-muted);
+ font-size: 12px;
+ font-weight: 500;
cursor: pointer;
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
+}
+
+.c2-events-time-preset-btn:hover {
+ border-color: var(--c2-border-hover);
+ color: var(--c2-text);
+}
+
+.c2-events-time-preset-btn.is-active {
+ background: var(--c2-accent-dim);
+ border-color: rgba(59, 130, 246, 0.45);
+ color: var(--c2-accent);
+ font-weight: 600;
+}
+
+.c2-events-filter-fields {
+ display: grid;
+ grid-template-columns: minmax(148px, 0.9fr) minmax(148px, 0.9fr) minmax(180px, 1.4fr) auto;
+ gap: 12px 14px;
+ align-items: end;
+}
+
+.c2-events-field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 0;
+}
+
+.c2-events-field > span {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--c2-text-dim);
+}
+
+#page-c2-events .c2-events-field .form-control {
+ width: 100%;
+ height: 36px;
+ min-height: 36px;
+ padding: 0 12px;
+ font-size: 13px;
+ line-height: 36px;
+ box-sizing: border-box;
+}
+
+#page-c2-events .c2-events-field select.form-control {
+ min-width: 148px;
+ padding: 0 36px 0 12px;
+ line-height: 34px;
+ cursor: pointer;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 12px center;
+ background-size: 12px;
+}
+
+.c2-events-filter-actions {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ gap: 8px;
+ height: 36px;
+}
+
+.c2-events-filter-actions .btn-small {
+ height: 36px;
+ min-height: 36px;
+ padding: 0 14px;
+ font-size: 13px;
+ line-height: 1;
+ box-sizing: border-box;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ white-space: nowrap;
+}
+
+.c2-events-table-col-check {
+ width: 44px;
+ text-align: center;
+}
+
+.c2-events-table-col-actions {
+ width: 1%;
+ white-space: nowrap;
+ text-align: right;
+}
+
+.c2-events-row {
+ cursor: pointer;
+ transition: background 0.12s ease;
+}
+
+.c2-events-row:hover td {
+ background: var(--c2-surface-alt);
+}
+
+.c2-events-row--critical td:first-child {
+ box-shadow: inset 3px 0 0 var(--c2-red);
+}
+
+.c2-events-row--warn td:first-child {
+ box-shadow: inset 3px 0 0 var(--c2-amber);
+}
+
+.c2-events-row--info td:first-child {
+ box-shadow: inset 3px 0 0 var(--c2-accent);
}
.c2-event-check-label {
- flex-shrink: 0;
display: flex;
- align-items: flex-start;
- padding-top: 4px;
+ justify-content: center;
+ align-items: center;
cursor: pointer;
}
@@ -2165,60 +2447,248 @@ html[data-theme="dark"] #page-c2-payloads .c2-form-select-option.is-selected {
cursor: pointer;
}
-.c2-event-row-delete {
- flex-shrink: 0;
- align-self: flex-start;
- margin-top: 2px;
- padding: 4px 10px;
- font-size: 14px;
- line-height: 1.2;
+.c2-events-col-time {
+ white-space: nowrap;
+ color: var(--c2-text-muted);
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
}
-.c2-event-list {
- background: var(--c2-surface);
- border-radius: var(--c2-radius);
+.c2-events-col-message {
+ max-width: 360px;
+ font-weight: 500;
+ color: var(--c2-text);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.c2-events-col-mono {
+ font-family: var(--c2-mono);
+ font-size: 12px;
+ color: var(--c2-text-muted);
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.c2-events-cell-muted {
+ color: var(--c2-text-dim);
+}
+
+.c2-event-level-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 3px 10px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 1.4;
+ white-space: nowrap;
+}
+
+.c2-event-level-badge--info {
+ background: var(--c2-accent-dim);
+ color: var(--c2-accent);
+}
+
+.c2-event-level-badge--warn {
+ background: var(--c2-amber-dim);
+ color: #b45309;
+}
+
+.c2-event-level-badge--critical {
+ background: var(--c2-red-dim);
+ color: var(--c2-red);
+}
+
+.c2-event-category-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 3px 10px;
+ border-radius: var(--c2-radius-xs);
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 1.4;
+ background: var(--c2-surface-alt);
+ color: var(--c2-text-muted);
border: 1px solid var(--c2-border);
- max-height: calc(100vh - 200px);
- overflow-y: auto;
}
-.c2-event-item {
- display: flex;
+.c2-event-category-badge--listener { color: #7c3aed; background: rgba(124, 58, 237, 0.1); border-color: rgba(124, 58, 237, 0.25); }
+.c2-event-category-badge--session { color: #059669; background: rgba(5, 150, 105, 0.1); border-color: rgba(5, 150, 105, 0.25); }
+.c2-event-category-badge--task { color: #2563eb; background: rgba(37, 99, 235, 0.1); border-color: rgba(37, 99, 235, 0.25); }
+.c2-event-category-badge--payload { color: #d97706; background: rgba(217, 119, 6, 0.1); border-color: rgba(217, 119, 6, 0.25); }
+.c2-event-category-badge--opsec { color: #dc2626; background: rgba(220, 38, 38, 0.1); border-color: rgba(220, 38, 38, 0.25); }
+
+.c2-events-delete-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ padding: 0;
+ border: 1px solid transparent;
+ border-radius: var(--c2-radius-xs);
+ background: transparent;
+ color: var(--c2-text-dim);
+ cursor: pointer;
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
+}
+
+.c2-events-delete-btn:hover {
+ background: var(--c2-red-dim);
+ border-color: rgba(239, 68, 68, 0.25);
+ color: var(--c2-red);
+}
+
+/* Event detail modal */
+.c2-event-modal-header {
align-items: flex-start;
- gap: 14px;
- padding: 14px 20px;
- border-bottom: 1px solid var(--c2-border);
- transition: background 0.1s;
}
-.c2-event-item:hover { background: var(--c2-surface-alt); }
-.c2-event-item:last-child { border-bottom: none; }
-
-.c2-event-level {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- margin-top: 6px;
- flex-shrink: 0;
+.c2-event-modal-heading {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
}
-.c2-event-level.info { background: var(--c2-accent); }
-.c2-event-level.warn { background: var(--c2-amber); }
-.c2-event-level.critical { background: var(--c2-red); }
+.c2-event-modal-heading h3 {
+ margin: 0;
+}
-.c2-event-content { flex: 1; }
+.c2-event-detail {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
-.c2-event-message {
+.c2-event-detail-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.c2-event-kv {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 10px 12px;
+ border-radius: var(--c2-radius-sm);
+ background: var(--c2-surface-alt);
+ border: 1px solid var(--c2-border);
+}
+
+.c2-event-kv__label {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--c2-text-dim);
+}
+
+.c2-event-kv__value {
font-size: 13px;
color: var(--c2-text);
- margin-bottom: 3px;
- font-weight: 500;
+ word-break: break-all;
}
-.c2-event-meta {
- font-size: 11px;
- color: var(--c2-text-muted);
+.c2-event-kv__value--mono {
font-family: var(--c2-mono);
+ font-size: 12px;
+}
+
+.c2-event-message-block,
+.c2-event-data-block {
+ border: 1px solid var(--c2-border);
+ border-radius: var(--c2-radius-sm);
+ overflow: hidden;
+}
+
+.c2-event-message-block__label,
+.c2-event-data-block__header {
+ padding: 8px 12px;
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--c2-text-dim);
+ background: var(--c2-surface-alt);
+ border-bottom: 1px solid var(--c2-border);
+}
+
+.c2-event-data-block__header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+
+.c2-event-message-block__body {
+ padding: 12px 14px;
+ font-size: 14px;
+ line-height: 1.6;
+ color: var(--c2-text);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+.c2-event-data-pre {
+ margin: 0;
+ padding: 12px 14px;
+ max-height: 280px;
+ overflow: auto;
+ font-family: var(--c2-mono);
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--c2-text);
+ background: transparent;
+}
+
+#page-c2-events .page-content.c2-events-page-wrap {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ flex: 1;
+ overflow: hidden;
+}
+
+#page-c2-events #c2-events-pagination {
+ flex-shrink: 0;
+ margin: 0;
+}
+
+#page-c2-events #c2-events-pagination .monitor-pagination {
+ margin-top: 0;
+ border: none;
+ border-top: 1px solid var(--c2-border);
+ border-radius: 0;
+ background: var(--c2-surface-alt);
+ box-shadow: none;
+}
+
+#page-c2-events #c2-events-pagination .pagination-page-size select {
+ border-radius: var(--c2-radius-xs);
+}
+
+#page-c2-events #c2-events-pagination .pagination-controls .btn-secondary {
+ border-radius: var(--c2-radius-xs);
+}
+
+@media (max-width: 900px) {
+ .c2-events-filter-fields {
+ grid-template-columns: 1fr;
+ }
+
+ .c2-events-filter-actions {
+ justify-content: flex-start;
+ }
+
+ .c2-event-detail-grid {
+ grid-template-columns: 1fr;
+ }
}
/* ============================================================================
diff --git a/web/static/css/style.css b/web/static/css/style.css
index cd894d58..4557a3f8 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -32253,11 +32253,52 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
#project-panel-vulns .projects-table-wrap {
overflow-x: hidden;
}
+#project-panel-assets .projects-table-wrap {
+ overflow: hidden;
+}
+#project-panel-assets .data-table--projects {
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+}
+#project-panel-assets .data-table--projects thead {
+ display: block;
+ flex: 0 0 auto;
+}
+#project-panel-assets .data-table--projects tbody {
+ display: block;
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ scrollbar-width: thin;
+ scrollbar-color: color-mix(in srgb, var(--text-muted, #94a3b8) 52%, transparent) transparent;
+}
+#project-panel-assets .data-table--projects tr {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+#project-panel-assets .data-table--projects tbody::-webkit-scrollbar {
+ width: 8px;
+}
+#project-panel-assets .data-table--projects tbody::-webkit-scrollbar-track {
+ background: var(--bg-primary, #fff);
+}
+#project-panel-assets .data-table--projects tbody::-webkit-scrollbar-thumb {
+ border: 2px solid transparent;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--text-muted, #94a3b8) 62%, transparent);
+ background-clip: content-box;
+}
+#project-panel-assets .data-table--projects tbody::-webkit-scrollbar-thumb:hover {
+ background: color-mix(in srgb, var(--text-secondary, #64748b) 82%, transparent);
+ background-clip: content-box;
+}
#project-panel-facts .projects-table-wrap {
overflow-x: auto;
}
#project-panel-facts .projects-table-wrap .data-table--projects thead th,
-#project-panel-assets .projects-table-wrap .data-table--projects thead th,
#project-panel-conversations .projects-table-wrap .data-table--projects thead th,
#project-panel-vulns .projects-table-wrap .data-table--projects thead th {
position: sticky;
@@ -32265,6 +32306,9 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
z-index: 2;
box-shadow: 0 1px 0 var(--border-color, #e2e8f0);
}
+#project-panel-assets .projects-table-wrap .data-table--projects thead th {
+ box-shadow: 0 1px 0 var(--border-color, #e2e8f0);
+}
.projects-panel-toolbar--hint {
margin-bottom: 14px;
padding: 0;
@@ -41171,14 +41215,18 @@ html[data-theme="dark"] .workflow-toolbar #workflow-connect-btn[aria-pressed="tr
.asset-batch-permission-action { display: contents; }
.asset-list-meta { margin-left: auto; }
.asset-list-card { overflow: hidden; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--card-bg, #fff); box-shadow: 0 1px 3px rgba(15,23,42,.03); }
-#page-asset-library .asset-list-card { overflow: visible; }
+#page-asset-library .asset-list-card { overflow: hidden; }
#page-asset-library .asset-list-card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.asset-table-wrap { overflow: auto; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--card-bg, #fff); }
.asset-list-card .asset-table-wrap { border: 0; border-radius: 0; }
-#page-asset-library .asset-list-card .asset-table-wrap { --table-scroll-header-height: 41px; flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; }
-#page-asset-library .asset-list-card .asset-table-wrap::-webkit-scrollbar,
-.info-collect-results-table-wrap::-webkit-scrollbar { width: 10px; }
-#page-asset-library .asset-list-card .asset-table-wrap::-webkit-scrollbar-track { margin-top: var(--table-scroll-header-height); background: transparent; }
+#page-asset-library .asset-list-card .asset-table-wrap { --table-scroll-header-height: 41px; --asset-table-header-bg: var(--card-bg, #fff); flex: 1 1 auto; min-height: 0; overflow: hidden; scrollbar-gutter: auto; }
+#page-asset-library .asset-table { display: flex; height: 100%; flex-direction: column; }
+#page-asset-library .asset-table thead { display: block; flex: 0 0 auto; }
+#page-asset-library .asset-table tbody { display: block; flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--text-muted, #94a3b8) 52%, transparent) transparent; }
+#page-asset-library .asset-table tr { display: table; width: 100%; table-layout: fixed; }
+#page-asset-library .asset-table tbody::-webkit-scrollbar,
+.info-collect-results-table-wrap::-webkit-scrollbar { width: 8px; }
+#page-asset-library .asset-table tbody::-webkit-scrollbar-track { background: var(--card-bg, #fff); }
.info-collect-results-table-wrap::-webkit-scrollbar-track {
background: linear-gradient(
to bottom,
@@ -41186,14 +41234,14 @@ html[data-theme="dark"] .workflow-toolbar #workflow-connect-btn[aria-pressed="tr
var(--bg-primary) var(--table-scroll-header-height) 100%
);
}
-#page-asset-library .asset-list-card .asset-table-wrap::-webkit-scrollbar-thumb,
+#page-asset-library .asset-table tbody::-webkit-scrollbar-thumb,
.info-collect-results-table-wrap::-webkit-scrollbar-thumb {
- border: 3px solid transparent;
+ border: 2px solid transparent;
border-radius: 999px;
background: color-mix(in srgb, var(--text-muted, #94a3b8) 62%, transparent);
background-clip: content-box;
}
-#page-asset-library .asset-list-card .asset-table-wrap::-webkit-scrollbar-thumb:hover,
+#page-asset-library .asset-table tbody::-webkit-scrollbar-thumb:hover,
.info-collect-results-table-wrap::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--text-secondary, #64748b) 82%, transparent);
background-clip: content-box;
@@ -41203,7 +41251,7 @@ html[data-theme="dark"] .workflow-toolbar #workflow-connect-btn[aria-pressed="tr
.asset-list-card #asset-pagination.pagination-fixed .pagination { border-radius: 0; }
.asset-table { width: 100%; min-width: 0; table-layout: fixed; border-collapse: collapse; }
.asset-table th,.asset-table td { padding: 13px 14px; border-bottom: 1px solid var(--border-color, #edf0f4); text-align: left; vertical-align: middle; }
-.asset-table th { position: sticky; top: 0; box-sizing: border-box; height: var(--table-scroll-header-height, auto); background: var(--card-bg, #fff); color: var(--text-secondary, #64748b); font-size: 12px; font-weight: 600; z-index: 1; }
+.asset-table th { box-sizing: border-box; height: var(--table-scroll-header-height, auto); background: var(--asset-table-header-bg, var(--card-bg, #fff)); color: var(--text-secondary, #64748b); font-size: 12px; font-weight: 600; z-index: 1; }
.asset-table td { font-size: 13px; }
.asset-table td:nth-child(2) { width: 220px; max-width: 280px; }
.asset-table th:nth-child(2),.asset-table td:nth-child(2) { width: auto; }
@@ -41769,6 +41817,18 @@ html[data-theme="dark"] #page-asset-library .asset-table-wrap {
border-color: #223047 !important;
}
+html[data-theme="dark"] #page-asset-library .asset-table-wrap {
+ --asset-table-header-bg: #172033;
+}
+
+html[data-theme="dark"] #page-asset-library .asset-table tbody::-webkit-scrollbar-track {
+ background: #0f172a;
+}
+
+html[data-theme="dark"] #project-panel-assets .data-table--projects tbody::-webkit-scrollbar-track {
+ background: #0f172a;
+}
+
html[data-theme="dark"] #page-projects .projects-panel-toolbar--hint {
background: transparent !important;
border-color: transparent !important;
diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json
index cd94a139..b96bbb23 100644
--- a/web/static/i18n/en-US.json
+++ b/web/static/i18n/en-US.json
@@ -3988,7 +3988,25 @@
"paginationPrev": "Previous",
"paginationPage": "Page {{current}} / {{total}}",
"paginationNext": "Next",
- "paginationLast": "Last"
+ "paginationLast": "Last",
+ "statTotal": "Filtered",
+ "timePresets": "Quick",
+ "preset15m": "Last 15 min",
+ "preset1h": "Last 1 hour",
+ "preset24h": "Last 24 hours",
+ "preset7d": "Last 7 days",
+ "presetAll": "All",
+ "filterAllLevels": "All levels",
+ "filterAllCategories": "All categories",
+ "filterSession": "Session ID",
+ "filterSessionPlaceholder": "s_xxxxxxxx",
+ "applyFilters": "Apply filters",
+ "resetFilters": "Reset",
+ "colSession": "Session",
+ "colTask": "Task",
+ "colId": "Event ID",
+ "detailTitle": "Event details",
+ "detailData": "Additional data"
},
"profiles": {
"title": "Malleable Profile",
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index e3b0dece..c415e7c1 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -3976,7 +3976,25 @@
"paginationPrev": "上一页",
"paginationPage": "第 {{current}} / {{total}} 页",
"paginationNext": "下一页",
- "paginationLast": "末页"
+ "paginationLast": "末页",
+ "statTotal": "当前筛选",
+ "timePresets": "快捷",
+ "preset15m": "最近15分钟",
+ "preset1h": "最近1小时",
+ "preset24h": "最近24小时",
+ "preset7d": "最近7天",
+ "presetAll": "全部",
+ "filterAllLevels": "全部级别",
+ "filterAllCategories": "全部类别",
+ "filterSession": "会话 ID",
+ "filterSessionPlaceholder": "s_xxxxxxxx",
+ "applyFilters": "应用筛选",
+ "resetFilters": "重置",
+ "colSession": "会话",
+ "colTask": "任务",
+ "colId": "事件 ID",
+ "detailTitle": "事件详情",
+ "detailData": "附加数据"
},
"profiles": {
"title": "流量伪装",
diff --git a/web/static/js/c2.js b/web/static/js/c2.js
index 546aa6bc..08f1c9e6 100644
--- a/web/static/js/c2.js
+++ b/web/static/js/c2.js
@@ -18,6 +18,8 @@
eventsPage: 1,
eventsPageSize: 10,
eventsTotal: 0,
+ eventsLevelCounts: { info: 0, warn: 0, critical: 0 },
+ eventsFilter: { level: '', category: '', session_id: '', since: '', timePreset: 'all' },
profiles: [],
selectedSessionId: null,
selectedListenerId: null,
@@ -3343,25 +3345,218 @@
// 事件审计
// ============================================================================
+ function eventLevelLabel(level) {
+ const key = 'c2.events.' + (level || 'info');
+ const translated = c2t(key);
+ return translated !== key ? translated : (level || 'info');
+ }
+
+ function eventCategoryLabel(category) {
+ const key = 'c2.events.' + (category || '');
+ const translated = c2t(key);
+ return translated !== key ? translated : (category || '-');
+ }
+
+ function eventLevelBadgeClass(level) {
+ if (level === 'warn') return 'c2-event-level-badge--warn';
+ if (level === 'critical') return 'c2-event-level-badge--critical';
+ return 'c2-event-level-badge--info';
+ }
+
+ function eventCategoryBadgeClass(category) {
+ return 'c2-event-category-badge c2-event-category-badge--' + (category || 'default');
+ }
+
+ function readEventsFilterFromDom() {
+ const levelEl = document.getElementById('c2-events-filter-level');
+ const catEl = document.getElementById('c2-events-filter-category');
+ const sessionEl = document.getElementById('c2-events-filter-session');
+ C2.eventsFilter = C2.eventsFilter || { level: '', category: '', session_id: '', since: '', timePreset: 'all' };
+ C2.eventsFilter.level = levelEl ? levelEl.value.trim() : '';
+ C2.eventsFilter.category = catEl ? catEl.value.trim() : '';
+ C2.eventsFilter.session_id = sessionEl ? sessionEl.value.trim() : '';
+ }
+
+ function buildEventsQueryParams(page, pageSize) {
+ const params = new URLSearchParams();
+ params.set('page', String(page));
+ params.set('page_size', String(pageSize));
+ const f = C2.eventsFilter || {};
+ if (f.level) params.set('level', f.level);
+ if (f.category) params.set('category', f.category);
+ if (f.session_id) params.set('session_id', f.session_id);
+ if (f.since) params.set('since', f.since);
+ return params.toString();
+ }
+
+ function eventsTimePresetToSince(preset) {
+ if (!preset || preset === 'all') return '';
+ const now = Date.now();
+ const map = { '15m': 15 * 60 * 1000, '1h': 60 * 60 * 1000, '24h': 24 * 60 * 60 * 1000, '7d': 7 * 24 * 60 * 60 * 1000 };
+ const ms = map[preset];
+ if (!ms) return '';
+ return new Date(now - ms).toISOString();
+ }
+
+ function syncEventsTimePresetButtons() {
+ const wrap = document.getElementById('c2-events-time-presets');
+ if (!wrap) return;
+ const active = (C2.eventsFilter && C2.eventsFilter.timePreset) || 'all';
+ wrap.querySelectorAll('.c2-events-time-preset-btn').forEach(btn => {
+ btn.classList.toggle('is-active', btn.getAttribute('data-preset') === active);
+ });
+ }
+
+ function bindEventsFilterUiOnce() {
+ if (document.documentElement.dataset.c2EventsFilterBound === '1') return;
+ document.documentElement.dataset.c2EventsFilterBound = '1';
+
+ const presets = document.getElementById('c2-events-time-presets');
+ if (presets) {
+ presets.addEventListener('click', (e) => {
+ const btn = e.target.closest('.c2-events-time-preset-btn');
+ if (!btn) return;
+ const preset = btn.getAttribute('data-preset') || 'all';
+ C2.eventsFilter = C2.eventsFilter || { level: '', category: '', session_id: '', since: '', timePreset: 'all' };
+ C2.eventsFilter.timePreset = preset;
+ C2.eventsFilter.since = eventsTimePresetToSince(preset);
+ syncEventsTimePresetButtons();
+ C2.loadEvents(1);
+ });
+ }
+
+ const sessionInput = document.getElementById('c2-events-filter-session');
+ if (sessionInput) {
+ sessionInput.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') C2.applyEventsFilters();
+ });
+ }
+ }
+
+ C2.applyEventsFilters = function() {
+ readEventsFilterFromDom();
+ C2.loadEvents(1);
+ };
+
+ C2.resetEventsFilters = function() {
+ C2.eventsFilter = { level: '', category: '', session_id: '', since: '', timePreset: 'all' };
+ const levelEl = document.getElementById('c2-events-filter-level');
+ const catEl = document.getElementById('c2-events-filter-category');
+ const sessionEl = document.getElementById('c2-events-filter-session');
+ if (levelEl) levelEl.value = '';
+ if (catEl) catEl.value = '';
+ if (sessionEl) sessionEl.value = '';
+ syncEventsTimePresetButtons();
+ C2.loadEvents(1);
+ };
+
+ C2.renderEventsSummary = function() {
+ const summary = document.getElementById('c2-events-summary');
+ if (!summary) return;
+ const total = C2.eventsTotal || 0;
+ const counts = C2.eventsLevelCounts || { info: 0, warn: 0, critical: 0 };
+ const set = (id, val) => {
+ const el = document.getElementById(id);
+ if (el) el.textContent = String(val);
+ };
+ set('c2-events-stat-total', total);
+ set('c2-events-stat-info', counts.info || 0);
+ set('c2-events-stat-warn', counts.warn || 0);
+ set('c2-events-stat-critical', counts.critical || 0);
+ summary.hidden = total === 0;
+ };
+
+ C2.viewEvent = function(id) {
+ const event = (C2.events || []).find(e => e.id === id);
+ if (!event) return;
+ const modal = document.getElementById('c2-modal');
+ const content = document.getElementById('c2-modal-content');
+ if (!content || !modal) return;
+
+ const modalBox = modal.querySelector('.c2-modal');
+ if (modalBox) modalBox.classList.add('c2-modal--wide');
+
+ const hasData = event.data && typeof event.data === 'object' && Object.keys(event.data).length > 0;
+ const levelCls = eventLevelBadgeClass(event.level);
+ const catCls = eventCategoryBadgeClass(event.category);
+
+ content.innerHTML = `
+
+
+
+
+
+ ${escapeHtml(c2t('c2.events.time'))}
+ ${escapeHtml(formatTime(event.createdAt))}
+
+
+ ${escapeHtml(c2t('c2.events.colSession'))}
+ ${escapeHtml(event.sessionId || '-')}
+
+
+ ${escapeHtml(c2t('c2.events.colTask'))}
+ ${escapeHtml(event.taskId || '-')}
+
+
+ ${escapeHtml(c2t('c2.events.colId'))}
+ ${escapeHtml(event.id || '-')}
+
+
+
+
${escapeHtml(c2t('c2.events.message'))}
+
${escapeHtml(event.message || '-')}
+
+ ${hasData ? `
+
+
+
${escapeHtml(JSON.stringify(event.data, null, 2))}
+
+ ` : ''}
+
+
+
+ `;
+ openAppModal('c2-modal');
+ if (typeof applyTranslations === 'function') applyTranslations(content);
+ };
+
C2.loadEvents = function(page) {
+ bindEventsFilterUiOnce();
+ readEventsFilterFromDom();
const p = page != null ? page : (C2.eventsPage || 1);
C2.eventsPage = p;
const ps = C2.eventsPageSize || 10;
- apiRequest('GET', `${API_BASE}/events?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => {
+ const qs = buildEventsQueryParams(p, ps);
+ apiRequest('GET', `${API_BASE}/events?${qs}`).then(data => {
if (data.error) {
showToast(String(data.error), 'error');
return;
}
C2.events = data.events || [];
C2.eventsTotal = typeof data.total === 'number' ? data.total : (C2.events.length || 0);
+ C2.eventsLevelCounts = data.level_counts || { info: 0, warn: 0, critical: 0 };
const maxPage = Math.max(1, Math.ceil(C2.eventsTotal / ps));
if (p > maxPage) {
C2.loadEvents(maxPage);
return;
}
C2.renderEvents();
+ C2.renderEventsSummary();
C2.renderEventsPagination();
C2.syncEventsToolbar();
+ syncEventsTimePresetButtons();
}).catch(err => {
showToast(err.message || String(err), 'error');
});
@@ -3497,7 +3692,7 @@
}
if (C2.events.length === 0) {
- container.innerHTML = '' + escapeHtml(c2t('c2.events.empty')) + '
';
+ container.innerHTML = '' + escapeHtml(c2t('c2.events.empty')) + '
';
if (selAll) selAll.disabled = true;
C2.syncEventsToolbar();
return;
@@ -3505,24 +3700,57 @@
if (selAll) selAll.disabled = false;
const delTitle = escapeHtml(c2t('c2.events.deleteOne'));
- container.innerHTML = C2.events.map(e => {
- const eid = escapeHtml(e.id || '');
- return `
-
-
-
-
-
${escapeHtml(e.message)}
-
- ${formatTime(e.createdAt)} · ${escapeHtml(e.category || '')}${e.sessionId ? ' · ' + escapeHtml(String(e.sessionId).substring(0, 8)) : ''}
-
-
-
-
+ const dash = '—';
+ const deleteIcon = '';
+
+ container.innerHTML = `
+
`;
- }).join('');
C2.syncEventsToolbar();
if (typeof applyTranslations === 'function') applyTranslations(container);
diff --git a/web/templates/index.html b/web/templates/index.html
index 10b83a31..124191e1 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -2928,14 +2928,68 @@