add support for multiple spaces
This commit is contained in:
parent
7988853b57
commit
351c4e4ef4
10 changed files with 498 additions and 92 deletions
269
src/blocks/sigma/sigma_stats_block.js
Normal file
269
src/blocks/sigma/sigma_stats_block.js
Normal file
|
@ -0,0 +1,269 @@
|
|||
/**
|
||||
* 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
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue