feat: 增加管理页面

This commit is contained in:
NiceAsiv 2024-08-08 18:17:20 +08:00
parent adc385edca
commit 697c1463ca
7 changed files with 513 additions and 4 deletions

View File

@ -69,6 +69,7 @@
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"vue": "^3.4.31", "vue": "^3.4.31",
"vue-chartjs": "^5.3.1",
"vue-router": "^4.4.0", "vue-router": "^4.4.0",
"vue-tippy": "^6.4.4", "vue-tippy": "^6.4.4",
"vue-types": "^5.1.2" "vue-types": "^5.1.2"

27
pnpm-lock.yaml generated
View File

@ -71,6 +71,9 @@ importers:
vue: vue:
specifier: ^3.4.31 specifier: ^3.4.31
version: 3.4.31(typescript@5.5.3) version: 3.4.31(typescript@5.5.3)
vue-chartjs:
specifier: ^5.3.1
version: 5.3.1(chart.js@4.4.3)(vue@3.4.31(typescript@5.5.3))
vue-router: vue-router:
specifier: ^4.4.0 specifier: ^4.4.0
version: 4.4.0(vue@3.4.31(typescript@5.5.3)) version: 4.4.0(vue@3.4.31(typescript@5.5.3))
@ -894,6 +897,9 @@ packages:
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@kurkle/color@0.3.2':
resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1450,6 +1456,10 @@ packages:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
chart.js@4.4.3:
resolution: {integrity: sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==}
engines: {pnpm: '>=8'}
chokidar@3.6.0: chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@ -3772,6 +3782,12 @@ packages:
vscode-uri@3.0.8: vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
vue-chartjs@5.3.1:
resolution: {integrity: sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==}
peerDependencies:
chart.js: ^4.1.1
vue: ^3.0.0-0 || ^2.7.0
vue-demi@0.14.8: vue-demi@0.14.8:
resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4511,6 +4527,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
'@kurkle/color@0.3.2': {}
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -5176,6 +5194,10 @@ snapshots:
chalk@5.3.0: {} chalk@5.3.0: {}
chart.js@4.4.3:
dependencies:
'@kurkle/color': 0.3.2
chokidar@3.6.0: chokidar@3.6.0:
dependencies: dependencies:
anymatch: 3.1.3 anymatch: 3.1.3
@ -7602,6 +7624,11 @@ snapshots:
vscode-uri@3.0.8: {} vscode-uri@3.0.8: {}
vue-chartjs@5.3.1(chart.js@4.4.3)(vue@3.4.31(typescript@5.5.3)):
dependencies:
chart.js: 4.4.3
vue: 3.4.31(typescript@5.5.3)
vue-demi@0.14.8(vue@3.4.31(typescript@5.5.3)): vue-demi@0.14.8(vue@3.4.31(typescript@5.5.3)):
dependencies: dependencies:
vue: 3.4.31(typescript@5.5.3) vue: 3.4.31(typescript@5.5.3)

View File

@ -0,0 +1,47 @@
<!-- src/components/Card.vue -->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ title }}</h3>
</div>
<div class="card-content">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
const props = defineProps({
title: {
type: String,
required: true
}
});
</script>
<style scoped lang="scss">
.card {
margin-bottom: 10px; /* Reduce the margin to make cards closer */
overflow: hidden;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
}
.card-header {
padding: 10px 15px; /* Reduce padding for a tighter look */
background-color: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
}
.card-title {
margin: 0;
font-size: 1.2rem;
}
.card-content {
padding: 15px; /* Reduce padding for a tighter look */
}
</style>

View File

@ -0,0 +1,22 @@
export default {
path: "/project",
name: "Project",
redirect: "/project/index",
meta: {
title: "项目管理",
icon: "ri:projector-line",
rank: 1
},
component: () => import("@/layout/index.vue"),
children: [
{
path: "/project/index",
name: "ProjectIndex",
component: () => import("@/views/project/ProjectManagement.vue"),
meta: {
title: "项目管理",
showLink: true
}
}
]
};

View File

