Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ node_modules
.cursor
.bin/
claude.md

# Generated datasheet components
mission-control/src/components/datasheets/Generated*.tsx
.playwright-mcp/
solutions/
11 changes: 11 additions & 0 deletions .valeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
prompts/
**/apm-hub/**
**/benchmark*
**/development*
**/node_modules/**
modules/
docs/incidents/
docs-vale-package/
docs/apm-hub/
scripts/
**/style-guide/
25 changes: 20 additions & 5 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,33 @@ tasks:
desc: Run vale on all documentation
deps: [vale:sync]
cmds:
- '{{.VALE}} canary-checker/docs --glob="!**/{README,CHANGELOG,readme,security,SECURITY,CONTRIBUTING,benchmark,development,LICENSE}.md"'
- '{{.VALE}} mission-control/docs --glob="!**/{README,CHANGELOG,readme,security,SECURITY,CONTRIBUTING,benchmark,development,LICENSE}.md"'
- |
files=$(rg --files --ignore-file=.valeignore -g "*.md" -g "*.mdx" -g '![A-Z]*.md' canary-checker/docs)
if [ -n "$files" ]; then
{{.VALE}} $files
fi
- |
files=$(rg --files --ignore-file=.valeignore -g "*.md" -g "*.mdx" -g '![A-Z]*.md' mission-control/docs)
if [ -n "$files" ]; then
{{.VALE}} $files
fi

vale:changed:
desc: Run vale on markdown files changed in current branch
deps: [vale:sync]
cmds:
- |
files=$(git diff --name-only main...HEAD | grep -E '\.(md|mdx)$' || true)
changed_files=$(git diff --name-only main...HEAD | grep -E '\.(md|mdx)$' || true)
allowed_files=$(rg --files --ignore-file=.valeignore -g "*.md" -g "*.mdx" -g '![A-Z]*.md' || true)
files=""
for file in $changed_files; do
if [ -f "$file" ] && echo "$allowed_files" | grep -q "^$file$"; then
files="$files $file"
fi
done
if [ -n "$files" ]; then
echo "Running vale on changed files: $files"
{{.VALE}} $files --glob='!**/{README,CHANGELOG,readme,security,AGENTS,Readme,SECURITY,CONTRIBUTING,benchmark,development,LICENSE}.md'
echo "Running vale on changed files:$files"
{{.VALE}} $files
else
echo "No markdown/mdx files changed"
fi
Expand Down
2 changes: 1 addition & 1 deletion canary-checker/docs/partials/_domain.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Choose a routable `DOMAIN` for Mission Control
> See [Ingress](/docs/installation/self-hosted/ingress) for more options on configuring the ingress including generating certs with cert-manager
> See [Ingress](/docs/installation/self-hosted) for more options on configuring the ingress including generating certs with cert-manager
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, explore the documentation directory structure
fd -t f -e md -e mdx "self-hosted" docs/ --exec basename {} \;

# Also check the full directory structure
find docs/installation -type f -o -type d 2>/dev/null | head -30

Repository: flanksource/docs

Length of output: 42


🏁 Script executed:

# Check if docs directory exists and explore its structure
ls -la docs/ 2>/dev/null | head -20

# List all directories in docs
find docs -type d -maxdepth 3 2>/dev/null | sort | head -30

# Search for any ingress-related documentation
rg -l "ingress" docs/ --type md --type mdx 2>/dev/null | head -20

Repository: flanksource/docs

Length of output: 993


🏁 Script executed:

# Explore canary-checker directory structure
find docs/canary-checker -type f -o -type d 2>/dev/null | sort

# Check if the file being reviewed exists
cat -n docs/canary-checker/docs/partials/_domain.mdx 2>/dev/null

# Search for any installation or ingress related files
find docs/canary-checker -name "*installation*" -o -name "*ingress*" 2>/dev/null

Repository: flanksource/docs

Length of output: 192


🏁 Script executed:

# Search for _domain.mdx file across entire repository
find . -name "*domain*" -type f 2>/dev/null

# Search for installation documentation anywhere
find . -path "*/installation/*" -type f 2>/dev/null | head -20

# Search for any ingress documentation
rg "ingress" --type md --type mdx -l 2>/dev/null | head -20

