diff --git a/crowdsec-docs/docusaurus.config.ts b/crowdsec-docs/docusaurus.config.ts index fdd185d17..956beb35a 100644 --- a/crowdsec-docs/docusaurus.config.ts +++ b/crowdsec-docs/docusaurus.config.ts @@ -8,7 +8,7 @@ import sidebarsUnversioned from "./sidebarsUnversioned"; const extractPreprocessor = require("./plugins/extract-preprocessor"); -const generateCurrentAndNextRedirects = (s) => [ +const generateCurrentAndNextRedirects = (s: string): { from: string; to: string }[] => [ { from: `/docs/${s}`, to: `/u/${s}`, @@ -19,7 +19,9 @@ const generateCurrentAndNextRedirects = (s) => [ }, ]; -function handleSidebarItems(items) { +type SidebarItem = string | { id?: string; link?: { id?: string }; items?: SidebarItem[] }; + +function handleSidebarItems(items: SidebarItem[]): { from: string; to: string }[] { const arr = []; for (const item of items) { if (typeof item === "string") { @@ -32,7 +34,7 @@ function handleSidebarItems(items) { } // This function generates redirects for all items in the unversioned sidebars, so that if we move a doc from versioned to unversioned, we don't break existing links. It handles both string items (doc ids) and nested objects (categories with their own items). -const backportRedirect = (s) => { +const backportRedirect = (s: SidebarItem): { from: string; to: string }[] => { const arr = []; if (typeof s === "string") { arr.push(...generateCurrentAndNextRedirects(s)); @@ -153,7 +155,7 @@ const FOOTER_LINKS = [ }, { label: "Discourse", href: "https://discourse.crowdsec.net/" }, { label: "Discord", href: "https://discord.gg/crowdsec" }, - { label: "Twitter", href: "https://twitter.com/crowd_security" }, + { label: "X", href: "https://x.com/crowd_security" }, { label: "LinkedIn", href: "https://www.linkedin.com/company/crowdsec/" }, { label: "YouTube", href: "https://www.youtube.com/@crowdsec" }, ], @@ -169,7 +171,10 @@ const FOOTER_LINKS = [ href: "https://crowdsec.net/blog/category/tutorial/", }, { label: "Academy", href: "https://academy.crowdsec.net/" }, - { label: "Custom GPT", href: "https://chatgpt.com/g/g-682c3a61a78081918417571116c2b563-crowdsec-documentation" }, + { + label: "Custom GPT", + href: "https://chatgpt.com/g/g-682c3a61a78081918417571116c2b563-crowdsec-documentation", + }, ], }, ]; @@ -202,15 +207,33 @@ const redirects = [ }, // CTI Web UI pages moved to console/ip_reputation { from: "/u/cti_api/getting_started", to: "/u/console/ip_reputation/intro" }, - { from: "/u/cti_api/api_getting_started", to: "/u/console/ip_reputation/api_keys" }, + { + from: "/u/cti_api/api_getting_started", + to: "/u/console/ip_reputation/api_keys", + }, { from: "/u/cti_api/ip_report", to: "/u/console/ip_reputation/ip_report" }, - { from: "/u/cti_api/search_queries", to: "/u/console/ip_reputation/search_ui" }, - { from: "/u/cti_api/advanced_search", to: "/u/console/ip_reputation/search_ui_advanced" }, - { from: "/u/cti_api/cve_explorer", to: "/u/console/ip_reputation/intro#live-exploit-tracker" }, + { + from: "/u/cti_api/search_queries", + to: "/u/console/ip_reputation/search_ui", + }, + { + from: "/u/cti_api/advanced_search", + to: "/u/console/ip_reputation/search_ui_advanced", + }, + { + from: "/u/cti_api/cve_explorer", + to: "/u/console/ip_reputation/intro#live-exploit-tracker", + }, // other CTI pages redirect / fixes { from: "/next/cti_api/intro", to: "/u/console/ip_reputation/api_keys" }, - { from: "/next/cti_api/getting_started", to: "/u/console/ip_reputation/api_keys" }, - { from: "/u/console/ip_reputation/api_keys_premium", to: "/u/console/ip_reputation/api_keys" }, + { + from: "/next/cti_api/getting_started", + to: "/u/console/ip_reputation/api_keys", + }, + { + from: "/u/console/ip_reputation/api_keys_premium", + to: "/u/console/ip_reputation/api_keys", + }, ]; function redirectsGlobalDataPlugin() { @@ -251,6 +274,10 @@ const config: Config = { { href: "https://fonts.googleapis.com/icon?family=Material+Icons", }, + { + href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap", + rel: "stylesheet", + }, ], themes: ["@docusaurus/theme-mermaid"], themeConfig: { @@ -258,7 +285,7 @@ const config: Config = { colorMode: { defaultMode: "dark", disableSwitch: false, - respectPrefersColorScheme: true, + respectPrefersColorScheme: false, }, announcementBar: { id: "banner_docs", @@ -318,6 +345,10 @@ const config: Config = { path: "/next", }, }, + admonitions: { + keywords: ["premium"], + extendDefaults: true, + }, }, blog: { showReadingTime: true, @@ -338,6 +369,10 @@ const config: Config = { path: "unversioned", routeBasePath: "u", sidebarPath: "./sidebarsUnversioned.ts", + admonitions: { + keywords: ["premium"], + extendDefaults: true, + }, }, ], diff --git a/crowdsec-docs/plugins/gtag/gtag.ts b/crowdsec-docs/plugins/gtag/gtag.ts index 163057706..fbbf10d41 100644 --- a/crowdsec-docs/plugins/gtag/gtag.ts +++ b/crowdsec-docs/plugins/gtag/gtag.ts @@ -24,8 +24,10 @@ const clientModule = { setTimeout(() => { // Always refer to the variable on window in case it gets overridden // elsewhere. - window.gtag("set", "page_path", location.pathname + location.search + location.hash) - window.gtag("event", "page_view") + if (typeof window.gtag === "function") { + window.gtag("set", "page_path", location.pathname + location.search + location.hash) + window.gtag("event", "page_view") + } }) } }, diff --git a/crowdsec-docs/plugins/gtag/theme/cookieconsent.js b/crowdsec-docs/plugins/gtag/theme/cookieconsent.js index 4af2799eb..354649305 100644 --- a/crowdsec-docs/plugins/gtag/theme/cookieconsent.js +++ b/crowdsec-docs/plugins/gtag/theme/cookieconsent.js @@ -1,42 +1,56 @@ -import clsx from 'clsx'; -import styles from './styles.module.css'; -import {createStorageSlot} from '@docusaurus/theme-common'; -import React, {useState} from 'react'; +import { createStorageSlot } from "@docusaurus/theme-common"; +import React, { useState } from "react"; -const storage = createStorageSlot('docusaurus.cookieConsent'); +const storage = createStorageSlot("docusaurus.cookieConsent"); export default function CookieConsent() { - const [dismissed, setDismissed] = useState(false); - if (dismissed) { - return null; - } - return ( -
-

- This website uses cookies to help us improve. Click "accept" - to allow us to continue using cookies. -

-
- - -
-
- ); + const [dismissed, setDismissed] = useState(false); + if (dismissed) return null; + + return ( +
+ {/* Eyebrow */} +
+ CrowdSec Docs +
+ + {/* Title + body */} +
+
+ We use cookies +
+

+ This site uses cookies to help us improve your experience. You can + accept or decline below. +

+
+ + {/* Actions */} +
+ + +
+
+ ); } diff --git a/crowdsec-docs/plugins/gtag/theme/styles.module.css b/crowdsec-docs/plugins/gtag/theme/styles.module.css index 2492f28f8..456886f04 100644 --- a/crowdsec-docs/plugins/gtag/theme/styles.module.css +++ b/crowdsec-docs/plugins/gtag/theme/styles.module.css @@ -1,38 +1 @@ -.banner { - background-color: var(--ifm-color-emphasis-200); - position: fixed; - bottom: 20px; - right: 20px; - width: 300px; - height: 200px; - border-radius: var(--ifm-global-radius); - box-shadow: 0 1.5px 6px 0 rgba(0, 0, 0, 0.15) inset; - z-index: 1000; - } - - .text { - font-size: small; - padding: 30px; - } - - .buttons { - position: absolute; - bottom: 20px; - display: flex; - justify-content: center; - width: 100%; - } - - .button { - width: 100px; - background-color: #d0d0d0 !important; - margin: 10px; - /* TODO this is because the CSS is imported as a client module as well, and happens to be before Infima */ - padding: 5px !important; - border-radius: var(--ifm-global-radius); - } - - html[data-theme='dark'] .button { - background: #333333 !important; - } - \ No newline at end of file +/* Cookie consent styles moved to inline styles in cookieconsent.js */ diff --git a/crowdsec-docs/sidebarsUnversioned.ts b/crowdsec-docs/sidebarsUnversioned.ts index a1d8fcebf..552492454 100644 --- a/crowdsec-docs/sidebarsUnversioned.ts +++ b/crowdsec-docs/sidebarsUnversioned.ts @@ -451,11 +451,6 @@ const sidebarsUnversionedConfig: SidebarConfig = { tag: "otherSection", }, }, - { - type: "doc", - label: "IP Reputation Report", - id: "console/ip_reputation/ip_report", - }, ], }, { diff --git a/crowdsec-docs/src/components/docs/AccessCard/index.tsx b/crowdsec-docs/src/components/docs/AccessCard/index.tsx new file mode 100644 index 000000000..30ac783ac --- /dev/null +++ b/crowdsec-docs/src/components/docs/AccessCard/index.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +type Props = { + icon?: React.ReactNode; + title: string; + command?: string; + ctaLabel: string; + ctaHref: string; +}; + +function DefaultIcon() { + return ( + + ); +} + +export default function AccessCard({ icon, title, command, ctaLabel, ctaHref }: Props) { + return ( +
+
+
+ {icon ?? } +
+
{title}
+
+ {command && ( +
+ $ + {command} +
+ )} + + {ctaLabel} → + +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx new file mode 100644 index 000000000..1a071afdd --- /dev/null +++ b/crowdsec-docs/src/components/docs/ChallengeGrid/index.tsx @@ -0,0 +1,56 @@ +import { CIcon } from "@coreui/icons-react"; +import React from "react"; +import { CUI } from "../icons/cuiMap"; + +export type Challenge = { + iconName?: string; + icon?: React.ReactNode; + color: string; + title: string; + body: string; +}; + +type Props = { + challenges: Challenge[]; +}; + +export default function ChallengeGrid({ challenges }: Props) { + return ( +
+ {challenges.map((c, i) => { + const iconEl = + c.icon ?? + (c.iconName && CUI[c.iconName] ? ( + [0]["icon"]} aria-hidden="true" /> + ) : null); + return ( +
+
+ {iconEl && ( +
+
{iconEl}
+
+ )} +
+ {String(i + 1).padStart(2, "0")} +
+
+
{c.title}
+
{c.body}
+
+ ); + })} +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx b/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx new file mode 100644 index 000000000..1a377e175 --- /dev/null +++ b/crowdsec-docs/src/components/docs/ConsoleMockup/index.tsx @@ -0,0 +1,164 @@ +export default function ConsoleMockup() { + return ( +
+
+
+
+
+ app.crowdsec.net — Console +
+ + {/* Background */} + + + {/* Left sidebar */} + + {/* Logo area */} + + + C + + + {/* Nav items */} + {[60, 90, 120, 150, 180, 210].map((y, i) => ( + + + + + + ))} + + {/* Top bar */} + + + + + + + + {/* Stat cards row */} + {[ + { x: 172, label: "Alerts Today", value: "1,284", color: "var(--cs-orange)" }, + { x: 340, label: "Blocked IPs", value: "3,921", color: "var(--cs-teal)" }, + { x: 508, label: "Decisions", value: "8,406", color: "var(--cs-violet)" }, + ].map((card) => ( + + + + {card.label.toUpperCase()} + + + {card.value} + + {/* Sparkline */} + + + ))} + + {/* Main chart area */} + + + ALERTS OVER TIME + + {/* Chart bars */} + {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((i) => { + const heights = [40, 65, 50, 80, 55, 90, 45, 70, 60, 85, 50, 75]; + return ( + + ); + })} + {/* X-axis labels */} + {["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].map((m, i) => ( + + {m} + + ))} + + {/* Right panel */} + + + TOP THREATS + + {[ + { label: "SSH Bruteforce", pct: 82, color: "var(--cs-orange)" }, + { label: "Port Scan", pct: 61, color: "var(--cs-violet)" }, + { label: "HTTP Flood", pct: 45, color: "var(--cs-teal)" }, + { label: "CVE Exploit", pct: 34, color: "var(--cs-pink)" }, + { label: "Credential Stuff", pct: 22, color: "var(--cs-blue)" }, + ].map((row, i) => ( + + + {row.label} + + + + + ))} + +
+ ); +} diff --git a/crowdsec-docs/src/components/docs/DocCard/index.tsx b/crowdsec-docs/src/components/docs/DocCard/index.tsx new file mode 100644 index 000000000..5574554d9 --- /dev/null +++ b/crowdsec-docs/src/components/docs/DocCard/index.tsx @@ -0,0 +1,152 @@ +import { cilArrowRight } from "@coreui/icons"; +import { CIcon } from "@coreui/icons-react"; +import React from "react"; +import { mix } from "../../../utils/colorMix"; +import { CUI } from "../icons/cuiMap"; + +function renderIcon(name: string) { + const icon = CUI[name]; + if (!icon) return null; + return [0]["icon"]} aria-hidden="true" />; +} + +export type DocCardLink = { + label: string; + href: string; + external?: boolean; +}; + +export type DocCardProps = { + title: string; + desc?: string; + badge?: string; + iconName?: string; + icon?: React.ReactNode; + color?: string; + href?: string; + ctaLabel?: string; + links?: DocCardLink[]; + command?: string; + premium?: boolean; + children?: React.ReactNode; +}; + +function ExternalArrow() { + return ( +