feat: scaffold headlamp-argocd-plugin with standard plugin structure #1
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@headlamp-k8s/eslint-config'],
|
||||||
|
};
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.pnp/
|
||||||
|
.pnp.js
|
||||||
|
*.log
|
||||||
|
*.tgz
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
.cache/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
Headlamp plugin for ArgoCD visibility. Monitors ArgoCD Applications, Rollouts, and health status via the ArgoCD REST API proxied through Kubernetes. Read-only — no cluster write operations.
|
||||||
|
|
||||||
|
- **Plugin name**: `argocd`
|
||||||
|
- **Target**: Headlamp >= v0.26
|
||||||
|
- **Data source**: ArgoCD server at `/api/v1/namespaces/argocd/services/argocd-server/proxy/api/v1/applications`
|
||||||
|
- **RBAC**: `get`/`list` on `services/proxy` for `argocd-server` in `argocd` namespace
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start # dev server with hot reload
|
||||||
|
pnpm build # production build
|
||||||
|
pnpm package # package for headlamp
|
||||||
|
pnpm tsc # TypeScript type check (no emit)
|
||||||
|
pnpm lint # ESLint
|
||||||
|
pnpm lint:fix # ESLint with auto-fix
|
||||||
|
pnpm format # Prettier write
|
||||||
|
pnpm format:check # Prettier check
|
||||||
|
pnpm test # vitest run
|
||||||
|
pnpm test:watch # vitest watch mode
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests and `pnpm tsc` must pass before committing.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── index.tsx # Plugin entry: registerRoute, registerSidebarEntry; ArgoCDErrorBoundary
|
||||||
|
├── test-utils.tsx # Shared test fixtures
|
||||||
|
├── api/
|
||||||
|
│ └── argocd.ts # ArgoCD API types, ApiProxy request helpers
|
||||||
|
└── components/
|
||||||
|
└── ArgoCDStubView.tsx # Placeholder Applications List view
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code conventions
|
||||||
|
|
||||||
|
- Functional React components only — class components only for error boundaries (ArgoCDErrorBoundary in index.tsx)
|
||||||
|
- All imports from `@kinvolk/headlamp-plugin/lib` and `@kinvolk/headlamp-plugin/lib/CommonComponents`
|
||||||
|
- `@mui/material` is available as a shared external via Headlamp — use `useTheme` from `@mui/material/styles` for theming. Do NOT add `@mui/material` to package.json dependencies.
|
||||||
|
- Use `useTheme()` + `theme.palette.*` for all theme-aware colors — never use `var(--mui-palette-*)` CSS variables
|
||||||
|
- No other UI libraries (no Ant Design, etc.)
|
||||||
|
- TypeScript strict mode — no `any`, use `unknown` + type guards at API boundaries
|
||||||
|
- Context provider wraps each route component in `index.tsx`
|
||||||
|
- All registered components wrapped in `ArgoCDErrorBoundary` for graceful error handling
|
||||||
|
- Tests: vitest + @testing-library/react, mock with `vi.mock('@kinvolk/headlamp-plugin/lib', ...)`
|
||||||
|
- `vitest.setup.ts` provides a spec-compliant `localStorage` shim for Node 22+ compatibility
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Mock pattern for headlamp APIs:
|
||||||
|
```typescript
|
||||||
|
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||||
|
ApiProxy: { request: vi.fn().mockResolvedValue({}) },
|
||||||
|
K8s: { ResourceClasses: {} },
|
||||||
|
}));
|
||||||
|
```
|
||||||
+1
-1
@@ -27,7 +27,7 @@ provider:
|
|||||||
name: privilegedescalation
|
name: privilegedescalation
|
||||||
annotations:
|
annotations:
|
||||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-argocd-plugin/releases/download/v0.1.0/headlamp-argocd-0.1.0.tar.gz"
|
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-argocd-plugin/releases/download/v0.1.0/headlamp-argocd-0.1.0.tar.gz"
|
||||||
headlamp/plugin/archive-checksum: "sha256:REPLACE_WITH_ACTUAL_CHECKSUM"
|
headlamp/plugin/archive-checksum: "sha256:1f4df43f79b795bdf4f70e1e3aa5bacadf689ea5584fdadf92fb677faab21c2c"
|
||||||
headlamp/plugin/version-compat: ">=0.26"
|
headlamp/plugin/version-compat: ">=0.26"
|
||||||
headlamp/plugin/distro-compat: "in-cluster"
|
headlamp/plugin/distro-compat: "in-cluster"
|
||||||
changes:
|
changes:
|
||||||
|
|||||||
+52
-10
@@ -1,18 +1,60 @@
|
|||||||
{
|
{
|
||||||
"name": "headlamp-argocd",
|
"name": "@privilegedescalation/headlamp-argocd-plugin",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Headlamp plugin for ArgoCD visibility",
|
"description": "Headlamp plugin for ArgoCD visibility — monitors ArgoCD Applications, Rollouts, and health status",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/privilegedescalation/headlamp-argocd-plugin#readme",
|
||||||
|
"author": "privilegedescalation",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"keywords": ["headlamp", "argocd", "kubernetes", "gitops"],
|
"packageManager": "pnpm@10.32.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "headlamp-plugin start",
|
||||||
"build": "headlamp-plugin build",
|
"build": "headlamp-plugin build",
|
||||||
"lint": "headlamp-plugin lint",
|
"package": "headlamp-plugin package",
|
||||||
"tsc": "headlamp-plugin tsc",
|
"tsc": "tsc --noEmit",
|
||||||
"format:check": "headlamp-plugin format:check",
|
"lint": "eslint --ext .ts,.tsx src/",
|
||||||
"test": "headlamp-plugin test",
|
"lint:fix": "eslint --ext .ts,.tsx --fix src/",
|
||||||
"package": "headlamp-plugin package"
|
"format": "prettier --write src/",
|
||||||
|
"format:check": "prettier --check src/",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"tar": "^7.5.11",
|
||||||
|
"undici": "^7.24.3",
|
||||||
|
"flatted": "^3.4.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kinvolk/headlamp-plugin": "^0.26.0"
|
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||||
|
"@mui/material": "^5.15.14",
|
||||||
|
"@testing-library/jest-dom": "^6.4.8",
|
||||||
|
"@testing-library/react": "^16.0.0",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
|
"@headlamp-k8s/eslint-config": "^0.6.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"jsdom": "^24.0.0",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^5.3.0",
|
||||||
|
"tar": "^7.5.11",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"undici": "^7.24.3",
|
||||||
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+12150
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
// Minimal smoke test — verify the test file itself is valid and can run.
|
||||||
|
// Full plugin component tests will be added in subsequent tasks per PRI-189.
|
||||||
|
describe("ArgoCD Plugin Scaffold", () => {
|
||||||
|
it("test suite loads without errors", () => {
|
||||||
|
// Intentionally simple: just verify vitest is working and this file parses
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
registerRoute,
|
||||||
|
registerSidebarEntry,
|
||||||
|
} from "@kinvolk/headlamp-plugin/lib";
|
||||||
|
import {
|
||||||
|
SectionBox,
|
||||||
|
StatusLabel,
|
||||||
|
} from "@kinvolk/headlamp-plugin/lib/CommonComponents";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
// --- Error boundary for plugin components ---
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArgoCDErrorBoundary extends React.Component<
|
||||||
|
{ children: React.ReactNode },
|
||||||
|
ErrorBoundaryState
|
||||||
|
> {
|
||||||
|
state: ErrorBoundaryState = { error: null };
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
|
return { error: error.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.error) {
|
||||||
|
return (
|
||||||
|
<SectionBox title="ArgoCD Plugin Error">
|
||||||
|
<StatusLabel status="error">{this.state.error}</StatusLabel>
|
||||||
|
</SectionBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Stub Applications List View ---
|
||||||
|
|
||||||
|
function ArgoCDStubView() {
|
||||||
|
return (
|
||||||
|
<SectionBox title="ArgoCD Applications">
|
||||||
|
<StatusLabel status="info">
|
||||||
|
Plugin scaffold — features coming soon.
|
||||||
|
</StatusLabel>
|
||||||
|
</SectionBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sidebar entry ---
|
||||||
|
|
||||||
|
registerSidebarEntry({
|
||||||
|
parent: null,
|
||||||
|
name: "argocd",
|
||||||
|
label: "ArgoCD",
|
||||||
|
url: "/argocd",
|
||||||
|
icon: "mdi:git",
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSidebarEntry({
|
||||||
|
parent: "argocd",
|
||||||
|
name: "argocd-overview",
|
||||||
|
label: "Applications",
|
||||||
|
url: "/argocd",
|
||||||
|
icon: "mdi:view-list",
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Routes ---
|
||||||
|
|
||||||
|
registerRoute({
|
||||||
|
path: "/argocd",
|
||||||
|
sidebar: "argocd-overview",
|
||||||
|
name: "argocd",
|
||||||
|
exact: true,
|
||||||
|
component: () => (
|
||||||
|
<ArgoCDErrorBoundary>
|
||||||
|
<ArgoCDStubView />
|
||||||
|
</ArgoCDErrorBoundary>
|
||||||
|
),
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vitest/globals", "@testing-library/jest-dom"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': '"test"',
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./vitest.setup.ts'],
|
||||||
|
exclude: ['e2e/**', 'node_modules/**'],
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
include: ['src/**/*.{ts,tsx}'],
|
||||||
|
exclude: ['src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/index.tsx'],
|
||||||
|
thresholds: {
|
||||||
|
lines: 80,
|
||||||
|
functions: 80,
|
||||||
|
branches: 80,
|
||||||
|
statements: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
// Node 22+ ships a minimal built-in `localStorage` global (property-bag only,
|
||||||
|
// no getItem/setItem/removeItem/clear) that shadows jsdom's Web Storage
|
||||||
|
// implementation. Provide a spec-compliant shim so code under test works.
|
||||||
|
if (typeof localStorage !== 'undefined' && typeof localStorage.getItem !== 'function') {
|
||||||
|
const store = new Map<string, string>();
|
||||||
|
|
||||||
|
const storage = {
|
||||||
|
getItem(key: string): string | null {
|
||||||
|
return store.get(key) ?? null;
|
||||||
|
},
|
||||||
|
setItem(key: string, value: string): void {
|
||||||
|
store.set(key, String(value));
|
||||||
|
},
|
||||||
|
removeItem(key: string): void {
|
||||||
|
store.delete(key);
|
||||||
|
},
|
||||||
|
clear(): void {
|
||||||
|
store.clear();
|
||||||
|
},
|
||||||
|
get length(): number {
|
||||||
|
return store.size;
|
||||||
|
},
|
||||||
|
key(index: number): string | null {
|
||||||
|
return [...store.keys()][index] ?? null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(globalThis, 'localStorage', {
|
||||||
|
value: storage,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: storage,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user