Repository: flanksource/docs

Length of output: 1171


🏁 Script executed:

# Read the file being reviewed
cat -n ./canary-checker/docs/partials/_domain.mdx

# List all installation files
ls -la ./canary-checker/docs/installation/

# Search for self-hosted related files
find . -name "*self-hosted*" -type f 2>/dev/null

# Search for ingress in canary-checker
rg "ingress" ./canary-checker --type md --type mdx -l 2>/dev/null

Repository: flanksource/docs

Length of output: 892


🏁 Script executed:

# Read the installation index to see what's there
cat -n ./canary-checker/docs/installation/index.md

# Read the helm installation file
cat -n ./canary-checker/docs/installation/1-helm.canary.md | head -50

# Check if any installation file mentions ingress or self-hosted
rg "ingress|self.hosted" ./canary-checker/docs/installation/ --type md --type mdx -i -A 2 -B 2

Repository: flanksource/docs

Length of output: 1912


🏁 Script executed:

# Use find with exec to read all installation files
find ./canary-checker/docs/installation -type f \( -name "*.md" -o -name "*.mdx" \) -exec cat {} +

# Check if there are any other self-hosted related docs in the entire canary-checker repo
find ./canary-checker -type f \( -name "*.md" -o -name "*.mdx" \) -exec grep -l "self.hosted\|ingress" {} \; 2>/dev/null

Repository: flanksource/docs

Length of output: 6487


🏁 Script executed:

# Check the slug/title of cli.mdx to understand the URL pattern
head -20 ./canary-checker/docs/installation/cli.mdx

