mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 13:09:00 +00:00
Getting a skeleton in place for the control service.
This commit is contained in:
parent
2ae0b8c159
commit
097a163cf5
@ -26,6 +26,7 @@ dependencies {
|
||||
implementation project(':code:common:model')
|
||||
implementation project(':code:common:service')
|
||||
implementation project(':code:common:config')
|
||||
implementation project(':code:common:renderer')
|
||||
implementation project(':code:common:service-discovery')
|
||||
implementation project(':code:common:service-client')
|
||||
implementation project(':code:api:search-api')
|
||||
|
@ -4,35 +4,53 @@ import com.google.gson.Gson;
|
||||
import com.google.inject.Inject;
|
||||
import nu.marginalia.client.ServiceMonitors;
|
||||
import nu.marginalia.model.gson.GsonFactory;
|
||||
import nu.marginalia.renderer.MustacheRenderer;
|
||||
import nu.marginalia.renderer.RendererFactory;
|
||||
import nu.marginalia.service.server.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import spark.Spark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class ControlService extends Service {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
private final Gson gson = GsonFactory.get();
|
||||
|
||||
private final ServiceMonitors monitors;
|
||||
private final MustacheRenderer<Object> indexRenderer;
|
||||
private final MustacheRenderer<Map<?,?>> servicesRenderer;
|
||||
|
||||
|
||||
@Inject
|
||||
public ControlService(BaseServiceParams params,
|
||||
ServiceMonitors monitors,
|
||||
HeartbeatService heartbeatService
|
||||
) {
|
||||
HeartbeatService heartbeatService,
|
||||
EventLogService eventLogService,
|
||||
RendererFactory rendererFactory
|
||||
) throws IOException {
|
||||
|
||||
super(params);
|
||||
this.monitors = monitors;
|
||||
indexRenderer = rendererFactory.renderer("control/index");
|
||||
servicesRenderer = rendererFactory.renderer("control/services");
|
||||
|
||||
Spark.get("/public/heartbeats", (req, res) -> {
|
||||
res.type("application/json");
|
||||
return heartbeatService.getHeartbeats();
|
||||
}, gson::toJson);
|
||||
|
||||
Spark.get("/public/", (req, rsp) -> indexRenderer.render(Map.of()));
|
||||
Spark.get("/public/services", (req, rsp) -> servicesRenderer.render(
|
||||
Map.of("heartbeats", heartbeatService.getHeartbeats(),
|
||||
"events", eventLogService.getLastEntries(100)
|
||||
)));
|
||||
|
||||
monitors.subscribe(this::logMonitorStateChange);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void logMonitorStateChange() {
|
||||
|
@ -0,0 +1,49 @@
|
||||
package nu.marginalia.control;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import nu.marginalia.control.model.EventLogEntry;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
public class EventLogService {
|
||||
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
@Inject
|
||||
public EventLogService(HikariDataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
public List<EventLogEntry> getLastEntries(int n) {
|
||||
try (var conn = dataSource.getConnection();
|
||||
var query = conn.prepareStatement("""
|
||||
SELECT SERVICE_NAME, INSTANCE, EVENT_TIME, EVENT_TYPE, EVENT_MESSAGE
|
||||
FROM PROC_SERVICE_EVENTLOG ORDER BY ID DESC LIMIT ?
|
||||
""")) {
|
||||
|
||||
query.setInt(1, n);
|
||||
List<EventLogEntry> entries = new ArrayList<>(n);
|
||||
var rs = query.executeQuery();
|
||||
while (rs.next()) {
|
||||
entries.add(new EventLogEntry(
|
||||
rs.getString("SERVICE_NAME"),
|
||||
rs.getString("INSTANCE"),
|
||||
rs.getTimestamp("EVENT_TIME").toLocalDateTime().toLocalTime().toString(),
|
||||
rs.getString("EVENT_TYPE"),
|
||||
rs.getString("EVENT_MESSAGE")
|
||||
));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package nu.marginalia.control.model;
|
||||
|
||||
public record EventLogEntry(
|
||||
String serviceName,
|
||||
String instance,
|
||||
String eventTime,
|
||||
String eventType,
|
||||
String eventMessage)
|
||||
{
|
||||
}
|
@ -7,5 +7,8 @@ public record ServiceHeartbeat(
|
||||
double lastSeenMillis,
|
||||
boolean alive
|
||||
) {
|
||||
public boolean isMissing() {
|
||||
return lastSeenMillis > 10000;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
body {
|
||||
font-family: serif;
|
||||
line-height: 1.6;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<viewport content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{> control/partials/nav}}
|
||||
<section>
|
||||
<h1>Overview</h1>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Overview</a></li>
|
||||
<li><a href="services">Services</a></li>
|
||||
<li><a href="processes">Processes</a></li>
|
||||
</ul>
|
||||
</nav>
|
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
<viewport content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{> control/partials/nav}}
|
||||
|
||||
<section>
|
||||
<h1>Services</h1>
|
||||
<table id="heartbeats">
|
||||
<tr>
|
||||
<th>Service ID</th>
|
||||
<th>UUID</th>
|
||||
<th>Last Seen (ms)</th>
|
||||
</tr>
|
||||
{{#each heartbeats}}
|
||||
<tr class="{{#if isMissing}}missing{{/if}} {{#unless alive}}terminated{{/unless}}">
|
||||
<td>{{serviceId}}</td>
|
||||
<td>{{uuid}}</td>
|
||||
<td>{{lastSeenMillis}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
|
||||
<h2>Events</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Service Name</th>
|
||||
<th>Instance</th>
|
||||
<th>Event Time</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
{{#each events}}
|
||||
<tr>
|
||||
<td>{{serviceName}}</td>
|
||||
<td>{{instance}}</td>
|
||||
<td>{{eventTime}}</td>
|
||||
<td>{{eventType}}</td>
|
||||
<td>{{eventMessage}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user