24033ca977
Remove all references to the incorrect `config.watchPlugins: false` requirement that was believed necessary for Headlamp v0.39.0+. Investigation revealed that plugins work correctly with the default `watchPlugins: true` setting. The earlier documentation was based on a misunderstanding of the plugin loading mechanism. Changes: - Remove watchPlugins: false from all YAML configuration examples - Remove warning sections about watchPlugins requirement - Update troubleshooting guides to focus on actual issues - Simplify installation instructions by removing unnecessary config Files updated: - README.md (main installation docs and troubleshooting table) - docs/DEPLOYMENT.md - docs/TROUBLESHOOTING.md - docs/getting-started/* (quick-start, installation, prerequisites) - docs/deployment/* (helm, production) - docs/troubleshooting/* (common-issues, README) - Multiple other doc files formatted by prettier This cleanup ensures ArtifactHub and GitHub documentation show correct, simplified installation instructions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
398 lines
9.1 KiB
Markdown
398 lines
9.1 KiB
Markdown
# Data Flow
|
|
|
|
Detailed data flow sequences for the Headlamp Polaris Plugin.
|
|
|
|
## 1. Initial Load
|
|
|
|
```
|
|
User loads Headlamp
|
|
↓
|
|
Headlamp loads plugins
|
|
↓
|
|
Plugin registers routes, sidebar, app bar actions
|
|
↓
|
|
User navigates to /polaris
|
|
↓
|
|
DashboardView mounts
|
|
↓
|
|
PolarisDataContext.Provider wraps component
|
|
↓
|
|
usePolarisDataContext() hook triggers fetch
|
|
↓
|
|
ApiProxy.request() → K8s API → Service Proxy → Polaris
|
|
↓
|
|
AuditData returned and cached in Context
|
|
↓
|
|
Components receive data and render
|
|
```
|
|
|
|
## 2. Data Refresh
|
|
|
|
```
|
|
User clicks "Refresh" button or auto-refresh interval elapses
|
|
↓
|
|
refresh() function called in Context
|
|
↓
|
|
setRefreshKey() increments (forces re-fetch)
|
|
↓
|
|
useEffect dependency triggers new fetch
|
|
↓
|
|
ApiProxy.request() → Polaris Dashboard
|
|
↓
|
|
Context state updated with new data
|
|
↓
|
|
All consuming components re-render automatically
|
|
```
|
|
|
|
## 3. Navigation Flow
|
|
|
|
```
|
|
User clicks "Polaris" in sidebar
|
|
↓
|
|
Route: /c/main/polaris (DashboardView)
|
|
↓
|
|
Display cluster score, check distribution
|
|
↓
|
|
User clicks "Namespaces" submenu
|
|
↓
|
|
Route: /c/main/polaris/namespaces (NamespacesListView)
|
|
↓
|
|
Display table of namespaces with scores
|
|
↓
|
|
User clicks namespace button in table
|
|
↓
|
|
Drawer opens, URL hash updates (#namespace-name)
|
|
↓
|
|
NamespaceDetailView renders in drawer
|
|
↓
|
|
Display namespace score + resource table
|
|
```
|
|
|
|
## 4. Error Handling Flow
|
|
|
|
```
|
|
ApiProxy.request() called
|
|
↓
|
|
Fetch fails with HTTP error
|
|
↓
|
|
Error caught in usePolarisData hook
|
|
↓
|
|
Error status code checked (403, 404, 503, etc.)
|
|
↓
|
|
Context-specific error message set:
|
|
• 403: RBAC permission denied
|
|
• 404/503: Polaris not installed
|
|
• Other: Generic network error
|
|
↓
|
|
Error state propagated to consuming components
|
|
↓
|
|
Components render error UI with StatusLabel
|
|
↓
|
|
User sees error message with actionable guidance
|
|
```
|
|
|
|
## 5. Service Proxy Request Flow
|
|
|
|
```
|
|
Plugin code: ApiProxy.request(path)
|
|
↓
|
|
Headlamp backend proxies request
|
|
↓
|
|
HTTP GET to Kubernetes API server
|
|
↓
|
|
API server authenticates request (service account or user token)
|
|
↓
|
|
API server checks RBAC:
|
|
• Verb: get
|
|
• Resource: services/proxy
|
|
• ResourceName: polaris-dashboard
|
|
• Namespace: polaris
|
|
↓
|
|
If authorized:
|
|
API server proxies to Polaris service
|
|
↓
|
|
Polaris dashboard returns results.json
|
|
↓
|
|
Response flows back to plugin
|
|
↓
|
|
If denied (403):
|
|
RBAC error returned to plugin
|
|
↓
|
|
Plugin displays error with RBAC guidance
|
|
```
|
|
|
|
## 6. Settings Persistence Flow
|
|
|
|
```
|
|
User navigates to Settings → Plugins → Polaris
|
|
↓
|
|
PolarisSettings component mounts
|
|
↓
|
|
Component reads localStorage:
|
|
• polaris-plugin-refresh-interval
|
|
• polaris-plugin-dashboard-url
|
|
↓
|
|
Form populated with current values
|
|
↓
|
|
User modifies settings (refresh interval, dashboard URL)
|
|
↓
|
|
User clicks "Save"
|
|
↓
|
|
Settings written to localStorage:
|
|
localStorage.setItem('polaris-plugin-refresh-interval', value)
|
|
localStorage.setItem('polaris-plugin-dashboard-url', url)
|
|
↓
|
|
Success message displayed
|
|
↓
|
|
Context refreshes data with new interval
|
|
↓
|
|
All plugin views use new settings immediately
|
|
```
|
|
|
|
## 7. App Bar Badge Flow
|
|
|
|
```
|
|
Headlamp renders app bar
|
|
↓
|
|
Plugin's registerAppBarAction called
|
|
↓
|
|
AppBarScoreBadge component rendered in app bar
|
|
↓
|
|
Component uses usePolarisDataContext()
|
|
↓
|
|
Data fetched from Polaris (shared with views)
|
|
↓
|
|
Score computed: (pass / total) * 100
|
|
↓
|
|
Badge color determined:
|
|
• Green: score ≥ 80
|
|
• Yellow: score 50-79
|
|
• Red: score < 50
|
|
↓
|
|
Badge rendered with score and shield icon
|
|
↓
|
|
User clicks badge
|
|
↓
|
|
Navigate to /polaris (overview page)
|
|
```
|
|
|
|
## 8. Inline Audit Section Flow
|
|
|
|
```
|
|
User views Deployment/StatefulSet detail page
|
|
↓
|
|
Headlamp calls registered details view sections
|
|
↓
|
|
Plugin's InlineAuditSection component rendered
|
|
↓
|
|
Component receives resource metadata from Headlamp
|
|
↓
|
|
Component uses usePolarisDataContext()
|
|
↓
|
|
Filters audit results by:
|
|
• Namespace === resource.namespace
|
|
• Kind === resource.kind
|
|
• Name === resource.name
|
|
↓
|
|
If matching audit result found:
|
|
Extract check counts (pass/warning/danger)
|
|
↓
|
|
Render compact audit section:
|
|
• Score badge
|
|
• Check counts
|
|
• Link to full Polaris report
|
|
↓
|
|
If no match found:
|
|
Render "No audit data available" message
|
|
```
|
|
|
|
## Data Structures
|
|
|
|
### AuditData Schema
|
|
|
|
```typescript
|
|
interface AuditData {
|
|
PolarisOutputVersion: string; // "1.0"
|
|
AuditTime: string; // ISO 8601 timestamp
|
|
SourceType: string; // "Cluster"
|
|
SourceName: string; // Cluster identifier
|
|
DisplayName: string; // Human-readable name
|
|
ClusterInfo: {
|
|
Version: string; // K8s version
|
|
Nodes: number;
|
|
Pods: number;
|
|
Namespaces: number;
|
|
Controllers: number;
|
|
};
|
|
Results: Result[]; // Array of resource audit results
|
|
}
|
|
|
|
interface Result {
|
|
Name: string; // Resource name
|
|
Namespace: string; // Kubernetes namespace
|
|
Kind: string; // "Deployment", "StatefulSet", etc.
|
|
Results: ResultSet; // Resource-level checks
|
|
PodResult?: {
|
|
Name: string;
|
|
Results: ResultSet; // Pod-level checks
|
|
ContainerResults: {
|
|
Name: string;
|
|
Results: ResultSet; // Container-level checks
|
|
}[];
|
|
};
|
|
CreatedTime: string; // ISO 8601 timestamp
|
|
}
|
|
|
|
type ResultSet = Record<string, ResultMessage>;
|
|
|
|
interface ResultMessage {
|
|
ID: string; // Check ID (e.g., "cpuLimitsMissing")
|
|
Message: string; // Human-readable message
|
|
Details: string[]; // Additional context
|
|
Success: boolean; // true = passed, false = failed
|
|
Severity: 'ignore' | 'warning' | 'danger';
|
|
Category: string; // "Security", "Efficiency", etc.
|
|
}
|
|
```
|
|
|
|
### Result Counts
|
|
|
|
```typescript
|
|
interface ResultCounts {
|
|
total: number; // Total checks performed
|
|
pass: number; // Checks that passed (Success: true)
|
|
warning: number; // Failed checks with Severity: "warning"
|
|
danger: number; // Failed checks with Severity: "danger"
|
|
skipped: number; // Failed checks with Severity: "ignore"
|
|
}
|
|
```
|
|
|
|
## Data Transformations
|
|
|
|
### 1. Aggregating Counts
|
|
|
|
```typescript
|
|
// Input: AuditData.Results[]
|
|
// Output: ResultCounts
|
|
|
|
function countResults(data: AuditData): ResultCounts {
|
|
const counts = { total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 };
|
|
|
|
for (const result of data.Results) {
|
|
// Count resource-level checks
|
|
countResultSet(result.Results, counts);
|
|
|
|
// Count pod-level checks
|
|
if (result.PodResult) {
|
|
countResultSet(result.PodResult.Results, counts);
|
|
|
|
// Count container-level checks
|
|
for (const container of result.PodResult.ContainerResults) {
|
|
countResultSet(container.Results, counts);
|
|
}
|
|
}
|
|
}
|
|
|
|
return counts;
|
|
}
|
|
|
|
function countResultSet(rs: ResultSet, counts: ResultCounts): void {
|
|
for (const key in rs) {
|
|
const msg = rs[key];
|
|
counts.total++;
|
|
if (msg.Success) {
|
|
counts.pass++;
|
|
} else if (msg.Severity === 'ignore') {
|
|
counts.skipped++;
|
|
} else if (msg.Severity === 'warning') {
|
|
counts.warning++;
|
|
} else if (msg.Severity === 'danger') {
|
|
counts.danger++;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Computing Score
|
|
|
|
```typescript
|
|
// Input: ResultCounts
|
|
// Output: Score (0-100)
|
|
|
|
function computeScore(counts: ResultCounts): number {
|
|
if (counts.total === 0) return 0;
|
|
return Math.round((counts.pass / counts.total) * 100);
|
|
}
|
|
|
|
// Examples:
|
|
// { total: 100, pass: 90, ... } → 90
|
|
// { total: 100, pass: 50, ... } → 50
|
|
// { total: 0, pass: 0, ... } → 0
|
|
```
|
|
|
|
### 3. Filtering by Namespace
|
|
|
|
```typescript
|
|
// Input: AuditData, namespace string
|
|
// Output: Result[] for that namespace
|
|
|
|
function filterResultsByNamespace(data: AuditData, namespace: string): Result[] {
|
|
return data.Results.filter(r => r.Namespace === namespace);
|
|
}
|
|
```
|
|
|
|
### 4. Extracting Namespaces
|
|
|
|
```typescript
|
|
// Input: AuditData
|
|
// Output: Sorted array of unique namespace names
|
|
|
|
function getNamespaces(data: AuditData): string[] {
|
|
const namespaces = new Set<string>();
|
|
for (const result of data.Results) {
|
|
if (result.Namespace) {
|
|
namespaces.add(result.Namespace);
|
|
}
|
|
}
|
|
return Array.from(namespaces).sort();
|
|
}
|
|
```
|
|
|
|
## Caching Strategy
|
|
|
|
**Current Implementation:**
|
|
|
|
- Data fetched once and stored in React Context
|
|
- Shared across all plugin views (no duplicate fetches)
|
|
- Cached until manual refresh or auto-refresh interval
|
|
|
|
**Cache Invalidation:**
|
|
|
|
- Manual refresh button click
|
|
- Auto-refresh interval elapses
|
|
- Settings change (dashboard URL)
|
|
|
|
**No Persistence:**
|
|
|
|
- Data NOT persisted to localStorage
|
|
- Each browser session fetches fresh data
|
|
- No offline mode
|
|
|
|
**Future Enhancement:**
|
|
|
|
- IndexedDB caching for offline access
|
|
- Incremental updates (fetch only changed namespaces)
|
|
- Service Worker for background refresh
|
|
|
|
## Next Steps
|
|
|
|
- **[Architecture Overview](overview.md)** - High-level component hierarchy
|
|
- **[Design Decisions](design-decisions.md)** - Key architectural choices
|
|
- **[ADRs](adr/README.md)** - Formal Architecture Decision Records
|
|
|
|
## References
|
|
|
|
- [Polaris API Documentation](https://polaris.docs.fairwinds.com/)
|
|
- [React Context API](https://react.dev/reference/react/useContext)
|
|
- [Headlamp ApiProxy](https://headlamp.dev/docs/latest/development/api/)
|