269 lines
No EOL
6.4 KiB
JavaScript
269 lines
No EOL
6.4 KiB
JavaScript
/**
|
|
* sigma_stats_block.js
|
|
*
|
|
* Creates Slack Block Kit blocks for displaying Sigma rule database statistics
|
|
*/
|
|
const logger = require('../../utils/logger');
|
|
|
|
const { getFileName } = require('../../utils/file_utils');
|
|
const FILE_NAME = getFileName(__filename);
|
|
|
|
/**
|
|
* Create Slack block kit blocks for statistics display
|
|
*
|
|
* @param {Object} stats - The statistics object with all statistical data
|
|
* @returns {Array} Formatted Slack blocks ready for display
|
|
*/
|
|
function getStatsBlocks(stats) {
|
|
logger.debug(`${FILE_NAME}: Creating statistics display blocks`);
|
|
|
|
if (!stats) {
|
|
logger.error(`${FILE_NAME}: Failed to create statistics blocks: No stats object provided`);
|
|
return [
|
|
{
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: 'Error: No statistics data provided'
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
// Format the date for display
|
|
const formatDate = (dateString) => {
|
|
if (!dateString) return 'Unknown';
|
|
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString();
|
|
} catch (error) {
|
|
return dateString;
|
|
}
|
|
};
|
|
|
|
// Start with header block
|
|
const blocks = [
|
|
{
|
|
type: 'header',
|
|
text: {
|
|
type: 'plain_text',
|
|
text: 'Sigma Rule Database Statistics',
|
|
emoji: true
|
|
}
|
|
},
|
|
{
|
|
type: 'context',
|
|
elements: [
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `Last updated: ${formatDate(stats.lastUpdate)}`
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
// Add divider for visual separation
|
|
blocks.push({ type: 'divider' });
|
|
|
|
// Overall statistics section
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: '*Overall Statistics*'
|
|
}
|
|
});
|
|
|
|
blocks.push({
|
|
type: 'section',
|
|
fields: [
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Total Rules:* ${stats.totalRules.toLocaleString()}`
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Database Health:* ${stats.databaseHealth.contentPercentage}% Complete`
|
|
}
|
|
]
|
|
});
|
|
|
|
// Add divider for visual separation
|
|
blocks.push({ type: 'divider' });
|
|
|
|
// Operating system breakdown
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: '*Rules by Operating System*'
|
|
}
|
|
});
|
|
|
|
blocks.push({
|
|
type: 'section',
|
|
fields: [
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Windows:* ${stats.operatingSystems.windows.toLocaleString()} rules`
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Linux:* ${stats.operatingSystems.linux.toLocaleString()} rules`
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*macOS:* ${stats.operatingSystems.macos.toLocaleString()} rules`
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Other/Unknown:* ${stats.operatingSystems.other.toLocaleString()} rules`
|
|
}
|
|
]
|
|
});
|
|
|
|
// Add divider for visual separation
|
|
blocks.push({ type: 'divider' });
|
|
|
|
// Severity breakdown
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: '*Rules by Severity Level*'
|
|
}
|
|
});
|
|
|
|
// Create a colorful representation of severity levels
|
|
const severityEmoji = {
|
|
'critical': '🔴',
|
|
'high': '🟠',
|
|
'medium': '🟡',
|
|
'low': '🟢',
|
|
'informational': '🔵'
|
|
};
|
|
|
|
let severityFields = [];
|
|
stats.severityLevels.forEach(level => {
|
|
const emoji = severityEmoji[level.level?.toLowerCase()] || '⚪';
|
|
severityFields.push({
|
|
type: 'mrkdwn',
|
|
text: `*${emoji} ${level.level ? (level.level.charAt(0).toUpperCase() + level.level.slice(1)) : 'Unknown'}:* ${level.count.toLocaleString()} rules`
|
|
});
|
|
});
|
|
|
|
// Ensure we have an even number of fields for layout
|
|
if (severityFields.length % 2 !== 0) {
|
|
severityFields.push({
|
|
type: 'mrkdwn',
|
|
text: ' ' // Empty space to balance fields
|
|
});
|
|
}
|
|
|
|
blocks.push({
|
|
type: 'section',
|
|
fields: severityFields
|
|
});
|
|
|
|
// Add divider for visual separation
|
|
blocks.push({ type: 'divider' });
|
|
|
|
// Top MITRE ATT&CK tactics
|
|
if (stats.mitreTactics && stats.mitreTactics.length > 0) {
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: '*Top MITRE ATT&CK Tactics*'
|
|
}
|
|
});
|
|
|
|
const mitreFields = stats.mitreTactics.map(tactic => {
|
|
// Format tactic name for better readability
|
|
const formattedTactic = tactic.tactic
|
|
.replace(/-/g, ' ')
|
|
.split(' ')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
|
|
return {
|
|
type: 'mrkdwn',
|
|
text: `*${formattedTactic}:* ${tactic.count.toLocaleString()} rules`
|
|
};
|
|
});
|
|
|
|
// Split into multiple sections if needed for layout
|
|
for (let i = 0; i < mitreFields.length; i += 2) {
|
|
const sectionFields = mitreFields.slice(i, Math.min(i + 2, mitreFields.length));
|
|
|
|
// If we have an odd number at the end, add an empty field
|
|
if (sectionFields.length === 1) {
|
|
sectionFields.push({
|
|
type: 'mrkdwn',
|
|
text: ' ' // Empty space to balance fields
|
|
});
|
|
}
|
|
|
|
blocks.push({
|
|
type: 'section',
|
|
fields: sectionFields
|
|
});
|
|
}
|
|
|
|
blocks.push({ type: 'divider' });
|
|
}
|
|
|
|
// Top authors
|
|
if (stats.topAuthors && stats.topAuthors.length > 0) {
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: '*Top Rule Authors*'
|
|
}
|
|
});
|
|
|
|
const authorFields = stats.topAuthors.map(author => ({
|
|
type: 'mrkdwn',
|
|
text: `*${author.name || 'Unknown'}:* ${author.count.toLocaleString()} rules`
|
|
}));
|
|
|
|
// Split into multiple sections if needed for layout
|
|
for (let i = 0; i < authorFields.length; i += 2) {
|
|
const sectionFields = authorFields.slice(i, Math.min(i + 2, authorFields.length));
|
|
|
|
// If we have an odd number at the end, add an empty field
|
|
if (sectionFields.length === 1) {
|
|
sectionFields.push({
|
|
type: 'mrkdwn',
|
|
text: ' ' // Empty space to balance fields
|
|
});
|
|
}
|
|
|
|
blocks.push({
|
|
type: 'section',
|
|
fields: sectionFields
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add a footer
|
|
blocks.push({ type: 'divider' });
|
|
blocks.push({
|
|
type: 'context',
|
|
elements: [
|
|
{
|
|
type: 'mrkdwn',
|
|
text: 'Use `/sigma-search [keyword]` to search for specific rules and `/sigma-details [id]` to get detailed information about a rule.'
|
|
}
|
|
]
|
|
});
|
|
|
|
logger.debug(`${FILE_NAME}: Created ${blocks.length} blocks for statistics display`);
|
|
return blocks;
|
|
}
|
|
|
|
module.exports = {
|
|
getStatsBlocks
|
|
}; |