diff --git a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/ControlService.java b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/ControlService.java index 93873abb..2a566077 100644 --- a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/ControlService.java +++ b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/ControlService.java @@ -11,6 +11,8 @@ import nu.marginalia.renderer.RendererFactory; import nu.marginalia.service.server.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import spark.Request; +import spark.Response; import spark.Spark; import java.io.IOException; @@ -25,7 +27,10 @@ public class ControlService extends Service { private final ServiceMonitors monitors; private final MustacheRenderer indexRenderer; private final MustacheRenderer> servicesRenderer; + private final MustacheRenderer> eventsRenderer; + private final MustacheRenderer> messageQueueRenderer; private final MqPersistence messageQueuePersistence; + private final StaticResources staticResources; @Inject @@ -35,14 +40,20 @@ public class ControlService extends Service { EventLogService eventLogService, RendererFactory rendererFactory, MqPersistence messageQueuePersistence, - ControlProcesses controlProcesses + ControlProcesses controlProcesses, + StaticResources staticResources, + MessageQueueViewService messageQueueViewService ) throws IOException { super(params); this.monitors = monitors; indexRenderer = rendererFactory.renderer("control/index"); servicesRenderer = rendererFactory.renderer("control/services"); + eventsRenderer = rendererFactory.renderer("control/events"); + messageQueueRenderer = rendererFactory.renderer("control/message-queue"); + this.messageQueuePersistence = messageQueuePersistence; + this.staticResources = staticResources; Spark.get("/public/heartbeats", (req, res) -> { res.type("application/json"); @@ -50,15 +61,18 @@ public class ControlService extends Service { }, 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) - ))); + + Spark.get("/public/services", (req, rsp) -> servicesRenderer.render(Map.of("heartbeats", heartbeatService.getHeartbeats()))); + Spark.get("/public/events", (req, rsp) -> eventsRenderer.render(Map.of("events", eventLogService.getLastEntries(20)))); + Spark.get("/public/message-queue", (req, rsp) -> messageQueueRenderer.render(Map.of("messages", messageQueueViewService.getLastEntries(20)))); + Spark.get("/public/repartition", (req, rsp) -> { controlProcesses.start("REPARTITION-REINDEX"); return "OK"; }); + Spark.get("/public/:resource", this::serveStatic); + monitors.subscribe(this::logMonitorStateChange); Thread reaperThread = new Thread(this::reapMessageQueue, "message-queue-reaper"); @@ -66,6 +80,16 @@ public class ControlService extends Service { reaperThread.start(); } + + private Object serveStatic(Request request, Response response) { + String resource = request.params("resource"); + + staticResources.serveStatic("control", resource, request, response); + + return ""; + } + + private void reapMessageQueue() { for (;;) { diff --git a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/EventLogService.java b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/EventLogService.java index 842fe86e..9165204d 100644 --- a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/EventLogService.java +++ b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/EventLogService.java @@ -32,7 +32,7 @@ public class EventLogService { while (rs.next()) { entries.add(new EventLogEntry( rs.getString("SERVICE_NAME"), - rs.getString("INSTANCE"), + trimUUID(rs.getString("INSTANCE")), rs.getTimestamp("EVENT_TIME").toLocalDateTime().toLocalTime().toString(), rs.getString("EVENT_TYPE"), rs.getString("EVENT_MESSAGE") @@ -44,6 +44,11 @@ public class EventLogService { throw new RuntimeException(ex); } } - + private String trimUUID(String uuid) { + if (uuid.length() > 8) { + return uuid.substring(0, 8); + } + return uuid; + } } diff --git a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/HeartbeatService.java b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/HeartbeatService.java index d0fd67cb..370fa15d 100644 --- a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/HeartbeatService.java +++ b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/HeartbeatService.java @@ -33,7 +33,7 @@ public class HeartbeatService { heartbeats.add(new ServiceHeartbeat( rs.getString("SERVICE_NAME"), rs.getString("SERVICE_BASE"), - rs.getString("INSTANCE"), + trimUUID(rs.getString("INSTANCE")), rs.getInt("TSDIFF") / 1000., rs.getBoolean("ALIVE") )); @@ -45,4 +45,11 @@ public class HeartbeatService { return heartbeats; } + + private String trimUUID(String uuid) { + if (uuid.length() > 8) { + return uuid.substring(0, 8); + } + return uuid; + } } diff --git a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/MessageQueueViewService.java b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/MessageQueueViewService.java new file mode 100644 index 00000000..9d49c1ed --- /dev/null +++ b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/MessageQueueViewService.java @@ -0,0 +1,67 @@ +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 nu.marginalia.control.model.MessageQueueEntry; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Singleton +public class MessageQueueViewService { + + private final HikariDataSource dataSource; + + @Inject + public MessageQueueViewService(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + public List getLastEntries(int n) { + try (var conn = dataSource.getConnection(); + var query = conn.prepareStatement(""" + SELECT ID, RELATED_ID, SENDER_INBOX, RECIPIENT_INBOX, FUNCTION, OWNER_INSTANCE, OWNER_TICK, STATE, CREATED_TIME, UPDATED_TIME, TTL + FROM PROC_MESSAGE + ORDER BY ID DESC + LIMIT ? + """)) { + + query.setInt(1, n); + List entries = new ArrayList<>(n); + var rs = query.executeQuery(); + while (rs.next()) { + entries.add(new MessageQueueEntry( + rs.getLong("ID"), + rs.getLong("RELATED_ID"), + rs.getString("SENDER_INBOX"), + rs.getString("RECIPIENT_INBOX"), + rs.getString("FUNCTION"), + trimUUID(rs.getString("OWNER_INSTANCE")), + rs.getLong("OWNER_TICK"), + rs.getString("STATE"), + rs.getTimestamp("CREATED_TIME").toLocalDateTime().toLocalTime().toString(), + rs.getTimestamp("UPDATED_TIME").toLocalDateTime().toLocalTime().toString(), + rs.getInt("TTL") + )); + } + return entries; + } + catch (SQLException ex) { + throw new RuntimeException(ex); + } + } + private String trimUUID(String uuid) { + if (null == uuid) { + return ""; + } + + if (uuid.length() > 8) { + return uuid.substring(0, 8); + } + return uuid; + } + +} diff --git a/code/services-satellite/control-service/src/main/java/nu/marginalia/control/model/MessageQueueEntry.java b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/model/MessageQueueEntry.java new file mode 100644 index 00000000..9694ac1e --- /dev/null +++ b/code/services-satellite/control-service/src/main/java/nu/marginalia/control/model/MessageQueueEntry.java @@ -0,0 +1,17 @@ +package nu.marginalia.control.model; + +public record MessageQueueEntry ( + long id, + long relatedId, + String senderInbox, + String recipientInbox, + String function, + String ownerInstance, + long ownerTick, + String state, + String createdTime, + String updatedTime, + int ttl +) +{ +} diff --git a/code/services-satellite/control-service/src/main/resources/static/control/style.css b/code/services-satellite/control-service/src/main/resources/static/control/style.css index 6bd9166e..3df019e5 100644 --- a/code/services-satellite/control-service/src/main/resources/static/control/style.css +++ b/code/services-satellite/control-service/src/main/resources/static/control/style.css @@ -1,4 +1,38 @@ body { font-family: serif; line-height: 1.6; + + display: grid; + grid-template-columns: 20ch auto; + grid-gap: 1em; + grid-template-areas: + "left right"; +} +body > nav { + grid-area: left; +} +nav ul { + list-style-type: none; + padding: 0; +} +nav ul li { + line-height: 2; +} +nav ul li a { + text-decoration: none; + padding: 0.5ch; + display: block; + color: #000; + background-color: #ccc; +} +nav ul li a:focus { + text-decoration: underline; +} +nav ul li a.current { + color: #000; + background-color: #fff; +} + +body > section { + grid-area: right; } \ No newline at end of file diff --git a/code/services-satellite/control-service/src/main/resources/templates/control/events.hdb b/code/services-satellite/control-service/src/main/resources/templates/control/events.hdb new file mode 100644 index 00000000..941c4dea --- /dev/null +++ b/code/services-satellite/control-service/src/main/resources/templates/control/events.hdb @@ -0,0 +1,34 @@ + + + + Control Service + + + + + {{> control/partials/nav}} + +
+

Events

+ + + + + + + + + + {{#each events}} + + + + + + + + {{/each}} +
Service NameInstanceEvent TimeTypeMessage
{{serviceName}}{{instance}}{{eventTime}}{{eventType}}{{eventMessage}}
+
+ + \ No newline at end of file diff --git a/code/services-satellite/control-service/src/main/resources/templates/control/index.hdb b/code/services-satellite/control-service/src/main/resources/templates/control/index.hdb index 701ed915..6ca3119f 100644 --- a/code/services-satellite/control-service/src/main/resources/templates/control/index.hdb +++ b/code/services-satellite/control-service/src/main/resources/templates/control/index.hdb @@ -2,7 +2,7 @@ Control Service - + diff --git a/code/services-satellite/control-service/src/main/resources/templates/control/message-queue.hdb b/code/services-satellite/control-service/src/main/resources/templates/control/message-queue.hdb new file mode 100644 index 00000000..06817679 --- /dev/null +++ b/code/services-satellite/control-service/src/main/resources/templates/control/message-queue.hdb @@ -0,0 +1,47 @@ + + + + Control Service + + + + + {{> control/partials/nav}} + +
+

Events

+ + + + + + + + + + + + + + + + {{#each messages}} + + + + + + + + + + + + + + + {{/each}} +
Message IDRelated IDRecipientSenderFunctionOwner InstanceOwner TickStateCreated TimeUpdated TimeTTL
{{id}}{{relatedId}}{{recipientInbox}}{{senderInbox}}{{function}}{{ownerInstance}}{{ownerTick}}{{state}}{{createdTime}}{{updatedTime}}{{ttl}}
+
+ + \ No newline at end of file diff --git a/code/services-satellite/control-service/src/main/resources/templates/control/partials/nav.hdb b/code/services-satellite/control-service/src/main/resources/templates/control/partials/nav.hdb index 9b68f4b2..98283bfc 100644 --- a/code/services-satellite/control-service/src/main/resources/templates/control/partials/nav.hdb +++ b/code/services-satellite/control-service/src/main/resources/templates/control/partials/nav.hdb @@ -2,6 +2,8 @@ \ No newline at end of file diff --git a/code/services-satellite/control-service/src/main/resources/templates/control/services.hdb b/code/services-satellite/control-service/src/main/resources/templates/control/services.hdb index 5b5febf2..41184eab 100644 --- a/code/services-satellite/control-service/src/main/resources/templates/control/services.hdb +++ b/code/services-satellite/control-service/src/main/resources/templates/control/services.hdb @@ -2,7 +2,7 @@ Control Service - + @@ -24,27 +24,6 @@ {{/each}} - -

Events

- - - - - - - - - - {{#each events}} - - - - - - - - {{/each}} -
Service NameInstanceEvent TimeTypeMessage
{{serviceName}}{{instance}}{{eventTime}}{{eventType}}{{eventMessage}}
\ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fc88dcc3..8490d5a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,6 +98,7 @@ services: container_name: "nginx-gw" ports: - "127.0.0.1:8080:80" + - "127.0.0.1:8081:81" volumes: - "./run/nginx-site.conf:/etc/nginx/conf.d/default.conf" networks: diff --git a/run/nginx-site.conf b/run/nginx-site.conf index 90f93ff9..f9887ad3 100644 --- a/run/nginx-site.conf +++ b/run/nginx-site.conf @@ -33,11 +33,27 @@ server { proxy_pass http://assistant-service:5025/public$request_uri; access_log off; } - location /control/ { - proxy_pass http://control-service:5090/public/; - } location / { proxy_pass http://search-service:5023/public/; } } + + +server { + listen 81; + listen [::]:81; + server_name control; + + proxy_set_header X-Context $remote_addr-$connection; + proxy_set_header X-Extern-Url $scheme://$host$request_uri; + proxy_set_header X-Extern-Domain $scheme://$host; + proxy_set_header X-User-Agent $http_user_agent; + + proxy_set_header X-Public "1"; + + location / { + proxy_pass http://control-service:5090/public/; + } + +} \ No newline at end of file