Skip to content

Commit 1fb6063

Browse files
committed
Add Grafana integration and service health check features
1 parent 3bd7c98 commit 1fb6063

File tree

16 files changed

+931
-9
lines changed

16 files changed

+931
-9
lines changed

config/grafana/grafana.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[security]
2+
allow_embedding = true
3+
cookie_samesite = none
4+
cookie_secure = false
5+
6+
[server]
7+
enable_gzip = true

docker-compose.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ services:
55
hostname: ${SENTINELKIT_FRONTEND_HOSTNAME}
66
working_dir: /app
77
environment:
8-
- VITE_ALLOWED_HOSTS=.${SENTINELKIT_FRONTEND_HOSTNAME}
8+
- VITE_ALLOWED_HOSTS=${SENTINELKIT_FRONTEND_HOSTNAME},${SENTINELKIT_BACKEND_HOSTNAME},${SENTINELKIT_KIBANA_HOSTNAME},${SENTINELKIT_GRAFANA_HOSTNAME}
99
- VITE_API_BASE_URL=https://${SENTINELKIT_BACKEND_HOSTNAME}/api
10+
- VITE_BACKEND_URL=https://${SENTINELKIT_BACKEND_HOSTNAME}
11+
- VITE_KIBANA_URL=https://${SENTINELKIT_KIBANA_HOSTNAME}
12+
- VITE_GRAFANA_URL=https://${SENTINELKIT_GRAFANA_HOSTNAME}
1013
volumes:
1114
- ./sentinel-kit_server_frontend:/app
1215
command: sh -c "npm install && npm run dev -- --host '0.0.0.0' --port 3000"
@@ -339,10 +342,12 @@ services:
339342
- ./data/grafana:/var/lib/grafana
340343
- ./config/grafana/datasources:/etc/grafana/provisioning/datasources
341344
- ./config/grafana/dashboards:/etc/grafana/provisioning/dashboards
345+
- ./config/grafana/grafana.ini:/etc/grafana/grafana.ini
342346
environment:
343347
- GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
344348
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
345349
- GF_SERVER_HTTP_PORT=3000
350+
- GF_SECURITY_ALLOW_EMBEDDING=true
346351
- ES_PASSWORD=${ELASTICSEARCH_PASSWORD}
347352
- MYSQL_DATABASE=${MYSQL_DATABASE}
348353
- MYSQL_USER=${MYSQL_USER}

sentinel-kit_server_backend/src/Controller/MainController.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,98 @@
77
use Symfony\Component\HttpFoundation\Response;
88
use Symfony\Component\HttpFoundation\JsonResponse;
99
use Symfony\Component\Routing\Attribute\Route;
10+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1011
use App\Repository\ServerTokenRepository;
1112
use Doctrine\ORM\EntityManagerInterface;
1213
use App\Entity\Agent;
1314

1415
final class MainController extends AbstractController
1516
{
17+
private $httpClient;
18+
19+
function __construct(HttpClientInterface $httpClient)
20+
{
21+
$this->httpClient = $httpClient;
22+
}
23+
1624
#[Route('/', name: 'app_main')]
1725
public function index(): Response
1826
{
1927
return $this->render('main/index.html.twig', [
2028
'controller_name' => 'MainController',
2129
]);
2230
}
31+
32+
33+
#[Route('/api/health-check/{service}', name: 'app_service_health_check', methods: ['GET', 'HEAD'])]
34+
public function serviceHealthCheck(string $service): JsonResponse
35+
{
36+
$services = [
37+
'kibana' => 'http://sentinel-kit-utils-kibana:5601',
38+
'grafana' => 'http://sentinel-kit-utils-grafana:3000',
39+
'backend' => 'http://sentinel-kit-app-backend:8000'
40+
];
41+
42+
if (!isset($services[$service])) {
43+
return new JsonResponse([
44+
'service' => $service,
45+
'status' => 'error',
46+
'error' => 'Unknown service'
47+
], Response::HTTP_BAD_REQUEST);
48+
}
49+
50+
$url = $services[$service];
51+
52+
try {
53+
$response = $this->httpClient->request('GET', $url, [
54+
'timeout' => 10,
55+
'verify_peer' => false,
56+
'verify_host' => false,
57+
'max_redirects' => 0
58+
]);
59+
$statusCode = $response->getStatusCode();
60+
61+
if ($statusCode >= 200 && $statusCode < 400) {
62+
$status = 'healthy';
63+
$error = null;
64+
} else {
65+
$status = 'unhealthy';
66+
$error = $this->getErrorMessage($statusCode);
67+
}
68+
69+
return new JsonResponse([
70+
'service' => $service,
71+
'url' => $url,
72+
'status' => $status,
73+
'error' => $error,
74+
'httpStatus' => $statusCode,
75+
'lastChecked' => (new \DateTime())->format('c')
76+
], Response::HTTP_OK);
77+
78+
} catch (\Exception $e) {
79+
return new JsonResponse([
80+
'service' => $service,
81+
'url' => $url,
82+
'status' => 'unhealthy',
83+
'error' => 'Connection failed: ' . $e->getMessage(),
84+
'httpStatus' => null,
85+
'lastChecked' => (new \DateTime())->format('c')
86+
], Response::HTTP_OK);
87+
}
88+
}
89+
90+
private function getErrorMessage(int $statusCode): string
91+
{
92+
return match($statusCode) {
93+
502 => 'Bad Gateway: Service is down or unreachable',
94+
503 => 'Service Unavailable: Service is temporarily down',
95+
504 => 'Gateway Timeout: Service is not responding',
96+
500 => 'Internal Server Error: Service has internal issues',
97+
404 => 'Not Found: Service endpoint not found',
98+
403 => 'Forbidden: Access denied',
99+
401 => 'Unauthorized: Authentication required',
100+
302, 301 => 'Redirect: Service is redirecting requests',
101+
default => "HTTP $statusCode: Service returned error status"
102+
};
103+
}
23104
}

