可信代码库-软件详情增加恶意代码信息

This commit is contained in:
付民康
2025-04-16 14:57:11 +08:00
parent 82f47b1f7f
commit 08688bdaa7
7 changed files with 888 additions and 12 deletions

View File

@@ -0,0 +1,69 @@
import request from '@/utils/request';
import {
type ListReqResData,
type MalwareDetailType,
type MalwareListItemType,
type UnitRes
} from "@/api/inboundTask/inboundTaskType";
import axios from 'axios';
export function inboundTaskReview(params: { taskId: number, isPass: number, approvalComment: string }): Promise<UnitRes<boolean>> {
return request({
url: `/task/storage/approvalTask`,
method: 'post',
data: params,
});
}
export function getMalwareListApi(params: { taskId: number, softwareId: number, current: number, size: number }): Promise<UnitRes<ListReqResData<MalwareListItemType>>> {
return axios.request({
url: '/gateway/detect' + `/malicious/queryPage`,
method: 'POST',
data: params,
headers: {
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
},
});
}
export function getMalwareDetailApi(id: string | number): Promise<UnitRes<MalwareDetailType>> {
return axios.request({
url: '/gateway/detect' + `/maliciousDetails/detailById/${id}`,
method: 'get',
});
}
export function getMalwareDetailApi2(params) {
return request({
url: `/maliciousDetails/pageList`,
method: 'post',
data: params,
});
}
// 17. 申请入库-保存草稿
export function applicationTaskSave(params: any) {
return request({
url: `/software/saveInfo`,
method: 'post',
data: params,
}).then(res => res.data || {});
}
// 17. 申请入库-提交
export function applicationTaskSubmit(params: {softwareId: number}) {
return request({
url: `/task/storage/startTask`,
method: 'post',
data: params,
}).then(res => res.data || {});
}
// 17. 查询任务对应的入库软件id
export function queryTaskSoftwareDetail(params: {taskId: number}) {
return request({
url: `/task/storage/taskDetail`,
method: 'post',
data: params,
});
}

View File

@@ -0,0 +1,61 @@
import { type AxiosResponse } from "axios";
export interface CommonRes<T> {
code: number;
msg: string;
data: T;
}
export type UnitRes<T> = AxiosResponse<CommonRes<T>>;
export interface ListReqResData<T> {
records: T[];
total: number;
size: number;
current: number;
orders: any[];
optimizeCountSql: boolean;
searchCount: boolean;
countId: null;
maxLimit: null;
pages: number;
}
export interface MalwareListItemType {
id: number;
taskId: number; // 【扫描任务ID】
softwareId: number; // 【软件ID】
packageFullName: string; // 【包全名】
packagePath: string; // 【包路径】
packageName: string; // 【包名称】
packageVersion: string; // 【包版本号】
timestamp: string; // 【检测时间】
llmName: string; // 【使用的LLM名称】
totalApisCalledCount: number; // 【调用的敏感API总数】
suspiciousFilesCount: number; // 【检测的可疑文件总数】
isMalicious: number; // 【是否包含恶意代码】
isModelResponded: number; // 【大模型是否正常回复】
createTime: string; // 【创建时间】
updateTime: string;
}
export interface MalwareDetailType {
id: number;
resultId: number;
filePath: string;
codeType: string;
identifier: string;
tokenNum: number;
code: string;
suspiciousBehaviors: string; // 威胁信息 Array<string> 转换成的 JSON 字符串
apisCalled: string; // Array<string> 转换成的 JSON 字符串
isMalicious: number;
maliciousCodeCategory: string;
source: string;
sink: string;
flow: string;
purpose: string;
analysis: string;
createTime: string;
updateTime: string;
}

View File

