diff --git a/cloud-agent-next/src/auth.ts b/cloud-agent-next/src/auth.ts
index 3ea7f8663..be8a58b82 100644
--- a/cloud-agent-next/src/auth.ts
+++ b/cloud-agent-next/src/auth.ts
@@ -17,6 +17,10 @@ export function validateKiloToken(
):
| { success: true; userId: string; token: string; botId?: string }
| { success: false; error: string } {
+ if (!secret) {
+ return { success: false, error: 'NEXTAUTH_SECRET is not configured on the worker' };
+ }
+
// Check header exists and has Bearer format
if (!authHeader) {
return { success: false, error: 'Missing Authorization header' };
diff --git a/src/components/security-agent/AnalysisJobsCard.tsx b/src/components/security-agent/AnalysisJobsCard.tsx
index 021e7575e..3228dd352 100644
--- a/src/components/security-agent/AnalysisJobsCard.tsx
+++ b/src/components/security-agent/AnalysisJobsCard.tsx
@@ -1,7 +1,6 @@
'use client';
import { useState } from 'react';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
@@ -10,20 +9,21 @@ import {
CheckCircle2,
XCircle,
Clock,
- AlertCircle,
ChevronLeft,
ChevronRight,
RotateCcw,
Package,
- ExternalLink,
+ RefreshCw,
} from 'lucide-react';
-import Link from 'next/link';
import { useTRPC } from '@/lib/trpc/utils';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { formatDistanceToNow } from 'date-fns';
+import { differenceInDays, differenceInHours, differenceInMinutes } from 'date-fns';
import { toast } from 'sonner';
import { SeverityBadge } from './SeverityBadge';
-import type { SecurityFindingAnalysis } from '@/lib/security-agent/core/types';
+import { FindingDetailDialog } from './FindingDetailDialog';
+import { DismissFindingDialog, type DismissReason } from './DismissFindingDialog';
+import { cn } from '@/lib/utils';
+import type { SecurityFinding } from '@/db/schema';
type AnalysisJobsCardProps = {
organizationId?: string;
@@ -31,39 +31,50 @@ type AnalysisJobsCardProps = {
};
type AnalysisStatus = 'pending' | 'running' | 'completed' | 'failed';
+type Severity = 'critical' | 'high' | 'medium' | 'low';
const statusConfig: Record<
AnalysisStatus,
{
icon: React.ComponentType<{ className?: string }>;
- variant: 'default' | 'secondary' | 'destructive' | 'outline';
label: string;
+ badgeClass: string;
}
> = {
- pending: { icon: Clock, variant: 'secondary', label: 'Queued' },
- running: { icon: Loader2, variant: 'default', label: 'Analyzing' },
- completed: { icon: CheckCircle2, variant: 'default', label: 'Completed' },
- failed: { icon: XCircle, variant: 'destructive', label: 'Failed' },
+ pending: {
+ icon: Clock,
+ label: 'Queued',
+ badgeClass: 'border-gray-500/30 bg-gray-500/20 text-gray-400',
+ },
+ running: {
+ icon: Loader2,
+ label: 'Analyzing',
+ badgeClass: 'border-yellow-500/30 bg-yellow-500/20 text-yellow-400',
+ },
+ completed: {
+ icon: CheckCircle2,
+ label: 'Completed',
+ badgeClass: 'border-green-500/30 bg-green-500/20 text-green-400',
+ },
+ failed: {
+ icon: XCircle,
+ label: 'Failed',
+ badgeClass: 'border-red-500/30 bg-red-500/20 text-red-400',
+ },
};
const PAGE_SIZE = 10;
-type AnalysisJob = {
- id: string;
- package_name: string;
- severity: string;
- repo_full_name: string;
- title: string;
- analysis_status: string | null;
- analysis_started_at: Date | null;
- analysis_completed_at: Date | null;
- analysis_error: string | null;
- analysis: SecurityFindingAnalysis | null;
- session_id: string | null;
- cli_session_id: string | null;
-};
+function formatCompactTimeAgo(date: Date) {
+ const now = new Date();
+ const days = Math.abs(differenceInDays(now, date));
+ if (days >= 1) return `${days}d ago`;
+ const hours = Math.abs(differenceInHours(now, date));
+ if (hours >= 1) return `${hours}h ago`;
+ const minutes = Math.abs(differenceInMinutes(now, date));
+ return `${minutes}m ago`;
+}
-// Helper to detect GitHub integration errors from error messages
function isGitHubIntegrationError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return (
@@ -71,19 +82,121 @@ function isGitHubIntegrationError(error: unknown): boolean {
message.includes('GitHub installation') ||
message.includes('installation_id') ||
message.includes('Bad credentials') ||
- message.includes('Not Found') // GitHub API returns 404 for uninstalled apps
+ message.includes('Not Found')
+ );
+}
+
+// ─── Row sub-components ──────────────────────────────────────────────────────
+
+function StatusBadge({ status }: { status: AnalysisStatus | null }) {
+ if (!status) return null;
+ const info = statusConfig[status];
+ const Icon = info.icon;
+ return (
+
- Analysis jobs will appear here when you analyze security findings. Click "Start - Analysis" on any finding to begin. -
-No analysis jobs yet
++ Click "Start Analysis" on any finding to begin. +
++ Showing {startItem}–{endItem} of {total} +
++ Codebase analysis failed: {analysisError || 'Unknown error'} +
+ +