Skip to content

Commit f983d7f

Browse files
committed
Refactor UI components for rule management: update button styles, improve loading messages, and enhance bulk action confirmation dialogs.
1 parent d38aaed commit f983d7f

26 files changed

+3897
-312
lines changed

sentinel-kit_server_backend/src/Controller/AlertController.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,51 @@ public function listAlerts(Request $request): JsonResponse
3535
$page = max(1, $request->query->getInt('page', 1));
3636
$limit = min(100, max(1, $request->query->getInt('limit', 20)));
3737
$since = $request->query->get('since');
38+
$startDate = $request->query->get('startDate');
39+
$endDate = $request->query->get('endDate');
40+
$filter = $request->query->get('filter');
3841

3942
$qb = $this->entityManager->getRepository(Alert::class)->createQueryBuilder('a')
4043
->leftJoin('a.rule', 'r')
4144
->leftJoin('a.sigmaRuleVersion', 'v')
4245
->addSelect('r', 'v')
4346
->orderBy('a.createdOn', 'DESC');
4447

45-
if ($since) {
48+
$whereConditions = [];
49+
$parameters = [];
50+
51+
if ($startDate && $endDate) {
52+
try {
53+
$start = new \DateTime($startDate);
54+
$end = new \DateTime($endDate);
55+
$whereConditions[] = 'a.createdOn >= :startDate AND a.createdOn <= :endDate';
56+
$parameters['startDate'] = $start;
57+
$parameters['endDate'] = $end;
58+
} catch (\Exception $e) {
59+
return new JsonResponse(['error' => 'Invalid date format for startDate or endDate parameter'], Response::HTTP_BAD_REQUEST);
60+
}
61+
} elseif ($since) {
4662
try {
4763
$sinceDate = new \DateTime($since);
48-
$qb->where('a.createdOn >= :since')
49-
->setParameter('since', $sinceDate);
64+
$whereConditions[] = 'a.createdOn >= :since';
65+
$parameters['since'] = $sinceDate;
5066
} catch (\Exception $e) {
5167
return new JsonResponse(['error' => 'Invalid date format for since parameter'], Response::HTTP_BAD_REQUEST);
5268
}
5369
}
5470

71+
if ($filter && trim($filter)) {
72+
$whereConditions[] = '(r.title LIKE :filter OR r.description LIKE :filter)';
73+
$parameters['filter'] = '%' . trim($filter) . '%';
74+
}
75+
76+
if (!empty($whereConditions)) {
77+
$qb->where(implode(' AND ', $whereConditions));
78+
foreach ($parameters as $key => $value) {
79+
$qb->setParameter($key, $value);
80+
}
81+
}
82+
5583
$offset = ($page - 1) * $limit;
5684
$qb->setFirstResult($offset)->setMaxResults($limit);
5785

@@ -60,7 +88,13 @@ public function listAlerts(Request $request): JsonResponse
6088
$totalQb = $this->entityManager->getRepository(Alert::class)->createQueryBuilder('a')
6189
->select('COUNT(a.id)');
6290

63-
if ($since) {
91+
if ($startDate && $endDate) {
92+
$start = new \DateTime($startDate);
93+
$end = new \DateTime($endDate);
94+
$totalQb->where('a.createdOn >= :startDate AND a.createdOn <= :endDate')
95+
->setParameter('startDate', $start)
96+
->setParameter('endDate', $end);
97+
} elseif ($since) {
6498
$sinceDate = new \DateTime($since);
6599
$totalQb->where('a.createdOn >= :since')
66100
->setParameter('since', $sinceDate);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace App\Controller;
4+
5+
use App\Service\ElasticsearchService;
6+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
use Symfony\Component\HttpFoundation\Request;
9+
use Symfony\Component\HttpFoundation\Response;
10+
use Symfony\Component\Routing\Annotation\Route;
11+
12+
#[Route('/api/elasticsearch')]
13+
class ElasticsearchController extends AbstractController
14+
{
15+
private ElasticsearchService $elasticsearchService;
16+
17+
public function __construct(ElasticsearchService $elasticsearchService)
18+
{
19+
$this->elasticsearchService = $elasticsearchService;
20+
}
21+
22+
#[Route('/search', name: 'elasticsearch_search', methods: ['POST'])]
23+
public function search(Request $request): JsonResponse
24+
{
25+
try {
26+
$requestData = json_decode($request->getContent(), true);
27+
28+
if (!$requestData) {
29+
return $this->json([
30+
'error' => 'Invalid JSON in request body'
31+
], Response::HTTP_BAD_REQUEST);
32+
}
33+
34+
// Log the incoming query for debugging
35+
error_log('Elasticsearch query received: ' . json_encode($requestData));
36+
37+
if (!$this->isAllowedQuery($requestData)) {
38+
error_log('Query rejected by validation: ' . json_encode($requestData));
39+
return $this->json([
40+
'error' => 'Only read-only search operations are allowed'
41+
], Response::HTTP_FORBIDDEN);
42+
}
43+
44+
$result = $this->elasticsearchService->rawQuery($requestData);
45+
46+
return $this->json([
47+
'success' => true,
48+
'data' => $result
49+
]);
50+
51+
} catch (\Exception $e) {
52+
error_log('Elasticsearch controller error: ' . $e->getMessage());
53+
error_log('Stack trace: ' . $e->getTraceAsString());
54+
return $this->json([
55+
'error' => 'Elasticsearch query error: ' . $e->getMessage()
56+
], Response::HTTP_INTERNAL_SERVER_ERROR);
57+
}
58+
}
59+
60+
/**
61+
* Validate that the query only contains allowed read-only operations
62+
*/
63+
private function isAllowedQuery(array $query): bool
64+
{
65+
// Simplified validation - just check for obviously dangerous operations
66+
$forbiddenOperations = [
67+
'delete',
68+
'update',
69+
'bulk',
70+
'_delete',
71+
'_update',
72+
'_bulk'
73+
];
74+
75+
// Convert query to string for simple pattern matching
76+
$queryString = json_encode($query);
77+
$queryLower = strtolower($queryString);
78+
79+
foreach ($forbiddenOperations as $forbidden) {
80+
if (strpos($queryLower, $forbidden) !== false) {
81+
error_log("Forbidden operation detected: $forbidden in query: $queryString");
82+
return false;
83+
}
84+
}
85+
86+
return true;
87+
}
88+
}

sentinel-kit_server_backend/src/Controller/SigmaController.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ public function addRuleVersion(Request $request, int $ruleId): JsonResponse {
182182
return new JsonResponse(['error' => 'The new version content is identical to the latest version'], Response::HTTP_BAD_REQUEST);
183183
}
184184

185+
if($rule->isActive()){
186+
$this->elastalertValidator->removeElastalertRule($latestVersion);
187+
}
185188

186189
try {
187190
$this->entityManger->persist($newRuleVersion);
@@ -190,6 +193,11 @@ public function addRuleVersion(Request $request, int $ruleId): JsonResponse {
190193
return new JsonResponse(['error' => 'Failed to save the new version: ' . $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
191194
}
192195

196+
$e = $this->elastalertValidator->createElastalertRule($newRuleVersion);
197+
if (isset($e['error'])) {
198+
return new JsonResponse(['error' => $e['error']], Response::HTTP_INTERNAL_SERVER_ERROR);
199+
}
200+
193201
return new JsonResponse(['error' => '', 'version_id' => $newRuleVersion->getId()], Response::HTTP_OK);
194202
}
195203

@@ -200,6 +208,10 @@ public function deleteRule(Request $request, int $ruleId): JsonResponse {
200208
return new JsonResponse(['error' => 'Rule not found'], Response::HTTP_NOT_FOUND);
201209
}
202210

211+
if($rule->isActive()){
212+
$this->elastalertValidator->removeElastalertRule($rule->getRuleLatestVersion());
213+
}
214+
203215
$this->entityManger->remove($rule);
204216
$this->entityManger->flush();
205217

sentinel-kit_server_backend/src/Service/ElasticsearchService.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,34 @@ public function getClusterHealth(): array
9191
return ['status' => 'unavailable'];
9292
}
9393
}
94+
public function rawQuery(array $queryData): array
95+
{
96+
try {
97+
$index = $queryData['index'] ?? 'sentinelkit-*';
98+
99+
$bodyData = $queryData;
100+
unset($bodyData['index']);
101+
102+
$params = [
103+
'index' => $index,
104+
'body' => $bodyData
105+
];
106+
107+
$this->logger->info('Elasticsearch raw query execution', [
108+
'index' => $index,
109+
'query_keys' => array_keys($bodyData)
110+
]);
111+
112+
$response = $this->client->search($params);
113+
return $response->asArray();
114+
} catch (\Exception $e) {
115+
$this->logger->error('Elasticsearch raw query error', [
116+
'error' => $e->getMessage(),
117+
'query' => $queryData
118+
]);
119+
throw $e;
120+
}
121+
}
94122

95123
public function getClient(): Client
96124
{

sentinel-kit_server_frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="stylesheet" href="/src/style.css" />
6-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<link rel="icon" type="image/png" href="/favicon.png" />
77
<link href="https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap" rel="stylesheet"/>
88
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
99
<title>Sentinel Kit - Admin portal</title>

sentinel-kit_server_frontend/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414
"@iconify/tailwind4": "^1.0.6",
1515
"@tailwindcss/vite": "^4.1.14",
1616
"@vitejs/plugin-vue": "^6.0.1",
17+
"chart.js": "^4.4.0",
18+
"chartjs-adapter-date-fns": "^3.0.0",
19+
"chartjs-plugin-zoom": "^2.2.0",
20+
"date-fns": "^2.30.0",
1721
"flyonui": "^2.4.1",
1822
"tailwindcss": "^4.1.14",
1923
"vue": "^3.5.22",
24+
"vue-chartjs": "^5.3.0",
2025
"vue-router": "^4.6.3"
2126
},
2227
"devDependencies": {
10.3 KB
Loading

sentinel-kit_server_frontend/src/App.vue

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
</aside>
3737

3838
<div :class="{ 'ml-64': !isCollapsed, 'ml-20': isCollapsed }" class="flex-1 flex flex-col transition-all duration-300 ease-in-out">
39-
<main class="p-6 flex-1 overflow-y-auto">
39+
<main class="flex-1 overflow-y-auto">
4040
<RouterView @show-notification="showNotification" />
4141
</main>
4242
</div>
@@ -124,6 +124,7 @@ import SSLChecker from './components/SSLChecker.vue';
124124
const router = useRouter();
125125
const isLoggedIn = ref(false);
126126
const isCollapsed = ref(true);
127+
const BASE_URL = import.meta.env.VITE_API_BASE_URL;
127128
128129
// Notification system
129130
const notification = ref({
@@ -161,18 +162,42 @@ const hideNotification = () => {
161162
onMounted(() => {
162163
const token = localStorage.getItem('auth_token');
163164
isLoggedIn.value = !!token;
165+
166+
// if route is not login or logout, check auth
167+
const currentRoute = router.currentRoute.value.name;
168+
if (currentRoute !== 'Login' && currentRoute !== 'Logout') {
169+
checkAuth();
170+
}
164171
});
165172
166173
const logout = () => {
167174
router.push({ name: 'Logout' });
168175
};
169176
177+
// Authentication check
178+
const checkAuth = async () => {
179+
try {
180+
const response = await fetch(`${BASE_URL}/user/profile`, {
181+
headers: {
182+
'Content-Type': 'application/json',
183+
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
184+
}
185+
})
186+
if (response.status === 401) {
187+
localStorage.removeItem('auth_token')
188+
router.push({ name: 'Login' })
189+
}
190+
} catch (error) {
191+
console.error('Error checking authentication:', error)
192+
}
193+
}
194+
170195
// Side menu items
171196
const menuItems = [
172197
{ name: 'Home', icon: 'icon-[svg-spinners--blocks-wave]', route: 'Home' },
173198
{ name: 'Rulesets', icon: 'icon-[carbon--rule-draft]', route: 'RulesList' },
199+
{ name: 'Alerts', icon: 'icon-[solar--eye-scan-broken]', route: 'AlertsList' },
174200
{ name: 'Logs', icon: 'icon-[icon-park-outline--log]', route: 'Kibana' },
175-
{ name: 'Alerts', icon: 'icon-[solar--eye-scan-broken]', route: 'Home' },
176201
{ name: 'Endpoint detection', icon: 'icon-[line-md--computer-twotone]', route: 'Home' },
177202
{ name: 'Perf. monitoring', icon: 'icon-[material-symbols--monitor-heart-outline]', route: 'Grafana' }
178203
];

sentinel-kit_server_frontend/src/components/RuleEditor.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const emit = defineEmits(['update:modelValue']);
5454
5555
const editorOptions = computed(() => ({
5656
automaticLayout: true,
57-
minimap: { enabled: true },
57+
minimap: { enabled: false },
5858
readOnly: props.readOnly,
5959
tabSize: 2,
6060
scrollBeyondLastLine: false,
@@ -63,7 +63,7 @@ const editorOptions = computed(() => ({
6363
6464
const diffOptions = computed(() => ({
6565
automaticLayout: true,
66-
minimap: { enabled: true },
66+
minimap: { enabled: false },
6767
readOnly: true,
6868
tabSize: 2,
6969
scrollBeyondLastLine: false,

sentinel-kit_server_frontend/src/components/RuleSummary.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
</a>
130130
<a
131131
@click="confirmDelete"
132-
class="btn btn-primary px-4 py-2 text-white bg-red-600 hover:bg-red-700 rounded-lg transition duration-150"
132+
class="btn btn-primary px-4 py-2 rounded-lg transition duration-150"
133133
>
134134
Remove
135135
</a>

0 commit comments

Comments
 (0)