(control) UX fixes, node GUI doesn't break when an executor service goes offline.
This commit is contained in:
parent
c0fb9e17e8
commit
8dea7217a6
@ -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) {
|
||||||
|
@ -9,4 +9,7 @@ public record NodeConfiguration(int node,
|
|||||||
boolean disabled
|
boolean disabled
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
public int getId() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package nu.marginalia.control.node.model;
|
|
||||||
|
|
||||||
public record IndexNode(int id) {
|
|
||||||
}
|
|
@ -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),
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user