Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crowdsec-docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { themes } from "prism-react-renderer";
import tailwindPlugin from "./plugins/tailwind-config";
import { ctiApiSidebar, guidesSideBar, remediationSideBar } from "./sidebarsUnversioned";

const extractPreprocessor = require("./plugins/extract-preprocessor");

const generateCurrentAndNextRedirects = (s) => [
{
from: `/docs/${s}`,
Expand Down Expand Up @@ -220,6 +222,7 @@ const config: Config = {
admonitions: true,
headingIds: true,
},
preprocessor: extractPreprocessor,
},
stylesheets: [
{
Expand Down
104 changes: 104 additions & 0 deletions crowdsec-docs/plugins/extract-preprocessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const fs = require('fs');
const path = require('path');

// --- CONFIGURATION ---
// The directories to scan for snippets
const DOCS_DIRS = ['./docs', './unversioned'];
// ---------------------

const snippetRegistry = new Map();
let isIndexed = false;

// Helper: Recursively find all .md/.mdx files
const getAllFiles = (dirPath, arrayOfFiles = []) => {
if (!fs.existsSync(dirPath)) return arrayOfFiles;

const files = fs.readdirSync(dirPath);
files.forEach((file) => {
const fullPath = path.join(dirPath, file);
if (fs.statSync(fullPath).isDirectory()) {
getAllFiles(fullPath, arrayOfFiles);
} else if (file.endsWith('.md') || file.endsWith('.mdx')) {
arrayOfFiles.push(fullPath);
}
});
return arrayOfFiles;
};

// Helper: Extract Doc ID from Frontmatter
const getDocId = (content, filename) => {
const idMatch = content.match(/^---\s+[\s\S]*?\nid:\s*(.*?)\s*[\n\r]/m);
if (idMatch && idMatch[1]) {
return idMatch[1].replace(/['"]/g, '').trim();
}
return filename;
};

// --- CORE LOGIC ---
const buildIndex = () => {
if (isIndexed) return;
console.log('[ExtractPreprocessor] ⚡ Indexing snippets via Regex...');

const allFiles = [];
DOCS_DIRS.forEach(dir => getAllFiles(path.resolve(process.cwd(), dir), allFiles));

let count = 0;

// Regex to find: <div data-extract="ID"> CONTENT </div>
// We use [\s\S]*? to match content across multiple lines (lazy match)
const extractRegex = /<div\s+data-extract=["']([^"']+)["'][^>]*>([\s\S]*?)<\/div>/g;

allFiles.forEach(filePath => {
try {
const content = fs.readFileSync(filePath, 'utf8');
const filename = path.basename(filePath, path.extname(filePath));
const docId = getDocId(content, filename);

let match;
// Loop through all matches in the file
while ((match = extractRegex.exec(content)) !== null) {
let [fullTag, extractId, snippetContent] = match;

// Clean up the content (optional: trim leading/trailing newlines)
snippetContent = snippetContent.replace(/^\n+|\n+$/g, '');

// Generate Key: "docId:snippetId"
// If the ID already has a colon, assume user provided full ID
const key = extractId.includes(':') ? extractId : `${docId}:${extractId}`;

snippetRegistry.set(key, snippetContent);
console.log(`[ExtractPreprocessor] ⚡ Indexed snippet: ${key}`);
count++;
}
} catch (e) {
console.warn(`[ExtractPreprocessor] Failed to read ${filePath}`);
}
});

isIndexed = true;
console.log(`[ExtractPreprocessor] ⚡ Indexed ${count} snippets.`);
};

// This function is called by Docusaurus for EVERY markdown file
const preprocessor = ({ filePath, fileContent }) => {
// 1. Ensure Index exists (runs once)
buildIndex();

// 2. Regex to find: <div data-extract-copy="ID" />
// Matches <div data-extract-copy="xyz"></div> OR <div data-extract-copy="xyz" />
const copyRegex = /<div\s+data-extract-copy=["']([^"']+)["']\s*\/?>\s*(?:<\/div>)?/g;

// 3. Replace with content
return fileContent.replace(copyRegex, (match, requestedId) => {
if (snippetRegistry.has(requestedId)) {
// Return the stored snippet content
return snippetRegistry.get(requestedId);
} else {
console.error(`[ExtractPreprocessor] ❌ Snippet not found: "${requestedId}" in ${path.basename(filePath)}`);
// Return an error message in the UI so you see it
return `> **Error: Snippet "${requestedId}" not found.**`;
}
});
};

module.exports = preprocessor;
2 changes: 2 additions & 0 deletions crowdsec-docs/unversioned/troubleshooting/console_issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This page lists all possible health check issues, their trigger conditions, and
- 🌟 **Bonus** : Optimization advises and upper tier recommendation with great return on value *(comming in next iterations of Stack Health)*

## Health Check Issues Overview
<div data-extract="stackhealth_issues_list">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the div once the extracts have been indexed


| Issue | Criticality | Summary | Resolution |
|-------|-------------|---------|------------|
Expand All @@ -27,6 +28,7 @@ This page lists all possible health check issues, their trigger conditions, and
| **Security Engine Offline** | 🔥 Critical | Security Engine has not reported to Console for 24+ hours | [Troubleshooting](/u/troubleshooting/issue_se_offline) |
| **Security Engine Too Many Alerts** | ⚠️ High | More than 250,000 alerts in 6 hours | [Troubleshooting](/u/troubleshooting/issue_se_too_many_alerts) |

</div>
## Issue Dependencies

Some issues are related and share common root causes:
Expand Down
4 changes: 4 additions & 0 deletions crowdsec-docs/unversioned/troubleshooting/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ When using `cscli` to list your parsers, scenarios and collections, some might a
### Which information is sent to your services ?

See [CAPI documentation](/docs/next/central_api/intro).

### stack Health issues list

<div data-extract-copy="console_issues:stackhealth_issues_list"></div>