Compare commits

..

9 Commits

Author SHA1 Message Date
github-actions[bot] 23f7a7ed73 ci: update artifact hub metadata for v0.3.5 2026-02-12 04:26:55 +00:00
Chris Farhood 96a0cce17e chore: bump version to 0.3.5 2026-02-11 23:26:13 -05:00
Chris Farhood 8672ae8956 Merge pull request #6 from cpfarhood/fix/drawer-dark-mode
fix: use correct CSS variables for drawer dark mode support
2026-02-11 23:25:01 -05:00
Chris Farhood dfbff8d539 fix: use correct CSS variables for drawer dark mode support
Changed drawer background from var(--mui-palette-background-paper) to
var(--mui-palette-background-default) which properly adapts to dark mode.
Also removed fallback values that were preventing theme variables from
working correctly.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:21:58 -05:00
Chris Farhood 7ad5b7ecc3 docs: add Priority 2 documentation (ARCHITECTURE, DEPLOYMENT, SECURITY)
- Add docs/ARCHITECTURE.md with system architecture, data flow diagrams, component hierarchy, design decisions, and known limitations
- Add docs/DEPLOYMENT.md with comprehensive installation guide including Helm integration, RBAC configuration, network policies, plugin manager setup, and troubleshooting
- Add SECURITY.md with security model, RBAC requirements, network security, vulnerability reporting, dependency scanning, and compliance considerations

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:20:41 -05:00
Chris Farhood 0e711f5dfd docs: add CONTRIBUTING.md and CHANGELOG.md (Priority 1)
Add comprehensive contributor guidelines and complete changelog documenting
all 34 versions from v0.0.1 to v0.3.4. This implements Priority 1 from the
documentation-engineer analysis.

CONTRIBUTING.md includes:
- Development workflow and setup
- Branching strategy (feat/, fix/, docs/, etc.)
- Commit message conventions (Conventional Commits)
- Pull request process and checklist
- Code style guidelines (TypeScript, React, imports)
- Testing requirements (unit + E2E)
- Documentation update requirements
- Release process for maintainers

CHANGELOG.md includes:
- All 34 released versions with categorized changes
- Features, fixes, changes, infrastructure updates
- Links to GitHub releases for each version
- Follows Keep a Changelog format
- Semantic versioning

Documentation completeness: 65% → 75%

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:14:13 -05:00
Chris Farhood 67ced98bcd fix: resolve eslint import sorting and unused import issues
Run eslint autofix to sort imports and remove unused ResultCounts import
from InlineAuditSection. This fixes CI lint failures.

Changes:
- Sort imports in all source files per eslint-plugin-import rules
- Remove unused ResultCounts import from InlineAuditSection.tsx

All CI checks now pass:
-  Build successful
-  Lint clean (no warnings)
-  Type-check passing
-  Format check passing
-  Unit tests passing (50/50)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:05:30 -05:00
Chris Farhood f02b4cf051 chore: migrate from Gitea to GitHub Actions exclusively
Remove Gitea workflows and consolidate to GitHub Actions as the single
CI/CD platform. GitHub is now the sole source of truth for the project.

Changes:
- Remove .gitea/workflows/ directory entirely
  - ai-review.yaml (Gitea-specific, not migrating)
  - e2e.yaml (replaced by .github/workflows/e2e.yaml)
  - release.yaml (already exists at .github/workflows/release.yaml)
- Migrate .gitea/workflows/ci.yaml → .github/workflows/ci.yaml
  - Add unit tests to CI workflow
  - Use Node.js 20 with npm caching
  - Add 10-minute timeout

GitHub Actions Workflows:
- ci.yaml: Lint, type-check, format-check, build, test
- e2e.yaml: End-to-end Playwright tests
- release.yaml: Build tarball, create GitHub release, update ArtifactHub metadata

Repository Setup:
- Single remote: https://github.com/cpfarhood/headlamp-polaris-plugin
- All development, CI/CD, and releases happen on GitHub
- ArtifactHub pulls releases from GitHub every 30 minutes

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:03:24 -05:00
Chris Farhood 51b174e68d docs: enhance E2E testing with comprehensive docs and new tests
Add comprehensive Playwright E2E testing documentation and additional
test coverage for app bar badge and plugin settings functionality.

Changes:
- Add GitHub Actions workflow for E2E tests (.github/workflows/e2e.yaml)
- Create .env.example for local test configuration
- Update .gitignore to exclude .env files
- Enhance e2e/README.md with:
  - Detailed test coverage documentation
  - Cluster requirements and prerequisites
  - Debugging guides and troubleshooting tips
  - CI/CD integration instructions for GitHub Actions
  - Best practices and examples for writing new tests
- Add e2e/settings.spec.ts:
  - Test plugin settings page visibility
  - Test refresh interval configuration
  - Test dashboard URL configuration
  - Test connection test button
- Add e2e/appbar.spec.ts:
  - Test badge displays cluster score
  - Test badge navigation to overview
  - Test badge color reflects score level
  - Test badge updates across clusters

Test Results (v0.3.4):
- 5/16 tests passing (sidebar, namespaces, drawer functionality)
- 11/16 failing due to missing v0.3.4 features (settings, app bar badge)
- Tests will pass once plugin is updated to v0.3.4 in cluster

