关联分析-攻击链路展示

业务逻辑:根据左侧的攻击链路展示对应的场景溯源报告,切换链路展示对应的链路拓扑视图;视图中的节点与溯源以及所处的攻击阶段相关联,切换不同的攻击阶段,对应的告警溯源流会发生变化

界面展示效果

界面实现

<template>
  <div class="attack-chain-page">
    <!-- 左侧:攻击链列表 -->
    <div class="left-sidebar">
      <div class="sidebar-header">
        <span class="sidebar-title">攻击链列表 ({
  
  { chainTotal }})</span>
        <div class="filter-tools">
          <el-icon class="icon-btn"><Search /></el-icon>
          <el-popover
            placement="bottom"
            :width="320"
            trigger="click"
            v-model:visible="filterVisible"
            @show="resetNewFilterForm"
          >
            <template #reference>
              <el-button link type="primary" size="small">筛选</el-button>
            </template>
            <el-form
              :model="filterForm"
              label-position="top"
              size="small"
              ref="newFilterFormRef"
            >
              <el-form-item label="时间范围">
                <el-date-picker
                  v-model="dateRange"
                  type="datetimerange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  style="width: 100%"
                  value-format="YYYY-MM-DD HH:mm:ss"
                  :teleported="false"
                  :shortcuts="timeShortcuts"
                />
              </el-form-item>
              <el-form-item label="页数选择">
                <el-select
                  v-model="filterForm.pageSize"
                  placeholder="请选择"
                  style="width: 100%"
                  :teleported="false"
                >
                  <el-option label="10条/页" :value="10" />
                  <el-option label="20条/页" :value="20" />
                  <el-option label="50条/页" :value="50" />
                </el-select>
              </el-form-item>
              <el-form-item label="攻击方向">
                <el-select
                  v-model="filterForm.direction"
                  placeholder="请选择"
                  style="width: 100%"
                  clearable
                  :teleported="false"
                >
                  <el-option label="全部" value="" />
                  <el-option label="外网 → 内网" value="1" />
                  <el-option label="内网 → 外网" value="2" />
                  <el-option label="内网横向" value="3" />
                </el-select>
              </el-form-item>
            </el-form>
            <div class="popover-footer" style="float: right">
              <el-button size="small" @click="resetForm">重置</el-button>
              <el-button size="small" type="primary" @click="blurPopover"
                >确定</el-button
              >
            </div>
          </el-popover>
        </div>
      </div>

      <div class="filter-tags" v-if="selectedFilters.length">
        <el-tag
          v-for="item in selectedFilters"
          :key="item.id"
          size="small"
          closable
          @close.stop="removeSelectedFilter(item.id)"
        >
          {
  
  { item.tagText || item.label }}
        </el-tag>
      </div>

      <div class="filter-panel" v-if="showFilterPanel">
        <!-- <el-button
          type="primary"
          class="new-filter-btn"
          @click.stop="openNewFilterDialog"
          >+ 新建过滤器</el-button
        >

        <div class="filter-section">
          <div class="section-title">快捷预设</div>
          <div
            v-for="item in quickFilters"
            :key="item.id"
            class="filter-item"
            :class="{ active: isFilterSelected(item.id) }"
            @click="selectFilter(item)"
          >
            <span class="label">{
  
  { item.label }}</span>
            <span class="extra" v-if="item.extra">{
  
  { item.extra }}</span>
            <span class="dot" v-if="item.showDot"></span>
          </div>
        </div>

        <div class="filter-section">
          <div class="section-title">业务维度</div>
          <div
            v-for="item in businessFilters"
            :key="item.id"
            class="filter-item"
            :class="{ active: isFilterSelected(item.id) }"
            @click="selectFilter(item)"
          >
            <span class="label">{
  
  { item.label }}</span>
            <span class="en" v-if="item.enText">{
  
  { item.enText }}</span>
          </div>
        </div>

        <div class="filter-section">
          <div class="section-title">我的过滤器</div>
          <div v-if="!customFilters.length" class="empty-custom-filter">
            暂无自定义过滤器
          </div>
          <div v-else>
            <div
              v-for="item in customFilters"
              :key="item.id"
              class="filter-item"
              :class="{ active: isFilterSelected(item.id) }"
              @click="selectFilter(item)"
            >
              <span class="label">{
  
  { item.label }}</span>
            </div>
          </div>
        </div> -->
      </div>

      <div class="chain-list" @scroll="handleChainListScroll">
        <div v-if="chainList.length">
          <div
            class="chain-card"
            v-for="(item, idx) in chainList"
            :key="idx"
            @click="handleChainClick(item)"
            :class="{ active: activeChainIndex === idx }"
          >
            <div class="card-header">
              <span class="chain-time">{
  
  { item.createTime }}</span>
            </div>
            <div class="chain-name">{
  
  { item.chainName }}</div>
            <div class="card-tags">
              <el-tag
                size="small"
                :class="'tag-' + getAlertLevelClass(item.alertLevel)"
                >{
  
  { item.alertLevel }}</el-tag
              >
              <span class="alert-count">告警: {
  
  { item.alertCount }}</span>
            </div>
          </div>
        </div>
        <div v-else><el-empty description="暂无数据" /></div>
      </div>
    </div>

    <!-- 中间:拓扑分析区 -->
    <div class="center-panel">
      <!-- 顶部:场景描述与进度 -->
      <div class="panel-header">
        <div class="scene-info">
          <div class="scene-title">
            <span class="title-bar">|</span> 攻击场景溯源分析报告
          </div>
          <div class="scene-desc">
            <span class="label">场景描述:</span>
            {
  
  {
              currentChain && currentChain.attackDescription
                ? currentChain.attackDescription
                : "_"
            }}
          </div>
          <div class="scene-meta">
            <span
              >持续时间:<span class="chainValue">{
  
  {
                currentChain &&
                currentChain.timeline &&
                currentChain.timeline.durationSeconds
                  ? formatDuration(currentChain.timeline.durationSeconds)
                  : "_"
              }}</span></span
            >
            <span
              >受影响资产:<span class="chainValue"
                >{
  
  {
                  currentChain && currentChain.assetList
                    ? currentChain.assetList.length
                    : ""
                }}台<span
                  style="margin: 0 2px"
                  v-if="
                    currentChain &&
                    currentChain.assetList &&
                    currentChain.assetList.length
                  "
                  >({
  
  {
                    currentChain && currentChain.assetList
                      ? currentChain.assetList.join(",")
                      : "_"
                  }})</span
                ></span
              >
            </span>
            <span
              >攻击阶段:<span class="chainValue"
                >{
  
  {
                  currentChain && currentChain.attackPhaseList
                    ? currentChain.attackPhaseList.length
                    : "_"
                }}个阶段</span
              ></span
            >
            <span
              >危害等级:
              <el-tag
                size="small"
                :class="'tag-' + getAlertLevelClass(currentChain.riskLevel)"
                >{
  
  {
                  currentChain && currentChain.riskLevel
                    ? currentChain.riskLevel
                    : "_"
                }}</el-tag
              >
              <!-- <span class="danger-text">{
  
  {
                currentChain && currentChain.riskLevel
                  ? currentChain.riskLevel
                  : "_"
              }}</span> -->
            </span>
          </div>
        </div>

        <div class="stage-progress">
          <div class="stage-track">
            <!-- 'is-disabled': !(
                  currentChain &&
                  currentChain.attackPhaseList &&
                  currentChain.attackPhaseList.includes(stage.value)
                ), -->
            <!-- 'has-data':
                  currentChain &&
                  currentChain.attackPhaseList &&
                  currentChain.attackPhaseList.includes(stage.value), -->
            <div
              class="stage-item"
              v-for="(stage, index) in stages"
              :key="index"
              :class="['has-data', currentStage === index ? 'is-selected' : '']"
              @click="handleStageClick(index, stage.value)"
            >
              <div class="stage-content-box">
                <div class="stage-dot"></div>
                <div class="stage-name">{
  
  { stage.name }}</div>
              </div>
              <div class="stage-line" v-if="index < stages.length - 1"></div>
            </div>
          </div>
        </div>

        <div class="view-tabs">
          <div
            class="tab-item"
            :class="{ active: currentTab === 'topology' }"
            @click="currentTab = 'topology'"
          >
            <el-icon class="tab-icon"><Connection /></el-icon>
            攻击链路拓扑视图
          </div>
          <div
            class="tab-item"
            :class="{ active: currentTab === 'mitre' }"
            @click="currentTab = 'mitre'"
          >
            <el-icon class="tab-icon"><Grid /></el-icon>
            MITRE ATT&CK 矩阵
          </div>
        </div>
      </div>

      <!-- 拓扑图区域 -->
      <div class="panel-body" v-if="currentTab === 'topology'">
        <div class="graph-area">
          <div
            class="graph-viewport"
            ref="graphViewport"
            @mousedown="startDrag"
            @mousemove="onDrag"
            @mouseup="endDrag"
            @mouseleave="endDrag"
            @wheel.prevent="onWheel"
          >
            <div class="graph-content" :style="graphContentStyle">
              <svg
                class="graph-svg"
                :width="canvas.width"
                :height="canvas.height"
              >
                <!-- 箭头区域 -->
                <defs>
                  <!-- <marker
                    id="arrow"
                    markerWidth="10"
                    markerHeight="10"
                    refX="18"
                    refY="3"
                    orient="auto"
                    markerUnits="strokeWidth"
                  >
                    <path d="M0,0 L0,6 L9,3 z" fill="#c0c4cc" />
                  </marker> -->
                  <!-- <marker
                    id="arrow-blue"
                    markerWidth="10"
                    markerHeight="10"
                    refX="18"
                    refY="3"
                    orient="auto"
                    markerUnits="strokeWidth"
                  >
                    <path d="M0,0 L0,6 L9,3 z" fill="#409eff" />
                  </marker> -->
                  <!-- 红色箭头 (Critical) -->
                  <!-- <marker
                    id="arrow-critical"
                    markerWidth="10"
                    markerHeight="10"
                    refX="18"
                    refY="3"
                    orient="auto"
                    markerUnits="strokeWidth"
                  >
                    <path d="M0,0 L0,6 L9,3 z" fill="#f56c6c" />
                  </marker> -->
                  <!-- 橙色箭头 (High) -->
                  <!-- <marker
                    id="arrow-high"
                    markerWidth="10"
                    markerHeight="10"
                    refX="18"
                    refY="3"
                    orient="auto"
                    markerUnits="strokeWidth"
                  >
                    <path d="M0,0 L0,6 L9,3 z" fill="#e6a23c" />
                  </marker> -->
                  <!-- 动态流光效果的渐变定义 -->
                  <linearGradient
                    id="flowGradient"
                    x1="0%"
                    y1="0%"
                    x2="100%"
                    y2="0%"
                  >
                    <stop offset="0%" stop-color="transparent" />
                    <stop offset="50%" stop-color="#409eff" />
                    <stop offset="100%" stop-color="transparent" />
                  </linearGradient>
                </defs>
                <!-- 连线组 -->
                <g v-for="e in edges" :key="e.id">
                  <!-- 基础线 (半透明同色系背景) -->
                  <path
                    :d="edgePath(e)"
                    fill="none"
                    :stroke="getLineColor(e.level)"
                    stroke-width="1"
                    stroke-dasharray="3,2"
                    stroke-opacity="0.4"
                  />
                  <!-- 动态流动线 -->
                  <path
                    class="flow-line"
                    :d="edgePath(e)"
                    fill="none"
                    :stroke="getLineColor(e.level)"
                    stroke-width="2"
                    stroke-dasharray="5,3"
                    :marker-end="getMarkerId(e.level)"
                  />

                  <!-- 连线上的标签/徽章 -->
                  <!-- <foreignObject
                    :x="getEdgeCenter(e).x - 60"
                    :y="getEdgeCenter(e).y - 15"
                    width="120"
                    height="30"
                  >
                    <div class="edge-label" :class="e.level">
                      {
  
  { e.label }}
                    </div>
                  </foreignObject> -->
                </g>
              </svg>

              <!-- 节点组 -->
              <div class="graph-nodes" v-if="nodes.length">
                <div
                  v-for="n in nodes"
                  :key="n.id"
                  class="g-node"
                  :class="[
                    n.type,
                    {
                      danger: n.danger,
                      warning: n.warning,
                      'is-selected': selectedNodeId === n.id,
                    },
                  ]"
                  :style="{
                    left: n.x + 'px',
                    top: n.y + 'px',
                  }"
                  @click.stop="handleNodeClick(n)"
                >
                  <!-- 涟漪光环 -->
                  <div
                    class="ripple-ring"
                    v-if="n.danger || selectedNodeId === n.id"
                  ></div>

                  <div class="node-icon-wrapper">
                    <img :src="https://blog.csdn.net/getSvgIcon(n.icon)" class="node-icon" />
                  </div>
                  <div class="node-info">
                    <div class="node-name">{
  
  { n.name }}</div>
                    <div class="node-ip" v-if="n.ip">{
  
  { n.ip }}</div>
                  </div>
                  <!-- 节点旁边的告警标签 -->
                  <div class="node-alerts" v-if="n.alerts && n.alerts.length">
                    <div
                      v-for="(alert, ai) in n.expanded
                        ? n.alerts
                        : n.alerts.slice(0, 2)"
                      :key="ai"
                      class="alert-tag"
                      :class="getAlertLevelClass(alert.level)"
                    >
                      {
  
  { alert.text }}
                    </div>
                    <div
                      v-if="n.alerts.length > 2"
                      class="expand-btn"
                      @click.stop="n.expanded = !n.expanded"
                    >
                      {
  
  {
                        n.expanded
                          ? "收起"
                          : "展开 (" + (n.alerts.length - 2) + ")"
                      }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="graph-empty" v-if="!nodes.length">
            <el-empty 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值