feat: Add CrowdSec management endpoints and feature flags handler

- Implemented CrowdSec process management with start, stop, and status endpoints.
- Added import functionality for CrowdSec configuration files with backup support.
- Introduced a new FeatureFlagsHandler to manage feature flags with database and environment variable fallback.
- Created tests for CrowdSec handler and feature flags handler.
- Updated routes to include new feature flags and CrowdSec management endpoints.
- Enhanced import handler with better error logging and diagnostics.
- Added frontend API calls for CrowdSec management and feature flags.
- Updated SystemSettings page to manage feature flags and CrowdSec controls.
- Refactored logs and other components for improved functionality and UI consistency.
This commit is contained in:
GitHub Actions
2025-11-30 03:10:42 +00:00
parent fa3ed5a135
commit 83afbbf1fc
22 changed files with 902 additions and 235 deletions

View File

@@ -6,7 +6,9 @@ import { Input } from '../components/ui/Input'
import { Switch } from '../components/ui/Switch'
import { toast } from '../utils/toast'
import { getSettings, updateSetting } from '../api/settings'
import { getFeatureFlags, updateFeatureFlags } from '../api/featureFlags'
import client from '../api/client'
import { startCrowdsec, stopCrowdsec, statusCrowdsec, importCrowdsecConfig } from '../api/crowdsec'
import { Loader2, Server, RefreshCw, Save, Activity } from 'lucide-react'
interface HealthResponse {
@@ -86,6 +88,52 @@ export default function SystemSettings() {
},
})
// Feature Flags
const { data: featureFlags, refetch: refetchFlags } = useQuery({
queryKey: ['feature-flags'],
queryFn: getFeatureFlags,
})
const updateFlagMutation = useMutation({
mutationFn: async (payload: Record<string, boolean>) => updateFeatureFlags(payload),
onSuccess: () => {
refetchFlags()
toast.success('Feature flag updated')
},
onError: (err: any) => {
toast.error(`Failed to update flag: ${err?.message || err}`)
},
})
// CrowdSec control
const [crowdsecStatus, setCrowdsecStatus] = useState<{ running: boolean; pid?: number } | null>(null)
const fetchCrowdsecStatus = async () => {
try {
const s = await statusCrowdsec()
setCrowdsecStatus(s)
} catch {
setCrowdsecStatus(null)
}
}
useEffect(() => { fetchCrowdsecStatus() }, [])
const startMutation = useMutation({ mutationFn: () => startCrowdsec(), onSuccess: () => fetchCrowdsecStatus(), onError: (e:any) => toast.error(String(e)) })
const stopMutation = useMutation({ mutationFn: () => stopCrowdsec(), onSuccess: () => fetchCrowdsecStatus(), onError: (e:any) => toast.error(String(e)) })
const importMutation = useMutation({
mutationFn: async (file: File) => importCrowdsecConfig(file),
onSuccess: () => { toast.success('CrowdSec config imported'); fetchCrowdsecStatus() },
onError: (e:any) => toast.error(String(e)),
})
const handleCrowdsecUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const f = e.target.files?.[0]
if (!f) return
importMutation.mutate(f)
}
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
@@ -171,6 +219,29 @@ export default function SystemSettings() {
</div>
</Card>
{/* Feature Flags */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Feature Flags</h2>
<div className="space-y-4">
{featureFlags ? (
Object.keys(featureFlags).map((key) => (
<div key={key} className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">{key}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">Toggle feature {key}</p>
</div>
<Switch
checked={!!featureFlags[key]}
onChange={(e) => updateFlagMutation.mutate({ [key]: e.target.checked })}
/>
</div>
))
) : (
<p className="text-sm text-gray-500">Loading feature flags...</p>
)}
</div>
</Card>
{/* System Status */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
@@ -271,6 +342,29 @@ export default function SystemSettings() {
</Button>
</div>
</Card>
{/* CrowdSec Controls */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">CrowdSec</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">Status</p>
<p className="text-xs text-gray-500 dark:text-gray-400">{crowdsecStatus ? (crowdsecStatus.running ? `Running (pid ${crowdsecStatus.pid})` : 'Stopped') : 'Unknown'}</p>
</div>
<div className="flex items-center gap-3">
<Button onClick={() => startMutation.mutate()} isLoading={startMutation.isPending}>Start</Button>
<Button onClick={() => stopMutation.mutate()} isLoading={stopMutation.isPending} variant="secondary">Stop</Button>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1.5">Import CrowdSec Config</label>
<input type="file" onChange={handleCrowdsecUpload} />
<p className="text-sm text-gray-500 mt-1">Upload a tar.gz or zip with your CrowdSec configuration. Existing config will be backed up.</p>
</div>
</div>
</Card>
</div>
)
}