diff --git a/code/common/config/src/main/java/nu/marginalia/storage/FileStorageService.java b/code/common/config/src/main/java/nu/marginalia/storage/FileStorageService.java index bec2a240..51776da3 100644 --- a/code/common/config/src/main/java/nu/marginalia/storage/FileStorageService.java +++ b/code/common/config/src/main/java/nu/marginalia/storage/FileStorageService.java @@ -215,10 +215,10 @@ public class FileStorageService { return null; } public FileStorageBase createStorageBase(String name, Path path, FileStorageBaseType type) throws SQLException, FileNotFoundException { + return createStorageBase(name, path, node, type); + } - if (!Files.exists(path)) { - throw new FileNotFoundException("Storage base path does not exist: " + path); - } + public FileStorageBase createStorageBase(String name, Path path, int node, FileStorageBaseType type) throws SQLException, FileNotFoundException { try (var conn = dataSource.getConnection(); var stmt = conn.prepareStatement(""" @@ -238,7 +238,6 @@ public class FileStorageService { return getStorageBase(type); } - @SneakyThrows private Path allocateDirectory(Path basePath, String prefix) throws IOException { LocalDateTime now = LocalDateTime.now(); diff --git a/code/services-core/control-service/src/main/java/nu/marginalia/control/node/svc/ControlNodeService.java b/code/services-core/control-service/src/main/java/nu/marginalia/control/node/svc/ControlNodeService.java index 5375ee05..ecc68392 100644 --- a/code/services-core/control-service/src/main/java/nu/marginalia/control/node/svc/ControlNodeService.java +++ b/code/services-core/control-service/src/main/java/nu/marginalia/control/node/svc/ControlNodeService.java @@ -25,6 +25,7 @@ import spark.Request; import spark.Response; import spark.Spark; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -64,6 +65,7 @@ public class ControlNodeService { } public void register() throws IOException { + var nodeListRenderer = rendererFactory.renderer("control/node/nodes-list"); var overviewRenderer = rendererFactory.renderer("control/node/node-overview"); var actionsRenderer = rendererFactory.renderer("control/node/node-actions"); var actorsRenderer = rendererFactory.renderer("control/node/node-actors"); @@ -74,6 +76,8 @@ public class ControlNodeService { var newSpecsFormRenderer = rendererFactory.renderer("control/node/node-new-specs-form"); + Spark.get("/public/nodes", this::nodeListModel, nodeListRenderer::render); + Spark.post("/public/nodes", this::createNode); Spark.get("/public/nodes/:id", this::nodeOverviewModel, overviewRenderer::render); Spark.get("/public/nodes/:id/", this::nodeOverviewModel, overviewRenderer::render); Spark.get("/public/nodes/:id/actors", this::nodeActorsModel, actorsRenderer::render); @@ -102,6 +106,27 @@ public class ControlNodeService { } + private Object createNode(Request request, Response response) throws SQLException, FileNotFoundException { + var newConfig = nodeConfigurationService.create(request.queryParams("description"), "on".equalsIgnoreCase(request.queryParams("acceptQueries"))); + int id = newConfig.node(); + + fileStorageService.createStorageBase("Index Data", Path.of("/idx"), id, FileStorageBaseType.CURRENT); + fileStorageService.createStorageBase("Index Backups", Path.of("/backup"), id, FileStorageBaseType.BACKUP); + fileStorageService.createStorageBase("Crawl Data", Path.of("/storage"), id, FileStorageBaseType.STORAGE); + fileStorageService.createStorageBase("Work Area", Path.of("/work"), id, FileStorageBaseType.WORK); + + return redirectToOverview(id); + } + + private Object nodeListModel(Request request, Response response) throws SQLException { + var configs = nodeConfigurationService.getAll(); + + int nextId = configs.stream().mapToInt(NodeConfiguration::node).map(i -> i+1).max().orElse(1); + + return Map.of("nodes", nodeConfigurationService.getAll(), + "nextNodeId", nextId); + } + private Object triggerCrawl(Request request, Response response) throws Exception { int nodeId = Integer.parseInt(request.params("id")); @@ -117,10 +142,14 @@ public class ControlNodeService { return redirectToOverview(request); } + @SneakyThrows + public String redirectToOverview(int nodeId) { + return new Redirects.HtmlRedirect("/nodes/"+nodeId).render(null); + } @SneakyThrows public String redirectToOverview(Request request) { - return new Redirects.HtmlRedirect("/nodes/"+request.params("id")).render(null); + return redirectToOverview(Integer.parseInt(request.params("id"))); } private Object createNewSpecsAction(Request request, Response response) { diff --git a/code/services-core/control-service/src/main/resources/templates/control/node/nodes-list.hdb b/code/services-core/control-service/src/main/resources/templates/control/node/nodes-list.hdb new file mode 100644 index 00000000..9e1f14c9 --- /dev/null +++ b/code/services-core/control-service/src/main/resources/templates/control/node/nodes-list.hdb @@ -0,0 +1,62 @@ + + +{{> control/partials/head-includes }} +Control Service + +{{> control/partials/nav}} + +
+

Index Nodes

+ + {{#unless nodes}} + It appears no nodes have been configured! This is necessary before any index or executor services + can be started. At least a single node needs to be configured to serve search queries. + {{/unless}} + + {{#if nodes}} + + + + + + + {{#each nodes}} + + + + + + {{/each}} + {{/if}} +
Node IDDescriptionAccept Queries
node-{{node}}{{description}}{{acceptQueries}}
+ + +
+

Add Node

+
+
+ + +
+ +
+ + +
+ +
+ + + +
Sets whether queries will be routed to this node. This can be modified later.
+
+ + +
+
+ +
+ + +{{> control/partials/foot-includes }} + \ No newline at end of file diff --git a/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb b/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb index 8e84ce49..234136e1 100644 --- a/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb +++ b/code/services-core/control-service/src/main/resources/templates/control/partials/nav.hdb @@ -21,6 +21,7 @@