fylgja/src/blocks/sigma/sigma_stats_block.js
2025-04-16 18:01:35 -04:00

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
};