@ -1,11 +1,171 @@
<script setup lang="ts"></script> <script setup lang="ts">
import { ref } from "vue";
import { Bar } from "vue-chartjs";
import {
Chart as ChartJS,
BarElement,
CategoryScale,
LinearScale,
Tooltip,
Legend
} from "chart.js";
import Card from "@/components/Card/Card.vue";
ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend);
const projectInfo = ref({
projects: 32,
repositories: 60,
scanTimes: 10,
scanFiles: 62655
});
const vulnerabilities = ref([
{ id: "CVE-2024-9999", description: "发生堆缓冲区溢出攻击" },
{ id: "CVE-123456", description: "发布在一天之前 2024-7-23 11:44" }
]);
const recentScansData = ref({
labels: ["一月", "二月", "三月", "四月", "五月"],
datasets: [
{
label: "软件成分分析",
backgroundColor: "#72a4d2",
data: [613, 448, 540, 652, 977]
},
{
label: "静态代码扫描(SAST)",
backgroundColor: "#8cd17d",
data: [138, 936, 777, 590, 54]
}
]
});
const issuesRankingData = ref({
labels: ["TypeError", "IndexError", "ValueError"],
datasets: [
{
label: "违规问题",
backgroundColor: ["#ff6384", "#36a2eb", "#ffcd56"],
data: [582, 421, 747]
}
]
});
const appRankingData = ref({
labels: ["TestGood", "Remix", "App2", "阿坡坡", "A泡泡"],
datasets: [
{
label: "致命",
backgroundColor: "#ff6384",
data: [61, 54, 77, 65, 54]
},
{
label: "严重",
backgroundColor: "#36a2eb",
data: [44, 44, 31, 59, 12]
},
{
label: "重要",
backgroundColor: "#ffcd56",
data: [13, 13, 65, 43, 97]
}
]
});
</script>
<!--<template>-->
<!-- <div class="dashboard">-->
<!-- <h1>软件安全赋能平台</h1>-->
<!-- <div class="status-overview">-->
<!-- <div class="project-info">-->
<!-- <h2>项目信息</h2>-->
<!-- <div class="info-box">-->
<!-- <p>项目数量(): {{ projectInfo.projects }}</p>-->
<!-- <p>仓库数量(): {{ projectInfo.repositories }}</p>-->
<!-- <p>扫描次数: {{ projectInfo.scanTimes }}</p>-->
<!-- <p>扫描文件数量: {{ projectInfo.scanFiles }}</p>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="vulnerabilities">-->
<!-- <h2>最新漏洞与补丁提示</h2>-->
<!-- <ul>-->
<!-- <li v-for="vulnerability in vulnerabilities" :key="vulnerability.id">-->
<!-- {{ vulnerability.id }}: {{ vulnerability.description }}-->
<!-- </li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- <div class="recent-scans">-->
<!-- <h2>近期扫描趋势</h2>-->
<!-- <Bar :data="recentScansData" />-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="details">-->
<!-- <div class="issues-ranking">-->
<!-- <h2>违规问题排行</h2>-->
<!-- <Bar :data="issuesRankingData" />-->
<!-- </div>-->
<!-- <div class="app-ranking">-->
<!-- <h2>违规应用排行</h2>-->
<!-- <Bar :data="appRankingData" />-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</template>-->
<template> <template>
<div> <div class="dashboard">
<h1>软件安全赋能平台</h1> <div class="grid-container">
<Card title="项目信息">
<div class="info-box">
<p>项目数量(): {{ projectInfo.projects }}</p>
<p>仓库数量(): {{ projectInfo.repositories }}</p>
<p>扫描次数: {{ projectInfo.scanTimes }}</p>
<p>扫描文件数量: {{ projectInfo.scanFiles }}</p>
</div>
</Card>
<Card title="最新漏洞与补丁提示">
<ul>
<li v-for="vulnerability in vulnerabilities" :key="vulnerability.id">
{{ vulnerability.id }}: {{ vulnerability.description }}
</li>
</ul>
</Card>
<Card title="近期扫描趋势">
<Bar :data="recentScansData" />
</Card>
<Card title="违规问题排行">
<Bar :data="issuesRankingData" />
</Card>
<Card title="违规应用排行">
<Bar :data="appRankingData" />
</Card>
</div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
/* Add your styles here */ .dashboard {
padding: 20px;
background-color: #f4f5f7;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 10px; /* Reduce the gap to make cards closer */
}
.info-box p {
margin: 10px 0;
}
ul {
padding: 0;
list-style: none;
}
ul li {
padding: 10px;
margin-bottom: 5px; /* Reduce margin for a tighter look */
background: #f2f2f2;
border-radius: 4px;
}
</style> </style>

View File

