Conversation
- Delete orphaned database schema files (audit-schema.sql, migrations.sql, rbac-schema.sql) - Rename initial migration to 000_initial_schema.sql for clarity - Update DatabaseService to load only numbered migrations instead of base schema files - Move all schema definitions to the migrations directory as single source of truth - Add documentation in copilot-instructions.md explaining migration-first approach - Create internal task documentation for database schema cleanup process - Eliminate duplicate table definitions that existed across base schemas and migrations
…a file - Update COPY instruction to include all database files and migrations - Change from copying only schema.sql to copying entire src/database/ directory - Ensures future database-related files are automatically included in builds - Improves maintainability by avoiding need to update Dockerfile when new database files are added
…tern - Add new Proxmox integration with ProxmoxClient, ProxmoxService, and ProxmoxIntegration classes - Implement Proxmox API client with authentication and resource management capabilities - Add comprehensive test coverage for Proxmox integration and service layer - Update IntegrationManager to register and manage Proxmox integration - Add dedicated Proxmox routes handler for API endpoints - Update integration types to include Proxmox configuration schema - Refactor ConfigService and schema to support Proxmox settings - Update server configuration to include Proxmox routes - Add Kiro specification files for puppet-pabawi refactoring workflow - Update vitest configuration for improved test execution - Improves infrastructure flexibility by adding virtualization platform support alongside existing integrations
…ycle management - Add ProvisionPage with dynamic form generation for VM and LXC creation - Add ManageTab component for node lifecycle actions (start, stop, reboot, destroy) - Add ProxmoxProvisionForm and ProxmoxSetupGuide components for integration setup - Add formGenerator utility for dynamic form rendering based on capability metadata - Add permissions system for RBAC-aware UI element visibility - Add comprehensive validation and error handling for provisioning operations - Add test utilities and generators for provisioning-related tests - Add documentation for Proxmox setup, provisioning, permissions, and management workflows - Add Kiro specification files for Proxmox frontend UI and integration features - Update Navigation component to include new Provision page route - Update IntegrationSetupPage to support Proxmox configuration - Update API client with provisioning endpoints and type definitions - Update package.json with required dependencies
There was a problem hiding this comment.
Pull request overview
Adds Proxmox provisioning and lifecycle management support across backend + frontend, including a new Provision page, Manage tab actions, and migration-first DB packaging updates.
Changes:
- Frontend: adds
/provisionroute + navigation gating, newManageTab, and several accessibility-oriented UI tweaks. - Backend: registers Proxmox plugin, adds provisioning discovery endpoint, adds node lifecycle/destroy endpoints, and extends node linking to retain per-source identifiers.
- Ops/Docs/Tests: migration-first DB packaging updates, new Proxmox tests + generators, and updated documentation/version bumps to
0.9.0.
Reviewed changes
Copilot reviewed 74 out of 103 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/PackageInstallInterface.svelte | Adds header icon and adjusts execution-tool selector markup. |
| frontend/src/components/Navigation.svelte | Adds Provision nav item gated by provisioning permission; bumps displayed version. |
| frontend/src/components/ManageTab.svelte | New node lifecycle management UI + confirmation dialog. |
| frontend/src/components/IntegrationBadge.svelte | Adds “Proxmox” label. |
| frontend/src/components/InstallSoftwareForm.svelte | Adjusts execution-tool selector markup. |
| frontend/src/components/GroupActionModal.svelte | Removes tabindex from scrollable target list region. |
| frontend/src/components/FactsViewer.svelte | Adjusts source/category selector markup; adds ARIA grouping. |
| frontend/src/components/ExecuteTaskForm.svelte | Adjusts “Select Task” label markup. |
| frontend/src/components/ExecuteCommandForm.svelte | Adjusts execution-tool selector markup; adds ARIA grouping. |
| frontend/src/components/AnsiblePlaybookInterface.svelte | Adds header icon. |
| frontend/src/tests/generators.ts | New fast-check generators for provisioning UI tests. |
| frontend/src/tests/generators.test.ts | Tests validating the new fast-check generators. |
| frontend/src/tests/README.md | Documentation for property-based generators and usage. |
| frontend/src/App.svelte | Registers new /provision route. |
| frontend/package.json | Bumps version to 0.9.0; adds fast-check + jest-dom dev deps. |
| docs/provisioning-guide.md | New user documentation for provisioning workflows. |
| docs/development/BACKEND_CODE_ANALYSIS.md | Updates DB schema info to migration-first. |
| backend/vitest.config.ts | Includes Proxmox integration tests in Vitest config. |
| backend/test/integrations/NodeLinkingService.test.ts | Adds coverage for per-source node identifier preservation. |
| backend/test/integrations/IntegrationManager.test.ts | Expands node dedupe assertions and adds provisioning-capabilities tests. |
| backend/src/server.ts | Registers Proxmox integration when configured; logs setup info. |
| backend/src/routes/streaming.ts | Adds “token in query param” fallback for EventSource auth. |
| backend/src/routes/inventory.ts | Adds node lifecycle action + destroy endpoints for Proxmox nodes. |
| backend/src/routes/integrations/status.ts | Adds “proxmox not configured” status entry. |
| backend/src/routes/integrations/provisioning.ts | New provisioning integration discovery endpoint. |
| backend/src/routes/integrations.ts | Mounts Proxmox + provisioning routers with auth/RBAC when available. |
| backend/src/integrations/types.ts | Adds ProvisioningCapability interface. |
| backend/src/integrations/proxmox/types.ts | New Proxmox integration type definitions + error types. |
| backend/src/integrations/proxmox/tests/ProxmoxIntegration.test.ts | New unit tests for ProxmoxIntegration. |
| backend/src/integrations/proxmox/ProxmoxIntegration.ts | New Proxmox plugin implementing info + execution tool interfaces. |
| backend/src/integrations/proxmox/ProxmoxClient.ts | New Proxmox API client with retries + task polling. |
| backend/src/integrations/NodeLinkingService.ts | Adds sourceData to linked nodes; adjusts identifier extraction. |
| backend/src/integrations/IntegrationManager.ts | Changes aggregated nodes to LinkedNode[]; adds provisioning capability aggregation. |
| backend/src/database/rbac-schema.sql | Removes legacy RBAC schema file (migration-first). |
| backend/src/database/migrations/000_initial_schema.sql | Adds header comments to initial migration. |
| backend/src/database/migrations.sql | Removes legacy monolithic migrations file. |
| backend/src/database/audit-schema.sql | Removes legacy audit schema file (migration-first). |
| backend/src/database/DatabaseService.ts | Removes schema-file loading; runs migrations only. |
| backend/src/config/schema.ts | Adds Zod schema for Proxmox integration config. |
| backend/src/config/ConfigService.ts | Parses Proxmox env vars into integration config. |
| backend/package.json | Bumps version to 0.9.0; copies migrations directory in build. |
| Dockerfile | Copies full src/database/ into image dist/database/. |
| CLAUDE.md | Updates DB schema notes to migration-first approach. |
| .kiro/todo/proxmox-ssl-fix.md | Notes about Proxmox SSL workaround. |
| .kiro/todo/proxmox-restart-required.md | Notes about stale compiled code requiring restart. |
| .kiro/todo/provisioning-endpoint-fix.md | Notes about provisioning endpoint being added. |
| .kiro/todo/node-linking-redesign.md | Notes about node linking redesign using sourceData. |
| .kiro/todo/expert-mode-prototype-pollution.md | Adds todo note about prototype pollution hardening. |
| .kiro/todo/docker-missing-schema-files.md | Notes about Docker schema packaging fix. |
| .kiro/todo/database-schema-cleanup-task.md | Notes about schema cleanup and migration-first completion. |
| .kiro/steering/security-best-practices.md | Adds note about secret allowlist pragma; contains a typo. |
| .kiro/specs/puppet-pabawi-refactoring/tasks.md | Adds spec tasks doc for puppet refactor. |
| .kiro/specs/puppet-pabawi-refactoring/requirements.md | Adds spec requirements doc for puppet refactor. |
| .kiro/specs/puppet-pabawi-refactoring/.config.kiro | Adds Kiro spec configuration. |
| .kiro/specs/proxmox-integration/requirements.md | Adds Proxmox integration requirements doc. |
| .kiro/specs/proxmox-integration/.config.kiro | Adds Kiro spec configuration. |
| .kiro/specs/proxmox-frontend-ui/tasks.md | Adds Proxmox frontend UI implementation plan. |
| .kiro/specs/proxmox-frontend-ui/requirements.md | Adds Proxmox frontend UI requirements doc. |
| .kiro/specs/proxmox-frontend-ui/.config.kiro | Adds Kiro spec configuration. |
| .kiro/database-cleanup-prompt.md | Adds prompt doc for DB schema cleanup. |
| .github/copilot-instructions.md | Updates DB schema notes to migration-first approach. |
backend/src/routes/inventory.ts
Outdated
| return; | ||
| } | ||
|
|
||
| const proxmoxSource = integrationManager.getInformationSource("proxmox"); |
There was a problem hiding this comment.
The route calls executeAction with a non-Action payload ({ action, target }), but the plugin API elsewhere uses an Action type (e.g., including type: "task"). This will very likely break at runtime if the underlying Proxmox execution path expects action.type or other required fields. Prefer retrieving the execution tool (integrationManager.getExecutionTool("proxmox")) and calling executeAction with the correct Action shape (including type) instead of casting an information source to an execution tool.
backend/src/routes/inventory.ts
Outdated
| // Execute the action | ||
| const proxmoxService = proxmoxSource as unknown as { | ||
| executeAction: (action: { action: string; target: string }) => Promise<unknown>; | ||
| }; | ||
|
|
||
| const result = await proxmoxService.executeAction({ | ||
| action: body.action, | ||
| target: nodeId, | ||
| }); |
There was a problem hiding this comment.
The route calls executeAction with a non-Action payload ({ action, target }), but the plugin API elsewhere uses an Action type (e.g., including type: "task"). This will very likely break at runtime if the underlying Proxmox execution path expects action.type or other required fields. Prefer retrieving the execution tool (integrationManager.getExecutionTool("proxmox")) and calling executeAction with the correct Action shape (including type) instead of casting an information source to an execution tool.
backend/src/routes/inventory.ts
Outdated
| return; | ||
| } | ||
|
|
||
| const proxmoxSource = integrationManager.getInformationSource("proxmox"); |
There was a problem hiding this comment.
getInformationSource("proxmox") returns an information-source plugin, but destroyGuest is not part of that interface (and ProxmoxIntegration in this PR doesn’t expose a destroyGuest method). This cast is likely to fail at runtime. A safer design is to perform destroy via the execution tool interface (e.g., executeAction with a destroy action), or to expose a typed destroy method on the Proxmox execution plugin and retrieve it via getExecutionTool("proxmox").
backend/src/routes/inventory.ts
Outdated
| const proxmoxService = proxmoxSource as unknown as { | ||
| destroyGuest: (node: string, vmid: number) => Promise<unknown>; | ||
| }; | ||
|
|
||
| const result = await proxmoxService.destroyGuest(node, vmid); |
There was a problem hiding this comment.
getInformationSource("proxmox") returns an information-source plugin, but destroyGuest is not part of that interface (and ProxmoxIntegration in this PR doesn’t expose a destroyGuest method). This cast is likely to fail at runtime. A safer design is to perform destroy via the execution tool interface (e.g., executeAction with a destroy action), or to expose a typed destroy method on the Proxmox execution plugin and retrieve it via getExecutionTool("proxmox").
backend/src/routes/streaming.ts
Outdated
| // If token is in query param, move it to Authorization header for middleware | ||
| if (req.query.token && typeof req.query.token === 'string') { | ||
| req.headers.authorization = `Bearer ${req.query.token}`; |
There was a problem hiding this comment.
Accepting bearer tokens via query string is high risk (URLs end up in logs, browser history, proxies, and referrers). If this fallback is required for EventSource, consider tightening it: only set the header when no Authorization header exists, ensure access logs (and any expert/debug logs) never record req.originalUrl unredacted, and set SSE responses to Cache-Control: no-store to reduce accidental persistence. Also consider a short-lived one-time stream token flow instead of reusing the main JWT in the URL.
| // If token is in query param, move it to Authorization header for middleware | |
| if (req.query.token && typeof req.query.token === 'string') { | |
| req.headers.authorization = `Bearer ${req.query.token}`; | |
| // If token is in query param, move it to Authorization header for middleware, | |
| // but only when no Authorization header is already present. Also remove the | |
| // token from the query to reduce the chance of it being logged downstream. | |
| if ( | |
| typeof req.query.token === "string" && | |
| !req.headers.authorization | |
| ) { | |
| req.headers.authorization = `Bearer ${req.query.token}`; | |
| // eslint-disable-next-line @typescript-eslint/no-dynamic-delete | |
| delete (req.query as any).token; |
| <div | ||
| class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity dark:bg-gray-900 dark:bg-opacity-75" | ||
| onclick={cancelConfirmation} | ||
| onkeydown={(e) => e.key === 'Escape' && cancelConfirmation()} | ||
| role="button" | ||
| tabindex="-1" | ||
| aria-label="Close dialog" | ||
| ></div> |
There was a problem hiding this comment.
With tabindex="-1", this overlay won’t receive keyboard focus via Tab, so the onkeydown handler is unlikely to fire for typical users. If Escape-to-close is required, attach the Escape key handler to the dialog container (or window) rather than relying on an unfocusable overlay, and ensure focus management for the modal (initial focus + trapping) so keyboard users can interact reliably.
| - Validate all user inputs | ||
| - Use parameterized queries to prevent SQL injection | ||
| - Implement proper authentication and authorization | ||
| - Use pragma: allowlist secret comment to lint secrets catched by precommit |
There was a problem hiding this comment.
Typo: change 'catched' to 'caught' (and consider rephrasing to 'to allowlist secrets caught by pre-commit').
| - Use pragma: allowlist secret comment to lint secrets catched by precommit | |
| - Use pragma: allowlist secret comment to allowlist secrets caught by pre-commit |
backend/src/server.ts
Outdated
| name: "proxmox", | ||
| type: "both", | ||
| config: proxmoxConfig as unknown as Record<string, unknown>, | ||
| priority: proxmoxConfig.priority ?? 7, // Between Puppetserver (8) and Hiera (6) |
There was a problem hiding this comment.
The inline priority comment looks inconsistent with the repository’s stated priority scheme (e.g., Puppetserver typically has a much higher priority than single digits). If the numeric scale has changed, update the comment to match the actual priority conventions; otherwise, correct the referenced values to avoid misleading future changes.
| priority: proxmoxConfig.priority ?? 7, // Between Puppetserver (8) and Hiera (6) | |
| priority: proxmoxConfig.priority ?? 7, // Default 7: between Bolt/PuppetDB (10) and Hiera (6) |
| * Note: RBAC middleware should be applied at the route mounting level in server.ts | ||
| * Required permission: lifecycle:* or lifecycle:{action} | ||
| */ | ||
| router.post( |
There was a problem hiding this comment.
New API surface (POST /api/nodes/:id/action and DELETE /api/nodes/:id) introduces important lifecycle/destructive behavior, but there are no accompanying route-level tests here. Add backend tests validating: (1) only supported Proxmox node ids are accepted, (2) correct integration method is invoked with the correct action payload, and (3) error/status codes (400/503/500) match the intended contract.
| * Note: RBAC middleware should be applied at the route mounting level in server.ts | ||
| * Required permission: lifecycle:destroy | ||
| */ | ||
| router.delete( |
There was a problem hiding this comment.
New API surface (POST /api/nodes/:id/action and DELETE /api/nodes/:id) introduces important lifecycle/destructive behavior, but there are no accompanying route-level tests here. Add backend tests validating: (1) only supported Proxmox node ids are accepted, (2) correct integration method is invoked with the correct action payload, and (3) error/status codes (400/503/500) match the intended contract.
- Move 7 completed task documents from .kiro/todo to .kiro/done directory - Add comprehensive REMAINING_TODOS_REPORT.md with prioritized task breakdown - Include test failure analysis, RBAC issues, and environment configuration items - Add SQLite test database temporary files (test-migration.db-shm, test-migration.db-wal) - Update frontend logger with minor improvements - Consolidate task tracking and provide clear roadmap for remaining work
- Add getNodes() method to retrieve PVE cluster nodes with status and resource info - Add getNextVMID() method to fetch next available VM ID from cluster - Add getISOImages() method to list ISO images available on node storage - Add getTemplates() method to list OS templates available on node storage - Implement caching for node and resource queries (60-120 second TTL) - Add corresponding integration layer methods to expose Proxmox service capabilities - Update frontend navigation and routing to support new provisioning workflows - Enhance ProxmoxProvisionForm with node selection and resource discovery UI - Update API client and type definitions for provisioning operations - Improve error handling and logging across Proxmox integration layer
- Update foundFilter initial state from 'all' to 'found' for better UX - Aligns with filterMode default behavior to show relevant data by default - Reduces noise by filtering out not-found entries on initial load
| {#each displayableActions as action} | ||
| <button | ||
| type="button" | ||
| onclick={() => executeAction(action.name)} |
| onclick={cancelConfirmation} | ||
| onkeydown={(e) => e.key === 'Escape' && cancelConfirmation()} |
backend/src/routes/inventory.ts
Outdated
| executeAction: (action: { action: string; target: string }) => Promise<unknown>; | ||
| }; | ||
|
|
||
| const result = await proxmoxService.executeAction({ |
|
|
||
| const node = parts[1]; | ||
| const vmid = parseInt(parts[2], 10); | ||
|
|
| // Combine all URIs | ||
| linkedNode.uri = Array.from(new Set(allUris)).join(", "); |
| // Handle token from query parameter (EventSource doesn't support headers) | ||
| // If token is in query param, move it to Authorization header for middleware | ||
| if (req.query.token && typeof req.query.token === 'string') { | ||
| req.headers.authorization = `Bearer ${req.query.token}`; |
| // Check Proxmox integration | ||
| const proxmox = integrationManager.getExecutionTool("proxmox") as ProxmoxIntegration | null; | ||
|
|
||
| if (proxmox) { | ||
| // Determine integration status based on health check | ||
| let status: 'connected' | 'degraded' | 'not_configured' = 'not_configured'; | ||
| const healthCheck = proxmox.getLastHealthCheck(); | ||
|
|
||
| if (healthCheck) { | ||
| if (healthCheck.healthy) { | ||
| status = 'connected'; | ||
| } else if (healthCheck.message?.includes('not initialized') || healthCheck.message?.includes('disabled')) { | ||
| status = 'not_configured'; | ||
| } else { | ||
| status = 'degraded'; | ||
| } | ||
| } |
| <div | ||
| class="max-h-48 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-md bg-gray-50 dark:bg-gray-900/50" | ||
| role="region" | ||
| aria-labelledby="target-nodes-label" |
| <svg class="h-6 w-6 text-gray-700 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | ||
| </svg> |
| - Validate all user inputs | ||
| - Use parameterized queries to prevent SQL injection | ||
| - Implement proper authentication and authorization | ||
| - Use pragma: allowlist secret comment to lint secrets catched by precommit |
…ved API handling - Fix ProxmoxClient to use form-urlencoded encoding for POST/PUT/DELETE requests instead of JSON, matching Proxmox API requirements - Add detailed error messages in API responses by including response body text for better diagnostics - Add getStorages() method to ProxmoxService and ProxmoxIntegration to query available storage devices on nodes with optional content type filtering - Add getNetworkBridges() method to ProxmoxService and ProxmoxIntegration to query network interfaces on nodes with bridge type filtering - Implement caching for both storage and network queries with 120-second TTL to reduce API calls - Update ProxmoxProvisionForm frontend component to use new storage and network discovery endpoints - Extend provisioning types to support storage and network configuration options - Update API client to expose new storage and network discovery endpoints
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@alvagante I've opened a new pull request, #35, to work on those changes. Once the pull request is ready, I'll request review from you. |
… and code quality improvements Co-authored-by: alvagante <283804+alvagante@users.noreply.github.com>
| asyncHandler(async (req: Request, res: Response): Promise<void> => { | ||
| const startTime = Date.now(); | ||
| const expertModeService = new ExpertModeService(); | ||
| const requestId = req.id ?? expertModeService.generateRequestId(); |
| asyncHandler(async (req: Request, res: Response): Promise<void> => { | ||
| const startTime = Date.now(); | ||
| const expertModeService = new ExpertModeService(); | ||
| const requestId = req.id ?? expertModeService.generateRequestId(); |
| <div class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> | ||
| Execution Tool | ||
| </label> | ||
| <div class="flex gap-2"> | ||
| </div> | ||
| <div class="flex gap-2" role="group" aria-label="Execution Tool"> |
| <div class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> | ||
| Facts Source | ||
| </label> | ||
| <div class="flex flex-wrap gap-2"> | ||
| </div> | ||
| <div class="flex flex-wrap gap-2" role="group" aria-label="Facts Source"> |
| <div class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> | ||
| Facts Category | ||
| </label> | ||
| <div class="flex flex-wrap gap-2"> | ||
| </div> | ||
| <div class="flex flex-wrap gap-2" role="group" aria-label="Facts Category"> |
| <div class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> | ||
| Select Task <span class="text-red-500">*</span> | ||
| </label> | ||
| </div> |
| <div | ||
| class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity dark:bg-gray-900 dark:bg-opacity-75" | ||
| onclick={cancelConfirmation} | ||
| role="button" | ||
| tabindex="0" | ||
| aria-label="Close dialog" | ||
| ></div> |
| const result = await proxmoxTool.executeAction({ | ||
| type: "task", | ||
| target: nodeId, | ||
| action: "destroy_vm", | ||
| parameters: { node, vmid }, | ||
| }); |
| const proxmox = integrationManager.getExecutionTool("proxmox") as ProxmoxIntegration | null; | ||
|
|
||
| if (proxmox) { | ||
| // Determine integration status based on health check | ||
| let status: 'connected' | 'degraded' | 'not_configured' = 'not_configured'; | ||
| const healthCheck = proxmox.getLastHealthCheck(); |
| if (config.ssl && config.ssl.rejectUnauthorized === false) { | ||
| this.logger.warn( | ||
| "Proxmox ssl.rejectUnauthorized=false is set, but per-client TLS bypass is not yet implemented. " + | ||
| "Proxmox connections will use the default TLS verification. " + | ||
| "Configure a trusted CA certificate (ssl.ca) to connect to Proxmox with self-signed certs.", | ||
| { | ||
| component: "ProxmoxClient", | ||
| operation: "constructor", | ||
| } | ||
| ); | ||
| } |
| const expertModeService = new ExpertModeService(); | ||
| const requestId = req.id ?? expertModeService.generateRequestId(); |
| getAllProvisioningCapabilities(): Array<{ | ||
| source: string; | ||
| capabilities: Array<{ | ||
| name: string; | ||
| description: string; | ||
| operation: "create" | "destroy"; | ||
| parameters: Array<{ | ||
| name: string; | ||
| type: string; | ||
| required: boolean; | ||
| default?: unknown; | ||
| }>; | ||
| }>; | ||
| }> { |
No description provided.