sentinel-kit_server_frontend/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<!doctype html data-theme="claude">
2-
<html lang="en">
1+
<!DOCTYPE html>
2+
<html lang="en" data-theme="claude">
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="stylesheet" href="/src/style.css" />

sentinel-kit_server_frontend/src/App.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<template>
2+
<SSLChecker />
3+
24
<div class="flex h-screen w-screen bg-gray-50">
35
<!-- Side menu -->
46
<aside v-if="isLoggedIn" :class="{ 'w-64': !isCollapsed, 'w-20': isCollapsed }" class="flex flex-col bg-gray-800 text-white transition-all duration-300 ease-in-out shadow-lg fixed h-full z-20">
@@ -112,6 +114,7 @@
112114
<script setup>
113115
import { onMounted, ref } from 'vue';
114116
import { useRouter, RouterView, RouterLink } from 'vue-router';
117+
import SSLChecker from './components/SSLChecker.vue';
115118
116119
const router = useRouter();
117120
const isLoggedIn = ref(false);
@@ -163,11 +166,11 @@ const logout = () => {
163166
// Side menu items
164167
const menuItems = [
165168
{ name: 'Home', icon: 'icon-[svg-spinners--blocks-wave]', route: 'Home' },
166-
{ name: 'Kibana', icon: 'icon-[solar--presentation-graph-bold]', route: 'Kibana' },
169+
{ name: 'Logs', icon: 'icon-[solar--presentation-graph-bold]', route: 'Kibana' },
167170
{ name: 'Rulesets', icon: 'icon-[mdi--account-child]', route: 'RulesList' },
168171
{ name: 'Detections', icon: 'icon-[solar--eye-scan-broken]', route: 'Home' },
169172
{ name: 'Assets & groups', icon: 'icon-[line-md--computer-twotone]', route: 'Home' },
170-
{ name: 'Settings', icon: 'icon-[solar--settings-bold-duotone]', route: 'Home' }
173+
{ name: 'Monitoring', icon: 'icon-[material-symbols--monitor-heart-outline]', route: 'Grafana' }
171174
];
172175
</script>
173176

sentinel-kit_server_frontend/src/components/LoginOTPForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const router = useRouter();
2929
3030
const isInvalid = ref(false);
3131
const otp = ref(['', '', '', '', '', '']);
32-
const emit = defineEmits(['postauth-success']);
32+
const emit = defineEmits(['postauth-success', 'showNotification']);
3333
const props = defineProps({
3434
postAuthUrl: {
3535
type: String,

sentinel-kit_server_frontend/src/components/LoginPasswordForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const BASE_URL = import.meta.env.VITE_API_BASE_URL;
2424
const email = ref('');
2525
const password = ref('');
2626
const isInvalid = ref(false);
27-
const emit = defineEmits(['preauth-success']);
27+
const emit = defineEmits(['preauth-success', 'showNotification']);
2828
2929
watch([email, password], () => {
3030
isInvalid.value = false;

0 commit comments

Comments
 (0)