Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17495d4883 | |||
| 01eed82efc | |||
| 5dab426fe8 | |||
| 5eaa6603f1 | |||
| b67f770660 | |||
| 20e8063cbb | |||
| c1156e5cf5 | |||
| cab2118a88 |
@@ -0,0 +1,290 @@
|
|||||||
|
# Headlamp Polaris Plugin - Project Assessment
|
||||||
|
|
||||||
|
**Date:** 2026-02-11
|
||||||
|
**Version:** v0.3.0
|
||||||
|
**Status:** Active Development
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This assessment identifies critical issues and improvement opportunities for the headlamp-polaris-plugin project. The plugin is currently non-functional in production due to Headlamp v0.39.0 compatibility issues, and has several TypeScript compilation errors that need immediate attention.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 Critical Issues (Must Fix Immediately)
|
||||||
|
|
||||||
|
### 1. TypeScript Compilation Errors
|
||||||
|
**Severity:** CRITICAL
|
||||||
|
**Impact:** Build failures, type safety compromised
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- `src/index.tsx:72` - `registerDetailsViewSection` expects 1 argument, got 2
|
||||||
|
- `src/index.tsx:87` - `registerAppBarAction` expects 1 argument, got 2
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
Update Headlamp plugin API calls to match the current version. Check @kinvolk/headlamp-plugin version compatibility.
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Review Headlamp plugin API documentation
|
||||||
|
- [ ] Update `registerDetailsViewSection` and `registerAppBarAction` calls
|
||||||
|
- [ ] Run `npm run tsc` to verify fixes
|
||||||
|
- [ ] Update CI to fail on TypeScript errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Production Plugin Loading Failure
|
||||||
|
**Severity:** CRITICAL
|
||||||
|
**Impact:** Plugin is completely non-functional in production
|
||||||
|
|
||||||
|
**Root Cause:**
|
||||||
|
Headlamp v0.39.0 with default `watchPlugins: true` treats catalog-managed plugins as "development directory" plugins, preventing frontend JavaScript execution.
|
||||||
|
|
||||||
|
**Current Status:**
|
||||||
|
- Deployment patched to install plugins to `/headlamp/static-plugins`
|
||||||
|
- `watchPlugins: false` configured
|
||||||
|
- Waiting for user to test if plugins now load
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Confirm plugins load after recent deployment changes
|
||||||
|
- [ ] Document the fix in deployment guide
|
||||||
|
- [ ] Update MEMORY.md with final resolution
|
||||||
|
- [ ] Consider downgrading Headlamp if issue persists
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Test Failures
|
||||||
|
**Severity:** HIGH
|
||||||
|
**Impact:** CI failures, reduced confidence in changes
|
||||||
|
|
||||||
|
**Current Status:**
|
||||||
|
- 1 test file failing (DashboardView)
|
||||||
|
- 49 tests passing
|
||||||
|
- Error related to `SimpleTable` component mock
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Fix DashboardView test mocking
|
||||||
|
- [ ] Ensure all tests pass before merging PRs
|
||||||
|
- [ ] Add test for top issues feature
|
||||||
|
- [ ] Increase test coverage to >80%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 High Priority Improvements
|
||||||
|
|
||||||
|
### 4. Type Safety Enhancements
|
||||||
|
**Severity:** HIGH
|
||||||
|
**Impact:** Better developer experience, catch errors earlier
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
- Enable stricter TypeScript checks in `tsconfig.json`
|
||||||
|
- Add type definitions for all Headlamp plugin APIs
|
||||||
|
- Ensure no `any` types in production code
|
||||||
|
- Add JSDoc comments for complex types
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Audit codebase for `any` types
|
||||||
|
- [ ] Enable `noImplicitAny` and `strictNullChecks`
|
||||||
|
- [ ] Add type guards for API responses
|
||||||
|
- [ ] Document complex type structures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Security Hardening
|
||||||
|
**Severity:** HIGH
|
||||||
|
**Impact:** Prevent vulnerabilities, protect user data
|
||||||
|
|
||||||
|
**Current Risks:**
|
||||||
|
- Direct Kubernetes API access via service proxy
|
||||||
|
- User input in exemption annotations (potential injection)
|
||||||
|
- External URL configuration for Polaris dashboard
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
- Validate and sanitize all user inputs
|
||||||
|
- Implement input validation for dashboard URL
|
||||||
|
- Add CSRF protection for exemption management
|
||||||
|
- Audit dependencies for known vulnerabilities
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Add input validation utilities
|
||||||
|
- [ ] Sanitize exemption annotation values
|
||||||
|
- [ ] Validate URL format for dashboard configuration
|
||||||
|
- [ ] Run `npm audit` and fix vulnerabilities
|
||||||
|
- [ ] Add security testing to CI/CD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Error Handling & User Experience
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
**Impact:** Better error messages, improved debugging
|
||||||
|
|
||||||
|
**Current Gaps:**
|
||||||
|
- Generic error messages don't help users troubleshoot
|
||||||
|
- No retry logic for transient API failures
|
||||||
|
- Missing loading states in some components
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
- Provide specific, actionable error messages
|
||||||
|
- Implement retry logic with exponential backoff
|
||||||
|
- Add loading skeletons for all async operations
|
||||||
|
- Show connection test results with specific failure reasons
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Create error message constants with solutions
|
||||||
|
- [ ] Add retry logic to API calls
|
||||||
|
- [ ] Implement loading skeletons
|
||||||
|
- [ ] Improve connection test error messages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 Medium Priority Enhancements
|
||||||
|
|
||||||
|
### 7. Testing Coverage
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
**Impact:** Confidence in changes, regression prevention
|
||||||
|
|
||||||
|
**Current Coverage:**
|
||||||
|
- Unit tests: Good coverage for API utilities
|
||||||
|
- Component tests: Some coverage, gaps exist
|
||||||
|
- E2E tests: Minimal (Playwright configured but underutilized)
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
- Add E2E tests for critical user flows
|
||||||
|
- Test error scenarios and edge cases
|
||||||
|
- Add visual regression tests
|
||||||
|
- Test RBAC permission denied scenarios
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Write E2E test for complete audit workflow
|
||||||
|
- [ ] Add tests for error states
|
||||||
|
- [ ] Test exemption management flow
|
||||||
|
- [ ] Add Playwright tests to CI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Performance Optimization
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
**Impact:** Faster load times, better UX
|
||||||
|
|
||||||
|
**Opportunities:**
|
||||||
|
- Memoize expensive calculations (score computation)
|
||||||
|
- Lazy load namespace detail views
|
||||||
|
- Debounce search/filter operations
|
||||||
|
- Cache Polaris data with stale-while-revalidate
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Add React.memo to pure components
|
||||||
|
- [ ] Memoize score calculations
|
||||||
|
- [ ] Implement data caching strategy
|
||||||
|
- [ ] Profile component render times
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Code Quality & Maintainability
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
**Impact:** Easier maintenance, onboarding
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
- Extract magic strings to constants
|
||||||
|
- Reduce component complexity
|
||||||
|
- Add JSDoc comments for public APIs
|
||||||
|
- Improve code organization
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Create constants file for check IDs
|
||||||
|
- [ ] Split large components (DashboardView, NamespaceDetailView)
|
||||||
|
- [ ] Add comments for complex logic
|
||||||
|
- [ ] Establish code review checklist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔵 Low Priority / Future Enhancements
|
||||||
|
|
||||||
|
### 10. Documentation
|
||||||
|
**Severity:** LOW
|
||||||
|
**Impact:** Better onboarding, user adoption
|
||||||
|
|
||||||
|
**Gaps:**
|
||||||
|
- No architecture documentation
|
||||||
|
- Limited inline code comments
|
||||||
|
- Missing troubleshooting guide
|
||||||
|
- No contributor guidelines
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Create architecture diagram
|
||||||
|
- [ ] Document component hierarchy
|
||||||
|
- [ ] Add troubleshooting section to README
|
||||||
|
- [ ] Create CONTRIBUTING.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. CI/CD Pipeline Optimization
|
||||||
|
**Severity:** LOW
|
||||||
|
**Impact:** Faster feedback, automated releases
|
||||||
|
|
||||||
|
**Opportunities:**
|
||||||
|
- Run tests in parallel
|
||||||
|
- Cache npm dependencies
|
||||||
|
- Add automated security scanning
|
||||||
|
- Implement semantic versioning
|
||||||
|
|
||||||
|
**Action Items:**
|
||||||
|
- [ ] Parallelize test execution
|
||||||
|
- [ ] Add npm cache to GitHub Actions
|
||||||
|
- [ ] Integrate Dependabot
|
||||||
|
- [ ] Add semantic-release
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary & Prioritization
|
||||||
|
|
||||||
|
### Week 1 (Immediate)
|
||||||
|
1. ✅ Fix TypeScript compilation errors
|
||||||
|
2. ✅ Resolve production plugin loading issue
|
||||||
|
3. ✅ Fix failing DashboardView test
|
||||||
|
|
||||||
|
### Week 2 (High Priority)
|
||||||
|
4. Enhance type safety (strict mode)
|
||||||
|
5. Implement security hardening
|
||||||
|
6. Improve error handling and UX
|
||||||
|
|
||||||
|
### Week 3-4 (Medium Priority)
|
||||||
|
7. Increase test coverage to >80%
|
||||||
|
8. Optimize performance (memoization, caching)
|
||||||
|
9. Refactor for maintainability
|
||||||
|
|
||||||
|
### Ongoing (Low Priority)
|
||||||
|
10. Documentation improvements
|
||||||
|
11. CI/CD optimizations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
**Code Quality:**
|
||||||
|
- ✅ Zero TypeScript errors
|
||||||
|
- ✅ All tests passing
|
||||||
|
- 🎯 Test coverage >80%
|
||||||
|
- 🎯 No high/critical security vulnerabilities
|
||||||
|
|
||||||
|
**Production Readiness:**
|
||||||
|
- ✅ Plugin loads successfully in Headlamp
|
||||||
|
- ✅ All features functional
|
||||||
|
- 🎯 Error rate <1%
|
||||||
|
- 🎯 Average response time <500ms
|
||||||
|
|
||||||
|
**Developer Experience:**
|
||||||
|
- ✅ Clear documentation
|
||||||
|
- ✅ Easy local setup
|
||||||
|
- 🎯 Fast CI/CD (<5 min)
|
||||||
|
- 🎯 Automated releases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Immediate:** Fix TypeScript errors and verify plugin loads
|
||||||
|
2. **Short-term:** Complete Week 1-2 priorities
|
||||||
|
3. **Long-term:** Address medium and low priority items
|
||||||
|
4. **Continuous:** Monitor metrics and iterate
|
||||||
|
|
||||||
|
**Recommended First Action:**
|
||||||
|
Fix the TypeScript compilation errors in `src/index.tsx` by updating the Headlamp plugin API calls.
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
version: 0.3.0
|
version: 0.3.2
|
||||||
name: headlamp-polaris-plugin
|
name: headlamp-polaris-plugin
|
||||||
displayName: Polaris
|
displayName: Polaris
|
||||||
createdAt: "2026-02-05T19:00:00Z"
|
createdAt: "2026-02-05T19:00:00Z"
|
||||||
@@ -28,7 +28,7 @@ maintainers:
|
|||||||
- name: cpfarhood
|
- name: cpfarhood
|
||||||
email: "chris@farhood.org"
|
email: "chris@farhood.org"
|
||||||
annotations:
|
annotations:
|
||||||
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.0/headlamp-polaris-plugin-0.3.0.tar.gz"
|
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.2/headlamp-polaris-plugin-0.3.2.tar.gz"
|
||||||
headlamp/plugin/version-compat: ">=0.26"
|
headlamp/plugin/version-compat: ">=0.26"
|
||||||
headlamp/plugin/archive-checksum: sha256:fbe29c07478f28433f5859f452880929717f5ee1d5baebe7e9dbd8880ba483d1
|
headlamp/plugin/archive-checksum: sha256:66d51513a6bf73b6f67af10d2dc55dabea7340d551faf3d59a9cd34b232ca868
|
||||||
headlamp/plugin/distro-compat: in-cluster
|
headlamp/plugin/distro-compat: in-cluster
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Headlamp Plugin Loading Issue - Root Cause and Fix
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
Headlamp v0.39.0 was not loading plugins installed via the plugin manager. Plugins appeared in Settings → Plugins but:
|
||||||
|
- No sidebar entries appeared
|
||||||
|
- No plugin settings were available
|
||||||
|
- Plugin JavaScript was not being executed in the browser
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
When `config.watchPlugins: true` (the default), Headlamp treats catalog-managed plugins in `/headlamp/plugins/` as "development directory" plugins. This causes:
|
||||||
|
- Backend serves plugin metadata correctly
|
||||||
|
- Backend logs show "Treating catalog-installed plugin in development directory as user plugin"
|
||||||
|
- **Frontend does NOT execute the plugin JavaScript**
|
||||||
|
- Plugin registrations (`registerSidebarEntry`, `registerRoute`, etc.) never happen
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
Set `config.watchPlugins: false` in the Headlamp HelmRelease values:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
values:
|
||||||
|
config:
|
||||||
|
watchPlugins: false
|
||||||
|
pluginsManager:
|
||||||
|
enabled: true
|
||||||
|
configContent: |
|
||||||
|
plugins:
|
||||||
|
- name: polaris
|
||||||
|
source: https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin
|
||||||
|
# ... other plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Works
|
||||||
|
With `watchPlugins: false`:
|
||||||
|
- Headlamp no longer treats catalog-managed plugins as "development" plugins
|
||||||
|
- Frontend properly loads and executes plugin JavaScript on startup
|
||||||
|
- Plugin registrations happen correctly
|
||||||
|
- All plugin features (sidebar, routes, settings, etc.) work as expected
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
After applying this fix:
|
||||||
|
1. Verify plugins are installed: `kubectl logs -n kube-system <headlamp-pod> -c headlamp-plugin`
|
||||||
|
2. Verify watchPlugins is false: `kubectl logs -n kube-system <headlamp-pod> -c headlamp | grep "Watch Plugins"`
|
||||||
|
3. Hard refresh browser (Cmd+Shift+R / Ctrl+Shift+F5) to clear cached JavaScript
|
||||||
|
4. Verify plugin sidebar entries appear
|
||||||
|
5. Verify plugin functionality works
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
- This appears to be a bug/limitation in Headlamp v0.39.0
|
||||||
|
- The `watchPlugins` feature is intended for development scenarios where plugins are being actively modified
|
||||||
|
- For production deployments with catalog-managed plugins, `watchPlugins: false` is the correct configuration
|
||||||
|
- Once plugins are loaded, subsequent restarts or updates work correctly as long as `watchPlugins` remains false
|
||||||
|
|
||||||
|
## References
|
||||||
|
- Headlamp Helm Chart: https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp
|
||||||
|
- Plugin Manager: https://github.com/headlamp-k8s/headlamp/tree/main/plugins/headlamp-plugin
|
||||||
|
- Issue discovered: 2026-02-11
|
||||||
|
- Fix applied: 2026-02-12
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
# Custom Headlamp values for static plugin installation
|
||||||
|
# This disables the plugin manager and uses an init container instead
|
||||||
|
|
||||||
|
# Disable the plugin manager sidecar
|
||||||
|
pluginsManager:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Use an init container to install plugins to /headlamp/static-plugins
|
||||||
|
initContainers:
|
||||||
|
- name: install-plugins
|
||||||
|
image: node:lts-alpine
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Installing plugins to /headlamp/static-plugins..."
|
||||||
|
|
||||||
|
# Create plugins directory
|
||||||
|
mkdir -p /headlamp/static-plugins
|
||||||
|
|
||||||
|
# Set up npm cache
|
||||||
|
export NPM_CONFIG_CACHE=/tmp/npm-cache
|
||||||
|
export NPM_CONFIG_USERCONFIG=/tmp/npm-userconfig
|
||||||
|
mkdir -p /tmp/npm-cache /tmp/npm-userconfig
|
||||||
|
|
||||||
|
# Install polaris plugin
|
||||||
|
echo "Installing polaris plugin..."
|
||||||
|
cd /headlamp/static-plugins
|
||||||
|
npm pack headlamp-polaris-plugin@0.3.0
|
||||||
|
tar -xzf headlamp-polaris-plugin-0.3.0.tgz
|
||||||
|
mv package headlamp-polaris-plugin
|
||||||
|
rm headlamp-polaris-plugin-0.3.0.tgz
|
||||||
|
|
||||||
|
# Install other plugins
|
||||||
|
npx --yes @headlamp-k8s/plugin@latest install \
|
||||||
|
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_flux \
|
||||||
|
--folderName /headlamp/static-plugins
|
||||||
|
|
||||||
|
npx --yes @headlamp-k8s/plugin@latest install \
|
||||||
|
--source https://artifacthub.io/packages/headlamp/headlamp-trivy/headlamp_trivy \
|
||||||
|
--folderName /headlamp/static-plugins
|
||||||
|
|
||||||
|
npx --yes @headlamp-k8s/plugin@latest install \
|
||||||
|
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_cert-manager \
|
||||||
|
--folderName /headlamp/static-plugins
|
||||||
|
|
||||||
|
npx --yes @headlamp-k8s/plugin@latest install \
|
||||||
|
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_ai_assistant \
|
||||||
|
--folderName /headlamp/static-plugins
|
||||||
|
|
||||||
|
echo "All plugins installed successfully"
|
||||||
|
ls -la /headlamp/static-plugins
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 100
|
||||||
|
runAsGroup: 101
|
||||||
|
runAsNonRoot: true
|
||||||
|
privileged: false
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: static-plugins
|
||||||
|
mountPath: /headlamp/static-plugins
|
||||||
|
|
||||||
|
# Configure headlamp to use static plugins
|
||||||
|
config:
|
||||||
|
pluginsDir: /headlamp/static-plugins
|
||||||
|
|
||||||
|
# Add volume for static plugins
|
||||||
|
volumes:
|
||||||
|
- name: static-plugins
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
# Add volume mount to main container
|
||||||
|
volumeMounts:
|
||||||
|
- name: static-plugins
|
||||||
|
mountPath: /headlamp/static-plugins
|
||||||
|
readOnly: true
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "headlamp-polaris-plugin",
|
"name": "headlamp-polaris-plugin",
|
||||||
"version": "0.3.0",
|
"version": "0.3.2",
|
||||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "headlamp-plugin start",
|
"start": "headlamp-plugin start",
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Chip } from '@mui/material';
|
|
||||||
import { Shield as ShieldIcon } from '@mui/icons-material';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||||
@@ -21,10 +19,10 @@ export default function AppBarScoreBadge() {
|
|||||||
const score = computeScore(counts);
|
const score = computeScore(counts);
|
||||||
|
|
||||||
// Color based on score
|
// Color based on score
|
||||||
const getColor = (score: number): 'success' | 'warning' | 'error' => {
|
const getColor = (score: number): string => {
|
||||||
if (score >= 80) return 'success';
|
if (score >= 80) return '#4caf50'; // green
|
||||||
if (score >= 50) return 'warning';
|
if (score >= 50) return '#ff9800'; // orange
|
||||||
return 'error';
|
return '#f44336'; // red
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@@ -32,13 +30,26 @@ export default function AppBarScoreBadge() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chip
|
<button
|
||||||
icon={<ShieldIcon />}
|
|
||||||
label={`Polaris: ${score}%`}
|
|
||||||
color={getColor(score)}
|
|
||||||
size="small"
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
style={{ cursor: 'pointer', marginRight: '8px' }}
|
style={{
|
||||||
/>
|
cursor: 'pointer',
|
||||||
|
marginRight: '8px',
|
||||||
|
padding: '4px 12px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
border: 'none',
|
||||||
|
backgroundColor: getColor(score),
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 500,
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
}}
|
||||||
|
aria-label={`Polaris cluster score: ${score}%`}
|
||||||
|
>
|
||||||
|
<span>🛡️</span>
|
||||||
|
<span>Polaris: {score}%</span>
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
),
|
),
|
||||||
|
SimpleTable: ({ data }: { data: Array<any> }) => (
|
||||||
|
<table data-testid="simple-table">
|
||||||
|
<tbody>
|
||||||
|
{data.map((item, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td>{JSON.stringify(item)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
),
|
||||||
PercentageCircle: ({ label }: { label: string }) => (
|
PercentageCircle: ({ label }: { label: string }) => (
|
||||||
<div data-testid="percentage-circle">{label}</div>
|
<div data-testid="percentage-circle">{label}</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import {
|
|||||||
SimpleTable,
|
SimpleTable,
|
||||||
StatusLabel,
|
StatusLabel,
|
||||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import { Refresh as RefreshIcon } from '@mui/icons-material';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
|
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
|
||||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||||
@@ -100,21 +98,39 @@ export default function DashboardView() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SectionHeader title="Polaris — Overview" />
|
<SectionHeader title="Polaris — Overview" />
|
||||||
{data && (
|
{data && (
|
||||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
||||||
<span style={{ fontSize: '14px', color: 'var(--mui-palette-text-secondary, #666)' }}>
|
<span style={{ fontSize: '14px', color: 'var(--mui-palette-text-secondary, #666)' }}>
|
||||||
Last updated: {formatAuditTime(data.AuditTime)}
|
Last updated: {formatAuditTime(data.AuditTime)}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<button
|
||||||
variant="outlined"
|
|
||||||
startIcon={<RefreshIcon />}
|
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
size="small"
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: '#1976d2',
|
||||||
|
border: '1px solid #1976d2',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 500,
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Refresh
|
<span>🔄</span>
|
||||||
</Button>
|
<span>Refresh</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NameValueTable, SectionBox, Dialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import { NameValueTable, SectionBox, Dialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
||||||
import { Button, Checkbox, FormControlLabel, FormGroup } from '@mui/material';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Result } from '../api/polaris';
|
import { Result } from '../api/polaris';
|
||||||
import { getCheckName } from '../api/checkMapping';
|
import { getCheckName } from '../api/checkMapping';
|
||||||
@@ -21,7 +20,12 @@ interface CheckFailure {
|
|||||||
* Exemption management UI for adding/removing Polaris exemptions
|
* Exemption management UI for adding/removing Polaris exemptions
|
||||||
* Uses annotation patches on the workload resource
|
* Uses annotation patches on the workload resource
|
||||||
*/
|
*/
|
||||||
export default function ExemptionManager({ workloadResult, namespace, kind, name }: ExemptionManagerProps) {
|
export default function ExemptionManager({
|
||||||
|
workloadResult,
|
||||||
|
namespace,
|
||||||
|
kind,
|
||||||
|
name,
|
||||||
|
}: ExemptionManagerProps) {
|
||||||
const [dialogOpen, setDialogOpen] = React.useState(false);
|
const [dialogOpen, setDialogOpen] = React.useState(false);
|
||||||
const [selectedChecks, setSelectedChecks] = React.useState<Set<string>>(new Set());
|
const [selectedChecks, setSelectedChecks] = React.useState<Set<string>>(new Set());
|
||||||
const [exemptAll, setExemptAll] = React.useState(false);
|
const [exemptAll, setExemptAll] = React.useState(false);
|
||||||
@@ -142,16 +146,23 @@ export default function ExemptionManager({ workloadResult, namespace, kind, name
|
|||||||
rows={currentExemptions.map(exemption => ({
|
rows={currentExemptions.map(exemption => ({
|
||||||
name: exemption,
|
name: exemption,
|
||||||
value: (
|
value: (
|
||||||
<Button
|
<button
|
||||||
size="small"
|
style={{
|
||||||
color="error"
|
padding: '4px 12px',
|
||||||
|
backgroundColor: '#f44336',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px',
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Remove exemption logic
|
// Remove exemption logic
|
||||||
alert('Remove exemption: ' + exemption);
|
alert('Remove exemption: ' + exemption);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</Button>
|
</button>
|
||||||
),
|
),
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
@@ -159,65 +170,100 @@ export default function ExemptionManager({ workloadResult, namespace, kind, name
|
|||||||
<p>No exemptions configured</p>
|
<p>No exemptions configured</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<button
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setDialogOpen(true)}
|
onClick={() => setDialogOpen(true)}
|
||||||
disabled={failingChecks.length === 0}
|
disabled={failingChecks.length === 0}
|
||||||
style={{ marginTop: '8px' }}
|
style={{
|
||||||
|
marginTop: '8px',
|
||||||
|
padding: '6px 16px',
|
||||||
|
backgroundColor: failingChecks.length === 0 ? '#ccc' : 'transparent',
|
||||||
|
color: failingChecks.length === 0 ? '#999' : '#1976d2',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: failingChecks.length === 0 ? '#ccc' : '#1976d2',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: failingChecks.length === 0 ? 'not-allowed' : 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Add Exemption
|
Add Exemption
|
||||||
</Button>
|
</button>
|
||||||
</SectionBox>
|
</SectionBox>
|
||||||
|
|
||||||
<Dialog
|
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} title="Add Exemptions">
|
||||||
open={dialogOpen}
|
|
||||||
onClose={() => setDialogOpen(false)}
|
|
||||||
title="Add Exemptions"
|
|
||||||
>
|
|
||||||
<div style={{ padding: '16px', minWidth: '400px' }}>
|
<div style={{ padding: '16px', minWidth: '400px' }}>
|
||||||
<FormControlLabel
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||||
control={
|
<input
|
||||||
<Checkbox
|
type="checkbox"
|
||||||
checked={exemptAll}
|
checked={exemptAll}
|
||||||
onChange={(e) => setExemptAll(e.target.checked)}
|
onChange={e => setExemptAll(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
}
|
<span>Exempt from all checks</span>
|
||||||
label="Exempt from all checks"
|
</label>
|
||||||
/>
|
|
||||||
|
|
||||||
{!exemptAll && (
|
{!exemptAll && (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginTop: '16px', marginBottom: '8px', fontWeight: 600 }}>
|
<div style={{ marginTop: '16px', marginBottom: '8px', fontWeight: 600 }}>
|
||||||
Select checks to exempt:
|
Select checks to exempt:
|
||||||
</div>
|
</div>
|
||||||
<FormGroup>
|
<div>
|
||||||
{failingChecks.map(check => (
|
{failingChecks.map(check => (
|
||||||
<FormControlLabel
|
<label
|
||||||
key={check.checkId}
|
key={check.checkId}
|
||||||
control={
|
style={{
|
||||||
<Checkbox
|
display: 'flex',
|
||||||
checked={selectedChecks.has(check.checkId)}
|
alignItems: 'center',
|
||||||
onChange={() => handleCheckToggle(check.checkId)}
|
gap: '8px',
|
||||||
/>
|
marginBottom: '8px',
|
||||||
}
|
cursor: 'pointer',
|
||||||
label={check.checkName}
|
}}
|
||||||
/>
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedChecks.has(check.checkId)}
|
||||||
|
onChange={() => handleCheckToggle(check.checkId)}
|
||||||
|
/>
|
||||||
|
<span>{check.checkName}</span>
|
||||||
|
</label>
|
||||||
))}
|
))}
|
||||||
</FormGroup>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: '16px', display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
|
<div
|
||||||
<Button onClick={() => setDialogOpen(false)}>
|
style={{ marginTop: '16px', display: 'flex', gap: '8px', justifyContent: 'flex-end' }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setDialogOpen(false)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: '#1976d2',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
variant="contained"
|
|
||||||
onClick={applyExemptions}
|
onClick={applyExemptions}
|
||||||
disabled={applying || (!exemptAll && selectedChecks.size === 0)}
|
disabled={applying || (!exemptAll && selectedChecks.size === 0)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
backgroundColor:
|
||||||
|
applying || (!exemptAll && selectedChecks.size === 0) ? '#ccc' : '#1976d2',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor:
|
||||||
|
applying || (!exemptAll && selectedChecks.size === 0) ? 'not-allowed' : 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{applying ? 'Applying...' : 'Apply'}
|
{applying ? 'Applying...' : 'Apply'}
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { NameValueTable, SectionBox, StatusLabel, SimpleTable } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import {
|
||||||
|
NameValueTable,
|
||||||
|
SectionBox,
|
||||||
|
StatusLabel,
|
||||||
|
SimpleTable,
|
||||||
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||||
@@ -140,9 +145,7 @@ export default function InlineAuditSection({ resource }: InlineAuditSectionProps
|
|||||||
{
|
{
|
||||||
label: 'Severity',
|
label: 'Severity',
|
||||||
getter: (f: CheckFailure) => (
|
getter: (f: CheckFailure) => (
|
||||||
<StatusLabel status={getSeverityStatus(f.severity)}>
|
<StatusLabel status={getSeverityStatus(f.severity)}>{f.severity}</StatusLabel>
|
||||||
{f.severity}
|
|
||||||
</StatusLabel>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ label: 'Message', getter: (f: CheckFailure) => f.message },
|
{ label: 'Message', getter: (f: CheckFailure) => f.message },
|
||||||
@@ -153,7 +156,10 @@ export default function InlineAuditSection({ resource }: InlineAuditSectionProps
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: '16px' }}>
|
<div style={{ marginTop: '16px' }}>
|
||||||
<Link to={`/polaris/namespaces#${namespace}`} style={{ color: 'var(--link-color, #1976d2)' }}>
|
<Link
|
||||||
|
to={`/polaris/namespaces#${namespace}`}
|
||||||
|
style={{ color: 'var(--link-color, #1976d2)' }}
|
||||||
|
>
|
||||||
View Full Report →
|
View Full Report →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,7 +118,11 @@ function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps)
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 style={{ margin: 0, color: 'var(--mui-palette-text-primary, var(--text-primary, #000))' }}>Polaris — {namespace}</h2>
|
<h2
|
||||||
|
style={{ margin: 0, color: 'var(--mui-palette-text-primary, var(--text-primary, #000))' }}
|
||||||
|
>
|
||||||
|
Polaris — {namespace}
|
||||||
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { NameValueTable, SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
import {
|
||||||
|
NameValueTable,
|
||||||
|
SectionBox,
|
||||||
|
StatusLabel,
|
||||||
|
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||||
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getDashboardUrl, getRefreshInterval, INTERVAL_OPTIONS, setDashboardUrl, setRefreshInterval, AuditData } from '../api/polaris';
|
import {
|
||||||
|
getDashboardUrl,
|
||||||
|
getRefreshInterval,
|
||||||
|
INTERVAL_OPTIONS,
|
||||||
|
setDashboardUrl,
|
||||||
|
setRefreshInterval,
|
||||||
|
AuditData,
|
||||||
|
} from '../api/polaris';
|
||||||
|
|
||||||
interface PluginSettingsProps {
|
interface PluginSettingsProps {
|
||||||
data?: { [key: string]: string | number | boolean };
|
data?: { [key: string]: string | number | boolean };
|
||||||
@@ -14,7 +24,9 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
|||||||
const currentInterval = (data?.refreshInterval as number) ?? getRefreshInterval();
|
const currentInterval = (data?.refreshInterval as number) ?? getRefreshInterval();
|
||||||
const currentUrl = (data?.dashboardUrl as string) ?? getDashboardUrl();
|
const currentUrl = (data?.dashboardUrl as string) ?? getDashboardUrl();
|
||||||
const [testing, setTesting] = React.useState(false);
|
const [testing, setTesting] = React.useState(false);
|
||||||
const [testResult, setTestResult] = React.useState<{ success: boolean; message: string } | null>(null);
|
const [testResult, setTestResult] = React.useState<{ success: boolean; message: string } | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
function handleIntervalChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
function handleIntervalChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
const seconds = Number(e.target.value);
|
const seconds = Number(e.target.value);
|
||||||
@@ -51,7 +63,9 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
|||||||
|
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: true,
|
success: true,
|
||||||
message: `Connected successfully! Version: ${result.PolarisOutputVersion}, Last audit: ${new Date(result.AuditTime).toLocaleString()}`,
|
message: `Connected successfully! Version: ${
|
||||||
|
result.PolarisOutputVersion
|
||||||
|
}, Last audit: ${new Date(result.AuditTime).toLocaleString()}`,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setTestResult({
|
setTestResult({
|
||||||
@@ -97,9 +111,10 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
|
<div style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
|
||||||
Examples:<br />
|
Examples:
|
||||||
• K8s proxy: <code>/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/</code><br />
|
<br />• K8s proxy:{' '}
|
||||||
• Full URL: <code>https://my-polaris.example.com</code>
|
<code>/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/</code>
|
||||||
|
<br />• Full URL: <code>https://my-polaris.example.com</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -108,14 +123,22 @@ export default function PolarisSettings(props: PluginSettingsProps) {
|
|||||||
name: 'Connection Test',
|
name: 'Connection Test',
|
||||||
value: (
|
value: (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<button
|
||||||
variant="contained"
|
|
||||||
onClick={testConnection}
|
onClick={testConnection}
|
||||||
disabled={testing}
|
disabled={testing}
|
||||||
size="small"
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
backgroundColor: testing ? '#ccc' : '#1976d2',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: testing ? 'not-allowed' : 'pointer',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{testing ? 'Testing...' : 'Test Connection'}
|
{testing ? 'Testing...' : 'Test Connection'}
|
||||||
</Button>
|
</button>
|
||||||
{testResult && (
|
{testResult && (
|
||||||
<div style={{ marginTop: '8px' }}>
|
<div style={{ marginTop: '8px' }}>
|
||||||
<StatusLabel status={testResult.success ? 'success' : 'error'}>
|
<StatusLabel status={testResult.success ? 'success' : 'error'}>
|
||||||
|
|||||||
+2
-2
@@ -69,7 +69,7 @@ registerRoute({
|
|||||||
registerPluginSettings('polaris', PolarisSettings);
|
registerPluginSettings('polaris', PolarisSettings);
|
||||||
|
|
||||||
// Register details view section for supported controller types
|
// Register details view section for supported controller types
|
||||||
registerDetailsViewSection('polaris-audit', ({ resource }) => {
|
registerDetailsViewSection(({ resource }) => {
|
||||||
const supportedKinds = ['Deployment', 'StatefulSet', 'DaemonSet', 'Job', 'CronJob'];
|
const supportedKinds = ['Deployment', 'StatefulSet', 'DaemonSet', 'Job', 'CronJob'];
|
||||||
|
|
||||||
if (!supportedKinds.includes(resource?.kind)) {
|
if (!supportedKinds.includes(resource?.kind)) {
|
||||||
@@ -84,7 +84,7 @@ registerDetailsViewSection('polaris-audit', ({ resource }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Register app bar score badge
|
// Register app bar score badge
|
||||||
registerAppBarAction('polaris-score', () => (
|
registerAppBarAction(() => (
|
||||||
<PolarisDataProvider>
|
<PolarisDataProvider>
|
||||||
<AppBarScoreBadge />
|
<AppBarScoreBadge />
|
||||||
</PolarisDataProvider>
|
</PolarisDataProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user