(control) UX fixes, node GUI doesn't break when an executor service goes offline.

This commit is contained in:
Viktor Lofgren 2024-01-13 12:17:30 +01:00
parent c0fb9e17e8
commit 8dea7217a6
7 changed files with 45 additions and 32 deletions

View File

@ -1,8 +1,10 @@
package nu.marginalia.executor.client; package nu.marginalia.executor.client;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.reactivex.rxjava3.core.Observable;
import nu.marginalia.client.AbstractDynamicClient; import nu.marginalia.client.AbstractDynamicClient;
import nu.marginalia.client.Context; import nu.marginalia.client.Context;
import nu.marginalia.client.exception.RouteNotConfiguredException;
import nu.marginalia.executor.model.ActorRunStates; import nu.marginalia.executor.model.ActorRunStates;
import nu.marginalia.executor.model.load.LoadParameters; import nu.marginalia.executor.model.load.LoadParameters;
import nu.marginalia.executor.model.transfer.TransferItem; import nu.marginalia.executor.model.transfer.TransferItem;
@ -18,6 +20,8 @@ import java.io.OutputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class ExecutorClient extends AbstractDynamicClient { public class ExecutorClient extends AbstractDynamicClient {
@ -63,11 +67,6 @@ public class ExecutorClient extends AbstractDynamicClient {
post(ctx, node, "/process/adjacency-calculation", "").blockingSubscribe(); post(ctx, node, "/process/adjacency-calculation", "").blockingSubscribe();
} }
public void exportData(Context ctx) {
// post(ctx, node, "/process/adjacency-calculation/", "").blockingSubscribe();
// FIXME this shouldn't be done in the executor
}
public void sideloadEncyclopedia(Context ctx, int node, Path sourcePath, String baseUrl) { public void sideloadEncyclopedia(Context ctx, int node, Path sourcePath, String baseUrl) {
post(ctx, node, post(ctx, node,
"/sideload/encyclopedia?path="+ URLEncoder.encode(sourcePath.toString(), StandardCharsets.UTF_8) + "&baseUrl=" + URLEncoder.encode(baseUrl, StandardCharsets.UTF_8), "/sideload/encyclopedia?path="+ URLEncoder.encode(sourcePath.toString(), StandardCharsets.UTF_8) + "&baseUrl=" + URLEncoder.encode(baseUrl, StandardCharsets.UTF_8),
@ -110,15 +109,33 @@ public class ExecutorClient extends AbstractDynamicClient {
} }
public ActorRunStates getActorStates(Context context, int node) { public ActorRunStates getActorStates(Context context, int node) {
return get(context, node, "/actor", ActorRunStates.class).blockingFirst(); try {
return get(context, node, "/actor", ActorRunStates.class).blockingFirst();
}
catch (RouteNotConfiguredException ex) {
// node is down, return dummy data
return new ActorRunStates(node, new ArrayList<>());
}
} }
public UploadDirContents listSideloadDir(Context context, int node) { public UploadDirContents listSideloadDir(Context context, int node) {
return get(context, node, "/sideload/", UploadDirContents.class).blockingFirst(); try {
return get(context, node, "/sideload/", UploadDirContents.class).blockingFirst();
}
catch (RouteNotConfiguredException ex) {
// node is down, return dummy data
return new UploadDirContents("/", new ArrayList<>());
}
} }
public FileStorageContent listFileStorage(Context context, int node, FileStorageId fileId) { public FileStorageContent listFileStorage(Context context, int node, FileStorageId fileId) {
return get(context, node, "/storage/"+fileId.id(), FileStorageContent.class).blockingFirst(); try {
return get(context, node, "/storage/"+fileId.id(), FileStorageContent.class).blockingFirst();
}
catch (RouteNotConfiguredException ex) {
// node is down, return dummy data
return new FileStorageContent(new ArrayList<>());
}
} }
public void transferFile(Context context, int node, FileStorageId fileId, String path, OutputStream destOutputStream) { public void transferFile(Context context, int node, FileStorageId fileId, String path, OutputStream destOutputStream) {

View File

@ -9,4 +9,7 @@ public record NodeConfiguration(int node,
boolean disabled boolean disabled
) )
{ {
public int getId() {
return node;
}
} }

View File

@ -1,4 +0,0 @@
package nu.marginalia.control.node.model;
public record IndexNode(int id) {
}

View File

@ -183,7 +183,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("storage", true), "tab", Map.of("storage", true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"view", Map.of("specs", true) "view", Map.of("specs", true)
); );
} }
@ -193,7 +193,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("actors", true), "tab", Map.of("actors", true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"actors", executorClient.getActorStates(Context.fromRequest(request), nodeId).states() "actors", executorClient.getActorStates(Context.fromRequest(request), nodeId).states()
); );
} }
@ -203,7 +203,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("actions", true), "tab", Map.of("actions", true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"view", Map.of(request.queryParams("view"), true), "view", Map.of(request.queryParams("view"), true),
"uploadDirContents", executorClient.listSideloadDir(Context.fromRequest(request), nodeId), "uploadDirContents", executorClient.listSideloadDir(Context.fromRequest(request), nodeId),
"allBackups", "allBackups",
@ -223,7 +223,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("storage", true), "tab", Map.of("storage", true),
"view", Map.of("conf", true), "view", Map.of("conf", true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"storagebase", getStorageBaseList(nodeId) "storagebase", getStorageBaseList(nodeId)
); );
} }
@ -245,7 +245,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("storage", true), "tab", Map.of("storage", true),
"view", Map.of(view, true), "view", Map.of(view, true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"storage", makeFileStorageBaseWithStorage(getFileStorageIds(type, nodeId)) "storage", makeFileStorageBaseWithStorage(getFileStorageIds(type, nodeId))
); );
} }
@ -266,7 +266,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("storage", true), "tab", Map.of("storage", true),
"view", Map.of(view, true), "view", Map.of(view, true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"storage", storage "storage", storage
); );
} }
@ -284,7 +284,7 @@ public class ControlNodeService {
return Map.of( return Map.of(
"tab", Map.of("config", true), "tab", Map.of("config", true),
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"config", Objects.requireNonNull(nodeConfigurationService.get(nodeId), "Failed to fetch configuration"), "config", Objects.requireNonNull(nodeConfigurationService.get(nodeId), "Failed to fetch configuration"),
"storage", storage); "storage", storage);
} }
@ -325,7 +325,7 @@ public class ControlNodeService {
actors.removeIf(actor -> actor.state().equals("MONITOR")); actors.removeIf(actor -> actor.state().equals("MONITOR"));
return Map.of( return Map.of(
"node", new IndexNode(nodeId), "node", nodeConfigurationService.get(nodeId),
"status", getStatus(config), "status", getStatus(config),
"events", getEvents(nodeId), "events", getEvents(nodeId),
"processes", heartbeatService.getProcessHeartbeatsForNode(nodeId), "processes", heartbeatService.getProcessHeartbeatsForNode(nodeId),

View File

@ -72,7 +72,6 @@ public class ControlSysActionsService {
Spark.post("/public/actions/recrawl-all", this::recrawlAll, Redirects.redirectToOverview); Spark.post("/public/actions/recrawl-all", this::recrawlAll, Redirects.redirectToOverview);
Spark.post("/public/actions/flush-api-caches", this::flushApiCaches, Redirects.redirectToOverview); Spark.post("/public/actions/flush-api-caches", this::flushApiCaches, Redirects.redirectToOverview);
Spark.post("/public/actions/reload-blogs-list", this::reloadBlogsList, Redirects.redirectToOverview); Spark.post("/public/actions/reload-blogs-list", this::reloadBlogsList, Redirects.redirectToOverview);
Spark.post("/public/actions/trigger-data-exports", this::triggerDataExports, Redirects.redirectToOverview);
} }
@SneakyThrows @SneakyThrows
@ -99,14 +98,6 @@ public class ControlSysActionsService {
return Map.of("precessionNodes", eligibleNodes); return Map.of("precessionNodes", eligibleNodes);
} }
public Object triggerDataExports(Request request, Response response) throws Exception {
eventLog.logEvent("USER-ACTION", "EXPORT-DATA");
executorClient.exportData(Context.fromRequest(request));
return "";
}
public Object reloadBlogsList(Request request, Response response) throws Exception { public Object reloadBlogsList(Request request, Response response) throws Exception {
eventLog.logEvent("USER-ACTION", "RELOAD-BLOGS-LIST"); eventLog.logEvent("USER-ACTION", "RELOAD-BLOGS-LIST");

View File

@ -10,8 +10,8 @@
This will perform a new crawl on node {{node.id}} based on the crawl spec you select below. This will perform a new crawl on node {{node.id}} based on the crawl spec you select below.
Additional specifications can be created <a href="/nodes/{{node.id}}/storage/new-specs">with this form</a>. Additional specifications can be created <a href="/nodes/{{node.id}}/storage/new-specs">with this form</a>.
</div> </div>
<div class="my-3 p-3 border text-danger"> <div class="my-3 p-3 border">
<p><strong>IMPORTANT!</strong> Be sure you've read and understood the <p><em class="text-danger">IMPORTANT!</em> Be sure you've read and understood the
<a href="https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/doc/crawling.md">crawling documentation</a> <a href="https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/doc/crawling.md">crawling documentation</a>
before you begin a crawl. You will be accessing real servers from your connection, and you may end up on IP greylists before you begin a crawl. You will be accessing real servers from your connection, and you may end up on IP greylists
that temporarily block your access to those servers for up to a few weeks; on rare occasions permanently. The crawler that temporarily block your access to those servers for up to a few weeks; on rare occasions permanently. The crawler

View File

@ -1,5 +1,11 @@
<h1 class="my-3">Index Node {{node.id}}</h1> <h1 class="my-3">Index Node {{node.id}}</h1>
{{#if node.disabled}}
<small class="text-danger">This index node is disabled!</small>
{{/if}}
{{#unless node.acceptQueries}}
<small class="text-danger">This index node is not accepting queries!</small>
{{/unless}}
<nav class="nav nav-tabs"> <nav class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{#if tab.overview}}active{{/if}}" href="/nodes/{{node.id}}/">Overview</a> <a class="nav-link {{#if tab.overview}}active{{/if}}" href="/nodes/{{node.id}}/">Overview</a>
@ -30,4 +36,4 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{#if tab.config}}active{{/if}}" href="/nodes/{{node.id}}/configuration">Configuration</a> <a class="nav-link {{#if tab.config}}active{{/if}}" href="/nodes/{{node.id}}/configuration">Configuration</a>
</li> </li>
</nav> </nav>