fix(resources): harden Resource Explorer with auth, validation, design, and UX fixes (#527)

- Sanitize error messages in all delete/prune/create/inspect endpoints
  to prevent Docker internals from leaking to the frontend
- Add CIDR, IPv4, and Docker resource ID input validation
- Add requirePaid gate to network topology endpoint
- Add invalidateNodeCaches after image/volume/network mutations
- Fix design system violations: card borders, destructive button variant,
  visible DialogDescription, overflow-auto replaced with ScrollArea,
  hardcoded Tailwind colors replaced with tokens
- Gate purge button behind isAdmin to prevent silent 403s
- Fix shared inspect loading state to be per-network-row
- Parse error response bodies for meaningful toast messages
- Add clipboard API fallback for non-HTTPS contexts
- Render Options section in network inspect sheet
- Add operational and diagnostic logging for resource operations
- Extend validation and DockerController test suites
- Update docs with Options field in network inspect
This commit is contained in:
Anso
2026-04-12 15:31:35 -04:00
committed by GitHub
parent a5cb316ca9
commit 4909c35e50
10 changed files with 303 additions and 52 deletions
+8
View File
@@ -9,6 +9,7 @@ import * as yaml from 'yaml';
import { NodeRegistry } from './NodeRegistry';
import { CacheService } from './CacheService';
import { isPathWithinBase } from '../utils/validation';
import { isDebugEnabled } from '../utils/debug';
const execAsync = promisify(exec);
const COMPOSE_DIR = process.env.COMPOSE_DIR || '/app/compose';
@@ -205,6 +206,8 @@ class DockerController {
volumes: ClassifiedVolume[];
networks: ClassifiedNetwork[];
}> {
const debug = isDebugEnabled();
const t0 = debug ? Date.now() : 0;
const knownSet = new Set(knownStackNames);
const [rawImages, rawVolumeData, rawNetworks, allContainers, projectToStack] = await Promise.all([
@@ -274,6 +277,10 @@ class DockerController {
};
});
if (debug) console.debug('[Resources:debug] Classification completed', {
ms: Date.now() - t0, images: images.length, volumes: volumes.length, networks: networks.length,
});
return { images, volumes, networks };
}
@@ -926,6 +933,7 @@ class DockerController {
}
public async removeContainers(containerIds: string[]) {
if (isDebugEnabled()) console.debug('[Resources:debug] removeContainers', { count: containerIds.length });
const results = [];
for (const id of containerIds) {
try {