@ -0,0 +1,70 @@
<!-- src/components/ProjectDetails.vue -->
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const projectName = ref("");
onMounted(() => {
projectName.value = route.params.name as string;
});
const projectDetails = ref({
creator: "Asiv",
createTime: "2024-7-11 15:30:46",
team: "Asiv's Team",
scanDetails: {
softwareComposition: {
scanTimes: "0x111",
avgScanTime: "20s",
sourceBranch: "main"
},
componentCount: 23,
vulnerabilities: 0,
licenses: 0
}
});
</script>
<template>
<div class="project-details">
<h1>项目详情: {{ projectName }}</h1>
<p>创建人: {{ projectDetails.creator }}</p>
<p>创建时间: {{ projectDetails.createTime }}</p>
<p>团队: {{ projectDetails.team }}</p>
<h2>扫描详情</h2>
<div class="scan-info">
<p>
软件成分分析扫描次数:
{{ projectDetails.scanDetails.softwareComposition.scanTimes }}
</p>
<p>
平均扫描时间:
{{ projectDetails.scanDetails.softwareComposition.avgScanTime }}
</p>
<p>
源代码分支:
{{ projectDetails.scanDetails.softwareComposition.sourceBranch }}
</p>
</div>
<h2>组件信息</h2>
<p>组件数量: {{ projectDetails.scanDetails.componentCount }}</p>
<p>漏洞数: {{ projectDetails.scanDetails.vulnerabilities }}</p>
<p>许可证数量: {{ projectDetails.scanDetails.licenses }}</p>
</div>
</template>
<style scoped lang="scss">
.project-details {
padding: 20px;
}
.scan-info {
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
</style>

View File

@ -0,0 +1,182 @@
<script setup lang="ts">
import { ref } from "vue";
const getStatLabel = (key: string): string => {
const labels: { [key: string]: string } = {
totalProjects: "项目总数",
scanTimes: "扫描次数",
unscanned: "未扫描",
outdatedScans: "未更新扫描",
scanned: "已扫描"
};
return labels[key] || key;
};
const projectStats = ref({
totalProjects: 32,
scanTimes: 9,
unscanned: 2,
outdatedScans: 4,
scanned: 4
});
const projects = ref([
{
name: "TestProject",
status: "已更新",
riskLevel: "严重",
lastScan: "2024-7-12",
riskDistribution: "高-2-中-1-低-3",
scanType: "软件成分分析",
scanTime: "20s",
projectLink: "github.com/a/b",
projectSource: "Github"
}
]);
</script>
<template>
<div class="project-management">
<div class="stats-container">
<div v-for="(value, key) in projectStats" :key="key" class="stat-card">
<p>{{ getStatLabel(key) }}</p>
<h2>{{ value }}</h2>
</div>
</div>
<div class="actions">
<button class="btn">+ 新建项目</button>
</div>
<div class="filter-container">
<el-input type="text" placeholder="选择搜索类型(ID或name或者tags)" />
<el-input type="text" placeholder="根据搜索类型搜索" />
</div>
<table class="project-table">
<thead>
<tr>
<th>项目名称</th>
<th>更新状态</th>
<th>风险等级</th>
<th>最近一次扫描时间</th>
<th>风险分布</th>
<th>扫描类别</th>
<th>扫描耗费时间</th>
<th>项目地址</th>
<th>项目来源</th>
<th>扫描</th>
</tr>
</thead>
<tbody>
<tr v-for="project in projects" :key="project.name">
<td>
<a
:href="'https://' + project.projectLink"
target="_blank"
rel="noopener noreferrer"
>
{{ project.name }}
</a>
</td>
<td>{{ project.status }}</td>
<td>{{ project.riskLevel }}</td>
<td>{{ project.lastScan }}</td>
<td>{{ project.riskDistribution }}</td>
<td>{{ project.scanType }}</td>
<td>{{ project.scanTime }}</td>
<td>
<a
:href="'https://' + project.projectLink"
target="_blank"
rel="noopener noreferrer"
>
{{ project.projectLink }}
</a>
</td>
<td>{{ project.projectSource }}</td>
<td><button class="scan-btn">扫描</button></td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped lang="scss">
.project-management {
padding: 20px;
background-color: #f4f5f7;
}
.stats-container {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.stat-card {
flex: 1;
padding: 20px;
margin: 0 10px;
text-align: center;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
}
.actions {
display: flex;
justify-content: flex-start;
margin-bottom: 20px;
}
.btn {
padding: 10px 20px;
color: white;
cursor: pointer;
background: #4caf50;
border: none;
border-radius: 4px;
}
.filter-container {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.filter-container input {
flex: 1;
padding: 10px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
.project-table {
width: 100%;
border-collapse: collapse;
}
.project-table th,
.project-table td {
padding: 8px;
text-align: left;
border: 1px solid #ddd;
}
.project-table th {
background-color: #f2f2f2;
}
.project-table td a {
color: #3498db;
text-decoration: none;
}
.scan-btn {
padding: 5px 10px;
color: white;
cursor: pointer;
background: #3498db;
border: none;
border-radius: 4px;
}
</style>