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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user