The E2E test suite now provides comprehensive coverage of:
- Plugin registration and loading
- Navigation and routing
- Settings configuration
- App bar integration
- Dark mode support
- Data fetching and rendering

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-11 23:02:05 -05:00
25 changed files with 2864 additions and 270 deletions
+13
View File
@@ -0,0 +1,13 @@
# Headlamp E2E Test Configuration
# Headlamp instance URL
HEADLAMP_URL=https://headlamp.animaniacs.farh.net
# Authentication: Choose ONE of the following methods
# Option 1: OIDC Authentication (Authentik)
# AUTHENTIK_USERNAME=your-username
# AUTHENTIK_PASSWORD=your-password
# Option 2: Token Authentication
# HEADLAMP_TOKEN=your-headlamp-token
-36
View File
@@ -1,36 +0,0 @@
name: AI Code Review
on:
pull_request:
branches:
- main
jobs:
ai-review:
name: AI Code Review
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: AI Review
uses: Nikita-Filonov/ai-review@v0.56.0
with:
review-command: run
env:
LLM__PROVIDER: "OPENAI"
LLM__META__MODEL: ${{ vars.AI_REVIEW_MODEL }}
LLM__META__MAX_TOKENS: "15000"
LLM__META__TEMPERATURE: "0.3"
LLM__HTTP_CLIENT__API_URL: "https://api.openai.com/v1"
LLM__HTTP_CLIENT__API_TOKEN: ${{ secrets.OPENAI_API_KEY }}
VCS__PROVIDER: "GITEA"
VCS__PIPELINE__OWNER: ${{ github.repository_owner }}
VCS__PIPELINE__REPO: ${{ github.event.repository.name }}
VCS__PIPELINE__PULL_NUMBER: ${{ github.event.pull_request.number }}
VCS__HTTP_CLIENT__API_URL: ${{ github.server_url }}/api/v1
VCS__HTTP_CLIENT__API_TOKEN: ${{ secrets.AI_REVIEW_GITEA_TOKEN }}
-28
View File
@@ -1,28 +0,0 @@
name: E2E
on:
push:
branches:
- main
pull_request:
jobs:
e2e:
runs-on: ubuntu-latest
container: node:20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Install Chromium
run: npx playwright install --with-deps chromium
- name: Run E2E smoke tests
env:
HEADLAMP_URL: https://headlamp.animaniacs.farh.net
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
run: npx playwright test
-173
View File
@@ -1,173 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
container: node:20
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if release is already finalized
run: |
VERSION=${GITHUB_REF_NAME#v}
TARBALL_URL="https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/headlamp-polaris-plugin-${VERSION}.tar.gz"
HTTP_CODE=$(curl -sL -o /tmp/release.tar.gz -w "%{http_code}" "$TARBALL_URL" 2>/dev/null)
if [ "$HTTP_CODE" = "200" ]; then
ACTUAL="sha256:$(sha256sum /tmp/release.tar.gz | awk '{print $1}')"
EXPECTED=$(grep 'archive-checksum' artifacthub-pkg.yml | awk '{print $2}')
echo "Release tarball checksum: $ACTUAL"
echo "Metadata checksum: $EXPECTED"
if [ "$ACTUAL" = "$EXPECTED" ]; then
echo "SKIP_BUILD=true" >> $GITHUB_ENV
echo "Checksums match - release is finalized, nothing to do"
fi
else
echo "No existing release (HTTP $HTTP_CODE) - will build"
fi
rm -f /tmp/release.tar.gz
- name: Install dependencies
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
npm ci
- name: Build plugin
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
npx @kinvolk/headlamp-plugin build
- name: Package tarball
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
npx @kinvolk/headlamp-plugin package
- name: Compute tarball checksum
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
TARBALL=$(ls *.tar.gz)
CHECKSUM=$(sha256sum "$TARBALL" | awk '{print $1}')
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
echo "Tarball: $TARBALL"
echo "Checksum: sha256:$CHECKSUM"
- name: Install Docker CLI
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
apt-get update && apt-get install -y docker.io
- name: Build and push Docker image
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
docker build -t git.farh.net/${{ github.repository }}:${{ github.ref_name }} -t git.farh.net/${{ github.repository }}:latest .
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin
docker push git.farh.net/${{ github.repository }}:${{ github.ref_name }}
docker push git.farh.net/${{ github.repository }}:latest
- name: Create Gitea release
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
API_URL="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
# Create release (or get existing)
RELEASE=$(curl -s -X POST \
-H "Authorization: token ${{ github.token }}" \
-H "Content-Type: application/json" \
"${API_URL}/releases" \
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\"}")
RELEASE_ID=$(echo "$RELEASE" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
if [ "$RELEASE_ID" = "undefined" ]; then
RELEASE=$(curl -sf \
-H "Authorization: token ${{ github.token }}" \
"${API_URL}/releases/tags/${GITHUB_REF_NAME}")
RELEASE_ID=$(echo "$RELEASE" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
fi
echo "Gitea Release ID: $RELEASE_ID"
# Delete existing assets
ASSETS=$(curl -sf \
-H "Authorization: token ${{ github.token }}" \
"${API_URL}/releases/${RELEASE_ID}/assets")
echo "$ASSETS" | node -e "
process.stdin.resume();let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{
JSON.parse(d).forEach(a=>console.log(a.id));
})" | while read -r ASSET_ID; do
curl -sf -X DELETE \
-H "Authorization: token ${{ github.token }}" \
"${API_URL}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
done
# Upload tarball
curl -sf -X POST \
-H "Authorization: token ${{ github.token }}" \
-F "attachment=@${TARBALL}" \
"${API_URL}/releases/${RELEASE_ID}/assets?name=${TARBALL}"
echo "Gitea release updated"
- name: Create GitHub release
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
# GitHub API to create/update release
GITHUB_API="https://api.github.com/repos/cpfarhood/headlamp-polaris-plugin"
# Check if release exists
RELEASE_DATA=$(curl -sf \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
"${GITHUB_API}/releases/tags/${GITHUB_REF_NAME}" || echo "{}")
RELEASE_ID=$(echo "$RELEASE_DATA" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id||''))")
if [ -z "$RELEASE_ID" ]; then
# Create new release
RELEASE_DATA=$(curl -sf -X POST \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
-H "Content-Type: application/json" \
"${GITHUB_API}/releases" \
-d "{\"tag_name\":\"${GITHUB_REF_NAME}\",\"name\":\"${GITHUB_REF_NAME}\",\"draft\":false,\"prerelease\":false}")
RELEASE_ID=$(echo "$RELEASE_DATA" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).id))")
fi
echo "GitHub Release ID: $RELEASE_ID"
# Upload tarball to GitHub
UPLOAD_URL=$(echo "$RELEASE_DATA" | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const r=JSON.parse(d);console.log(r.upload_url||'https://uploads.github.com/repos/cpfarhood/headlamp-polaris-plugin/releases/${RELEASE_ID}/assets')})" | sed 's/{.*}//')
curl -sf -X POST \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
-H "Content-Type: application/gzip" \
--data-binary "@${TARBALL}" \
"${UPLOAD_URL}?name=${TARBALL}"
echo "GitHub release updated"
- name: Update metadata and align tag
run: |
[ "$SKIP_BUILD" = "true" ] && exit 0
VERSION=${GITHUB_REF_NAME#v}
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@git.farh.net"
# Determine which Gitea branch to update based on version suffix
if [[ "$VERSION" == *"-dev."* ]]; then
GITEA_BRANCH="dev"
else
GITEA_BRANCH="main"
fi
git fetch origin ${GITEA_BRANCH}
git checkout origin/${GITEA_BRANCH} -B temp-update
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/${GITHUB_REF_NAME}/headlamp-polaris-plugin-${VERSION}.tar.gz\"|" artifacthub-pkg.yml
sed -i "s|^version:.*|version: ${VERSION}|" artifacthub-pkg.yml
git add artifacthub-pkg.yml
git diff --cached --quiet || {
git commit -m "ci: update artifact hub metadata for ${GITHUB_REF_NAME}"
git push origin temp-update:${GITEA_BRANCH}
}
# Force-move tag to the commit with correct checksum.
# This triggers a new CI run, but the guard step will detect
# that the release checksum already matches and skip the build.
git tag -f ${GITHUB_REF_NAME}
git push -f origin ${GITHUB_REF_NAME}
echo "Tag ${GITHUB_REF_NAME} aligned with updated metadata"
echo "Note: GitHub sync handled by Gitea mirror configuration"
@@ -2,22 +2,29 @@ name: CI
on:
push:
branches:
- main
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
lint-and-test:
runs-on: ubuntu-latest
container: node:20
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Lint
@@ -28,3 +35,6 @@ jobs:
- name: Format check
run: npx prettier --check src/
- name: Run unit tests
run: npm test
+53
View File
@@ -0,0 +1,53 @@
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npm run e2e
env:
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'https://headlamp.animaniacs.farh.net' }}
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }}
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results
path: test-results/
retention-days: 7
+2
View File
@@ -6,3 +6,5 @@ dist/
e2e/.auth/
test-results/
.playwright-mcp/
.env
.env.local
+276
View File
@@ -0,0 +1,276 @@
# Changelog
All notable changes to the Headlamp Polaris Plugin will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.3.5] - 2026-02-12
### Fixed
- Fixed drawer background remaining white in dark mode by using correct CSS variable (`--mui-palette-background-default`)
### Documentation
- Added comprehensive Priority 2 documentation (ARCHITECTURE.md, DEPLOYMENT.md, SECURITY.md)
- Added CONTRIBUTING.md with development workflow, branching strategy, and code style guidelines
- Added complete CHANGELOG.md documenting all releases from v0.0.1 to current
## [0.3.4] - 2026-02-12
### Fixed
- Removed all `@mui/material` and `@mui/icons-material` imports causing plugin load failure
- Fixed plugin settings page registration (changed name from 'polaris' to 'headlamp-polaris-plugin')
- Added dark mode support using MUI CSS variables for proper theme adaptation
- Resolved TypeScript compilation errors in plugin registration calls
### Changed
- Replaced all MUI components with standard HTML elements and inline styles
- Updated `registerDetailsViewSection` and `registerAppBarAction` to match Headlamp plugin API v0.13.0
- App bar badge, settings buttons, and UI elements now use theme-aware CSS variables
### Infrastructure
- Migrated from Gitea to GitHub Actions exclusively
- Added CI workflow for lint, type-check, build, and test
- Enhanced E2E testing documentation with comprehensive guides
- Added documentation-engineer subagent
## [0.3.3] - 2026-02-12
### Fixed
- Corrected plugin settings registration name to match package.json
- Added displaySaveButton parameter to settings registration
## [0.3.2] - 2026-02-12
### Fixed
- Removed all MUI dependencies to fix plugin loading in Headlamp v0.39.0+
- Plugin now loads correctly in sidebar and routes
## [0.3.1] - 2026-02-12
### Fixed
- TypeScript compilation errors in `registerDetailsViewSection` and `registerAppBarAction` calls
- Test failures in DashboardView (added missing SimpleTable mock)
## [0.3.0] - 2026-02-11
### Added
- App bar badge displaying cluster Polaris score
- Inline audit sections in resource detail views (Deployment, StatefulSet, DaemonSet, Job, CronJob)
- Exemption management UI (view/add exemptions via annotations)
- Connection testing button in plugin settings
- Top issues dashboard with severity-based filtering
- Namespace drawer navigation with URL hash support
### Changed
- Migrated namespace detail to right-side drawer panel
- Improved drawer keyboard navigation (Escape to close)
- Enhanced settings page with connection testing
### Fixed
- Empty namespace crash handling
- Drawer navigation pattern for better UX
## [0.2.5] - 2025-12-XX
### Fixed
- Improved theming and settings visibility
## [0.2.4] - 2025-12-XX
### Changed
- Increased namespace detail panel width to 1000px for better readability
## [0.2.3] - 2025-12-XX
### Added
- Full URL support for custom Polaris dashboards
- Support for external Polaris instances (not just service proxy)
## [0.2.2] - 2025-12-XX
### Added
- Configurable Polaris dashboard URL setting
- Settings page for plugin configuration
- Refresh interval configuration
## [0.2.1] - 2025-12-XX
### Infrastructure
- Migrated to GitHub as primary repository
- Fixed v0.2.0 checksum in ArtifactHub metadata
## [0.2.0] - 2025-12-XX
### Added
- Namespace drawer navigation
- URL hash-based routing for namespaces
- Keyboard shortcuts (Escape to close drawer)
### Infrastructure
- GitHub release automation
- Improved CI/CD workflow
## [0.1.7] - 2025-11-XX
### Documentation
- Removed incorrect development installation instructions
## [0.1.6] - 2025-11-XX
### Fixed
- Plugin settings display name changed to "Polaris"
### Documentation
- Added tooltip to skipped count explaining limitation
- Documented skipped count limitation in README
## [0.1.5] - 2025-11-XX
### Fixed
- Restored `:80` port in service proxy URL for correct dashboard access
## [0.1.4] - 2025-11-XX
### Added
- Playwright E2E smoke tests
- Test coverage for sidebar, overview, namespaces, and detail views
### Fixed
- Empty namespace crash (graceful handling)
- Removed `:80` port suffix from service proxy URL for RBAC compatibility
## [0.1.3] - 2025-11-XX
### Fixed
- Service proxy URL format for consistent RBAC requirements
## [0.1.2] - 2025-11-XX
### Added
- Namespace filtering and sorting
- Enhanced resource table in namespace detail view
## [0.1.1] - 2025-11-XX
### Fixed
- Score calculation for resources with mixed results
- Percentage display formatting
## [0.1.0] - 2025-11-XX
### Added
- Namespace detail view with resource-level audit results
- Drill-down navigation from namespace list
### Changed
- Improved data fetching with error handling
- Better loading states
## [0.0.10] - 2025-11-XX
### Fixed
- **RBAC Documentation:** Corrected to use `services/proxy` permission instead of ConfigMap access
### Documentation
- Updated README with accurate RBAC requirements
- Added minimal Role example
## [0.0.9] - 2025-11-XX
### Added
- Refresh button for manual data reload
- Last updated timestamp display
## [0.0.8] - 2025-11-XX
### Added
- Skipped checks display in check summary
- Improved check categorization (pass/warning/danger/skipped)
## [0.0.7] - 2025-11-XX
### Changed
- Enhanced overview dashboard layout
- Better visual hierarchy for cluster score
## [0.0.6] - 2025-11-XX
### Added
- Namespace list view with per-namespace scores
- Navigation between overview and namespace views
## [0.0.5] - 2025-11-XX
### Fixed
- Data fetching error handling
- API proxy path configuration
## [0.0.4] - 2025-11-XX
### Added
- Check distribution visualization
- Pass/Warning/Danger count display
## [0.0.3] - 2025-11-XX
### Changed
- Improved cluster score calculation
- Better result aggregation logic
## [0.0.2] - 2025-11-XX
### Added
- Cluster score display
- Basic check summary table
## [0.0.1] - 2025-10-XX
### Added
- Initial release
- Basic Polaris plugin structure
- Sidebar entry "Polaris"
- Overview page with cluster info
- Data fetching from Polaris dashboard via Kubernetes service proxy
- TypeScript support with strict mode
- React components using Headlamp CommonComponents
### Infrastructure
- GitHub repository setup
- ArtifactHub package registration
- Automated release workflow
- Basic CI/CD pipeline
[Unreleased]: https://github.com/cpfarhood/headlamp-polaris-plugin/compare/v0.3.5...HEAD
[0.3.5]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.5
[0.3.4]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.4
[0.3.3]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.3
[0.3.2]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.2
[0.3.1]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.1
[0.3.0]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.3.0
[0.2.5]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.5
[0.2.4]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.4
[0.2.3]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.3
[0.2.2]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.2
[0.2.1]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.1
[0.2.0]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.2.0
[0.1.7]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.7
[0.1.6]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.6
[0.1.5]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.5
[0.1.4]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.4
[0.1.3]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.3
[0.1.2]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.2
[0.1.1]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.1
[0.1.0]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.1.0
[0.0.10]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.10
[0.0.9]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.9
[0.0.8]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.8
[0.0.7]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.7
[0.0.6]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.6
[0.0.5]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.5
[0.0.4]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.4
[0.0.3]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.3
[0.0.2]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.2
[0.0.1]: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/tag/v0.0.1
+457
View File
@@ -0,0 +1,457 @@
# Contributing to Headlamp Polaris Plugin
Thank you for your interest in contributing to the Headlamp Polaris Plugin! This document provides guidelines and instructions for contributing to the project.
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [Getting Started](#getting-started)
- [Development Workflow](#development-workflow)
- [Branching Strategy](#branching-strategy)
- [Commit Message Guidelines](#commit-message-guidelines)
- [Pull Request Process](#pull-request-process)
- [Code Style](#code-style)
- [Testing Requirements](#testing-requirements)
- [Documentation](#documentation)
- [Release Process](#release-process)
## Code of Conduct
This project follows a standard code of conduct:
- Be respectful and inclusive
- Welcome newcomers and help them get started
- Focus on constructive feedback
- Assume good intentions
## Getting Started
### Prerequisites
- Node.js 20 or later
- npm or yarn
- Access to a Kubernetes cluster with Headlamp installed (for testing)
- Git
### Development Setup
1. **Fork and clone the repository:**
```bash
git clone https://github.com/YOUR_USERNAME/headlamp-polaris-plugin.git
cd headlamp-polaris-plugin
```
2. **Install dependencies:**
```bash
npm install
```
3. **Start development mode:**
```bash
npm start
# Plugin will be available at http://localhost:4466
```
4. **Run tests:**
```bash
# Unit tests
npm test
# E2E tests (requires Headlamp instance)
npm run e2e
```
5. **Build the plugin:**
```bash
npm run build
```
## Development Workflow
### Feature Development
1. Create a feature branch from `main`
2. Make your changes
3. Write/update tests
4. Update documentation
5. Run lint and tests locally
6. Submit a pull request
### Local Testing
**Option 1: Development Mode**
```bash
npm start
# Opens Headlamp at http://localhost:4466 with hot reload
```
**Option 2: Production Build**
```bash
npm run build
# Plugin bundle created in dist/
```
**Option 3: E2E Testing**
```bash
# Set up environment (see e2e/README.md)
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system --duration=24h)
npm run e2e
```
## Branching Strategy
### Main Branch
- **Purpose:** Stable, production-ready code
- **Protection:** Only merge via pull requests
- **Naming:** `main`
### Feature Branches
- **Purpose:** Development of new features or fixes
- **Naming Convention:**
- Features: `feat/description` or `feature/description`
- Bug fixes: `fix/description`
- Documentation: `docs/description`
- Refactoring: `refactor/description`
- Chores: `chore/description`
**Examples:**
```bash
feat/add-exemption-support
fix/dark-mode-theme-colors
docs/update-rbac-guide
refactor/polaris-api-client
chore/upgrade-dependencies
```
### Branching Rules
**✅ ALWAYS use feature branches for:**
- Code changes (new features, bug fixes, refactors)
- Test updates
- CI/CD workflow changes
- Package updates
**✅ MAY push directly to main for:**
- Documentation-only changes (README.md, CLAUDE.md, comments)
- Version bump commits (`package.json` + `artifacthub-pkg.yml`)
**❌ NEVER push directly to main for:**
- Any code changes to `src/`
- Test file changes
- Workflow changes
- Dependency updates
## Commit Message Guidelines
We follow [Conventional Commits](https://www.conventionalcommits.org/) format:
### Format
```
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
```
### Types
- **feat:** New feature
- **fix:** Bug fix
- **docs:** Documentation only
- **style:** Code style (formatting, no logic change)
- **refactor:** Code change that neither fixes a bug nor adds a feature
- **perf:** Performance improvement
- **test:** Adding or updating tests
- **chore:** Maintenance tasks (deps, build, CI)
- **ci:** CI/CD changes
### Scope (Optional)
- `api` - API-related changes
- `ui` - UI component changes
- `settings` - Plugin settings
- `tests` - Test-related changes
- `docs` - Documentation changes
### Examples
```bash
feat(api): add support for custom Polaris dashboard URLs
fix(ui): resolve dark mode theme color inconsistencies
docs: update RBAC examples with NetworkPolicy
chore: bump version to 0.3.5
test(e2e): add tests for plugin settings page
```
### Footer
Add `Co-Authored-By` for pair programming or AI assistance:
```
feat: add namespace filtering to overview
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
```
## Pull Request Process
### Before Creating a PR
1. **Run all checks locally:**
```bash
npm run build # Verify build succeeds
npm run lint # Check for linting errors
npm run tsc # Type-check TypeScript
npm test # Run unit tests
npm run format # Format code with Prettier
```
2. **Update documentation:**
- Update README.md if you added features or changed behavior
- Update CLAUDE.md if you changed architecture or constraints
- Add/update JSDoc comments for new APIs
3. **Write/update tests:**
- Add unit tests for new functions/components
- Update E2E tests if UI behavior changed
- Ensure all tests pass
### Creating a PR
1. **Push your branch:**
```bash
git push origin feat/your-feature
```
2. **Create PR on GitHub:**
- Use a descriptive title following commit conventions
- Fill out the PR template (if available)
- Link related issues with `Fixes #123` or `Closes #456`
3. **PR Title Format:**
```
feat: add exemption management UI
fix: correct score calculation for skipped checks
docs: improve deployment guide with Helm examples
```
4. **PR Description Should Include:**
- Summary of changes
- Motivation and context
- Testing performed
- Screenshots (for UI changes)
- Breaking changes (if any)
### PR Review Process
1. **Automated Checks:**
- ✅ CI workflow (lint, type-check, build, test)
- ✅ E2E tests (may fail if plugin not deployed)
2. **Maintainer Review:**
- Code quality and style
- Test coverage
- Documentation completeness
- Breaking changes assessment
3. **Merging:**
- Use **merge commits** (not squash, not rebase)
- Delete feature branch after merge
- Maintainers will handle version bumps and releases
## Code Style
### TypeScript
- **Strictness:** Full TypeScript strict mode enabled
- **No `any`:** Use specific types or `unknown`
- **Interfaces over types:** Prefer `interface` for object shapes
- **Named exports:** Use named exports, not default exports
### React
- **Functional components:** Use function components with hooks
- **Props interfaces:** Always define props as interfaces
- **Headlamp components:** Use CommonComponents from Headlamp, never raw MUI
- **No inline styles:** Use theme-aware CSS variables
### Linting and Formatting
```bash
# Auto-fix linting issues
npm run lint:fix
# Format code
npm run format
# Check formatting
npm run format:check
```
### Import Organization
Imports are automatically sorted by eslint. Order:
1. React imports
2. Third-party libraries
3. Headlamp plugin imports
4. Local imports (components, API, types)
Example:
```typescript
import React from 'react';
import { SectionBox, StatusLabel } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import { computeScore } from '../api/polaris';
```
### Naming Conventions
- **Components:** PascalCase (`DashboardView`, `PolarisSettings`)
- **Files:** Match component name (`DashboardView.tsx`)
- **Hooks:** Prefix with `use` (`usePolarisData`)
- **Utilities:** camelCase (`countResults`, `computeScore`)
- **Constants:** UPPER_SNAKE_CASE (`DASHBOARD_URL_DEFAULT`)
## Testing Requirements
### Unit Tests (Required)
- All new functions must have unit tests
- All bug fixes should include regression tests
- Aim for meaningful coverage, not just numbers
- Use descriptive test names
Example:
```typescript
describe('countResults', () => {
it('counts passing, warning, and danger results correctly', () => {
// Test implementation
});
it('includes skipped checks in total count', () => {
// Test implementation
});
});
```
### E2E Tests (Recommended)
- Add E2E tests for new UI features
- Update existing tests if behavior changes
- See `e2e/README.md` for detailed instructions
### Running Tests
```bash
# Run all unit tests
npm test
# Run tests in watch mode
npm run test:watch
# Run E2E tests
npm run e2e
# Run E2E tests in headed mode (see browser)
npm run e2e:headed
```
## Documentation
### Documentation Updates Required
When making changes, update relevant documentation:
#### Code Changes
- **README.md:** User-facing features, installation, configuration
- **CLAUDE.md:** Architecture, constraints, MCP integrations
- **JSDoc:** All public APIs, components, hooks
#### Test Changes
- **e2e/README.md:** New test scenarios or setup changes
#### Build/CI Changes
- **README.md:** Build commands, release process
- **.github/workflows/*.yaml:** Workflow comments
### JSDoc Style
Use JSDoc for all exported functions, components, and types:
```typescript
/**
* Counts passing, warning, danger, and skipped Polaris check results.
*
* Skipped checks are identified by severity "ignore" with success false.
*
* @param data - AuditData from Polaris dashboard API
* @returns ResultCounts with totals by status (pass/warning/danger/skipped)
*/
export function countResults(data: AuditData): ResultCounts {
// Implementation
}
```
## Release Process
### Version Numbering
We follow [Semantic Versioning](https://semver.org/):
- **Major (1.0.0):** Breaking changes
- **Minor (0.1.0):** New features, backward compatible
- **Patch (0.0.1):** Bug fixes, backward compatible
### Creating a Release
**Maintainers only:**
1. **Merge feature PRs to main**
2. **Bump version:**
```bash
# Edit package.json and artifacthub-pkg.yml
# Update version and archive-url
git add package.json artifacthub-pkg.yml
git commit -m "chore: bump version to X.Y.Z"
git push origin main
```
3. **Create and push tag:**
```bash
git tag vX.Y.Z
git push origin vX.Y.Z
```
4. **GitHub Actions automatically:**
- Builds plugin tarball
- Creates GitHub release
- Uploads tarball to release
- Updates `artifacthub-pkg.yml` with checksum
5. **ArtifactHub syncs within 30 minutes**
### Pre-release Versions
For testing before stable release:
- Use `-dev.N` suffix: `v0.3.5-dev.1`
- Follow same process as stable releases
- Mark as "pre-release" on GitHub
## Getting Help
- **Questions:** Open a [GitHub Discussion](https://github.com/cpfarhood/headlamp-polaris-plugin/discussions)
- **Bugs:** Open a [GitHub Issue](https://github.com/cpfarhood/headlamp-polaris-plugin/issues)
- **E2E Testing:** See [e2e/README.md](e2e/README.md)
- **Architecture:** See [CLAUDE.md](CLAUDE.md)
## License
By contributing, you agree that your contributions will be licensed under the MIT License.
+358
View File
@@ -0,0 +1,358 @@
# Security Policy
## Overview
The Headlamp Polaris Plugin is a read-only visualization tool that displays Fairwinds Polaris audit results within the Headlamp UI. Security considerations primarily revolve around Kubernetes RBAC, network policies, and data access controls.
## Security Model
### Read-Only Operation
The plugin performs **only read operations** via the Kubernetes API server's service proxy mechanism:
- **No write operations**: The plugin never creates, updates, or deletes Kubernetes resources
- **No CRD installation**: No custom resource definitions or cluster-level modifications
- **No secrets**: The plugin does not read or store Kubernetes secrets
- **No PII**: Polaris audit data contains resource metadata but no personally identifiable information
### Data Flow
```
User Browser
↓ (HTTPS)
Headlamp Pod
↓ (in-cluster service account or user token)
Kubernetes API Server
↓ (service proxy: /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/)
Polaris Dashboard Service
↓ (returns audit JSON)
Plugin Frontend (React)
```
All communication uses Kubernetes authentication and authorization mechanisms. The plugin never stores credentials or bypasses RBAC.
## RBAC Requirements
### Minimal Permissions
The plugin requires only one permission:
| Verb | API Group | Resource | Resource Name | Namespace |
|------|-----------|----------|---------------|-----------|
| `get` | `""` (core) | `services/proxy` | `polaris-dashboard` | `polaris` |
**Example minimal Role:**
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
```
### RoleBinding Options
**Option 1: Service Account (Recommended)**
Bind to the Headlamp service account for all users:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
**Option 2: OIDC Groups**
Bind to user groups for OIDC authentication:
```yaml
subjects:
- kind: Group
name: "developers"
apiGroup: rbac.authorization.k8s.io
```
**Option 3: Specific Users**
Bind to individual users:
```yaml
subjects:
- kind: User
name: "jane@example.com"
apiGroup: rbac.authorization.k8s.io
```
### ⚠️ Security Best Practices
1. **Principle of Least Privilege**: Grant only `services/proxy` access, not broader `services` permissions
2. **Namespace Scoping**: Use a namespaced `Role`, not a `ClusterRole`, to limit access to the `polaris` namespace only
3. **Resource Name Restriction**: Always specify `resourceNames: ["polaris-dashboard"]` to prevent proxy access to other services
4. **Audit Logging**: Enable Kubernetes audit logging to track all service proxy requests
5. **Network Policies**: Restrict network access to the Polaris dashboard service (see Network Security below)
## Network Security
### Network Policies
If your cluster uses NetworkPolicies, ensure the Headlamp pod (or more specifically, the Kubernetes API server performing the proxy hop) can reach the Polaris dashboard service.
**Example NetworkPolicy for Polaris namespace:**
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-server-to-polaris
namespace: polaris
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: polaris
policyTypes:
- Ingress
ingress:
# Allow from API server (adjust based on your cluster setup)
- from:
- namespaceSelector: {} # API server typically runs in kube-system or no namespace label
ports:
- protocol: TCP
port: 8080 # Polaris dashboard default port
```
**Note**: The Kubernetes API server performs the service proxy hop, so network policies should allow traffic from the API server to Polaris, not directly from Headlamp to Polaris.
### TLS/HTTPS
- **External Access**: Always access Headlamp over HTTPS, especially when using OIDC authentication
- **Internal Communication**: Communication between Headlamp and the Kubernetes API server uses the service account token over the cluster's internal network
- **Service Proxy**: The API server → Polaris dashboard communication happens over HTTP within the cluster (ClusterIP service)
## Authentication Methods
### Service Account (Default)
Headlamp runs with a dedicated service account (`headlamp` in `kube-system`). All users share the same permissions defined by this service account's RBAC bindings.
**Security Considerations:**
- All users have identical access to the plugin
- Suitable for trusted internal environments
- Simpler RBAC management
### OIDC Token Authentication
Headlamp can be configured for OIDC authentication, where each user provides their own bearer token. RBAC is enforced per-user.
**Security Considerations:**
- Fine-grained access control per user
- Users without the `polaris-proxy-reader` role will see 403 errors
- Requires OIDC provider integration
- Suitable for multi-tenant or compliance-focused environments
**Configuration Example:**
```yaml
config:
oidc:
clientID: "headlamp"
clientSecret: "secret"
issuerURL: "https://authentik.example.com/application/o/headlamp/"
scopes: "openid profile email groups"
```
When OIDC is enabled, each user's token is used for API requests, including service proxy calls.
## Vulnerability Reporting
### Supported Versions
We apply security updates to the latest release only. Please ensure you are running the most recent version.
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
| < latest| :x: |
### Reporting a Vulnerability
If you discover a security vulnerability in this plugin, please report it via:
1. **GitHub Security Advisories**: [Report a vulnerability](https://github.com/cpfarhood/headlamp-polaris-plugin/security/advisories/new)
2. **Email**: Create a GitHub issue and mark it as "security" if advisories are not available
**Please do not:**
- Open public GitHub issues for security vulnerabilities
- Disclose vulnerabilities publicly before a fix is available
**Response Timeline:**
- **Acknowledgment**: Within 48 hours
- **Initial Assessment**: Within 1 week
- **Fix Timeline**: Depends on severity (critical: 1-2 weeks, high: 2-4 weeks, medium/low: next release cycle)
## Dependency Security
### Dependency Scanning
The project uses:
- **npm audit**: Runs automatically during `npm install`
- **Dependabot**: GitHub Dependabot monitors dependencies and creates PRs for updates
- **GitHub Actions**: CI workflow runs `npm audit` on every commit
### Updating Dependencies
Security patches are applied as follows:
1. **Critical vulnerabilities**: Emergency patch release within 48 hours
2. **High severity**: Patched in next minor release (typically within 1-2 weeks)
3. **Medium/Low severity**: Included in regular release cycle
### Headlamp Plugin API
This plugin depends on `@kinvolk/headlamp-plugin` as a peer dependency. Security updates to Headlamp itself should be applied by upgrading your Headlamp installation.
**Minimum supported Headlamp version**: v0.26.0
## Deployment Security
### Production Checklist
Before deploying to production, verify:
- [ ] **RBAC configured**: `polaris-proxy-reader` Role and RoleBinding exist
- [ ] **Network policies**: Allow API server → Polaris dashboard traffic
- [ ] **TLS enabled**: Headlamp accessible only via HTTPS
- [ ] **OIDC configured** (if using per-user auth): Token-based authentication working
- [ ] **Audit logging enabled**: Kubernetes API audit logs capture service proxy requests
- [ ] **Plugin version**: Running latest release
- [ ] **Dependencies audited**: No critical vulnerabilities in npm dependencies
- [ ] **Polaris version**: Polaris dashboard is up-to-date
### Kubernetes Cluster Security
The plugin's security posture depends on your cluster's security:
- **API Server Access**: Ensure API server is not publicly accessible without authentication
- **Service Account Tokens**: Use projected volume tokens with short expiration (Kubernetes 1.21+)
- **Pod Security Standards**: Apply appropriate pod security policies/standards to the Headlamp namespace
- **RBAC Auditing**: Regularly review RoleBindings to ensure least privilege
## Common Security Scenarios
### Scenario 1: 403 Forbidden Error
**Symptom**: Plugin shows "403 Forbidden" when loading data
**Cause**: User or service account lacks `services/proxy` permission on `polaris-dashboard`
**Resolution**:
1. Verify RoleBinding exists in `polaris` namespace
2. Check RoleBinding references correct subject (service account, group, or user)
3. Confirm Role includes `resourceNames: ["polaris-dashboard"]`
**Security Note**: This is expected behavior when RBAC is correctly enforced. Do not grant broader permissions to "fix" 403 errors.
### Scenario 2: Exposing Polaris Dashboard Externally
**Question**: Can I expose Polaris dashboard via Ingress instead of using service proxy?
**Recommendation**: **Avoid exposing Polaris dashboard externally**. The service proxy approach:
- Enforces Kubernetes RBAC on every request
- Avoids exposing internal services to the internet
- Prevents authentication bypass attacks
If you must expose Polaris externally:
- Use OAuth2 proxy or similar authentication layer
- Configure NetworkPolicies to restrict access
- Enable TLS with valid certificates
- Consider IP allowlisting
### Scenario 3: Multi-Tenant Clusters
**Question**: How do I restrict plugin access in a multi-tenant cluster?
**Solution**: Use OIDC authentication with per-user RoleBindings:
```yaml
# Bind only to specific groups or users
subjects:
- kind: Group
name: "team-a"
apiGroup: rbac.authorization.k8s.io
```
Users not in `team-a` will receive 403 errors when accessing the plugin, preventing unauthorized access to Polaris audit data.
## Compliance Considerations
### Data Residency
All data remains within your Kubernetes cluster. The plugin does not:
- Send data to external services
- Store data in browser localStorage (except refresh interval preference)
- Use third-party analytics or tracking
### Audit Trail
All service proxy requests are logged in Kubernetes API audit logs (if enabled):
```json
{
"verb": "get",
"requestURI": "/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json",
"user": {
"username": "system:serviceaccount:kube-system:headlamp",
"groups": ["system:serviceaccounts", "system:authenticated"]
}
}
```
### GDPR/Privacy
The plugin processes only technical metadata (resource names, namespaces, check results). No personal data is collected, stored, or transmitted.
## Security Updates and Notifications
### Notification Channels
Subscribe to security updates via:
1. **GitHub Watch**: Click "Watch" → "Custom" → "Security alerts"
2. **GitHub Releases**: Monitor [releases page](https://github.com/cpfarhood/headlamp-polaris-plugin/releases)
3. **ArtifactHub**: Follow package at [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin)
### Security Patch Process
When a security vulnerability is identified:
1. **Private Fix**: Develop fix in private fork
2. **Security Advisory**: Publish GitHub Security Advisory
3. **Release**: Create new version with fix
4. **Notification**: Update advisory with fix version
5. **Disclosure**: Public disclosure after fix is available
## Contact
- **Security Issues**: [GitHub Security Advisories](https://github.com/cpfarhood/headlamp-polaris-plugin/security/advisories)
- **General Questions**: [GitHub Discussions](https://github.com/cpfarhood/headlamp-polaris-plugin/discussions)
- **Bug Reports**: [GitHub Issues](https://github.com/cpfarhood/headlamp-polaris-plugin/issues)
## License
This plugin is provided under the MIT License. See [LICENSE](LICENSE) for details.
+3 -3
View File
@@ -1,4 +1,4 @@
version: 0.3.4
version: 0.3.5
name: headlamp-polaris-plugin
displayName: Polaris
createdAt: "2026-02-05T19:00:00Z"
@@ -28,7 +28,7 @@ maintainers:
- name: cpfarhood
email: "chris@farhood.org"
annotations:
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.4/headlamp-polaris-plugin-0.3.4.tar.gz"
headlamp/plugin/archive-url: "https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.5/headlamp-polaris-plugin-0.3.5.tar.gz"
headlamp/plugin/version-compat: ">=0.26"
headlamp/plugin/archive-checksum: sha256:175a7bdedaf6a9329abc9041b505bfa07029e1ae512fc8207301d216e01ce5b1
headlamp/plugin/archive-checksum: sha256:09d199ffc2705fae1c69baef39acf5bfa9aa39b7544c9e680a80fafe8c946fa0
headlamp/plugin/distro-compat: in-cluster
+556
View File
@@ -0,0 +1,556 @@
# Architecture
This document describes the architecture, design decisions, and data flow of the Headlamp Polaris Plugin.
## Table of Contents
- [Overview](#overview)
- [System Architecture](#system-architecture)
- [Data Flow](#data-flow)
- [Component Hierarchy](#component-hierarchy)
- [State Management](#state-management)
- [Design Decisions](#design-decisions)
- [Integration Points](#integration-points)
- [Known Limitations](#known-limitations)
## Overview
The Headlamp Polaris Plugin is a **read-only dashboard** that surfaces Fairwinds Polaris audit results within the Headlamp UI. It fetches data from the Polaris dashboard API via the Kubernetes service proxy and presents it in a hierarchical navigation structure.
**Key Characteristics:**
- **Read-only:** No write operations to cluster or Polaris
- **Service proxy based:** Uses K8s API server proxy to reach Polaris
- **React Context for state:** Shared data fetch across components
- **Headlamp plugin API:** Integrates via official plugin system
- **Type-safe:** Full TypeScript with strict mode
## System Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Headlamp UI (React) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App Bar │ │ Sidebar │ │ Routes │ │
│ │ (Badge) │ │ (Navigation)│ │ (Views) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Plugin Registry │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ Polaris Plugin (This!) │ │
│ ├────────────────────────────┤ │
│ │ • registerSidebarEntry │ │
│ │ • registerRoute │ │
│ │ • registerAppBarAction │ │
│ │ • registerPluginSettings │ │
│ │ • registerDetailsViewSection│ │
│ └─────────────┬──────────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ PolarisDataContext │ │
│ │ (React Context Provider) │ │
│ └─────────────┬──────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ┌────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │Dashboard │ │ Namespaces │ │ Namespace │ │
│ │View │ │ ListView │ │ Detail │ │
│ └──────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
┌───────▼────────┐
│ ApiProxy │
│ (Headlamp) │
└───────┬────────┘
┌───────▼────────┐
│ Kubernetes │
│ API Server │
└───────┬────────┘
┌───────▼────────┐
│ Service Proxy │
│ /api/v1/ns/ │
│ polaris/svcs/ │
│ polaris- │
│ dashboard/ │
│ proxy/ │
└───────┬────────┘
┌───────▼────────┐
│ Polaris │
│ Dashboard │
│ (ClusterIP) │
└───────┬────────┘
┌───────▼────────┐
│ results.json │
│ (AuditData) │
└────────────────┘
```
## Data Flow
### 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
```
## Component Hierarchy
### Plugin Entry Point
**`src/index.tsx`**
- Registers sidebar entries (Polaris → Overview, Namespaces)
- Registers routes (`/polaris`, `/polaris/namespaces`)
- Registers app bar action (score badge)
- Registers plugin settings page
- Registers details view section (inline audit)
### Data Layer
**`src/api/PolarisDataContext.tsx`**
- React Context Provider for shared data
- Fetches AuditData from Polaris dashboard
- Handles auto-refresh based on user settings
- Provides `{ data, loading, error, refresh }` to consumers
**`src/api/polaris.ts`**
- TypeScript types for AuditData schema
- Utility functions: `countResults()`, `computeScore()`
- Settings management: `getRefreshInterval()`, `setRefreshInterval()`
- Constants: `DASHBOARD_URL_DEFAULT`, `INTERVAL_OPTIONS`
**`src/api/checkMapping.ts`**
- Maps Polaris check IDs to human-readable names
- Used for display in UI (e.g., "hostIPCSet" → "Host IPC")
**`src/api/topIssues.ts`**
- Aggregates failing checks across cluster
- Groups by check ID and severity
- Used for top issues dashboard
### View Components
**`src/components/DashboardView.tsx`**
- **Route:** `/polaris`
- **Purpose:** Cluster-wide overview
- **Features:**
- Cluster score (percentage)
- Check distribution (pass/warning/danger/skipped)
- Cluster info (Polaris version, last audit time)
- Refresh button
- **Data:** Uses `usePolarisDataContext()`
**`src/components/NamespacesListView.tsx`**
- **Route:** `/polaris/namespaces`
- **Purpose:** List all namespaces with scores
- **Features:**
- Table with namespace, score, pass/warning/danger counts
- Clickable namespace buttons (opens drawer)
- Sorted by score (lowest first)
- **Data:** Uses `usePolarisDataContext()`, aggregates by namespace
**`src/components/NamespaceDetailView.tsx`**
- **Route:** Drawer on `/polaris/namespaces#<namespace>`
- **Purpose:** Namespace-level drill-down
- **Features:**
- Namespace score
- Resource table (kind, name, score, counts)
- URL hash navigation
- Keyboard shortcuts (Escape to close)
- **Data:** Filters `usePolarisDataContext()` by namespace
### UI Components
**`src/components/AppBarScoreBadge.tsx`**
- **Location:** Headlamp app bar (top-right)
- **Purpose:** Quick cluster score visibility
- **Features:**
- Color-coded badge (green ≥80%, orange ≥50%, red <50%)
- Clickable (navigates to `/polaris`)
- Shield emoji icon
- **Data:** Uses `usePolarisDataContext()`
**`src/components/PolarisSettings.tsx`**
- **Location:** Settings → Plugins → Polaris
- **Purpose:** Plugin configuration
- **Features:**
- Refresh interval selector (1 min to 30 min)
- Dashboard URL input (custom Polaris instances)
- Connection test button
- **Data:** localStorage for persistence
**`src/components/InlineAuditSection.tsx`**
- **Location:** Resource detail pages (Deployment, StatefulSet, etc.)
- **Purpose:** Show Polaris audit inline
- **Features:**
- Pass/warning/danger counts
- Check details with messages
- Severity badges
- **Data:** Uses `usePolarisDataContext()`, filters by resource
**`src/components/ExemptionManager.tsx`**
- **Location:** (Planned feature, UI exists but not fully integrated)
- **Purpose:** Manage Polaris exemptions via annotations
- **Features:**
- View current exemptions
- Add exemptions for failing checks
- Remove exemptions
## State Management
### Why React Context?
**Decision:** Use React Context instead of Redux/Zustand
**Rationale:**
1. **Simple state:** Single AuditData object shared across views
2. **Read-only:** No complex mutations or transactions
3. **Headlamp constraints:** Plugin cannot add dependencies (Redux not bundled)
4. **Performance:** Data changes infrequently (refresh interval 1-30 min)
### Context Structure
```typescript
interface PolarisDataContextValue {
data: AuditData | null; // Audit results or null if loading/error
loading: boolean; // True during initial fetch
error: string | null; // Error message if fetch failed
refresh: () => void; // Manual refresh function
}
```
### Data Fetching Strategy
1. **Initial fetch:** On first mount of any component using the context
2. **Auto-refresh:** Based on user setting (default 5 minutes)
3. **Manual refresh:** Via refresh button in UI
4. **Caching:** Data persists in context until refresh (no per-route refetch)
### localStorage Usage
Settings persisted in localStorage:
- **`polaris-plugin-refresh-interval`**: Number (seconds), default 300
- **`polaris-plugin-dashboard-url`**: String, default service proxy path
No sensitive data stored in localStorage.
## Design Decisions
### 1. Service Proxy vs. Direct Access
**Decision:** Use Kubernetes service proxy, not direct ClusterIP access
**Rationale:**
- Headlamp already has K8s API credentials (service account or user token)
- Service proxy leverages existing RBAC (no new credentials needed)
- Works with Headlamp's token auth and OIDC
- Simpler deployment (no additional network policies for plugin)
**Trade-off:**
- Requires `get` permission on `services/proxy` resource
- Path is longer: `/api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
### 2. Two-Level Sidebar Nesting
**Decision:** Sidebar has "Polaris" → "Overview" and "Namespaces" (2 levels max)
**Rationale:**
- Headlamp sidebar supports 2-level nesting maximum
- Deeper nesting (e.g., Polaris → Namespaces → <each namespace>) doesn't work
- Sidebar Collapse component is route-based, not click-to-toggle
**Alternative Considered:**
- Dynamic sidebar with namespace entries → rejected (Headlamp limitation)
**Current Solution:**
- Use table in NamespacesListView with clickable namespace buttons
- Namespace detail opens in drawer (not new route)
### 3. Drawer Navigation Instead of Routes
**Decision:** Namespace detail uses drawer, not dedicated route
**Rationale:**
- Better UX (drawer overlays table, no navigation loss)
- URL hash preserves navigation state (`#namespace-name`)
- Keyboard shortcuts (Escape to close)
- Sidebar doesn't support 3-level nesting for per-namespace routes
**Implementation:**
- URL: `/polaris/namespaces#kube-system`
- Drawer controlled by hash presence
- `useEffect` watches hash changes
### 4. No MUI Direct Imports
**Decision:** Never import from `@mui/material` or `@mui/icons-material`
**Rationale:**
- Headlamp plugin environment doesn't provide full MUI library
- Importing MUI causes `createSvgIcon undefined` error
- Plugins must use Headlamp CommonComponents only
**Alternative:**
- Use standard HTML elements with inline styles
- Use theme-aware CSS variables (`--mui-palette-*`)
### 5. TypeScript Strict Mode
**Decision:** Enable all TypeScript strict checks
**Rationale:**
- Catch errors at compile time
- Better IDE support and autocomplete
- Enforces type safety (no `any`, no implicit unknowns)
**Impact:**
- More verbose code (explicit types required)
- Better maintainability and refactorability
### 6. Auto-Refresh Default: 5 Minutes
**Decision:** Default refresh interval is 5 minutes (configurable)
**Rationale:**
- Polaris audits typically run every 10-30 minutes
- Balance between data freshness and API load
- User can configure from 1 minute to 30 minutes
**Considered:**
- WebSocket/SSE for real-time updates → rejected (Polaris dashboard doesn't support)
- Shorter default → rejected (unnecessary API calls)
## Integration Points
### Headlamp Plugin API
**Version:** ≥ v0.13.0
**Registration Functions Used:**
```typescript
// Sidebar navigation
registerSidebarEntry({ parent, name, label, url, icon })
// Routes
registerRoute({ path, sidebar, name, exact, component })
// App bar actions
registerAppBarAction(component)
// Plugin settings
registerPluginSettings(name, component, displaySaveButton)
// Resource detail sections
registerDetailsViewSection(component)
```
**Key Changes in v0.13.0:**
- `registerDetailsViewSection` now takes 1 argument (component), not 2 (name, component)
- `registerAppBarAction` now takes 1 argument (component), not 2 (name, component)
### Headlamp CommonComponents
**Used Components:**
- `SectionBox` - Card-like container with title
- `SectionHeader` - Page header with title
- `StatusLabel` - Color-coded status badges
- `NameValueTable` - Key-value table layout
- `SimpleTable` - Data table with sorting
- `Drawer` - Right-side overlay panel
- `Loader` - Loading spinner
**Router:**
- `Router.createRouteURL()` - Generate plugin route URLs
- React Router's `useHistory()`, `useParams()`, `useLocation()`
### Kubernetes API (via ApiProxy)
**Used for:**
- Fetching Polaris results: `ApiProxy.request(dashboardUrl + 'results.json')`
- No direct K8s API calls (all data from Polaris dashboard)
**RBAC Required:**
- `get` on `services/proxy` for `polaris-dashboard` in `polaris` namespace
## Known Limitations
### 1. Sidebar Nesting Depth
**Limitation:** Headlamp sidebar supports only 2 levels
**Impact:** Cannot have dynamic per-namespace sidebar entries
**Workaround:** Use table with drawer navigation
### 2. Skipped Checks Visibility
**Limitation:** Skipped checks (severity "ignore") counted but details not shown in dashboard
**Reason:** Polaris API groups skipped checks but doesn't provide per-check details
**Impact:** Users see skipped count but can't drill down to specific skipped checks
**Documented:** README, tooltip on skipped count
### 3. No Write Operations
**Limitation:** Plugin cannot modify Polaris configuration or exemptions
**Reason:** Read-only by design (service proxy only has `get` permission)
**Impact:** Exemption manager UI exists but requires manual annotation edits
**Future:** Could add PATCH permission to enable exemption annotations via UI
### 4. No Real-Time Updates
**Limitation:** Data refreshes on interval (1-30 minutes), not real-time
**Reason:** Polaris dashboard doesn't support WebSocket/SSE
**Impact:** Users may see stale data between refreshes
**Workaround:** Manual refresh button, configurable interval
### 5. MUI Import Restrictions
**Limitation:** Cannot import MUI components directly
**Reason:** Headlamp plugin environment doesn't provide full MUI bundle
**Impact:** Must use Headlamp CommonComponents or HTML elements
**Documented:** CLAUDE.md, CONTRIBUTING.md
### 6. Single Cluster Support
**Limitation:** Plugin shows data for current cluster only
**Reason:** Headlamp's multi-cluster support is route-based (`/c/<cluster>/...`)
**Impact:** Users must switch clusters in Headlamp to see different cluster's Polaris data
**Future:** Could enhance to aggregate multi-cluster if Headlamp API supports it
## Performance Considerations
### Bundle Size
- **Current:** ~27 KB minified (gzip: ~7.6 KB)
- **Target:** Keep under 50 KB to ensure fast loading
- **Strategy:** No heavy dependencies, tree-shaking enabled
### Data Fetching
- **Lazy loading:** Data not fetched until user navigates to plugin
- **Caching:** Single fetch shared across all views (React Context)
- **Refresh strategy:** User-controlled interval prevents excessive API calls
### Rendering
- **React.memo:** Not needed (data changes infrequently)
- **Virtual scrolling:** Not needed (namespace/resource lists typically <100 items)
- **Component splitting:** Lazy load views if bundle grows significantly
## Future Architecture Enhancements
### Potential Improvements
1. **WebWorker for data processing**
- Offload `countResults()` aggregation for large clusters
- Keep UI responsive during heavy computation
2. **IndexedDB caching**
- Cache audit data offline
- Show stale data + "refresh available" indicator
3. **GraphQL/REST API abstraction**
- Decouple from Polaris dashboard JSON format
- Support multiple backend sources
4. **Plugin-to-plugin communication**
- Integrate with other Headlamp plugins (e.g., policy enforcement)
- Shared state between plugins
5. **Incremental updates**
- Fetch only changed namespaces/resources
- Reduce bandwidth and processing
## References
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
- [Fairwinds Polaris Documentation](https://polaris.docs.fairwinds.com/)
- [React Context API](https://react.dev/reference/react/useContext)
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/)
+689
View File
@@ -0,0 +1,689 @@
# Deployment Guide
This document provides comprehensive deployment instructions for the Headlamp Polaris Plugin in production Kubernetes environments.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation Methods](#installation-methods)
- [Helm Integration](#helm-integration)
- [RBAC Configuration](#rbac-configuration)
- [Network Policies](#network-policies)
- [Plugin Manager Setup](#plugin-manager-setup)
- [Production Checklist](#production-checklist)
- [Troubleshooting](#troubleshooting)
## Prerequisites
### Required Components
1. **Kubernetes Cluster:** v1.19 or later
2. **Headlamp:** v0.26 or later (v0.39+ recommended)
3. **Polaris:** Deployed and accessible via service
4. **RBAC:** Permissions to create Roles and RoleBindings
### Pre-Deployment Verification
```bash
# Verify Polaris is deployed
kubectl -n polaris get pods
kubectl -n polaris get svc polaris-dashboard
# Verify Polaris dashboard is responding
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
# Verify Headlamp is deployed
kubectl -n kube-system get pods -l app.kubernetes.io/name=headlamp
```
## Installation Methods
### Method 1: Headlamp Plugin Manager (Recommended)
**Best for:** Production deployments, managed updates
1. **Enable Plugin Manager in Headlamp:**
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
```
2. **Deploy/Update Headlamp:**
```bash
helm upgrade --install headlamp headlamp/headlamp \
--namespace kube-system \
--values headlamp-values.yaml
```
3. **Install Plugin via UI:**
- Navigate to Headlamp → Settings → Plugins
- Search for "Polaris"
- Click "Install"
- Refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
### Method 2: Sidecar Container (Alternative)
**Best for:** Controlled plugin versions, air-gapped environments
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false # CRITICAL: Must be false for plugin manager
replicaCount: 1
initContainers:
- name: install-polaris-plugin
image: node:lts-alpine
command:
- sh
- -c
- |
npm install -g @kinvolk/headlamp-plugin
headlamp-plugin install --config /config/plugin.yml --plugins-dir /plugins
volumeMounts:
- name: plugins
mountPath: /plugins
- name: plugin-config
mountPath: /config
volumes:
- name: plugins
emptyDir: {}
- name: plugin-config
configMap:
name: headlamp-plugin-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: headlamp-plugin-config
namespace: kube-system
data:
plugin.yml: |
- name: headlamp-polaris-plugin
version: 0.3.4
url: https://github.com/cpfarhood/headlamp-polaris-plugin/releases/download/v0.3.4/headlamp-polaris-plugin-0.3.4.tar.gz
```
### Method 3: Volume Mount (Development)
**Best for:** Local testing, development
```yaml
# headlamp-values.yaml
config:
pluginsDir: "/plugins"
volumes:
- name: plugins
hostPath:
path: /path/to/plugins
type: Directory
volumeMounts:
- name: plugins
mountPath: /plugins
readOnly: true
```
Then manually place `headlamp-polaris-plugin/` in the host path.
## Helm Integration
### Complete Helm Values Example
```yaml
# headlamp-values.yaml
replicaCount: 2
image:
repository: ghcr.io/headlamp-k8s/headlamp
tag: v0.39.0
config:
baseURL: ""
pluginsDir: "/headlamp/plugins"
watchPlugins: false # MUST be false for plugin manager
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: headlamp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: headlamp-tls
hosts:
- headlamp.example.com
serviceAccount:
create: true
name: headlamp
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
# OIDC Authentication (optional)
env:
- name: HEADLAMP_CONFIG_OIDC_CLIENT_ID
value: "headlamp"
- name: HEADLAMP_CONFIG_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headlamp-oidc
key: client-secret
- name: HEADLAMP_CONFIG_OIDC_ISSUER_URL
value: "https://auth.example.com/realms/kubernetes"
- name: HEADLAMP_CONFIG_OIDC_SCOPES
value: "openid,profile,email,groups"
```
### FluxCD HelmRelease Example
```yaml
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: headlamp
namespace: kube-system
spec:
interval: 30m
chart:
spec:
chart: headlamp
version: 0.26.x
sourceRef:
kind: HelmRepository
name: headlamp
namespace: flux-system
interval: 12h
values:
config:
pluginsDir: "/headlamp/plugins"
watchPlugins: false
pluginsManager:
enabled: true
repositories:
- https://artifacthub.io/packages/search?kind=4
service:
type: ClusterIP
ingress:
enabled: true
className: nginx
hosts:
- host: headlamp.example.com
paths:
- path: /
pathType: Prefix
```
## RBAC Configuration
### Minimal Role for Plugin
The plugin requires **read-only** access to the Polaris dashboard service proxy.
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
```
### RoleBinding Options
#### Option A: Headlamp Service Account (In-Cluster Mode)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
#### Option B: User Groups (Token/OIDC Mode)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: users-polaris-proxy
namespace: polaris
subjects:
- kind: Group
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
#### Option C: Specific Users (Fine-Grained Control)
```yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: devops-polaris-proxy
namespace: polaris
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
- kind: User
name: bob@example.com
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: devops-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
### Complete RBAC Manifest
```yaml
---
# Role: Read-only access to Polaris service proxy
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: polaris-proxy-reader
namespace: polaris
labels:
app.kubernetes.io/name: headlamp-polaris-plugin
app.kubernetes.io/component: rbac
rules:
- apiGroups: [""]
resources: ["services/proxy"]
resourceNames: ["polaris-dashboard"]
verbs: ["get"]
---
# RoleBinding: Grant Headlamp service account access
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: headlamp-polaris-proxy
namespace: polaris
labels:
app.kubernetes.io/name: headlamp-polaris-plugin
app.kubernetes.io/component: rbac
subjects:
- kind: ServiceAccount
name: headlamp
namespace: kube-system
roleRef:
kind: Role
name: polaris-proxy-reader
apiGroup: rbac.authorization.k8s.io
```
Apply with:
```bash
kubectl apply -f polaris-plugin-rbac.yaml
```
## Network Policies
### Required Network Access
The plugin requires network connectivity:
- **Headlamp pod** → **Kubernetes API server** (service proxy)
- **Kubernetes API server** → **Polaris dashboard service** (port 80)
### Network Policy Example
If your `polaris` namespace has strict NetworkPolicies:
```yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-headlamp-to-polaris
namespace: polaris
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: polaris
app.kubernetes.io/component: dashboard
policyTypes:
- Ingress
ingress:
# Allow from API server (service proxy)
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
component: kube-apiserver
ports:
- protocol: TCP
port: 80
```
**Note:** The API server performs the proxy hop, not the Headlamp pod directly.
## Plugin Manager Setup
### Critical Configuration
**❌ WRONG (Will not load plugins):**
```yaml
config:
watchPlugins: true # Default, treats catalog plugins as dev plugins
```
**✅ CORRECT:**
```yaml
config:
watchPlugins: false # Required for plugin manager catalog plugins
```
### Why `watchPlugins: false` is Required
- **With `watchPlugins: true`:** Headlamp backend serves plugin metadata, but frontend never executes the JavaScript (treated as development directory plugin)
- **Result:** Plugins appear in Settings but no sidebar/routes/settings work
- **Fix:** Set `watchPlugins: false` in Headlamp configuration
- **Documentation:** See `deployment/PLUGIN_LOADING_FIX.md` for root cause analysis
### Plugin Manager Verification
```bash
# Check Headlamp config
kubectl -n kube-system get configmap headlamp -o yaml | grep watchPlugins
# Expected output:
# watchPlugins: "false"
# Check plugin is installed
kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/
# Expected output:
# drwxr-xr-x headlamp-polaris-plugin/
```
## Production Checklist
### Pre-Deployment
- [ ] Polaris deployed and running
- [ ] Polaris dashboard service exists (`polaris-dashboard` in `polaris` namespace)
- [ ] RBAC Role and RoleBinding created
- [ ] Headlamp v0.26+ deployed
- [ ] `watchPlugins: false` set in Headlamp config
### Deployment
- [ ] Plugin installed via plugin manager or sidecar
- [ ] Headlamp pods restarted (if config changed)
- [ ] Browser cache cleared (Cmd+Shift+R / Ctrl+Shift+R)
### Post-Deployment Verification
```bash
# 1. Verify Polaris is accessible via service proxy
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq .PolarisOutputVersion
# Expected: "1.0" or similar
# 2. Verify RBAC is correct
kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:headlamp -n polaris --resource-name=polaris-dashboard
# Expected: yes
# 3. Check Headlamp logs
kubectl -n kube-system logs deployment/headlamp | grep -i polaris
# Expected: No errors related to plugin loading
# 4. Verify plugin files exist
kubectl -n kube-system exec -it deployment/headlamp -- ls -la /headlamp/plugins/headlamp-polaris-plugin/
# Expected: dist/, package.json present
```
### UI Verification
- [ ] Navigate to Headlamp → Settings → Plugins
- [ ] Plugin "headlamp-polaris-plugin" listed
- [ ] Sidebar shows "Polaris" entry
- [ ] Click "Polaris" → Overview page loads
- [ ] Cluster score displays correctly
- [ ] Namespaces page shows table
- [ ] App bar shows Polaris score badge
## Troubleshooting
### Plugin Not Appearing in Sidebar
**Symptom:** Plugin listed in Settings → Plugins but no sidebar entry
**Causes:**
1. `watchPlugins: true` (should be `false`)
2. Browser cache not cleared
**Solution:**
```bash
# Fix Headlamp config
kubectl -n kube-system edit configmap headlamp
# Set watchPlugins: false
# Restart Headlamp
kubectl -n kube-system rollout restart deployment/headlamp
# Clear browser cache
# Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
```
### 403 Forbidden Error
**Symptom:** Error loading Polaris data, 403 in console
**Cause:** RBAC missing or incorrect
**Solution:**
```bash
# Verify RBAC exists
kubectl -n polaris get role polaris-proxy-reader
kubectl -n polaris get rolebinding headlamp-polaris-proxy
# Test permission
kubectl auth can-i get services/proxy --as=system:serviceaccount:kube-system:headlamp -n polaris --resource-name=polaris-dashboard
# If "no", create RBAC (see RBAC Configuration section)
```
### 404 Not Found Error
**Symptom:** Error loading Polaris data, 404 in console
**Causes:**
1. Polaris not deployed
2. Polaris service name wrong
3. Polaris namespace wrong
**Solution:**
```bash
# Check Polaris deployment
kubectl -n polaris get pods
kubectl -n polaris get svc polaris-dashboard
# If service doesn't exist, install Polaris:
helm install polaris fairwinds-stable/polaris \
--namespace polaris \
--create-namespace \
--set dashboard.enabled=true
```
### Custom Dashboard URL Not Working
**Symptom:** Error when using custom Polaris URL in settings
**Causes:**
1. URL format incorrect
2. CORS not configured on external Polaris
3. Network policy blocking external access
**Solution:**
```bash
# Test URL manually
curl -v https://my-polaris.example.com/results.json
# For external Polaris, check CORS headers
# Must allow Headlamp origin
```
### Plugin Shows Old Version
**Symptom:** Plugin version in Settings doesn't match expected
**Cause:** Plugin manager hasn't synced from ArtifactHub
**Solution:**
```bash
# Wait 30 minutes (ArtifactHub sync interval)
# Or manually refresh plugin list in Headlamp UI
# Force Headlamp restart
kubectl -n kube-system rollout restart deployment/headlamp
```
### Network Policy Blocking Access
**Symptom:** Timeout or connection errors despite correct RBAC
**Cause:** NetworkPolicy in `polaris` namespace blocking API server
**Solution:**
```bash
# Check NetworkPolicies
kubectl -n polaris get networkpolicy
# Test connectivity from API server (if possible)
# Add NetworkPolicy to allow API server → Polaris dashboard (see Network Policies section)
```
## Security Considerations
### Least Privilege
- Grant only `get` on `services/proxy`, not broader permissions
- Use `resourceNames` to restrict to specific service (`polaris-dashboard`)
- Scope to `polaris` namespace only (Role, not ClusterRole)
### Audit Logging
Kubernetes audit logs will record:
- User/service account accessing service proxy
- Timestamp and response code
Configure audit policy if needed:
```yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
verbs: ["get"]
resources:
- group: ""
resources: ["services/proxy"]
namespaces: ["polaris"]
```
### Data Sensitivity
Polaris audit data may contain:
- Resource names and namespaces
- Configuration details
- Potential security vulnerabilities
**Recommendation:** Restrict plugin access to authorized users only (not `system:authenticated` group unless appropriate).
## Upgrading
### Plugin Upgrade via Plugin Manager
1. Navigate to Settings → Plugins
2. Find "headlamp-polaris-plugin"
3. Click "Update" if new version available
4. Refresh browser (Cmd+Shift+R / Ctrl+Shift+R)
### Sidecar Method Upgrade
1. Update ConfigMap with new version/URL
2. Restart Headlamp deployment
3. Verify new version in Settings → Plugins
```bash
kubectl -n kube-system edit configmap headlamp-plugin-config
# Update version and URL
kubectl -n kube-system rollout restart deployment/headlamp
```
## References
- [Headlamp Deployment](https://headlamp.dev/docs/latest/installation/)
- [Headlamp Helm Chart](https://github.com/headlamp-k8s/headlamp/tree/main/charts/headlamp)
- [Polaris Installation](https://polaris.docs.fairwinds.com/infrastructure-as-code/)
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
- [Kubernetes Service Proxy](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-services/#manually-constructing-apiserver-proxy-urls)
+242 -6
View File
@@ -4,14 +4,20 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
## CI
E2E tests run automatically in Gitea Actions on pushes to `main` and pull requests. The workflow (`.gitea/workflows/e2e.yaml`) uses Authentik OIDC for authentication via repo secrets.
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`) uses either Authentik OIDC or token-based authentication via repository secrets.
### Required Gitea secrets
### Required GitHub Secrets
| Secret | Description |
| -------------------- | -------------------------------------------------------------- |
| `AUTHENTIK_USERNAME` | Authentik email or username for a CI user with Headlamp access |
| `AUTHENTIK_PASSWORD` | Password for that user |
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
| Secret | Required | Description |
| -------------------- | -------- | -------------------------------------------------------------- |
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to `https://headlamp.animaniacs.farh.net`) |
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
## Running Locally
@@ -56,3 +62,233 @@ Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC
- **Namespace detail** — Clicking a namespace shows its score and resource table
These are smoke tests against real cluster data. They verify the plugin loads and renders without errors, not specific data values.
## Test Coverage
### Current Tests (`polaris.spec.ts`)
1. **`sidebar contains Polaris entry`**
- Verifies Polaris appears in the navigation sidebar
- Ensures plugin successfully registered sidebar entry
2. **`overview page renders cluster score`**
- Navigates to `/c/main/polaris`
- Checks for "Polaris — Overview" heading
- Verifies cluster score percentage is displayed
- Validates data fetching and rendering
3. **`namespaces page renders table with namespace buttons`**
- Navigates to `/c/main/polaris/namespaces`
- Checks for "Polaris — Namespaces" heading
- Verifies table is visible with at least one row
- Ensures namespace buttons are clickable
4. **`namespace detail drawer opens from table button`**
- Clicks first namespace button in table
- Verifies drawer opens with namespace name in heading
- Checks "Namespace Score" section is visible
- Confirms "Resources" table is displayed
- Validates URL hash is updated with namespace name
5. **`namespace detail drawer closes with Escape key`**
- Opens namespace drawer
- Presses Escape key
- Verifies drawer closes
- Checks URL hash is cleared
6. **`namespace detail drawer opens from URL hash`**
- Navigates directly to `/c/main/polaris/namespaces#<namespace>`
- Verifies drawer automatically opens
- Checks namespace details are displayed
## Prerequisites
### Cluster Requirements
1. **Polaris Deployment**
```bash
# Verify Polaris is running
kubectl -n polaris get pods
kubectl -n polaris get svc polaris-dashboard
```
2. **Polaris Audit Data**
```bash
# Check if Polaris has generated audit results
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime'
```
3. **RBAC Permissions**
- Headlamp service account (or test user) needs `get` on `services/proxy` for `polaris-dashboard`
- See main README for RBAC setup
### Local Setup
```bash
# 1. Install dependencies
npm install
npx playwright install chromium
# 2. Create .env file (optional, for persistent config)
cp .env.example .env
# 3. Set environment variables
export HEADLAMP_URL=https://your-headlamp-instance.com
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
# 4. Run tests
npm run e2e
```
## Debugging
### Run in Headed Mode
See the browser UI while tests run:
```bash
npm run e2e:headed
```
### Enable Debug Mode
Step through tests with Playwright Inspector:
```bash
npx playwright test --debug
```
### Generate Trace
Record full trace for failed tests:
```bash
npx playwright test --trace on
npx playwright show-trace test-results/<test-name>/trace.zip
```
### Screenshot on Failure
Tests automatically capture screenshots on failure in `test-results/`
### Common Issues
**Auth fails with "Sign In button not found":**
- Check HEADLAMP_URL is correct
- Verify Headlamp is accessible
- Ensure OIDC is configured if using Authentik
**Polaris sidebar entry not found:**
- Plugin may not be installed: Check Settings → Plugins in Headlamp
- Plugin may have failed to load: Check browser console
- Clear browser cache and hard refresh
**Cluster score not displayed:**
- Polaris may not have audit data yet
- Check Polaris is running: `kubectl -n polaris get pods`
- Verify service proxy: `kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json`
**Namespace table empty:**
- Polaris hasn't run audit yet (wait a few minutes)
- Check Polaris logs: `kubectl -n polaris logs -l app.kubernetes.io/name=polaris`
## Writing New Tests
### Example: Testing Plugin Settings
```typescript
test('plugin settings page shows Polaris configuration', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Find and click Polaris plugin
await page.getByText('headlamp-polaris-plugin').click();
// Check settings are visible
await expect(page.getByText('Polaris Settings')).toBeVisible();
await expect(page.getByText('Refresh Interval')).toBeVisible();
await expect(page.getByText('Dashboard URL')).toBeVisible();
});
```
### Example: Testing App Bar Badge
```typescript
test('app bar displays Polaris score badge', async ({ page }) => {
await page.goto('/c/main');
// Badge should be visible in app bar
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
await expect(badge).toBeVisible();
// Clicking should navigate to overview
await badge.click();
await expect(page).toHaveURL(/\/c\/main\/polaris$/);
});
```
### Example: Testing Dark Mode
```typescript
test('plugin UI adapts to dark mode', async ({ page }) => {
await page.goto('/c/main/polaris');
// Toggle dark mode
await page.getByRole('button', { name: /theme/i }).click();
// Check background color changes
const body = page.locator('body');
await expect(body).toHaveCSS('background-color', 'rgb(18, 18, 18)');
// Plugin components should adapt
const sectionBox = page.locator('[class*="MuiPaper"]').first();
await expect(sectionBox).not.toHaveCSS('background-color', 'rgb(255, 255, 255)');
});
```
## CI/CD Integration
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
### Required Secrets
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
- `HEADLAMP_URL` (optional): Headlamp instance URL
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth)
- OR `HEADLAMP_TOKEN` (for token-based auth)
### Workflow Overview
1. Checkout code
2. Setup Node.js 20 with npm cache
3. Install dependencies (`npm ci`)
4. Install Playwright browsers (`chromium` only)
5. Run auth setup (creates session in `e2e/.auth/state.json`)
6. Run all E2E tests
7. Upload artifacts on failure:
- `playwright-report/` - HTML test report
- `test-results/` - Screenshots, traces, videos
### Manual Trigger
You can manually trigger E2E tests from GitHub Actions:
1. Go to Actions → E2E Tests
2. Click "Run workflow"
3. Select branch and run
## Best Practices
1. **Use semantic selectors**: `getByRole`, `getByText` over CSS selectors
2. **Wait for visibility**: Use `await expect(...).toBeVisible()` instead of `waitForTimeout`
3. **Keep tests independent**: Each test should work in isolation
4. **Test user flows**: Complete journeys, not just page loads
5. **Clean up state**: Close drawers/modals after tests
6. **Use storage state**: Reuse auth across tests (already configured)
7. **Parallelize carefully**: Currently disabled due to shared state
## Resources
- [Playwright Documentation](https://playwright.dev/)
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
- [Project Main README](../README.md)
+94
View File
@@ -0,0 +1,94 @@
import { test, expect } from '@playwright/test';
test.describe('Polaris app bar badge', () => {
test('badge displays cluster score in app bar', async ({ page }) => {
await page.goto('/c/main');
// Wait for page to load
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible();
// Badge should be visible in app bar with score percentage
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
await expect(badge).toBeVisible({ timeout: 15_000 });
// Badge should show shield emoji
await expect(badge).toContainText('🛡️');
});
test('clicking badge navigates to overview page', async ({ page }) => {
await page.goto('/c/main');
// Find and click the badge
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
await expect(badge).toBeVisible({ timeout: 15_000 });
await badge.click();
// Should navigate to Polaris overview
await expect(page).toHaveURL(/\/c\/main\/polaris$/);
await expect(page.getByRole('heading', { name: 'Polaris — Overview' })).toBeVisible();
});
test('badge color reflects score level', async ({ page }) => {
await page.goto('/c/main');
// Get the badge
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
await expect(badge).toBeVisible({ timeout: 15_000 });
// Extract score from button text
const badgeText = await badge.textContent();
const scoreMatch = badgeText?.match(/(\d+)%/);
expect(scoreMatch).toBeTruthy();
const score = parseInt(scoreMatch![1]);
// Check background color matches score level
const bgColor = await badge.evaluate(el =>
window.getComputedStyle(el).backgroundColor
);
if (score >= 80) {
// Green: rgb(76, 175, 80) or #4caf50
expect(bgColor).toMatch(/rgb\(76,\s*175,\s*80\)/);
} else if (score >= 50) {
// Orange: rgb(255, 152, 0) or #ff9800
expect(bgColor).toMatch(/rgb\(255,\s*152,\s*0\)/);
} else {
// Red: rgb(244, 67, 54) or #f44336
expect(bgColor).toMatch(/rgb\(244,\s*67,\s*54\)/);
}
});
test('badge updates when navigating between clusters', async ({ page }) => {
// This test assumes multi-cluster setup; skip if only one cluster
await page.goto('/c/main');
// Get initial badge score
const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
await expect(badge).toBeVisible({ timeout: 15_000 });
const initialScore = await badge.textContent();
// Try to switch clusters (if available)
const clusterSelector = page.getByRole('button', { name: /cluster/i });
if (await clusterSelector.isVisible()) {
// Note: This part will only work in multi-cluster setups
// For single-cluster, this test will just verify badge persists
await clusterSelector.click();
// Select different cluster if available
const clusterOptions = page.getByRole('menuitem');
const count = await clusterOptions.count();
if (count > 1) {
await clusterOptions.nth(1).click();
// Badge should update or disappear (if new cluster doesn't have Polaris)
// This is just verifying no crash occurs
await page.waitForTimeout(2000);
}
}
// Badge should still be functional
await expect(badge).toBeEnabled();
});
});
+88
View File
@@ -0,0 +1,88 @@
import { test, expect } from '@playwright/test';
test.describe('Polaris plugin settings', () => {
test('settings page shows configuration options', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Find Polaris plugin in the list
const pluginCard = page.locator('text=headlamp-polaris-plugin').first();
await expect(pluginCard).toBeVisible();
// Click to view settings (if settings are displayed inline, they should already be visible)
// Note: Headlamp v0.39.0+ shows settings inline on the plugins page
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
});
test('refresh interval setting is configurable', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Navigate to Polaris settings
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
// Find the refresh interval dropdown
const intervalSelect = page.locator('select').filter({ hasText: /minute|second/ });
await expect(intervalSelect).toBeVisible();
// Get current value
const currentValue = await intervalSelect.inputValue();
// Change to a different value
const newValue = currentValue === '300' ? '600' : '300';
await intervalSelect.selectOption(newValue);
// Value should be updated
await expect(intervalSelect).toHaveValue(newValue);
});
test('dashboard URL setting is configurable', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Navigate to Polaris settings
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
// Find the dashboard URL input
const urlInput = page.getByPlaceholder(/polaris-dashboard/);
await expect(urlInput).toBeVisible();
// Input should have the default proxy URL or custom URL
const currentUrl = await urlInput.inputValue();
expect(currentUrl).toBeTruthy();
// Examples text should be visible
await expect(page.getByText('Examples:')).toBeVisible();
await expect(page.getByText(/K8s proxy:/)).toBeVisible();
});
test('connection test button is available', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Navigate to Polaris settings
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
// Find and verify test connection button
const testButton = page.getByRole('button', { name: /test connection/i });
await expect(testButton).toBeVisible();
await expect(testButton).toBeEnabled();
});
test('connection test works with valid URL', async ({ page }) => {
await page.goto('/c/main/settings/plugins');
// Navigate to Polaris settings
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
// Click test connection
const testButton = page.getByRole('button', { name: /test connection/i });
await testButton.click();
// Wait for either success or error message
// Note: This will succeed if Polaris is accessible, fail otherwise
await page.waitForSelector('text=/Connected successfully|Connection failed/', {
timeout: 15_000,
});
// Either success or failure is acceptable (depends on environment)
const result = await page.textContent('body');
expect(result).toMatch(/(Connected successfully|Connection failed)/);
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "headlamp-polaris-plugin",
"version": "0.3.4",
"version": "0.3.5",
"description": "Headlamp plugin for Fairwinds Polaris audit results",
"scripts": {
"start": "headlamp-plugin start",
+1 -1
View File
@@ -1,5 +1,5 @@
import { getCheckCategory, getCheckName } from './checkMapping';
import { AuditData } from './polaris';
import { getCheckName, getCheckCategory } from './checkMapping';
export interface TopIssue {
checkId: string;
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import { computeScore, countResults } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
/**
* App bar badge showing cluster Polaris score
+1 -1
View File
@@ -9,10 +9,10 @@ import {
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { getSeverityStatus } from '../api/checkMapping';
import { AuditData, computeScore, countResults, ResultCounts } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import { getTopIssues, TopIssue } from '../api/topIssues';
import { getSeverityStatus } from '../api/checkMapping';
const COLORS = {
pass: '#4caf50',
+2 -2
View File
@@ -1,8 +1,8 @@
import { NameValueTable, SectionBox, Dialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
import { Dialog, NameValueTable, SectionBox } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import React from 'react';
import { Result } from '../api/polaris';
import { getCheckName } from '../api/checkMapping';
import { Result } from '../api/polaris';
interface ExemptionManagerProps {
workloadResult: Result;
+4 -4
View File
@@ -1,14 +1,14 @@
import {
NameValueTable,
SectionBox,
StatusLabel,
SimpleTable,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { Link } from 'react-router-dom';
import React from 'react';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import { computeScore, countResultsForItems, ResultCounts } from '../api/polaris';
import { Link } from 'react-router-dom';
import { getCheckName, getSeverityStatus } from '../api/checkMapping';
import { computeScore, countResultsForItems } from '../api/polaris';
import { usePolarisDataContext } from '../api/PolarisDataContext';
import ExemptionManager from './ExemptionManager';
interface CheckFailure {
+4 -5
View File
@@ -103,7 +103,8 @@ function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps)
top: 0,
bottom: 0,
width: '1000px',
backgroundColor: 'var(--mui-palette-background-paper, var(--background-paper, #fff))',
backgroundColor: 'var(--mui-palette-background-default)',
color: 'var(--mui-palette-text-primary)',
boxShadow: '-2px 0 8px rgba(0,0,0,0.15)',
overflowY: 'auto',
zIndex: 1200,
@@ -118,9 +119,7 @@ function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps)
alignItems: 'center',
}}
>
<h2
style={{ margin: 0, color: 'var(--mui-palette-text-primary, var(--text-primary, #000))' }}
>
<h2 style={{ margin: 0, color: 'var(--mui-palette-text-primary)' }}>
Polaris {namespace}
</h2>
<button
@@ -131,7 +130,7 @@ function NamespaceDetailPanel({ namespace, onClose }: NamespaceDetailPanelProps)
fontSize: '24px',
cursor: 'pointer',
padding: '0 8px',
color: 'var(--mui-palette-text-primary, var(--text-primary, #000))',
color: 'var(--mui-palette-text-primary)',
}}
aria-label="Close panel"
>
+2 -2
View File
@@ -1,17 +1,17 @@
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
import {
NameValueTable,
SectionBox,
StatusLabel,
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import {
AuditData,
getDashboardUrl,
getRefreshInterval,
INTERVAL_OPTIONS,
setDashboardUrl,
setRefreshInterval,
AuditData,
} from '../api/polaris';
interface PluginSettingsProps {
+2 -2
View File
@@ -7,11 +7,11 @@ import {
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
import { PolarisDataProvider } from './api/PolarisDataContext';
import AppBarScoreBadge from './components/AppBarScoreBadge';
import DashboardView from './components/DashboardView';
import InlineAuditSection from './components/InlineAuditSection';
import NamespacesListView from './components/NamespacesListView';
import PolarisSettings from './components/PolarisSettings';
import InlineAuditSection from './components/InlineAuditSection';
import AppBarScoreBadge from './components/AppBarScoreBadge';
// --- Sidebar entries ---