mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Vulnerability dashboard: Add new homepage (#35253)
Related to: https://github.com/fleetdm/fleet/issues/33661 Changes: - Updated the homepage of the vulnerability dashboard to be a new dashboard page. - Updated the `Vulnerability` model to make cveID a unique value.
This commit is contained in:
parent
3772ccfaa2
commit
48a26f3fe5
9 changed files with 2113 additions and 3 deletions
1476
ee/vulnerability-dashboard/api/controllers/view-dashboard.js
Normal file
1476
ee/vulnerability-dashboard/api/controllers/view-dashboard.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -15,6 +15,7 @@ module.exports = {
|
|||
installedAt: {
|
||||
example: 1670152500000,
|
||||
description: 'JS timestamp representing when this installation began on the host.',
|
||||
extendedDescription: 'This JS timestamp represents when the vulnerabiltiy dashboard first saw this vulnerable software on a host',
|
||||
type: 'number',
|
||||
isInteger: true,
|
||||
required: true,
|
||||
|
|
|
|||
156
ee/vulnerability-dashboard/assets/js/pages/dashboard.page.js
Normal file
156
ee/vulnerability-dashboard/assets/js/pages/dashboard.page.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
parasails.registerPage('dashboard', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
Chart.defaults.color = '#d4d4d8';
|
||||
Chart.defaults.borderColor = '#21262d';
|
||||
new Chart(document.getElementById('severityChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Critical', 'High', 'Medium', 'Low'],
|
||||
datasets: [{
|
||||
data: [this.totalUniqueCounts.critical, this.totalUniqueCounts.high, this.totalUniqueCounts.medium, this.totalUniqueCounts.low],
|
||||
backgroundColor: ['#dc2626', '#ea580c', '#ca8a04', '#16a34a'],
|
||||
borderWidth: 2,
|
||||
borderColor: '#010409'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
padding: 20,
|
||||
usePointStyle: true,
|
||||
color: '#d4d4d8'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
new Chart(document.getElementById('trendsChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
// labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4'],
|
||||
labels: _.pluck(this.activityTrendsTimeline, 'timelineLabel'),
|
||||
datasets: [{
|
||||
label: 'New CVEs',
|
||||
data: _.pluck(this.activityTrendsTimeline, 'newCves'),
|
||||
borderColor: '#dc2626',
|
||||
backgroundColor: 'rgba(220, 38, 38, 0.1)',
|
||||
tension: 0.4,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#dc2626',
|
||||
pointBorderColor: '#ffffff',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 5
|
||||
}, {
|
||||
label: 'Remediated CVEs',
|
||||
data: _.pluck(this.activityTrendsTimeline, 'remediatedCves'),
|
||||
borderColor: '#16a34a',
|
||||
backgroundColor: 'rgba(22, 163, 74, 0.1)',
|
||||
tension: 0.4,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#16a34a',
|
||||
pointBorderColor: '#ffffff',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 5
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#21262d' },
|
||||
ticks: { color: '#7d8590' }
|
||||
},
|
||||
x: {
|
||||
grid: { color: '#21262d' },
|
||||
ticks: { color: '#7d8590' }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: '#d4d4d8',
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Chart(document.getElementById('totalTrendChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: _.pluck(this.activityTrendsTimeline, 'timelineLabel'),
|
||||
datasets: [{
|
||||
label: 'Total CVEs',
|
||||
data: _.pluck(this.activityTrendsTimeline, 'totalNumberOfCves'),
|
||||
borderColor: '#8b5cf6',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
borderWidth: 3,
|
||||
pointBackgroundColor: '#8b5cf6',
|
||||
pointBorderColor: '#ffffff',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 6,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
min: 25000,
|
||||
grid: { color: '#21262d' },
|
||||
ticks: {
|
||||
color: '#7d8590',
|
||||
callback: function(value) {
|
||||
return (value / 1000).toFixed(1) + 'k';
|
||||
}
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: { color: '#21262d' },
|
||||
ticks: { color: '#7d8590' }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: '#d4d4d8',
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
||||
|
|
@ -44,3 +44,4 @@
|
|||
@import 'pages/500.less';
|
||||
@import 'pages/498.less';
|
||||
@import 'pages/patch-progress.less';
|
||||
@import 'pages/dashboard.less';
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ html, body {
|
|||
// ^^The above is to disable "importantRule" and "duplicateProperty" rules.
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
|
||||
padding-bottom: @footer-height;
|
||||
&.header-hidden {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
background-color: #f8f9fa;
|
||||
color: #292b2d;
|
||||
a {
|
||||
|
|
|
|||
280
ee/vulnerability-dashboard/assets/styles/pages/dashboard.less
Normal file
280
ee/vulnerability-dashboard/assets/styles/pages/dashboard.less
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
#dashboard {
|
||||
background: #111111;
|
||||
|
||||
.dashboard-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
background: #111111;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
// border: 1px solid #1f1f1f;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #1f1f1f;
|
||||
}
|
||||
|
||||
.dashboard-links {
|
||||
a {
|
||||
|
||||
color: #FFF;
|
||||
&::hover {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 2.2em;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
color: #7d8590;
|
||||
font-size: 0.9em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.metrics-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.chart-row {
|
||||
display: grid;
|
||||
// grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: #0d1117;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
border: 1px solid #1f2328;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.panel:hover {
|
||||
border-color: #2563eb;
|
||||
background: #161b22;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: #0d1117;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border: 1px solid #1f2328;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
border-color: #2563eb;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 2.5em;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 0.85em;
|
||||
color: #7d8590;
|
||||
font-weight: 300;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.severity-critical { color: #ef4444; }
|
||||
.severity-high { color: #f97316; }
|
||||
.severity-medium { color: #eab308; }
|
||||
.severity-low { color: #22c55e; }
|
||||
|
||||
.panel-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: 400;
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #1f2328;
|
||||
}
|
||||
|
||||
.host-list {
|
||||
list-style: none;
|
||||
padding-inline-start: 0px;
|
||||
}
|
||||
|
||||
.host-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #1f2328;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.host-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.host-name {
|
||||
color: #d4d4d8;
|
||||
font-size: 0.9em;
|
||||
a {
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.vulnerability-score {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.software-list {
|
||||
list-style: none;
|
||||
padding-inline-start: 0px;
|
||||
}
|
||||
|
||||
.software-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #1f2328;
|
||||
}
|
||||
|
||||
.software-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.software-name {
|
||||
color: #d4d4d8;
|
||||
font-size: 0.9em;
|
||||
font-weight: 300;
|
||||
a {
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.vuln-count {
|
||||
background: #21262d;
|
||||
color: #d4d4d8;
|
||||
padding: 3px 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.8em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
background: #010409;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
border: 1px solid #0d1117;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.remediation-summary {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.remediation-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.status-external { background: #ef4444; }
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.chart-panel {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.main-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.metrics-row {
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.chart-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
// .main-grid {
|
||||
// grid-template-columns: 1fr;
|
||||
// }
|
||||
|
||||
.chart-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.dashboard-container {
|
||||
padding: 20px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.remediation-metrics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.metrics-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ module.exports.routes = {
|
|||
// ║║║║╣ ╠╩╗╠═╝╠═╣║ ╦║╣ ╚═╗
|
||||
// ╚╩╝╚═╝╚═╝╩ ╩ ╩╚═╝╚═╝╚═╝
|
||||
// 'GET /': { action: 'view-homepage-or-redirect' },
|
||||
'GET /dashboard': { action: 'dashboard/view-welcome' },
|
||||
// 'GET /dashboard': { action: 'dashboard/view-welcome' },
|
||||
'GET /vulnerability-list': { action: 'dashboard/view-vulnerability-list' },
|
||||
'GET /patch-progress': { action: 'view-patch-progress' },
|
||||
|
||||
|
|
@ -35,7 +35,12 @@ module.exports.routes = {
|
|||
'GET /account': { action: 'account/view-account-overview' },
|
||||
'GET /account/password': { action: 'account/view-edit-password' },
|
||||
'GET /account/profile': { action: 'account/view-edit-profile' },
|
||||
|
||||
'GET /dashboard': {
|
||||
action: 'view-dashboard',
|
||||
locals: {
|
||||
headerHidden: true,
|
||||
}
|
||||
},
|
||||
|
||||
// ╔╦╗╦╔═╗╔═╗ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ ┬ ╔╦╗╔═╗╦ ╦╔╗╔╦ ╔═╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║╚═╗║ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ ┌┼─ ║║║ ║║║║║║║║ ║ ║╠═╣ ║║╚═╗
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// In case we're displaying the 404 or 500 page and relevant code in the "custom" hook was not able to run,
|
||||
// we make sure `me` exists. This ensures we don't have to do `typeof` checks below.
|
||||
var me = undefined;
|
||||
var headerHidden;
|
||||
}
|
||||
let replaceBuiltInAuthWithOkta = (sails.config.custom.oktaClientSecret !== undefined);
|
||||
if(typeof replaceBuiltInAuthWithOkta === 'undefined') {
|
||||
|
|
@ -53,7 +54,8 @@
|
|||
<!--STYLES END-->
|
||||
</head>
|
||||
<body>
|
||||
<div purpose="page-wrap">
|
||||
<div purpose="page-wrap" class="<%- headerHidden ? 'header-hidden' : '' %>">
|
||||
<%if(!headerHidden) {%>
|
||||
<header class="navbar navbar-expand-sm navbar flex-row justify-content-between align-items-center" purpose="page-header">
|
||||
<a style="cursor: pointer;" class="navbar-brand mr-0" href="/"><img style="height: 20px;" class="logo" alt="NEW_APP_NAME logo" src="images/fleet-logo-black-118x40@2x.png"/></a>
|
||||
<div class="navbar-nav flex-row d-none d-md-flex">
|
||||
|
|
@ -95,6 +97,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<% } %>
|
||||
|
||||
<%- body %>
|
||||
|
||||
|
|
@ -151,6 +154,7 @@
|
|||
<script src="/js/pages/account/edit-password.page.js"></script>
|
||||
<script src="/js/pages/account/edit-profile.page.js"></script>
|
||||
<script src="/js/pages/contact.page.js"></script>
|
||||
<script src="/js/pages/dashboard.page.js"></script>
|
||||
<script src="/js/pages/dashboard/vulnerability-list.page.js"></script>
|
||||
<script src="/js/pages/dashboard/welcome.page.js"></script>
|
||||
<script src="/js/pages/entrance/confirmed-email.page.js"></script>
|
||||
|
|
|
|||
183
ee/vulnerability-dashboard/views/pages/dashboard.ejs
Normal file
183
ee/vulnerability-dashboard/views/pages/dashboard.ejs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<div id="dashboard" v-cloak>
|
||||
<div class="dashboard-container container-fluid d-flex flex-column">
|
||||
<div class="dashboard-header">
|
||||
<h1 class="dashboard-title">Lets get to a better state</h1>
|
||||
<p class="dashboard-subtitle">Last Updated: <js-timestamp :at="lastUpdatedAt"></js-timestamp></p>
|
||||
<div class="dashboard-links d-flex flex-row justify-content-center">
|
||||
<p class="mr-3"><a href="/vulnerability-list">Vulnerability list</a></p>
|
||||
<p><a href="/patch-progress">Patch progress</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics-row">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-critical">{{(totalUniqueCounts.critical).toLocaleString()}}</div>
|
||||
<div class="metric-label">Unique critical</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-high">{{(totalUniqueCounts.high).toLocaleString()}}</div>
|
||||
<div class="metric-label">Unique high</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-medium">{{(totalUniqueCounts.medium).toLocaleString()}}</div>
|
||||
<div class="metric-label">Unique medium</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-low">{{(totalUniqueCounts.low).toLocaleString()}}</div>
|
||||
<div class="metric-label">Unique low</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics-row">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-critical">{{(totalNumberOfVulns.critical).toLocaleString()}}</div>
|
||||
<div class="metric-label">Total critical</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-high">{{(totalNumberOfVulns.high).toLocaleString()}}</div>
|
||||
<div class="metric-label">Total high</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-medium">{{(totalNumberOfVulns.medium).toLocaleString()}}</div>
|
||||
<div class="metric-label">Total medium</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value severity-low">{{(totalNumberOfVulns.low).toLocaleString()}}</div>
|
||||
<div class="metric-label">Total low</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">The riskiest hosts right now</h2>
|
||||
<p><small class="text-white">Host risk scores range from 0-10. The score weighs the number and severity of CVEs, exploit availability (public exploits double the weight). A "10" means urgent, drop-everything action: critical vulnerabilities with known exploits and high exposure.</small></p>
|
||||
<ul class="host-list">
|
||||
<li class="host-item" v-for="host in mostVulnerableHosts">
|
||||
<span class="host-name"><a :href="host.fleetUrl" target="_blank">{{host.displayName}}</a></span>
|
||||
<span class="vulnerability-score">{{host.pps}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">The usual suspects in software</h2>
|
||||
<ul class="software-list">
|
||||
<li class="software-item" v-for="software in mostVulnerableSoftware">
|
||||
<span class="software-name"><a :href="software.fleetUrl" target="_blank">{{software.softwareNameAndVersion}}</a></span>
|
||||
<span class="vuln-count">{{software.numberOfVulns}} CVEs</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel chart-panel">
|
||||
<h2 class="panel-title">What percentage are critical?</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="severityChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-row">
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Who installed this? (Critically rare software)</h2>
|
||||
<ul class="software-list">
|
||||
<li class="software-item" v-for="software in uniqueCriticalSoftware">
|
||||
<!-- <span class="software-name"><a :href="software.fleetUrl" target="_blank">{{software.softwareNameAndVersion}}</a></span> -->
|
||||
<span class="software-name">{{software.softwareNameAndVersion}}</span>
|
||||
<span class="vuln-count">{{software.numberOfVulns}} critical</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Hosts with most CVEs</h2>
|
||||
<ul class="host-list">
|
||||
<li class="host-item" v-for="host in hostsWithMostVulns">
|
||||
<span class="host-name"><a :href="host.fleetUrl" target="_blank">{{host.displayName}}</a></span>
|
||||
<span class="vulnerability-score">{{host.numberOfVulns}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-row">
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">CVE activity trends</h2>
|
||||
<div class="chart-container" style="height: 250px;">
|
||||
<canvas id="trendsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Total CVE count trend</h2>
|
||||
<div class="chart-container" style="height: 250px;">
|
||||
<canvas id="totalTrendChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel full-width remediation-summary">
|
||||
<h2 class="panel-title">CVE remediation summary (past 90 Days)</h2>
|
||||
<div class="remediation-metrics">
|
||||
<div>
|
||||
<div style="font-size: 2em; color: #ef4444; font-weight: 300;">+{{remediationSummary.newCvesNinetyDays}}</div>
|
||||
<div style="color: #7d8590; font-size: 0.9em;">New CVEs (90d)</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 2em; color: #22c55e; font-weight: 300;">-{{remediationSummary.remediatedCvesNinetyDays}}</div>
|
||||
<div style="color: #7d8590; font-size: 0.9em;">Remediated (90d)</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 2em; color: #ef4444; font-weight: 300;">+{{remediationSummary.newCvesThirtyDays}}</div>
|
||||
<div style="color: #7d8590; font-size: 0.9em;">New CVEs (30d)</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 2em; color: #22c55e; font-weight: 300;">-{{remediationSummary.remediatedCvesThirtyDays}}</div>
|
||||
<div style="color: #7d8590; font-size: 0.9em;">Remediated (30d)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Exploitable CVE Analysis Row -->
|
||||
<div class="metrics-row">
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Top 5 oldest CVEs with exploits</h2>
|
||||
<ul class="software-list">
|
||||
<li class="software-item" v-for="cve in oldestExploitableCves">
|
||||
<span class="software-name"><a :href="cve.fleetUrl" target="_blank">{{cve.cveId}}</a></span>
|
||||
<span class="vuln-count" style="background: #dc2626;">{{cve.yearPublished}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Top 5 hosts with oldest exploitable CVEs</h2>
|
||||
<ul class="host-list">
|
||||
<li class="host-item" v-for="host in oldestExploitableHosts">
|
||||
<span class="host-name"><a :href="host.fleetUrl" target="_blank">{{host.displayName}}</a></span>
|
||||
<span class="vulnerability-score" style="background: #7c2d12;">{{host.yearPublished}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Top 5 newest CVEs with exploits</h2>
|
||||
<ul class="software-list">
|
||||
<li class="software-item" v-for="cve in newestExploitableCves">
|
||||
<span class="software-name"><a :href="cve.fleetUrl" target="_blank">{{cve.cveId}}</a></span>
|
||||
<span class="vuln-count" style="background: #dc2626;">{{cve.yearPublished}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2 class="panel-title">Top 5 hosts with newest exploitable CVEs</h2>
|
||||
<ul class="host-list">
|
||||
<li class="host-item" v-for="host in newestExploitableHosts">
|
||||
<span class="host-name"><a :href="host.fleetUrl" target="_blank">{{host.displayName}}</a></span>
|
||||
<span class="vulnerability-score" style="background: #7c2d12;">{{host.yearPublished}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
Loading…
Reference in a new issue