fix: resolve SecurityHeaders page rendering issue

Update frontend API layer to correctly unwrap backend response objects.
Backend returns wrapped responses (e.g., {profiles: [...]}) while frontend
was expecting unwrapped arrays. Fixed 6 API methods in securityHeaders.ts
to properly extract data from response wrappers.

Changes:
- listProfiles(): unwrap .profiles
- getProfile(): unwrap .profile
- createProfile(): unwrap .profile
- updateProfile(): unwrap .profile
- getPresets(): unwrap .presets
- applyPreset(): unwrap .profile
This commit is contained in:
GitHub Actions
2025-12-18 03:39:52 +00:00
parent f043a020c4
commit 8b49da4d25
6 changed files with 1132 additions and 23 deletions
+12 -12
View File
@@ -83,32 +83,32 @@ export const securityHeadersApi = {
* List all security header profiles
*/
async listProfiles(): Promise<SecurityHeaderProfile[]> {
const response = await client.get<SecurityHeaderProfile[]>('/security/headers/profiles');
return response.data;
const response = await client.get<{profiles: SecurityHeaderProfile[]}>('/security/headers/profiles');
return response.data.profiles;
},
/**
* Get a single profile by ID or UUID
*/
async getProfile(id: number | string): Promise<SecurityHeaderProfile> {
const response = await client.get<SecurityHeaderProfile>(`/security/headers/profiles/${id}`);
return response.data;
const response = await client.get<{profile: SecurityHeaderProfile}>(`/security/headers/profiles/${id}`);
return response.data.profile;
},
/**
* Create a new security header profile
*/
async createProfile(data: CreateProfileRequest): Promise<SecurityHeaderProfile> {
const response = await client.post<SecurityHeaderProfile>('/security/headers/profiles', data);
return response.data;
const response = await client.post<{profile: SecurityHeaderProfile}>('/security/headers/profiles', data);
return response.data.profile;
},
/**
* Update an existing profile
*/
async updateProfile(id: number, data: Partial<CreateProfileRequest>): Promise<SecurityHeaderProfile> {
const response = await client.put<SecurityHeaderProfile>(`/security/headers/profiles/${id}`, data);
return response.data;
const response = await client.put<{profile: SecurityHeaderProfile}>(`/security/headers/profiles/${id}`, data);
return response.data.profile;
},
/**
@@ -122,16 +122,16 @@ export const securityHeadersApi = {
* Get built-in presets
*/
async getPresets(): Promise<SecurityHeaderPreset[]> {
const response = await client.get<SecurityHeaderPreset[]>('/security/headers/presets');
return response.data;
const response = await client.get<{presets: SecurityHeaderPreset[]}>('/security/headers/presets');
return response.data.presets;
},
/**
* Apply a preset to create/update a profile
*/
async applyPreset(data: ApplyPresetRequest): Promise<SecurityHeaderProfile> {
const response = await client.post<SecurityHeaderProfile>('/security/headers/presets/apply', data);
return response.data;
const response = await client.post<{profile: SecurityHeaderProfile}>('/security/headers/presets/apply', data);
return response.data.profile;
},
/**
+7 -7
View File
@@ -70,7 +70,7 @@ export default function SecurityHeaders() {
setEditingProfile(null);
toast.success(`"${profile.name}" deleted. A backup was created before deletion.`);
},
onError: (error) => {
onError: (error: Error) => {
toast.error(`Failed to delete: ${error.message}`);
},
onSettled: () => {
@@ -114,8 +114,8 @@ export default function SecurityHeaders() {
createMutation.mutate(clonedData);
};
const customProfiles = profiles?.filter((p) => !p.is_preset) || [];
const presetProfiles = profiles?.filter((p) => p.is_preset) || [];
const customProfiles = profiles?.filter((p: SecurityHeaderProfile) => !p.is_preset) || [];
const presetProfiles = profiles?.filter((p: SecurityHeaderProfile) => p.is_preset) || [];
return (
<PageShell
@@ -181,7 +181,7 @@ export default function SecurityHeaders() {
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">System Presets</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{presetProfiles.map((profile) => (
{presetProfiles.map((profile: SecurityHeaderProfile) => (
<Card key={profile.id} className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
@@ -241,7 +241,7 @@ export default function SecurityHeaders() {
/>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{customProfiles.map((profile) => (
{customProfiles.map((profile: SecurityHeaderProfile) => (
<Card key={profile.id} className="p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
@@ -291,7 +291,7 @@ export default function SecurityHeaders() {
</div>
{/* Create/Edit Dialog */}
<Dialog open={showCreateForm || editingProfile !== null} onOpenChange={(open) => {
<Dialog open={showCreateForm || editingProfile !== null} onOpenChange={(open: boolean) => {
if (!open) {
setShowCreateForm(false);
setEditingProfile(null);
@@ -318,7 +318,7 @@ export default function SecurityHeaders() {
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog open={showDeleteConfirm !== null} onOpenChange={(open) => !open && setShowDeleteConfirm(null)}>
<Dialog open={showDeleteConfirm !== null} onOpenChange={(open: boolean) => !open && setShowDeleteConfirm(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Deletion</DialogTitle>