# Check all installation files for their slug definitions
grep -A 5 "^slug:" ./canary-checker/docs/installation/*.md* 2>/dev/null

Repository: flanksource/docs

Length of output: 1441


🏁 Script executed:

# Check what links are used in _domain.mdx file to understand the path pattern
cat -n ./canary-checker/docs/partials/_domain.mdx

# Check other files in canary-checker that link to installation pages
rg "/docs/installation" ./canary-checker --type md --type mdx -B 1 -A 1 | head -40

Repository: flanksource/docs

Length of output: 451


🏁 Script executed:

# Check if local-testing documentation exists
find . -name "*local*testing*" -type f 2>/dev/null

# Check all files in canary-checker/docs with their paths
find ./canary-checker/docs -type f \( -name "*.md" -o -name "*.mdx" \) 

# Look for any files that might be the self-hosted or local-testing docs
ls -la ./canary-checker/docs/installation/

Repository: flanksource/docs

Length of output: 4591


Both link targets do not exist in the canary-checker documentation.

The links at /docs/installation/self-hosted and /docs/installation/local-testing do not correspond to any documentation files in the canary-checker installation directory. Ingress configuration information is available in the Helm installation guide (/docs/installation/helm), but the links as currently written are broken and will fail to resolve for users.

🤖 Prompt for AI Agents
In canary-checker/docs/partials/_domain.mdx around line 2, the two link targets
(/docs/installation/self-hosted and /docs/installation/local-testing) do not
exist and therefore break docs; replace them with valid targets (for example
point the Ingress link to /docs/installation/helm which contains ingress
configuration and update or remove the local-testing link—if you need a
local-testing reference, create or point to the correct existing page path), and
ensure the link text and URL are consistent so the MDX resolves properly.

> <p>See [Local Testing](/docs/installation/local-testing) for testing using a kind or minikube without a routable domain</p>

4 changes: 4 additions & 0 deletions canary-checker/docs/partials/_integration_prereqs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

:::info Prerequisites
- Mission Control SaaS or Self-Hosted installed
:::
224 changes: 224 additions & 0 deletions common/src/components/ArchitectureDiagram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import React from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import Xarrow from 'react-xarrows';
import {
Aws,
Azure,
K8S,
GoogleCloud,
Terraform,
Flux,
Argo,
Prometheus,
Datadog,
Github,
Postgres,
ConfigDbWhite,
CanaryCheckerWhite,
Playbook,
Mcp,
Helm,
Kustomize,
AwsCloudwatch,
Opensearch,
Dynatrace,
SqlServer,
AzureDevops,
Gitlab,
} from '@flanksource/icons/mi';
import { PiBrain } from 'react-icons/pi';

interface IconGridProps {
items: Array<{ Icon: React.ComponentType<{ className?: string }>; label?: string }>;
cols?: number;
iconSize?: string;
}

function IconGrid({ items, cols = 5, iconSize = 'w-6 h-6' }: IconGridProps) {
return (
<div className="grid gap-2" style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}>
{items.map(({ Icon, label }, idx) => (
<div key={label || idx} className="flex flex-col items-center">
<Icon className={iconSize} />
{label && <span className="text-[9px] text-gray-500 mt-0.5">{label}</span>}
</div>
))}
</div>
);
}

interface BoxNodeProps {
id?: string;
title?: string | React.ReactNode;
className?: string;
bodyClassName?: string;
border?: 'solid' | 'dashed';
children?: React.ReactNode;
}

function BoxNode({ id, title, className = '', bodyClassName = '', border = 'solid', children }: BoxNodeProps) {
const bgMatch = className.match(/bg-(\w+)-(\d+)/);
const borderClass = bgMatch ? `border-${bgMatch[1]}-${bgMatch[2]}` : 'border-slate-300';
const borderStyle = `border-2 ${border === 'dashed' ? 'border-dashed' : 'border-solid'} ${borderClass}`;
const hasHeader = title !== undefined;

return (
<div id={id} className={`rounded-xl overflow-hidden shadow-lg min-w-[120px] ${borderStyle}`}>
{hasHeader && (
<div className={`px-3 py-2 text-center ${className}`}>
<span className="text-white text-xs font-bold">{title}</span>
</div>
)}
{children && (
<div className={`${bodyClassName} ${hasHeader ? 'p-3' : 'p-3'}`}>
{children}
</div>
)}
</div>
);
}

function K8SCRDsBox() {
return (
<div id="arch-k8s-crds" className="flex items-center gap-3 bg-blue-600 rounded-xl px-5 py-3 shadow-lg border-2 border-blue-400">
<K8S className="w-8 h-8 text-white" />
<span className="text-white font-bold text-sm">K8S CRDs</span>
</div>
);
}

function MissionControlBox() {
const features = [
{ Icon: CanaryCheckerWhite, label: 'Health Checks' },
{ Icon: ConfigDbWhite, label: 'Unified Catalog' },
{ Icon: Playbook, label: 'Playbooks' },
{ Icon: PiBrain, label: 'Real-Time RAG' },
];

return (
<div id="arch-missionControl" className="rounded-2xl overflow-hidden bg-gradient-to-br from-slate-700 to-slate-900 border-2 border-blue-500 shadow-2xl">
<div className="px-6 py-3 text-center border-b border-blue-800/50">
<span className="text-white text-lg font-bold tracking-wide">MISSION CONTROL</span>
</div>
<div className="p-4 flex flex-col gap-3">
<div className="grid grid-cols-2 gap-3">
{features.map(({ Icon, label }) => (
<div
key={label}
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-500 transition-colors rounded-lg px-3 py-2 cursor-default"
>
<Icon className="w-5 h-5 text-white" />
<span className="text-white text-xs font-medium">{label}</span>
</div>
))}
</div>
<div className="flex items-center justify-center gap-2 bg-slate-600 rounded-lg px-3 py-2">
<Postgres className="w-5 h-5" />
<span className="text-white text-xs font-medium">Postgres</span>
</div>
</div>
</div>
);
}

function MCPServerBox() {
return (
<div id="arch-mcp-server" className="flex items-center gap-3 bg-indigo-600 rounded-xl px-5 py-3 shadow-lg border-2 border-indigo-400">
<Mcp className="w-7 h-7 text-white fill-white" />
<span className="text-white font-bold text-sm">MCP Server</span>
</div>
);
}

function IntegrationsBox() {
const integrations = [
{ Icon: Aws },
{ Icon: Azure },
{ Icon: GoogleCloud },
{ Icon: K8S },
{ Icon: Terraform },
{ Icon: Flux },
{ Icon: Argo },
{ Icon: Helm },
{ Icon: Kustomize },
{ Icon: Prometheus },
{ Icon: Datadog },
{ Icon: Dynatrace },
{ Icon: AwsCloudwatch },
{ Icon: Opensearch },
{ Icon: Postgres },
{ Icon: SqlServer },
{ Icon: Github },
{ Icon: AzureDevops },
{ Icon: Gitlab },
{ Icon: Mcp },
];

return (
<div id="arch-integrations">
<BoxNode
title="40+ Integrations"
className="bg-slate-500"
bodyClassName="bg-slate-50"
border="solid"
>
<IconGrid items={integrations} cols={5} iconSize="w-7 h-7" />
</BoxNode>
</div>
);
}

interface ArchitectureDiagramProps {
className?: string;
variant?: string;
}

function ArchitectureDiagramInner({ className }: ArchitectureDiagramProps) {
return (
<div className={`${className || ''} relative flex flex-col items-center gap-10 py-8`}>
<K8SCRDsBox />
<MissionControlBox />
<MCPServerBox />
<IntegrationsBox />

<Xarrow
start="arch-k8s-crds"
end="arch-missionControl"
color="#3b82f6"
strokeWidth={3}
startAnchor="bottom"
endAnchor="top"
headSize={4}
dashness={{ strokeLen: 10, nonStrokeLen: 5, animation: 1 }}
/>
<Xarrow
start="arch-missionControl"
end="arch-mcp-server"
color="#6366f1"
strokeWidth={3}
startAnchor="bottom"
endAnchor="top"
headSize={4}
dashness={{ strokeLen: 10, nonStrokeLen: 5, animation: 1 }}
/>
<Xarrow
start="arch-mcp-server"
end="arch-integrations"
color="#6366f1"
strokeWidth={3}
startAnchor="bottom"
endAnchor="top"
headSize={4}
dashness={{ strokeLen: 10, nonStrokeLen: 5, animation: 1 }}
/>
</div>
);
}

export default function ArchitectureDiagram({ className, variant }: ArchitectureDiagramProps) {
return (
<BrowserOnly fallback={<div className="w-full h-96 flex items-center justify-center">Loading...</div>}>
{() => <ArchitectureDiagramInner className={className} variant={variant} />}
</BrowserOnly>
);
}
97 changes: 97 additions & 0 deletions common/src/components/CapabilityBadges.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import { ConfigDb, Health, Playbook, Bell, Webhook } from '@flanksource/icons/mi';

const capabilities = {
scraper: {
icon: ConfigDb,
label: 'Scraper',
color: '#2563eb',
bg: '#dbeafe',
border: '#93c5fd'
},
healthcheck: {
icon: Health,
label: 'Health Check',
color: '#d97706',
bg: '#fef3c7',
border: '#fcd34d'
},
playbook: {
icon: Playbook,
label: 'Playbook',
color: '#7c3aed',
bg: '#ede9fe',
border: '#c4b5fd'
},
notifications: {
icon: Bell,
label: 'Notifications',
color: '#16a34a',
bg: '#dcfce7',
border: '#86efac'
},
actions: {
icon: Webhook,
label: 'Actions',
color: '#4f46e5',
bg: '#e0e7ff',
border: '#a5b4fc'
},
'relationship': {
icon: ConfigDb,
label: 'Relationship',
color: '#d97706',
bg: '#fef3c7',
border: '#fcd34d'
}
};

export function CapabilityBadge({ type, label }) {
const cap = capabilities[type];
if (!cap) return null;

const Icon = cap.icon;
return (
<span
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium"
style={{
backgroundColor: cap.bg,
color: cap.color,
border: `1px solid ${cap.border}`
}}
>
<Icon className="w-4 h-4" />
{label || cap.label}
</span>
);
}

export function CapabilityBadges({ children }) {
return (
<div className="flex flex-wrap gap-2 mb-6 not-prose">
{children}
</div>
);
}

export function CapabilityHeading({ type }) {
const cap = capabilities[type];
if (!cap) return null;

const Icon = cap.icon;
return (
<h2 className="flex items-center gap-2 mt-8 mb-4 not-prose">
<span
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-base font-medium"
style={{
backgroundColor: cap.bg,
color: cap.color,
border: `1px solid ${cap.border}`
}}
>
<Icon className="w-5 h-5" />
{cap.label}
</span>
</h2>
);
}
Loading
Loading