feat: add security header profiles to bulk apply

Add support for bulk applying or removing security header profiles from multiple proxy hosts simultaneously via the Bulk Apply modal.

Features:
- New bulk endpoint: PUT /api/v1/proxy-hosts/bulk-update-security-headers
- Transaction-safe updates with single Caddy config reload
- Grouped profile selection (System/Custom profiles)
- Partial failure handling with detailed error reporting
- Support for profile removal via "None" option
- Full i18n support (en, de, es, fr, zh)

Backend:
- Add BulkUpdateSecurityHeaders handler with validation
- Add DB() getter to ProxyHostService
- 9 unit tests, 82.3% coverage

Frontend:
- Extend Bulk Apply modal with security header section
- Add bulkUpdateSecurityHeaders API function
- Add useBulkUpdateSecurityHeaders mutation hook
- 8 unit tests, 87.24% coverage

Testing:
- All tests passing (Backend + Frontend)
- Zero TypeScript errors
- Zero security vulnerabilities (Trivy + govulncheck)
- Pre-commit hooks passing
- No regressions

Docs:
- Update CHANGELOG.md
- Update docs/features.md with bulk workflow
This commit is contained in:
GitHub Actions
2025-12-20 15:19:06 +00:00
parent ab4db87f59
commit 72537c3bb4
16 changed files with 2380 additions and 371 deletions
+12 -1
View File
@@ -5,6 +5,7 @@ import {
updateProxyHost,
deleteProxyHost,
bulkUpdateACL,
bulkUpdateSecurityHeaders,
ProxyHost
} from '../api/proxyHosts';
@@ -49,6 +50,14 @@ export function useProxyHosts() {
},
});
const bulkUpdateSecurityHeadersMutation = useMutation({
mutationFn: ({ hostUUIDs, securityHeaderProfileId }: { hostUUIDs: string[]; securityHeaderProfileId: number | null }) =>
bulkUpdateSecurityHeaders(hostUUIDs, securityHeaderProfileId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
},
});
return {
hosts: query.data || [],
loading: query.isLoading,
@@ -59,10 +68,12 @@ export function useProxyHosts() {
deleteHost: (uuid: string, deleteUptime?: boolean) => deleteMutation.mutateAsync(deleteUptime !== undefined ? { uuid, deleteUptime } : uuid),
bulkUpdateACL: (hostUUIDs: string[], accessListID: number | null) =>
bulkUpdateACLMutation.mutateAsync({ hostUUIDs, accessListID }),
bulkUpdateSecurityHeaders: (hostUUIDs: string[], securityHeaderProfileId: number | null) =>
bulkUpdateSecurityHeadersMutation.mutateAsync({ hostUUIDs, securityHeaderProfileId }),
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
isDeleting: deleteMutation.isPending,
isBulkUpdating: bulkUpdateACLMutation.isPending,
isBulkUpdating: bulkUpdateACLMutation.isPending || bulkUpdateSecurityHeadersMutation.isPending,
};
}