mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-07 01:56:40 +02:00
Add files via upload
This commit is contained in:
+400
-175
@@ -6172,9 +6172,12 @@ header {
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 20px;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
box-shadow:
|
||||
0 32px 96px rgba(15, 23, 42, 0.28),
|
||||
0 8px 24px rgba(15, 23, 42, 0.1),
|
||||
0 0 0 1px rgba(15, 23, 42, 0.06);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
@@ -6184,7 +6187,10 @@ header {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
|
||||
background:
|
||||
radial-gradient(1200px 600px at 10% -10%, rgba(99, 102, 241, 0.06) 0%, transparent 60%),
|
||||
radial-gradient(1000px 500px at 100% 110%, rgba(14, 165, 233, 0.05) 0%, transparent 60%),
|
||||
linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
|
||||
}
|
||||
|
||||
.attack-chain-main-layout {
|
||||
@@ -6205,10 +6211,13 @@ header {
|
||||
}
|
||||
|
||||
.attack-chain-sidebar {
|
||||
width: 280px;
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.85) 0%, rgba(248, 250, 252, 0.9) 100%);
|
||||
backdrop-filter: saturate(1.1) blur(6px);
|
||||
-webkit-backdrop-filter: saturate(1.1) blur(6px);
|
||||
border-left: 1px solid rgba(15, 23, 42, 0.07);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -6224,43 +6233,67 @@ header {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.attack-chain-sidebar-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.attack-chain-sidebar-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.attack-chain-sidebar-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(15, 23, 42, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.attack-chain-sidebar-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
}
|
||||
|
||||
.attack-chain-toolbar {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.07);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 250, 252, 0.92) 100%);
|
||||
backdrop-filter: saturate(1.1) blur(6px);
|
||||
-webkit-backdrop-filter: saturate(1.1) blur(6px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
|
||||
box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.attack-chain-info {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-primary);
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
padding: 6px 12px;
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.1) 0%, rgba(0, 102, 255, 0.06) 100%);
|
||||
border: 1px solid rgba(0, 102, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0, 102, 255, 0.08);
|
||||
padding: 7px 14px;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(99, 102, 241, 0.08) 100%);
|
||||
border: 1px solid rgba(59, 130, 246, 0.22);
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 1px 2px rgba(59, 130, 246, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 7px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.attack-chain-info::before {
|
||||
content: '📊';
|
||||
font-size: 0.8125rem;
|
||||
content: '';
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
background:
|
||||
linear-gradient(135deg, #3b82f6 0%, #6366f1 100%);
|
||||
box-shadow: 0 1px 3px rgba(59, 130, 246, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attack-chain-legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 16px;
|
||||
gap: 18px;
|
||||
padding: 18px 16px 20px;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -6271,58 +6304,89 @@ header {
|
||||
gap: 10px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
padding: 4px 0;
|
||||
transition: all 0.2s ease;
|
||||
color: #334155;
|
||||
padding: 5px 8px;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.legend-item:hover {
|
||||
background: rgba(99, 102, 241, 0.06);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12), inset 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.2s ease;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 7px;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 2px 6px rgba(15, 23, 42, 0.18),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.legend-color::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.35) 0%, transparent 60%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.legend-item:hover .legend-color {
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
transform: scale(1.12);
|
||||
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.25), inset 0 1px 2px rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.legend-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
padding: 12px 12px 14px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.legend-title {
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
color: #0f172a;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 6px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
letter-spacing: 0.8px;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed rgba(15, 23, 42, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-title::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(180deg, #3b82f6 0%, #6366f1 100%);
|
||||
}
|
||||
|
||||
.legend-line {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
width: 34px;
|
||||
height: 0;
|
||||
margin-right: 0;
|
||||
vertical-align: middle;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.legend-item:hover .legend-line {
|
||||
@@ -6332,28 +6396,44 @@ header {
|
||||
.attack-chain-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: #ffffff;
|
||||
border: none;
|
||||
background:
|
||||
linear-gradient(180deg, #fbfcfe 0%, #f6f8fc 100%);
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 12px;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
margin: 14px 14px 14px 14px;
|
||||
border-radius: 14px;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(15, 23, 42, 0.03),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.attack-chain-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(0, 102, 255, 0.02) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(0, 102, 255, 0.02) 0%, transparent 50%);
|
||||
inset: 0;
|
||||
background-image:
|
||||
radial-gradient(circle at center, rgba(15, 23, 42, 0.07) 1px, transparent 1.5px);
|
||||
background-size: 22px 22px;
|
||||
background-position: 0 0;
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
mask-image: radial-gradient(ellipse at center, black 60%, transparent 100%);
|
||||
-webkit-mask-image: radial-gradient(ellipse at center, black 60%, transparent 100%);
|
||||
}
|
||||
|
||||
.attack-chain-container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(800px 400px at 15% 12%, rgba(59, 130, 246, 0.06) 0%, transparent 70%),
|
||||
radial-gradient(700px 360px at 85% 88%, rgba(168, 85, 247, 0.05) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* 攻击链筛选器样式 */
|
||||
@@ -6367,128 +6447,202 @@ header {
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"] {
|
||||
padding: 6px 12px;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
padding: 7px 14px 7px 34px;
|
||||
border: 1.5px solid rgba(15, 23, 42, 0.1);
|
||||
border-radius: 10px;
|
||||
font-size: 0.8125rem;
|
||||
min-width: 180px;
|
||||
min-width: 200px;
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
max-width: 320px;
|
||||
background:
|
||||
#ffffff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E") no-repeat 10px center;
|
||||
color: #0f172a;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.12), 0 2px 6px rgba(0, 102, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.15), 0 2px 6px rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]:hover {
|
||||
border-color: rgba(0, 102, 255, 0.3);
|
||||
border-color: rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.attack-chain-filters input[type="text"]::placeholder {
|
||||
color: var(--text-muted);
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.attack-chain-filters select {
|
||||
padding: 6px 12px;
|
||||
padding-right: 32px;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
padding: 7px 34px 7px 14px;
|
||||
border: 1.5px solid rgba(15, 23, 42, 0.1);
|
||||
border-radius: 10px;
|
||||
font-size: 0.8125rem;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
color: #0f172a;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
appearance: none;
|
||||
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='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.attack-chain-filters select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 4px rgba(0, 102, 255, 0.12), 0 2px 6px rgba(0, 102, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.15), 0 2px 6px rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.attack-chain-filters select:hover {
|
||||
border-color: rgba(0, 102, 255, 0.3);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
border-color: rgba(99, 102, 241, 0.4);
|
||||
box-shadow: 0 2px 4px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.attack-chain-filters button.btn-secondary {
|
||||
padding: 6px 14px;
|
||||
padding: 7px 16px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid rgba(15, 23, 42, 0.1);
|
||||
border-radius: 10px;
|
||||
background: #ffffff;
|
||||
color: var(--text-primary);
|
||||
color: #0f172a;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attack-chain-filters button.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.08) 0%, rgba(0, 102, 255, 0.04) 100%);
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 102, 255, 0.15);
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(59, 130, 246, 0.06) 100%);
|
||||
border-color: #6366f1;
|
||||
color: #4338ca;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 14px rgba(99, 102, 241, 0.18);
|
||||
}
|
||||
|
||||
.attack-chain-action-btn {
|
||||
padding: 10px 18px !important;
|
||||
font-size: 0.875rem !important;
|
||||
padding: 9px 16px !important;
|
||||
font-size: 0.8125rem !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: 10px !important;
|
||||
transition: all 0.25s ease !important;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important;
|
||||
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease !important;
|
||||
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.08) !important;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.14) !important;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 16px 20px !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06) !important;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%) !important;
|
||||
padding: 16px 22px !important;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.07) !important;
|
||||
background:
|
||||
linear-gradient(180deg, #ffffff 0%, #f8fafc 100%) !important;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
right: 22px;
|
||||
bottom: -1px;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(99, 102, 241, 0.25) 50%, transparent 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.25rem !important;
|
||||
font-size: 1.2rem !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.3px !important;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #4a4a4a 100%);
|
||||
background: linear-gradient(135deg, #0f172a 0%, #334155 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.modal-header h2::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 22px;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(180deg, #3b82f6 0%, #6366f1 60%, #a855f7 100%);
|
||||
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4);
|
||||
-webkit-text-fill-color: initial;
|
||||
}
|
||||
|
||||
.attack-chain-details {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
margin: 4px 16px 16px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fafbff 100%);
|
||||
border: 1px solid rgba(99, 102, 241, 0.15);
|
||||
border-radius: 14px;
|
||||
box-shadow:
|
||||
0 4px 14px rgba(15, 23, 42, 0.06),
|
||||
0 1px 2px rgba(15, 23, 42, 0.03);
|
||||
transition: opacity 0.2s ease;
|
||||
overflow: hidden;
|
||||
will-change: opacity;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ========== 节点详情独占态:点击节点后详情占满 sidebar,图例隐藏 ========== */
|
||||
/* 激活态:隐藏图例的两个辅助 section(风险等级、连线含义),保留详情 */
|
||||
.attack-chain-sidebar.details-active
|
||||
.attack-chain-legend > .legend-section:not(.attack-chain-details) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 激活态:让 legend 容器成为可撑满的 flex 列,详情占满剩余空间 */
|
||||
.attack-chain-sidebar.details-active .attack-chain-legend {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 激活态:详情面板填满 sidebar 全高 */
|
||||
.attack-chain-sidebar.details-active .attack-chain-details {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
margin: 14px 14px 16px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* 激活态:详情内容区占满剩余空间可滚动 */
|
||||
.attack-chain-sidebar.details-active .attack-chain-details-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.attack-chain-details::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #3b82f6 0%, #6366f1 50%, #a855f7 100%);
|
||||
border-radius: 14px 14px 0 0;
|
||||
}
|
||||
|
||||
.attack-chain-details[style*="display: none"] {
|
||||
@@ -6515,8 +6669,8 @@ header {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0 !important;
|
||||
padding-bottom: 8px !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
padding-bottom: 10px !important;
|
||||
border-bottom: 1px dashed rgba(15, 23, 42, 0.1) !important;
|
||||
}
|
||||
|
||||
.attack-chain-details-title span {
|
||||
@@ -6524,32 +6678,33 @@ header {
|
||||
}
|
||||
|
||||
.attack-chain-details-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
color: #64748b;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
background: rgba(15, 23, 42, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.6;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.attack-chain-details-close:hover {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
color: var(--text-primary);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
opacity: 1;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.attack-chain-details-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -6563,69 +6718,73 @@ header {
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
background: transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
background: rgba(15, 23, 42, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.attack-chain-details-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
}
|
||||
|
||||
.node-detail-item {
|
||||
margin-bottom: 0;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
padding: 10px 12px;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.65;
|
||||
padding: 10px 14px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.node-detail-item:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-color: rgba(0, 102, 255, 0.2);
|
||||
border-color: rgba(99, 102, 241, 0.3);
|
||||
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.node-detail-item strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-secondary);
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
.node-detail-item code {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
color: #4338ca;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 5px;
|
||||
font-size: 0.8rem;
|
||||
word-break: break-all;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
color: var(--text-primary);
|
||||
font-family: 'SFMono-Regular', 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
border: 1px solid rgba(99, 102, 241, 0.15);
|
||||
}
|
||||
|
||||
.node-detail-item pre {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.78rem;
|
||||
overflow-x: auto;
|
||||
margin: 8px 0 0 0;
|
||||
line-height: 1.5;
|
||||
line-height: 1.6;
|
||||
max-width: 100%;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
font-family: 'SFMono-Regular', 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
border: 1px solid rgba(15, 23, 42, 0.2);
|
||||
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.node-detail-item ul {
|
||||
@@ -6683,7 +6842,7 @@ header {
|
||||
|
||||
.modal-header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -6692,46 +6851,86 @@ header {
|
||||
padding: 8px 16px !important;
|
||||
font-size: 0.8125rem !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: 8px !important;
|
||||
transition: all 0.25s ease !important;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08) !important;
|
||||
border: 2px solid transparent !important;
|
||||
border-radius: 10px !important;
|
||||
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease !important;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06) !important;
|
||||
border: 1.5px solid transparent !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
gap: 7px !important;
|
||||
letter-spacing: 0.2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.15) 0%, transparent 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn:active {
|
||||
transform: translateY(0) !important;
|
||||
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.1) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary {
|
||||
background: linear-gradient(135deg, #0066ff 0%, #0052cc 100%) !important;
|
||||
color: white !important;
|
||||
border-color: #0066ff !important;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #6366f1 50%, #8b5cf6 100%) !important;
|
||||
color: #ffffff !important;
|
||||
border-color: transparent !important;
|
||||
box-shadow:
|
||||
0 4px 14px rgba(99, 102, 241, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.25) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0052cc 0%, #0040a3 100%) !important;
|
||||
box-shadow: 0 6px 20px rgba(0, 102, 255, 0.3) !important;
|
||||
background: linear-gradient(135deg, #2563eb 0%, #4f46e5 50%, #7c3aed 100%) !important;
|
||||
box-shadow:
|
||||
0 10px 22px rgba(99, 102, 241, 0.42),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-secondary {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-color: rgba(0, 0, 0, 0.1) !important;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%) !important;
|
||||
color: #334155 !important;
|
||||
border-color: rgba(15, 23, 42, 0.1) !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-secondary:hover {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f5 100%) !important;
|
||||
border-color: var(--accent-color) !important;
|
||||
color: var(--accent-color) !important;
|
||||
background: linear-gradient(180deg, #eef2ff 0%, #e0e7ff 100%) !important;
|
||||
border-color: #6366f1 !important;
|
||||
color: #4338ca !important;
|
||||
}
|
||||
|
||||
.attack-chain-action-btn.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #0052cc 0%, #0040a3 100%);
|
||||
border-color: #0052cc;
|
||||
.modal-header-actions .modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(15, 23, 42, 0.04);
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
border: 1.5px solid transparent;
|
||||
}
|
||||
|
||||
.modal-header-actions .modal-close:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
@@ -6739,8 +6938,28 @@ header {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
color: #64748b;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.attack-chain-container .loading-spinner::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2.5px solid rgba(99, 102, 241, 0.2);
|
||||
border-top-color: #6366f1;
|
||||
animation: ac-spin 0.9s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes ac-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
@@ -6748,8 +6967,11 @@ header {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
color: #64748b;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
@@ -6757,9 +6979,12 @@ header {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--error-color);
|
||||
font-size: 1rem;
|
||||
color: #ef4444;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* ==================== 知识管理样式 ==================== */
|
||||
|
||||
@@ -546,6 +546,17 @@
|
||||
"typeCustom": "Custom",
|
||||
"cmdParam": "Command parameter name",
|
||||
"cmdParamPlaceholder": "Leave empty for cmd; e.g. xxx for xxx=command",
|
||||
"encoding": "Response encoding",
|
||||
"encodingAuto": "Auto detect",
|
||||
"encodingUtf8": "UTF-8",
|
||||
"encodingGbk": "GBK (Simplified Chinese Windows)",
|
||||
"encodingGb18030": "GB18030",
|
||||
"encodingHint": "Switch to GBK or GB18030 if the Simplified Chinese Windows target shows garbled output.",
|
||||
"os": "Target OS",
|
||||
"osAuto": "Auto (infer from Shell type)",
|
||||
"osLinux": "Linux / Unix",
|
||||
"osWindows": "Windows",
|
||||
"osHint": "Determines whether file manager / uploads use Linux or Windows commands. Choose Windows for PHP/JSP hosted on Windows.",
|
||||
"remark": "Remark",
|
||||
"remarkPlaceholder": "Friendly name for this connection",
|
||||
"deleteConfirm": "Delete this connection?",
|
||||
|
||||
@@ -535,6 +535,17 @@
|
||||
"typeCustom": "自定义",
|
||||
"cmdParam": "命令参数名",
|
||||
"cmdParamPlaceholder": "不填默认为 cmd,如填 xxx 则请求为 xxx=命令",
|
||||
"encoding": "响应编码",
|
||||
"encodingAuto": "自动检测",
|
||||
"encodingUtf8": "UTF-8",
|
||||
"encodingGbk": "GBK(中文 Windows)",
|
||||
"encodingGb18030": "GB18030",
|
||||
"encodingHint": "中文 Windows 目标若出现乱码,请切换为 GBK 或 GB18030",
|
||||
"os": "目标系统",
|
||||
"osAuto": "自动(按 Shell 类型推断)",
|
||||
"osLinux": "Linux / Unix",
|
||||
"osWindows": "Windows",
|
||||
"osHint": "决定文件管理/上传使用 Linux 还是 Windows 命令;PHP/JSP 跑在 Windows 上请选 Windows",
|
||||
"remark": "备注",
|
||||
"remarkPlaceholder": "便于识别的备注名",
|
||||
"deleteConfirm": "确定要删除该连接吗?",
|
||||
|
||||
+1123
-651
File diff suppressed because it is too large
Load Diff
+149
-28
@@ -39,6 +39,100 @@ let webshellStreamingTypingId = 0;
|
||||
let webshellProbeStatusById = {};
|
||||
let webshellBatchProbeRunning = false;
|
||||
|
||||
/** 允许的响应编码,与后端 normalizeWebshellEncoding 对齐 */
|
||||
const WEBSHELL_ALLOWED_ENCODINGS = ['auto', 'utf-8', 'gbk', 'gb18030'];
|
||||
|
||||
/** 归一化连接的 encoding 字段,返回 'auto' | 'utf-8' | 'gbk' | 'gb18030'(空/未知 → auto) */
|
||||
function normalizeWebshellEncoding(v) {
|
||||
var s = (v == null ? '' : String(v)).trim().toLowerCase();
|
||||
if (s === 'utf8') s = 'utf-8';
|
||||
if (!s) return 'auto';
|
||||
return WEBSHELL_ALLOWED_ENCODINGS.indexOf(s) >= 0 ? s : 'auto';
|
||||
}
|
||||
|
||||
/** 从连接对象取编码,便于透传到 /api/webshell/exec 与 /api/webshell/file */
|
||||
function webshellConnEncoding(conn) {
|
||||
return normalizeWebshellEncoding(conn && conn.encoding);
|
||||
}
|
||||
|
||||
/** 允许的目标 OS,与后端 normalizeWebshellOS 对齐 */
|
||||
const WEBSHELL_ALLOWED_OS = ['auto', 'linux', 'windows'];
|
||||
|
||||
/** 归一化连接的 os 字段,返回 'auto' | 'linux' | 'windows'(空/未知 → auto) */
|
||||
function normalizeWebshellOS(v) {
|
||||
var s = (v == null ? '' : String(v)).trim().toLowerCase();
|
||||
if (!s) return 'auto';
|
||||
return WEBSHELL_ALLOWED_OS.indexOf(s) >= 0 ? s : 'auto';
|
||||
}
|
||||
|
||||
/** 从连接对象取目标 OS,便于透传到 /api/webshell/exec 与 /api/webshell/file */
|
||||
function webshellConnOS(conn) {
|
||||
return normalizeWebshellOS(conn && conn.os);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 /api/webshell/file 的公共请求体。
|
||||
* 所有文件管理调用点都应走此函数,避免遗漏字段(如 connection_id)。
|
||||
* @param {Object} conn 连接对象
|
||||
* @param {Object} extra 额外字段(action / path / content / target_path / chunk_index ...)
|
||||
* @returns {string} JSON 字符串
|
||||
*/
|
||||
function webshellFileRequestBody(conn, extra) {
|
||||
const base = {
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || ''
|
||||
};
|
||||
const merged = Object.assign(base, extra || {});
|
||||
return JSON.stringify(merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当服务端探活命中目标系统(仅 auto 连接首次列目录时出现)时,
|
||||
* 把结果同步到本地 webshellConnections 缓存 + 持久化到数据库。
|
||||
* 后续刷新不再探活,AI 也能直接看到正确的 OS 上下文。
|
||||
*/
|
||||
function applyWebshellDetectedOS(conn, data) {
|
||||
if (!conn || !data || !data.detected_os) return;
|
||||
const detected = normalizeWebshellOS(data.detected_os);
|
||||
if (detected !== 'linux' && detected !== 'windows') return;
|
||||
if (webshellConnOS(conn) !== 'auto') return; // 用户已显式配置,尊重之
|
||||
conn.os = detected;
|
||||
if (Array.isArray(webshellConnections)) {
|
||||
for (var i = 0; i < webshellConnections.length; i++) {
|
||||
if (webshellConnections[i] && webshellConnections[i].id === conn.id) {
|
||||
webshellConnections[i].os = detected;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof renderWebshellList === 'function') {
|
||||
try { renderWebshellList(); } catch (e) {}
|
||||
}
|
||||
// 服务端已经回写了 DB;但极少数情况下调用方未带 connection_id,这里再兜底 PUT 一次
|
||||
if (conn.id && typeof apiFetch === 'function') {
|
||||
apiFetch('/api/webshell/connections/' + encodeURIComponent(conn.id), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: conn.method || 'post',
|
||||
cmd_param: conn.cmdParam || '',
|
||||
remark: conn.remark || '',
|
||||
encoding: conn.encoding || 'auto',
|
||||
os: detected
|
||||
})
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
/** 与主对话页一致:Eino 模式走 /api/multi-agent/stream,body 带 orchestration */
|
||||
function resolveWebshellAiStreamRequest() {
|
||||
if (typeof apiFetch === 'undefined') {
|
||||
@@ -335,6 +429,17 @@ function wsT(key) {
|
||||
'webshell.addConnection': '添加连接',
|
||||
'webshell.cmdParam': '命令参数名',
|
||||
'webshell.cmdParamPlaceholder': '不填默认为 cmd,如填 xxx 则请求为 xxx=命令',
|
||||
'webshell.encoding': '响应编码',
|
||||
'webshell.encodingAuto': '自动检测',
|
||||
'webshell.encodingUtf8': 'UTF-8',
|
||||
'webshell.encodingGbk': 'GBK(中文 Windows)',
|
||||
'webshell.encodingGb18030': 'GB18030',
|
||||
'webshell.encodingHint': '中文 Windows 目标若出现乱码,请切换为 GBK 或 GB18030',
|
||||
'webshell.os': '目标系统',
|
||||
'webshell.osAuto': '自动(按 Shell 类型推断)',
|
||||
'webshell.osLinux': 'Linux / Unix',
|
||||
'webshell.osWindows': 'Windows',
|
||||
'webshell.osHint': '决定文件管理/上传使用 Linux 还是 Windows 命令;PHP/JSP 跑在 Windows 上请选 Windows',
|
||||
'webshell.connections': '连接列表',
|
||||
'webshell.noConnections': '暂无连接,请点击「添加连接」',
|
||||
'webshell.selectOrAdd': '请从左侧选择连接,或添加新的 WebShell 连接',
|
||||
@@ -661,9 +766,20 @@ function renderWebshellList() {
|
||||
} else if (probe && probe.state === 'fail') {
|
||||
probeHtml = '<span class="webshell-probe-badge fail" title="' + escapeHtml(probe.message || '') + '">' + (wsT('webshell.probeOffline') || '离线') + '</span>';
|
||||
}
|
||||
var encNorm = normalizeWebshellEncoding(conn.encoding);
|
||||
var encHtml = '';
|
||||
if (encNorm && encNorm !== 'auto') {
|
||||
encHtml = '<span class="webshell-probe-badge" title="' + escapeHtml(wsT('webshell.encoding') || '响应编码') + '">' + escapeHtml(encNorm.toUpperCase()) + '</span>';
|
||||
}
|
||||
var osNorm = normalizeWebshellOS(conn.os);
|
||||
var osHtml = '';
|
||||
if (osNorm && osNorm !== 'auto') {
|
||||
var osLabel = osNorm === 'windows' ? 'WIN' : 'LINUX';
|
||||
osHtml = '<span class="webshell-probe-badge" title="' + escapeHtml(wsT('webshell.os') || '目标系统') + '">' + osLabel + '</span>';
|
||||
}
|
||||
return (
|
||||
'<div class="webshell-item' + active + '" data-id="' + safeId + '">' +
|
||||
'<div class="webshell-item-remark-row"><div class="webshell-item-remark" title="' + urlTitle + '">' + remark + '</div>' + probeHtml + '</div>' +
|
||||
'<div class="webshell-item-remark-row"><div class="webshell-item-remark" title="' + urlTitle + '">' + remark + '</div>' + probeHtml + osHtml + encHtml + '</div>' +
|
||||
'<div class="webshell-item-url" title="' + urlTitle + '">' + url + '</div>' +
|
||||
'<div class="webshell-item-actions">' +
|
||||
'<details class="webshell-conn-actions"><summary class="btn-ghost btn-sm webshell-conn-actions-btn" title="' + actionsLabel + '">' + actionsLabel + '</summary>' +
|
||||
@@ -709,6 +825,8 @@ function probeWebshellConnection(conn) {
|
||||
type: conn.type || 'php',
|
||||
method: ((conn.method || 'post').toLowerCase() === 'get') ? 'get' : 'post',
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
command: 'echo 1'
|
||||
})
|
||||
})
|
||||
@@ -3365,6 +3483,8 @@ function execWebshellCommand(conn, command) {
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
command: command
|
||||
})
|
||||
}).then(function (r) { return r.json(); })
|
||||
@@ -3391,17 +3511,10 @@ function webshellFileListDir(conn, path) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
action: 'list',
|
||||
path: path
|
||||
})
|
||||
body: webshellFileRequestBody(conn, { action: 'list', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
applyWebshellDetectedOS(conn, data);
|
||||
if (!data.ok && data.error) {
|
||||
listEl.innerHTML = '<div class="webshell-file-error">' + escapeHtml(data.error) + '</div><pre class="webshell-file-raw">' + escapeHtml(data.output || '') + '</pre>';
|
||||
return;
|
||||
@@ -3497,16 +3610,9 @@ function fetchWebshellDirectoryItems(conn, path) {
|
||||
return apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
action: 'list',
|
||||
path: path
|
||||
})
|
||||
body: webshellFileRequestBody(conn, { action: 'list', path: path })
|
||||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||||
applyWebshellDetectedOS(conn, data);
|
||||
if (!data || data.error || !data.ok) return [];
|
||||
return parseWebshellListItems(data.output || '');
|
||||
}).catch(function () {
|
||||
@@ -3801,7 +3907,7 @@ function webshellFileMkdir(conn, pathInput) {
|
||||
var name = prompt(wsT('webshell.newDir') || '新建目录', 'newdir');
|
||||
if (name == null || !name.trim()) return;
|
||||
var path = base === '.' ? name.trim() : base + '/' + name.trim();
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'mkdir', path: path }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'mkdir', path: path }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { webshellFileListDir(conn, base); })
|
||||
.catch(function () { webshellFileListDir(conn, base); });
|
||||
@@ -3848,7 +3954,7 @@ function webshellFileUpload(conn, pathInput) {
|
||||
webshellFileListDir(conn, base);
|
||||
return;
|
||||
}
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'upload_chunk', path: path, content: base64Chunks[idx], chunk_index: idx }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'upload_chunk', path: path, content: base64Chunks[idx], chunk_index: idx }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { idx++; sendNext(); })
|
||||
.catch(function () { idx++; sendNext(); });
|
||||
@@ -3867,7 +3973,7 @@ function webshellFileRename(conn, oldPath, oldName, listEl) {
|
||||
var parts = oldPath.split('/');
|
||||
var dir = parts.length > 1 ? parts.slice(0, -1).join('/') + '/' : '';
|
||||
var newPath = dir + newName.trim();
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'rename', path: oldPath, target_path: newPath }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'rename', path: oldPath, target_path: newPath }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { webshellFileListDir(conn, document.getElementById('webshell-file-path').value.trim() || '.'); })
|
||||
.catch(function () { webshellFileListDir(conn, document.getElementById('webshell-file-path').value.trim() || '.'); });
|
||||
@@ -3906,7 +4012,7 @@ function webshellFileDownload(conn, path) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
var content = (data && data.output) != null ? data.output : (data.error || '');
|
||||
@@ -3927,7 +4033,7 @@ function webshellFileRead(conn, path, listEl, browsePath) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
const out = (data && data.output) ? data.output : (data.error || '');
|
||||
@@ -3956,7 +4062,7 @@ function webshellFileEdit(conn, path, listEl) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
const content = (data && data.output) ? data.output : (data.error || '');
|
||||
@@ -3992,7 +4098,7 @@ function webshellFileWrite(conn, path, content, onDone, listEl) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'write', path: path, content: content })
|
||||
body: webshellFileRequestBody(conn, { action: 'write', path: path, content: content })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (data && !data.ok && data.error && listEl) {
|
||||
@@ -4011,7 +4117,7 @@ function webshellFileDelete(conn, path, onDone) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'delete', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'delete', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function () { if (onDone) onDone(); })
|
||||
.catch(function () { if (onDone) onDone(); });
|
||||
@@ -4063,6 +4169,10 @@ function showAddWebshellModal() {
|
||||
document.getElementById('webshell-type').value = 'php';
|
||||
document.getElementById('webshell-method').value = 'post';
|
||||
document.getElementById('webshell-cmd-param').value = '';
|
||||
var osSelEl = document.getElementById('webshell-os');
|
||||
if (osSelEl) osSelEl.value = 'auto';
|
||||
var encSelEl = document.getElementById('webshell-encoding');
|
||||
if (encSelEl) encSelEl.value = 'auto';
|
||||
document.getElementById('webshell-remark').value = '';
|
||||
var titleEl = document.getElementById('webshell-modal-title');
|
||||
if (titleEl) titleEl.textContent = wsT('webshell.addConnection');
|
||||
@@ -4081,6 +4191,10 @@ function showEditWebshellModal(connId) {
|
||||
document.getElementById('webshell-type').value = conn.type || 'php';
|
||||
document.getElementById('webshell-method').value = (conn.method || 'post').toLowerCase();
|
||||
document.getElementById('webshell-cmd-param').value = conn.cmdParam || '';
|
||||
var osEditEl = document.getElementById('webshell-os');
|
||||
if (osEditEl) osEditEl.value = normalizeWebshellOS(conn.os);
|
||||
var encEditEl = document.getElementById('webshell-encoding');
|
||||
if (encEditEl) encEditEl.value = normalizeWebshellEncoding(conn.encoding);
|
||||
document.getElementById('webshell-remark').value = conn.remark || '';
|
||||
var titleEl = document.getElementById('webshell-modal-title');
|
||||
if (titleEl) titleEl.textContent = wsT('webshell.editConnectionTitle');
|
||||
@@ -4308,6 +4422,8 @@ function testWebshellConnection() {
|
||||
var method = ((document.getElementById('webshell-method') || {}).value || 'post').toLowerCase();
|
||||
var cmdParam = (document.getElementById('webshell-cmd-param') || {}).value;
|
||||
if (cmdParam && typeof cmdParam.trim === 'function') cmdParam = cmdParam.trim(); else cmdParam = '';
|
||||
var osTag = normalizeWebshellOS((document.getElementById('webshell-os') || {}).value);
|
||||
var encoding = normalizeWebshellEncoding((document.getElementById('webshell-encoding') || {}).value);
|
||||
var btn = document.getElementById('webshell-test-btn');
|
||||
if (btn) { btn.disabled = true; btn.textContent = (typeof wsT === 'function' ? wsT('common.refresh') : '刷新') + '...'; }
|
||||
if (typeof apiFetch === 'undefined') {
|
||||
@@ -4315,6 +4431,7 @@ function testWebshellConnection() {
|
||||
alert(wsT('webshell.testFailed') || '连通性测试失败');
|
||||
return;
|
||||
}
|
||||
// 连通性使用 Windows/Linux 都识别的最小内建命令作为探测(echo 1 在 cmd 和 sh 下行为等价)
|
||||
apiFetch('/api/webshell/exec', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -4324,6 +4441,8 @@ function testWebshellConnection() {
|
||||
type: type,
|
||||
method: method === 'get' ? 'get' : 'post',
|
||||
cmd_param: cmdParam || '',
|
||||
encoding: encoding,
|
||||
os: osTag,
|
||||
command: 'echo 1'
|
||||
})
|
||||
})
|
||||
@@ -4369,12 +4488,14 @@ function saveWebshellConnection() {
|
||||
var method = ((document.getElementById('webshell-method') || {}).value || 'post').toLowerCase();
|
||||
var cmdParam = (document.getElementById('webshell-cmd-param') || {}).value;
|
||||
if (cmdParam && typeof cmdParam.trim === 'function') cmdParam = cmdParam.trim(); else cmdParam = '';
|
||||
var osTag = normalizeWebshellOS((document.getElementById('webshell-os') || {}).value);
|
||||
var encoding = normalizeWebshellEncoding((document.getElementById('webshell-encoding') || {}).value);
|
||||
var remark = (document.getElementById('webshell-remark') || {}).value;
|
||||
if (remark && typeof remark.trim === 'function') remark = remark.trim(); else remark = '';
|
||||
|
||||
var editIdEl = document.getElementById('webshell-edit-id');
|
||||
var editId = editIdEl ? editIdEl.value.trim() : '';
|
||||
var body = { url: url, password: password, type: type, method: method === 'get' ? 'get' : 'post', cmd_param: cmdParam, remark: remark || url };
|
||||
var body = { url: url, password: password, type: type, method: method === 'get' ? 'get' : 'post', cmd_param: cmdParam, encoding: encoding, os: osTag, remark: remark || url };
|
||||
if (typeof apiFetch === 'undefined') return;
|
||||
|
||||
var reqUrl = editId ? ('/api/webshell/connections/' + encodeURIComponent(editId)) : '/api/webshell/connections';
|
||||
|
||||
@@ -2925,6 +2925,25 @@
|
||||
<label for="webshell-cmd-param" data-i18n="webshell.cmdParam">命令参数名</label>
|
||||
<input type="text" id="webshell-cmd-param" data-i18n="webshell.cmdParamPlaceholder" data-i18n-attr="placeholder" placeholder="不填默认为 cmd,如 xxx 则请求为 xxx=命令" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webshell-os" data-i18n="webshell.os">目标系统</label>
|
||||
<select id="webshell-os">
|
||||
<option value="auto" data-i18n="webshell.osAuto">自动(按 Shell 类型推断)</option>
|
||||
<option value="linux" data-i18n="webshell.osLinux">Linux / Unix</option>
|
||||
<option value="windows" data-i18n="webshell.osWindows">Windows</option>
|
||||
</select>
|
||||
<small class="form-hint" data-i18n="webshell.osHint">决定文件管理/上传使用 Linux 还是 Windows 命令;PHP/JSP 跑在 Windows 上请选 Windows</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webshell-encoding" data-i18n="webshell.encoding">响应编码</label>
|
||||
<select id="webshell-encoding">
|
||||
<option value="auto" data-i18n="webshell.encodingAuto">自动检测</option>
|
||||
<option value="utf-8" data-i18n="webshell.encodingUtf8">UTF-8</option>
|
||||
<option value="gbk" data-i18n="webshell.encodingGbk">GBK(中文 Windows)</option>
|
||||
<option value="gb18030" data-i18n="webshell.encodingGb18030">GB18030</option>
|
||||
</select>
|
||||
<small class="form-hint" data-i18n="webshell.encodingHint">中文 Windows 目标若出现乱码,请切换为 GBK 或 GB18030</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webshell-remark" data-i18n="webshell.remark">备注</label>
|
||||
<input type="text" id="webshell-remark" data-i18n="webshell.remarkPlaceholder" data-i18n-attr="placeholder" placeholder="便于识别的备注名" />
|
||||
|
||||
Reference in New Issue
Block a user