fix(qa): CSP hash, remove pencil icon, fetch full template on modal open
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- nginx.conf: add new CSP script-src hash (sha256-Yvj83pg...) alongside previous one - SystemPage: remove pencil icon from template name button, keep cyan underline style - SystemPage: switch from selectedTemplate state to selectedTemplateId + useQuery for getTemplateById() — ensures full template data (description, attack_procedure, expected_detection, tool_suggested etc.) loads before modal opens - DB backfill already applied via SQL: UPDATE audit_logs SET timestamp = NOW() WHERE timestamp IS NULL (358 rows fixed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ server {
|
|||||||
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
# CSP: allow self + inline styles (React build) + data: URIs for fonts/images
|
# CSP: allow self + inline styles (React build) + data: URIs for fonts/images
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-31OgE8E9uFi947Hj0TYz0o9NSyrQOewgXrj1ZPfYDaY='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-31OgE8E9uFi947Hj0TYz0o9NSyrQOewgXrj1ZPfYDaY=' 'sha256-Yvj83pg9TGSmhZQWii1NGmFCIaX9trnlTFVkemiMlS8='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
|
||||||
|
|
||||||
# Hide Nginx version
|
# Hide Nginx version
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
ToggleRight,
|
ToggleRight,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
X,
|
X,
|
||||||
Pencil,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
triggerMitreSync,
|
triggerMitreSync,
|
||||||
@@ -30,6 +29,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getTemplateStats,
|
getTemplateStats,
|
||||||
getAllTemplates,
|
getAllTemplates,
|
||||||
|
getTemplateById,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
updateTemplate,
|
updateTemplate,
|
||||||
toggleTemplateActive,
|
toggleTemplateActive,
|
||||||
@@ -45,7 +45,7 @@ export default function SystemPage() {
|
|||||||
const [intelResult, setIntelResult] = useState<IntelScanResponse | null>(null);
|
const [intelResult, setIntelResult] = useState<IntelScanResponse | null>(null);
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
||||||
const [bulkConfirm, setBulkConfirm] = useState<"activate" | "deactivate" | null>(null);
|
const [bulkConfirm, setBulkConfirm] = useState<"activate" | "deactivate" | null>(null);
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<TestTemplate | null>(null);
|
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
|
||||||
|
|
||||||
// ── Existing queries ─────────────────────────────────────────────
|
// ── Existing queries ─────────────────────────────────────────────
|
||||||
const {
|
const {
|
||||||
@@ -75,6 +75,15 @@ export default function SystemPage() {
|
|||||||
queryFn: () => getAllTemplates({ limit: 200 }),
|
queryFn: () => getAllTemplates({ limit: 200 }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: selectedTemplate,
|
||||||
|
isLoading: selectedTemplateLoading,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["template-detail", selectedTemplateId],
|
||||||
|
queryFn: () => getTemplateById(selectedTemplateId!),
|
||||||
|
enabled: !!selectedTemplateId,
|
||||||
|
});
|
||||||
|
|
||||||
// ── Mutations ────────────────────────────────────────────────────
|
// ── Mutations ────────────────────────────────────────────────────
|
||||||
const mitreSyncMutation = useMutation({
|
const mitreSyncMutation = useMutation({
|
||||||
mutationFn: triggerMitreSync,
|
mutationFn: triggerMitreSync,
|
||||||
@@ -125,10 +134,10 @@ export default function SystemPage() {
|
|||||||
const updateTemplateMutation = useMutation({
|
const updateTemplateMutation = useMutation({
|
||||||
mutationFn: ({ id, payload }: { id: string; payload: Partial<CreateTemplatePayload> }) =>
|
mutationFn: ({ id, payload }: { id: string; payload: Partial<CreateTemplatePayload> }) =>
|
||||||
updateTemplate(id, payload),
|
updateTemplate(id, payload),
|
||||||
onSuccess: (updated) => {
|
onSuccess: () => {
|
||||||
setSelectedTemplate(updated);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["templates-admin"] });
|
queryClient.invalidateQueries({ queryKey: ["templates-admin"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["test-templates"] });
|
queryClient.invalidateQueries({ queryKey: ["test-templates"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["template-detail", selectedTemplateId] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -481,12 +490,11 @@ export default function SystemPage() {
|
|||||||
>
|
>
|
||||||
<td className="py-3 pr-4">
|
<td className="py-3 pr-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedTemplate(tpl)}
|
onClick={() => setSelectedTemplateId(tpl.id)}
|
||||||
className="flex items-center gap-1.5 text-left font-medium text-cyan-400 hover:text-cyan-300 truncate max-w-[200px] transition-colors"
|
className="text-left font-medium text-cyan-400 hover:text-cyan-300 hover:underline truncate block max-w-[200px] transition-colors"
|
||||||
title="Click to view/edit"
|
title="Click to view/edit"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3 shrink-0 opacity-60" />
|
{tpl.name}
|
||||||
<span className="truncate">{tpl.name}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4">
|
<td className="py-3 px-4">
|
||||||
@@ -704,15 +712,21 @@ export default function SystemPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Template Detail Modal */}
|
{/* Template Detail Modal */}
|
||||||
{selectedTemplate && (
|
{selectedTemplateId && (
|
||||||
|
selectedTemplateLoading ? (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
) : selectedTemplate ? (
|
||||||
<TemplateDetailModal
|
<TemplateDetailModal
|
||||||
template={selectedTemplate}
|
template={selectedTemplate}
|
||||||
onClose={() => setSelectedTemplate(null)}
|
onClose={() => setSelectedTemplateId(null)}
|
||||||
onSave={(id, payload) => updateTemplateMutation.mutate({ id, payload })}
|
onSave={(id, payload) => updateTemplateMutation.mutate({ id, payload })}
|
||||||
onToggleActive={(id) => toggleActiveMutation.mutate(id)}
|
onToggleActive={(id) => toggleActiveMutation.mutate(id)}
|
||||||
isSaving={updateTemplateMutation.isPending}
|
isSaving={updateTemplateMutation.isPending}
|
||||||
isToggling={toggleActiveMutation.isPending}
|
isToggling={toggleActiveMutation.isPending}
|
||||||
/>
|
/>
|
||||||
|
) : null
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user