@@ -0,0 +1,210 @@
<template>
<div>
<d-popover
style="background-color: white;"
trigger="click"
:position="['bottom', 'right', 'left', 'top']"
@show="handleShow"
>
<i :class="['filter-icon', !conditionIsEmpty && 'filter-icon-active']">
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g>
<polygon points="10.0085775 7 10.0085775 15 6 13 6 7 2 3 2 1 14 1 14 3"></polygon>
</g>
</g>
</svg>
</i>
<template #content>
<div class="py-[8px] px-[10px]">
<div>
<d-input v-if="type === 'input'" v-model="formData.input" placeholder="请输入" />
<div v-else-if="type === 'checkbox' || type === 'radio'">
<div>
<div v-if="type === 'checkbox'" class="flex">
<d-checkbox
v-model="allCheckbox"
label="全选"
class="mr-[8px]"
:half-checked="allCheckboxHalfFlag"
@change="allCheckboxChange"
/>
<d-button size="sm" @click="invertClick">反选</d-button>
</div>
<div class="max-h-[200px] overflow-y-scroll">
<d-radio-group
v-if="type === 'radio'"
v-model="formData.radio"
>
<d-radio
v-for="item of filterList"
:key="item.value"
:value="item.value"
>{{ item.label }}</d-radio>
</d-radio-group>
<d-checkbox-group v-else v-model="formData.checkbox">
<d-checkbox
v-for="item in filterList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</d-checkbox-group>
</div>
</div>
</div>
</div>
<div class="flex justify-center mt-[8px]">
<d-button
variant="solid"
size="sm"
class="mr-[8px]"
@click="confirm"
>确定</d-button>
<d-button size="sm" @click="resetClick">重置</d-button>
</div>
</div>
</template>
</d-popover>
<div ref="clickDivRef" />
</div>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from "vue";
type FilterItemType = string | number | boolean | null;
const props = defineProps({
type: {
type: String,
default: 'input',
validator: value => {
return ['input', 'radio', 'checkbox'].includes(value.type);
}
},
condition: { // 默认值
type: [String, Number, Array<FilterItemType>, Boolean],
default: '',
},
field: { // 过滤项的key
type: String,
required: true,
},
filterList: { // 多选、单选 的选项列表
type: Array<{ value: FilterItemType; label: string }>,
default: () => {
return [];
},
},
});
const emit = defineEmits(['filterChange']);
const clickDivRef = ref();
const formData = reactive({
input: '',
radio: null,
checkbox: [] as Array<FilterItemType>,
});
const allCheckbox = ref(false);
const allCheckboxHalfFlag = computed(() => {
if (formData.checkbox.length === props.filterList.length) {
allCheckbox.value = true;
}
return formData.checkbox.length && formData.checkbox.length < props.filterList.length;
});
const filterListValues = computed(() => {
return props.filterList.map(item => item.value);
});
const formDataIsEmpty = computed(() => {
if (props.type === 'input') {
return formData.input === '';
} else if (props.type === 'radio') {
return filterListValues.value.findIndex(item => formData.radio === item) === -1;
} else if (props.type === 'checkbox') {
return formData.checkbox.length === 0;
}
});
const conditionIsEmpty = computed(() => {
if (props.type === 'input') {
return props.condition === '';
} else if (props.type === 'radio') {
return filterListValues.value.findIndex(item => props.condition === item) === -1;
} else if (props.type === 'checkbox') {
return !Array.isArray(props.condition) || props.condition.length === 0;
}
});
function handleShow() {
formData[props.type] = props.condition;
}
function resetClick() {
if (props.type === 'input') {
formData.input = '';
} else if (props.type === 'radio') {
formData.radio = null;
}
emitFilterChange();
}
function confirm() {
emitFilterChange();
}
function emitFilterChange() {
emit('filterChange', {
field: props.field,
value: formData[props.type],
});
clickDivRef.value.click(); // 用于点击 确认/重置 后关闭弹窗
}
function allCheckboxChange(checkAll: boolean) {
if (checkAll) {
formData.checkbox = filterListValues.value;
} else {
formData.checkbox = [];
}
}
function invertClick() {
formData.checkbox = props.filterList.filter(item => !formData.checkbox.includes(item.value)).map(item => item.value);
}
</script>
<style scoped lang="scss">
.filter-icon {
flex: 1;
display: block;
height: 16px;
text-align: right;
cursor: pointer;
> svg g {
fill: $devui-dividing-line;
}
&:hover {
> svg g {
fill: $devui-icon-fill-active-hover;
}
}
}
.filter-icon-active {
visibility: visible !important;
> svg g {
fill: $devui-icon-fill-active;
}
&:hover {
> svg g {
fill: $devui-icon-fill-active-hover;
}
}
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<div>
<div>
<d-search
v-model="searchText"
icon-position="left"
class="w-[400px] mb-[12px]"
placeholder="请输入搜索内容"
@search="getMalwareList"
/>
</div>
<d-table
:data="tableData"
:show-loading="searchLoading"
class="w-full bg-white malware-list-table"
:fix-header="tableData.length > 0"
max-height="600px"
@sort-change="sortChange"
>
<d-column type="index" width="48px" />
<!-- <d-column field="packageFullName" width="220px">-->
<!-- <template #header>-->
<!-- 文件名称-->
<!-- <TableFilter field="packageFullName" :condition="filterData.packageFullName" @filter-change="filterChange" />-->
<!-- </template>-->
<!-- <template #default="{ row }">-->
<!-- <a class="devui-link" @click="openMalwareDetail(row)">{{ row.packageFullName }}</a>-->
<!-- </template>-->
<!-- </d-column>-->
<!-- <d-column field="category">-->
<!-- <template #header>-->
<!-- 恶意类别-->
<!-- <TableFilter-->
<!-- type="radio"-->
<!-- field="category"-->
<!-- :condition="filterData.category"-->
<!-- :filter-list="categoryFilterOptions"-->
<!-- @filter-change="filterChange"-->
<!-- />-->
<!-- </template>-->
<!-- </d-column>-->
<d-column field="packageName" header="文件名称">
<template #default="{ row }">
<a class="devui-link" @click="openMalwareDetail(row)">{{ row.packageFullName }}</a>
</template>
</d-column>
<d-column field="packagePath" header="文件路径" />
<d-column field="packageVersion" header="文件版本号" />
<d-column field="llmName" header="LLM名称" />
<d-column field="totalApisCalledCount" header="敏感API总数" />
<d-column field="suspiciousFilesCount" header="可疑文件总数" />
<d-column field="isMalicious" header="是否恶意代码">
<template #default="scope">
<span class="card-val" v-if="scope.row.isMalicious===0">
<d-tag color="#5e7ce0" size="sm">否</d-tag>
</span>
<span class="card-val" v-else>
<d-tag color="#c7000b" size="sm">是</d-tag>
</span>
</template>
</d-column>
<d-column field="isModelResponded" header="大模型是否正常回复" >
<template #default="scope">
<span class="card-val" v-if="scope.row.isModelResponded===0">
<d-tag color="#5e7ce0" size="sm">否</d-tag>
</span>
<span class="card-val" v-else>
<d-tag color="#c7000b" size="sm">是</d-tag>
</span>
</template>
</d-column>
<d-column field="createTime" header="检测时间" />
<!-- <d-column field="threatLevel">-->
<!-- <template #header>-->
<!-- 威胁级别-->
<!-- <TableFilter-->
<!-- type="radio"-->
<!-- field="threatLevel"-->
<!-- :condition="filterData.threatLevel"-->
<!-- :filter-list="threatLevelFilterList"-->
<!-- @filter-change="filterChange"-->
<!-- />-->
<!-- </template>-->
<!-- <template #default="{ row }">-->
<!-- <d-status :type="ThreatLevelMap[row.threatLevel].statusType">-->
<!-- {{ ThreatLevelMap[row.threatLevel].label }}-->
<!-- </d-status>-->
<!-- </template>-->
<!-- </d-column>-->
<!-- <d-column field="confidenceCoefficient">-->
<!-- <template #header>-->
<!-- 置信度-->
<!-- <TableFilter-->
<!-- type="radio"-->
<!-- field="confidenceCoefficient"-->
<!-- :condition="filterData.confidenceCoefficient"-->
<!-- :filter-list="confidenceCoefficientFilterList"-->
<!-- @filter-change="filterChange"-->
<!-- />-->
<!-- </template>-->
<!-- <template #default="{ row }">-->
<!-- <d-tag size="sm" :type="ConfidenceCoefficientMap[row.confidenceCoefficient].statusType">-->
<!-- {{ ConfidenceCoefficientMap[row.confidenceCoefficient].label }}-->
<!-- </d-tag>-->
<!-- </template>-->
<!-- </d-column>-->
<!-- <d-column field="testResult" header="检测结果" />-->
<template #empty>
<NoData v-if="!searchLoading" />
</template>
</d-table>
<d-pagination
class="flex justify-end mt-[20px]"
:total="pager.total"
v-model:pageSize="pager.pageSize"
v-model:pageIndex="pager.pageIndex"
:can-view-total="true"
:can-change-page-size="true"
:max-items="5"
@page-index-change="pageIndexChange"
@page-size-change="pageSizeChange"
/>
</div>
<MalwareDetail ref="MalwareDetailRef" :detailRow="detailRow" />
</template>
<script lang="ts" setup>
import { reactive, ref, shallowRef } from 'vue';
import TableFilter from '@/components/TableFilter/TableFilter.vue';
import NoData from '@/components/NoData/NoData.vue';
import MalwareDetail from '@/views/Jyh/Version/Detail/Malware/MalwareDetail.vue';
import { getMalwareListApi } from '@/api/inboundTask/inboundTaskApi';
import { type MalwareListItemType } from '@/api/inboundTask/inboundTaskType';
import { Message } from 'vue-devui/message';
import { useRoute } from 'vue-router';
enum ThreatLevelEnum {
HIGH = 'high',
MD = 'medium',
LOW = 'low'
}
enum ConfidenceCoefficientEnum {
HIGH = 'high',
MD = 'medium',
LOW = 'low'
}
const route = useRoute();
const versionId = route.params.versionId;
const searchText = ref('');
const searchLoading = ref(false);
const MalwareDetailRef = ref<InstanceType<typeof MalwareDetail> | null>(null)
const tableData = shallowRef<MalwareListItemType[]>([]);
const pager = reactive({
pageSize: 10,
pageIndex: 1,
total: 0
});
const ThreatLevelMap = {
[ThreatLevelEnum.HIGH]: { label: '高危', statusType: 'error' },
[ThreatLevelEnum.MD]: { label: '中危', statusType: 'warning' },
[ThreatLevelEnum.LOW]: { label: '低危', statusType: 'waiting' }
};
const ConfidenceCoefficientMap = {
[ConfidenceCoefficientEnum.HIGH]: { label: '高', statusType: 'success' },
[ConfidenceCoefficientEnum.MD]: { label: '中', statusType: 'warning' },
[ConfidenceCoefficientEnum.LOW]: { label: '低', statusType: 'danger' }
};
const confidenceCoefficientFilterList = [
{ label: '高', value: ConfidenceCoefficientEnum.HIGH },
{ label: '中', value: ConfidenceCoefficientEnum.MD },
{ label: '低', value: ConfidenceCoefficientEnum.LOW }
];
const threatLevelFilterList = [
{ label: '高危', value: ThreatLevelEnum.HIGH },
{ label: '中危', value: ThreatLevelEnum.MD },
{ label: '低危', value: ThreatLevelEnum.LOW }
];
const categoryFilterOptions = [{ label: 'Python', value: 'Python' }];
const filterData = reactive({
packageFullName: '',
threatLevel: '',
confidenceCoefficient: '',
category: ''
});
const detailRow = shallowRef<MalwareListItemType | null>(null);
init();
function init() {
getMalwareList();
}
async function getMalwareList() {
const params = {
current: pager.pageIndex,
size: pager.pageSize,
taskId: '',
softwareId: versionId
};
pager.total = 0;
tableData.value = [];
searchLoading.value = true;
try {
const res = await getMalwareListApi(params);
if (res.data?.code === 200) {
debugger
tableData.value = res.data.data.records.map((item) => {
return { ...item, threatLevel: ThreatLevelEnum.HIGH, confidenceCoefficient: ConfidenceCoefficientEnum.HIGH };
});
pager.total = res.data.data.total;
} else {
Message.error(res.data.msg || '查询恶意代码列表失败');
}
} catch (e) {
Message.error('接口错误,查询恶意代码列表失败');
}
searchLoading.value = false;
}
function sortChange(sortInfo: { field: string; direction: 'ASC' | 'DESC' | '' }) {
console.log(sortInfo);
getMalwareList();
}
function filterChange(filterInfo: { field: string; value: any }) {
filterData[filterInfo.field] = filterInfo.value;
getMalwareList();
}
function pageSizeChange() {
pager.pageIndex = 1;
getMalwareList();
}
function pageIndexChange() {
getMalwareList();
}
function openMalwareDetail(row: MalwareListItemType) {
detailRow.value = row;
// 添加检查
if (MalwareDetailRef.value && typeof MalwareDetailRef.value.openDetail === 'function') {
MalwareDetailRef.value.openDetail(row.id)
} else {
console.error('MalwareDetailRef 或 openDetail 方法不可用')
}
}
</script>
<style scoped lang="scss">
.malware-list-table {
:deep(.devui-table__row td) {
padding-top: 8px;
padding-bottom: 8px;
}
:deep(.devui-status.devui-status__bg--warning:before) {
background-color: #fcc800;
}
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<d-drawer v-model="detailDrawerCtrl" class="w-[800px] p-[20px]">
<div class="flex flex-col h-full">
<div class="flex justify-between items-center mb-[12px]">
<p class="text-[18px]/[26px] ">恶意代码详情</p>
<d-icon @click="detailDrawerCtrl = false" name="icon-close" size="18px" class="cursor-pointer text-[#71757F]" />
</div>
<div class="flex flex-col mb-[12px] flex-1 h-0">
<div class="malware-detail-card mb-[16px]">
<p class="title">文件基本信息</p>
<div class="content">
<!-- <div class="flex items-center">-->
<!-- <div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">文件名称</div>-->
<!-- <div class="single-line-ellipsis">{{ props.detailRow?.packageFullName }}</div>-->
<!-- <d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(props.detailRow?.packageFullName)" />-->
<!-- </div>-->
<div class="flex items-center">
<div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">文件路径</div>
<div class="single-line-ellipsis">{{ malwareDetails?.filePath }}</div>
<d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.filePath)" />
</div>
<div class="flex items-center">
<div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">切片类型</div>
<div class="single-line-ellipsis">{{ malwareDetails?.codeType }}</div>
<d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.codeType)" />
</div>
<div class="flex items-center">
<div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">标识符</div>
<div class="single-line-ellipsis">{{ malwareDetails?.identifier }}</div>
<d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.identifier)" />
</div>
<div class="flex items-center">
<div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">token数量</div>
<div class="single-line-ellipsis">{{ malwareDetails?.tokenNum }}</div>
<d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.tokenNum)" />
</div>
<div class="flex items-center">
<div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">代码片段</div>
<div class="single-line-ellipsis">{{ malwareDetails?.code }}</div>
<d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.code)" />
</div>
<!-- <div class="flex items-center">-->
<!-- <div class="w-[96px] pr-[20px] flex-shrink-0 text-[#595959]">文件sha256</div>-->
<!-- <div :title="malwareDetails?.code" class="single-line-ellipsis">{{ malwareDetails?.code }}</div>-->
<!-- <d-icon name="icon-copy" class="ml-[8px] cursor-pointer" @click="handleCopy(malwareDetails?.code)" />-->
<!-- </div>-->
</div>
</div>
<div class="malware-detail-card mb-[16px]">
<p class="title">调用的敏感API列表</p>
<div class="content">
<d-table
:data="malwareDetails?.apisCalled"
class="w-full bg-white malware-list-table"
:fix-header="malwareDetails?.apisCalled.length > 0"
max-height="300px"
>
<d-column field="isMalicious" header="是否恶意代码">
<template #default="scope">
<span class="card-val" v-if="scope.row.isMalicious===0">
<d-tag color="#5e7ce0" size="sm">否</d-tag>
</span>
<span class="card-val" v-else>
<d-tag color="#c7000b" size="sm">是</d-tag>
</span>
</template>
</d-column>
<d-column
field="maliciousCodeCategory"
header="恶意代码分类"
/>
<template #empty>
<NoData style="padding: 0; margin: -30px 0;" :width="80" />
</template>
</d-table>
<!-- <d-pagination-->
<!-- class="flex justify-end mt-[20px]"-->
<!-- :total="pager.total"-->
<!-- v-model:pageSize="pager.pageSize"-->
<!-- v-model:pageIndex="pager.pageIndex"-->
<!-- :can-view-total="true"-->
<!-- :can-change-page-size="true"-->
<!-- :max-items="5"-->
<!-- @page-index-change="pageIndexChange"-->
<!-- @page-size-change="pageSizeChange"-->
<!-- />-->
</div>
</div>
<div class="malware-detail-card mb-[16px]">
<p class="title">包含恶意代码的文件列表</p>
<div class="content">
<d-table
:data="malwareDetails?.source"
class="w-full bg-white malware-list-table"
:fix-header="malwareDetails?.source.length > 0"
max-height="300px"
>
<d-column
field="sink"
header="恶意汇"
/>
<d-column
field="flow"
header="传播流向"
/>
<d-column
field="purpose"
header="代码目的"
/>
<d-column
field="analysis"
header="LLM对于这个代码片段的分析"
/>
<d-column
field="createTime"
header="创建时间"
/>
<template #empty>
<NoData style="padding: 0; margin: -30px 0;" :width="80" />
</template>
</d-table>
<!-- <d-pagination-->
<!-- class="flex justify-end mt-[20px]"-->
<!-- :total="pager.total"-->
<!-- v-model:pageSize="pager.pageSize"-->
<!-- v-model:pageIndex="pager.pageIndex"-->
<!-- :can-view-total="true"-->
<!-- :can-change-page-size="true"-->
<!-- :max-items="5"-->
<!-- @page-index-change="pageIndexChange"-->
<!-- @page-size-change="pageSizeChange"-->
<!-- />-->
</div>
</div>
<!-- <div class="malware-detail-card flex-1 h-0">-->
<!-- <p class="title">关键日志</p>-->
<!-- <div class="content flex flex-col flex-1 h-0">-->
<!-- <div class="log-box flex-1 overflow-y-scroll">-->
<!-- {{ malwareDetails?.analysis }}-->
<!-- {{ malwareDetails?.code }}-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</d-drawer>
</template>
<script setup lang="ts">
import { reactive, shallowRef, defineProps, ref, defineExpose } from 'vue';
import NoData from "@/components/NoData/NoData.vue";
import { getMalwareDetailApi, getMalwareDetailApi2 } from '@/api/inboundTask/inboundTaskApi';
import {Message} from "vue-devui/message";
import { type MalwareDetailType } from "@/api/inboundTask/inboundTaskType";
const props = defineProps({
detailRow: {
type: Object,
required: true,
},
});
const detailDrawerCtrl = ref(false);
const malwareDetails = shallowRef<MalwareDetailType | null>(null);
const threatContentList = shallowRef([]);
const pager = reactive({
pageIndex: 1,
pageSize: 10,
total: 0,
});
async function getMalwareDetails(id) {
try {
const res = await getMalwareDetailApi2({
current: 1,
size: 10,
malwareId: id
});
if (res.data.code === 200) {
malwareDetails.value = res.data.data;
} else {
Message.error('查询详情失败');
}
} catch (e) {
Message.error('接口错误,查询详情失败');
}
getCurrentPageData();
}
function getCurrentPageData() {
// pager.total = malwareDetails.value.contentInfo.total;
// const endIndex = pager.pageIndex * pager.pageSize;
// threatContentList.value = malwareDetails.value.contentInfo.list.slice(endIndex - pager.pageSize, endIndex);
}
function pageIndexChange() {
getCurrentPageData();
}
function pageSizeChange() {
pager.pageIndex = 1;
getCurrentPageData();
}
function handleCopy(text: string) {
inputCopy(text);
}
const openDetail = (id) => {
detailDrawerCtrl.value = true;
getMalwareDetails(id)
}
function inputCopy(text: string) {
const aux = document.createElement('input');
aux.setAttribute('value', text);
document.body.appendChild(aux);
aux.select();
document.execCommand('copy');
document.body.removeChild(aux);
Message({ type: 'success', message: '复制成功' });
}
// 暴露方法,供父组件调用
defineExpose({
openDetail,
});
</script>
<style scoped lang="scss">
.malware-detail-card {
border-radius: 8px;
border: 1px solid #EEF0F5;
display: flex;
flex-direction: column;
.title {
color: #191919;
padding: 10px 16px;
font-size: 14px;
line-height: 22px;
background-color: #F3F3F3;
}
.content {
padding: 20px 16px;
:deep(.devui-table__thead .header-container) {
background-color: #F3F3F3;
}
}
}
.log-box {
border-radius: 8px;
background: #F7F8FA;
border: 1px solid #EEF0F5;
padding: 8px 16px;
}
.single-line-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -48,6 +48,7 @@
<d-tab id="bbxx" title="版本信息"></d-tab>
<d-tab id="SBOM" title="SBOM"></d-tab>
<d-tab id="ldxx" title="漏洞信息"></d-tab>
<d-tab id="Malware" title="恶意代码信息" />
<d-tab id="ai" title="AI修复建议"></d-tab>
<d-tab id="licenseInfo" title="许可证信息"></d-tab>
<d-tab id="communityInfo" title="开发者社区信息"></d-tab>
@@ -61,6 +62,7 @@
<SBOM :versionId="versionId" v-else-if="selectTab === 'SBOM'" />
<AIRepair :versionId="versionId" v-else-if="selectTab === 'ai'"></AIRepair>
<LicenseInfo :versionId="versionId" v-else-if="selectTab === 'licenseInfo'"></LicenseInfo>
<Malware :versionId="versionId" v-else-if="selectTab === 'Malware'" />
</div>
</div>
</template>
@@ -76,6 +78,7 @@ import SBOM from './SBOM.vue';
import AIRepair from '../../AIRepair/index.vue';
import LicenseInfo from '../../LicenseInfo/index.vue';
import Dependence from './Dependency.vue'
import Malware from "@/views/Jyh/Version/Detail/Malware/Malware.vue";
const { namespace } = getOrgInfo();
const route = useRoute(); // 获取路由对象
const avatarUrl = '/src/assets/imgs/jyh/工程首字母图标.png';