Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
257
src/Application/Security/WafFeedbackDashboardController.php
Normal file
257
src/Application/Security/WafFeedbackDashboardController.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Security;
|
||||
|
||||
use App\Framework\Attributes\Route;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\Http\HttpRequest;
|
||||
use App\Framework\Router\Result\ViewResult;
|
||||
use App\Framework\Waf\DetectionCategory;
|
||||
use App\Framework\Waf\Feedback\FeedbackRepositoryInterface;
|
||||
use App\Framework\Waf\Feedback\FeedbackService;
|
||||
use App\Framework\Waf\Feedback\FeedbackType;
|
||||
|
||||
/**
|
||||
* Controller for the WAF feedback dashboard
|
||||
*/
|
||||
final readonly class WafFeedbackDashboardController
|
||||
{
|
||||
/**
|
||||
* @param FeedbackRepositoryInterface $repository Repository for accessing feedback data
|
||||
* @param FeedbackService $feedbackService Service for handling WAF feedback
|
||||
*/
|
||||
public function __construct(
|
||||
private FeedbackRepositoryInterface $repository,
|
||||
private FeedbackService $feedbackService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the WAF feedback dashboard
|
||||
*/
|
||||
#[Route(path: '/admin/security/waf/feedback', method: 'GET')]
|
||||
public function showDashboard(HttpRequest $request): ViewResult
|
||||
{
|
||||
// Get feedback statistics
|
||||
$stats = $this->repository->getFeedbackStats();
|
||||
|
||||
// Get recent feedback
|
||||
$recentFeedback = $this->repository->getRecentFeedback(10);
|
||||
|
||||
// Get time period from query parameters (default to last 30 days)
|
||||
$period = $request->queryParams['period'] ?? '30d';
|
||||
$since = $this->getPeriodTimestamp($period);
|
||||
|
||||
// Get feedback by type for the selected period
|
||||
$falsePositives = $this->repository->getFeedbackByFeedbackType(
|
||||
FeedbackType::FALSE_POSITIVE,
|
||||
$since
|
||||
);
|
||||
|
||||
$falseNegatives = $this->repository->getFeedbackByFeedbackType(
|
||||
FeedbackType::FALSE_NEGATIVE,
|
||||
$since
|
||||
);
|
||||
|
||||
$correctDetections = $this->repository->getFeedbackByFeedbackType(
|
||||
FeedbackType::CORRECT_DETECTION,
|
||||
$since
|
||||
);
|
||||
|
||||
$severityAdjustments = $this->repository->getFeedbackByFeedbackType(
|
||||
FeedbackType::SEVERITY_ADJUSTMENT,
|
||||
$since
|
||||
);
|
||||
|
||||
// Calculate accuracy metrics
|
||||
$totalFeedback = count($falsePositives) + count($falseNegatives) + count($correctDetections);
|
||||
$accuracy = $totalFeedback > 0
|
||||
? (count($correctDetections) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
$falsePositiveRate = $totalFeedback > 0
|
||||
? (count($falsePositives) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
$falseNegativeRate = $totalFeedback > 0
|
||||
? (count($falseNegatives) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
// Get feedback by category
|
||||
$feedbackByCategory = $this->getFeedbackByCategory($since);
|
||||
|
||||
// Get trend data
|
||||
$trendData = $stats['trend_data'] ?? [];
|
||||
|
||||
return new ViewResult('admin/security/waf-feedback-dashboard', [
|
||||
'stats' => $stats,
|
||||
'recent_feedback' => $recentFeedback,
|
||||
'period' => $period,
|
||||
'accuracy' => round($accuracy, 1),
|
||||
'false_positive_rate' => round($falsePositiveRate, 1),
|
||||
'false_negative_rate' => round($falseNegativeRate, 1),
|
||||
'feedback_by_category' => $feedbackByCategory,
|
||||
'trend_data' => $trendData,
|
||||
'false_positives_count' => count($falsePositives),
|
||||
'false_negatives_count' => count($falseNegatives),
|
||||
'correct_detections_count' => count($correctDetections),
|
||||
'severity_adjustments_count' => count($severityAdjustments),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show detailed feedback for a specific category
|
||||
*/
|
||||
#[Route(path: '/admin/security/waf/feedback/category/{category}', method: 'GET')]
|
||||
public function showCategoryFeedback(HttpRequest $request, string $category): ViewResult
|
||||
{
|
||||
try {
|
||||
$detectionCategory = DetectionCategory::from($category);
|
||||
} catch (\ValueError $e) {
|
||||
// If category is invalid, redirect to dashboard
|
||||
return new ViewResult('admin/security/waf-feedback-dashboard', [
|
||||
'error' => 'Invalid category: ' . $category,
|
||||
]);
|
||||
}
|
||||
|
||||
// Get time period from query parameters (default to last 30 days)
|
||||
$period = $request->queryParams['period'] ?? '30d';
|
||||
$since = $this->getPeriodTimestamp($period);
|
||||
|
||||
// Get feedback for this category
|
||||
$feedback = $this->repository->getFeedbackByCategory($detectionCategory, $since);
|
||||
|
||||
// Group feedback by type
|
||||
$feedbackByType = [];
|
||||
foreach ($feedback as $item) {
|
||||
$type = $item->feedbackType->value;
|
||||
if (! isset($feedbackByType[$type])) {
|
||||
$feedbackByType[$type] = [];
|
||||
}
|
||||
$feedbackByType[$type][] = $item;
|
||||
}
|
||||
|
||||
// Calculate accuracy metrics for this category
|
||||
$falsePositives = $feedbackByType[FeedbackType::FALSE_POSITIVE->value] ?? [];
|
||||
$falseNegatives = $feedbackByType[FeedbackType::FALSE_NEGATIVE->value] ?? [];
|
||||
$correctDetections = $feedbackByType[FeedbackType::CORRECT_DETECTION->value] ?? [];
|
||||
|
||||
$totalFeedback = count($falsePositives) + count($falseNegatives) + count($correctDetections);
|
||||
$accuracy = $totalFeedback > 0
|
||||
? (count($correctDetections) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
$falsePositiveRate = $totalFeedback > 0
|
||||
? (count($falsePositives) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
$falseNegativeRate = $totalFeedback > 0
|
||||
? (count($falseNegatives) / $totalFeedback) * 100
|
||||
: 0;
|
||||
|
||||
return new ViewResult('admin/security/waf-feedback-category', [
|
||||
'category' => $detectionCategory,
|
||||
'feedback' => $feedback,
|
||||
'feedback_by_type' => $feedbackByType,
|
||||
'period' => $period,
|
||||
'accuracy' => round($accuracy, 1),
|
||||
'false_positive_rate' => round($falsePositiveRate, 1),
|
||||
'false_negative_rate' => round($falseNegativeRate, 1),
|
||||
'total_feedback' => $totalFeedback,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show feedback learning history
|
||||
*/
|
||||
#[Route(path: '/admin/security/waf/feedback/learning', method: 'GET')]
|
||||
public function showLearningHistory(HttpRequest $request): ViewResult
|
||||
{
|
||||
// In a real implementation, this would retrieve learning history from a database
|
||||
// For now, we'll return a placeholder view
|
||||
|
||||
return new ViewResult('admin/security/waf-feedback-learning', [
|
||||
'learning_history' => [],
|
||||
'message' => 'Learning history not yet implemented',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a timestamp for the specified period
|
||||
*
|
||||
* @param string $period Period string (e.g., '7d', '30d', '90d', 'all')
|
||||
* @return Timestamp|null Timestamp for the start of the period, or null for 'all'
|
||||
*/
|
||||
private function getPeriodTimestamp(string $period): ?Timestamp
|
||||
{
|
||||
return match($period) {
|
||||
'7d' => Timestamp::fromString('-7 days'),
|
||||
'30d' => Timestamp::fromString('-30 days'),
|
||||
'90d' => Timestamp::fromString('-90 days'),
|
||||
'all' => null,
|
||||
default => Timestamp::fromString('-30 days')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feedback grouped by category
|
||||
*
|
||||
* @param Timestamp|null $since Optional timestamp to filter feedback after a certain date
|
||||
* @return array<string, array<string, mixed>> Feedback data grouped by category
|
||||
*/
|
||||
private function getFeedbackByCategory(?Timestamp $since): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Get all categories
|
||||
$categories = DetectionCategory::cases();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
// Get feedback for this category
|
||||
$feedback = $this->repository->getFeedbackByCategory($category, $since);
|
||||
|
||||
if (empty($feedback)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group feedback by type
|
||||
$feedbackByType = [];
|
||||
foreach ($feedback as $item) {
|
||||
$type = $item->feedbackType->value;
|
||||
if (! isset($feedbackByType[$type])) {
|
||||
$feedbackByType[$type] = [];
|
||||
}
|
||||
$feedbackByType[$type][] = $item;
|
||||
}
|
||||
|
||||
// Calculate metrics
|
||||
$falsePositives = $feedbackByType[FeedbackType::FALSE_POSITIVE->value] ?? [];
|
||||
$falseNegatives = $feedbackByType[FeedbackType::FALSE_NEGATIVE->value] ?? [];
|
||||
$correctDetections = $feedbackByType[FeedbackType::CORRECT_DETECTION->value] ?? [];
|
||||
|
||||
$totalFeedback = count($falsePositives) + count($falseNegatives) + count($correctDetections);
|
||||
|
||||
if ($totalFeedback === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$accuracy = (count($correctDetections) / $totalFeedback) * 100;
|
||||
|
||||
$result[$category->value] = [
|
||||
'category' => $category,
|
||||
'total_feedback' => $totalFeedback,
|
||||
'false_positives' => count($falsePositives),
|
||||
'false_negatives' => count($falseNegatives),
|
||||
'correct_detections' => count($correctDetections),
|
||||
'accuracy' => round($accuracy, 1),
|
||||
];
|
||||
}
|
||||
|
||||
// Sort by total feedback count (descending)
|
||||
uasort($result, fn ($a, $b) => $b['total_feedback'] <=> $a['total_feedback']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user