mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 04:58:59 +00:00
(control) Clean up UX and accessibility for new domain ranking sets.
The change also adds basic support for error messages in the GUI.
This commit is contained in:
parent
2fe5705542
commit
7fd4c092e3
@ -54,7 +54,8 @@ public class ControlService extends Service {
|
|||||||
DataSetsService dataSetsService,
|
DataSetsService dataSetsService,
|
||||||
ControlNodeService controlNodeService,
|
ControlNodeService controlNodeService,
|
||||||
ControlDomainRankingSetsService controlDomainRankingSetsService,
|
ControlDomainRankingSetsService controlDomainRankingSetsService,
|
||||||
ControlActorService controlActorService
|
ControlActorService controlActorService,
|
||||||
|
ControlErrorHandler errorHandler
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
||||||
super(params);
|
super(params);
|
||||||
@ -81,6 +82,8 @@ public class ControlService extends Service {
|
|||||||
domainComplaintService.register();
|
domainComplaintService.register();
|
||||||
randomExplorationService.register();
|
randomExplorationService.register();
|
||||||
|
|
||||||
|
errorHandler.register();
|
||||||
|
|
||||||
var indexRenderer = rendererFactory.renderer("control/index");
|
var indexRenderer = rendererFactory.renderer("control/index");
|
||||||
var eventsRenderer = rendererFactory.renderer("control/sys/events");
|
var eventsRenderer = rendererFactory.renderer("control/sys/events");
|
||||||
var serviceByIdRenderer = rendererFactory.renderer("control/sys/service-by-id");
|
var serviceByIdRenderer = rendererFactory.renderer("control/sys/service-by-id");
|
||||||
@ -106,6 +109,7 @@ public class ControlService extends Service {
|
|||||||
|
|
||||||
Spark.get("/public/:resource", this::serveStatic);
|
Spark.get("/public/:resource", this::serveStatic);
|
||||||
|
|
||||||
|
|
||||||
monitors.subscribe(this::logMonitorStateChange);
|
monitors.subscribe(this::logMonitorStateChange);
|
||||||
|
|
||||||
controlActorService.startDefaultActors();
|
controlActorService.startDefaultActors();
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package nu.marginalia.control;
|
||||||
|
|
||||||
|
public class ControlValidationError extends RuntimeException {
|
||||||
|
public final String title;
|
||||||
|
public final String messageLong;
|
||||||
|
public final String redirect;
|
||||||
|
|
||||||
|
public ControlValidationError(String title, String messageLong, String redirect) {
|
||||||
|
super(title);
|
||||||
|
|
||||||
|
this.title = title;
|
||||||
|
this.messageLong = messageLong;
|
||||||
|
this.redirect = redirect;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package nu.marginalia.control.sys.svc;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import nu.marginalia.control.ControlRendererFactory;
|
import nu.marginalia.control.ControlRendererFactory;
|
||||||
|
import nu.marginalia.control.ControlValidationError;
|
||||||
import nu.marginalia.control.Redirects;
|
import nu.marginalia.control.Redirects;
|
||||||
import nu.marginalia.db.DomainRankingSetsService;
|
import nu.marginalia.db.DomainRankingSetsService;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
@ -41,6 +42,7 @@ public class ControlDomainRankingSetsService {
|
|||||||
private Object alterSetModel(Request request, Response response) throws SQLException {
|
private Object alterSetModel(Request request, Response response) throws SQLException {
|
||||||
final String act = request.queryParams("act");
|
final String act = request.queryParams("act");
|
||||||
final String id = request.params("id");
|
final String id = request.params("id");
|
||||||
|
|
||||||
if ("update".equals(act)) {
|
if ("update".equals(act)) {
|
||||||
domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet(
|
domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet(
|
||||||
id,
|
id,
|
||||||
@ -54,18 +56,26 @@ public class ControlDomainRankingSetsService {
|
|||||||
else if ("delete".equals(act)) {
|
else if ("delete".equals(act)) {
|
||||||
var model = domainRankingSetsService.get(id).orElseThrow();
|
var model = domainRankingSetsService.get(id).orElseThrow();
|
||||||
if (model.isSpecial()) {
|
if (model.isSpecial()) {
|
||||||
throw new IllegalArgumentException("Cannot delete special ranking set");
|
throw new ControlValidationError("Cannot delete special ranking set",
|
||||||
|
"""
|
||||||
|
SPECIAL data sets are reserved by the system and can not be deleted.
|
||||||
|
""",
|
||||||
|
"/domain-ranking-sets");
|
||||||
}
|
}
|
||||||
domainRankingSetsService.delete(model);
|
domainRankingSetsService.delete(model);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
else if ("create".equals(act)) {
|
else if ("create".equals(act)) {
|
||||||
if (domainRankingSetsService.get(request.queryParams("name")).isPresent()) {
|
if (domainRankingSetsService.get(request.queryParams("name")).isPresent()) {
|
||||||
throw new IllegalArgumentException("Ranking set with that name already exists");
|
throw new ControlValidationError("Ranking set with that name already exists",
|
||||||
|
"""
|
||||||
|
Ensure the new data set has a unique name and try again.
|
||||||
|
""",
|
||||||
|
"/domain-ranking-sets");
|
||||||
}
|
}
|
||||||
|
|
||||||
domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet(
|
domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet(
|
||||||
request.queryParams("name"),
|
request.queryParams("name").toUpperCase(),
|
||||||
request.queryParams("description"),
|
request.queryParams("description"),
|
||||||
DomainRankingSetsService.DomainSetAlgorithm.valueOf(request.queryParams("algorithm")),
|
DomainRankingSetsService.DomainSetAlgorithm.valueOf(request.queryParams("algorithm")),
|
||||||
Integer.parseInt(request.queryParams("depth")),
|
Integer.parseInt(request.queryParams("depth")),
|
||||||
@ -74,7 +84,10 @@ public class ControlDomainRankingSetsService {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
throw new ControlValidationError("Unknown action", """
|
||||||
|
An unknown action was requested and the system does not understand how to act on it.
|
||||||
|
""",
|
||||||
|
"/domain-ranking-sets");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object rankingSetsModel(Request request, Response response) {
|
private Object rankingSetsModel(Request request, Response response) {
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package nu.marginalia.control.sys.svc;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import nu.marginalia.control.ControlRendererFactory;
|
||||||
|
import nu.marginalia.control.ControlValidationError;
|
||||||
|
import spark.Request;
|
||||||
|
import spark.Response;
|
||||||
|
import spark.Spark;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ControlErrorHandler {
|
||||||
|
private final ControlRendererFactory.Renderer renderer;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ControlErrorHandler(ControlRendererFactory rendererFactory) {
|
||||||
|
this.renderer = rendererFactory.renderer("control/error");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(ControlValidationError error, Request request, Response response) {
|
||||||
|
String text = renderer.render(
|
||||||
|
Map.of(
|
||||||
|
"title", error.title,
|
||||||
|
"messageLong", error.messageLong,
|
||||||
|
"redirect", error.redirect
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
response.body(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
Spark.exception(ControlValidationError.class, this::render);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Control Service: Error</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
{{> control/partials/head-includes }}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{> control/partials/nav}}
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="my-3">Error: {{title}}</h1>
|
||||||
|
<div class="my-3 p-3 border bg-light">
|
||||||
|
<p>{{messageLong}}</p>
|
||||||
|
<a href="{{redirect}}">Go back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
{{> control/partials/foot-includes }}
|
||||||
|
<script>
|
||||||
|
window.setInterval(() => {
|
||||||
|
refresh(["processes", "services", "jobs", "events"]);
|
||||||
|
}, 2000);
|
||||||
|
</script>
|
||||||
|
</html>
|
@ -32,6 +32,21 @@
|
|||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<a href="/domain-ranking-sets/new" class="btn btn-primary">New Domain Ranking Set</a>
|
<a href="/domain-ranking-sets/new" class="btn btn-primary">New Domain Ranking Set</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="border my-3 p-3 bg-light">
|
||||||
|
<p>Several reserved ranking sets are available for use in the query parameters.</p>
|
||||||
|
<dl>
|
||||||
|
<dt>NONE</dt><dd>Placeholder for no restriction on the domains returned.
|
||||||
|
Does nothing, and exists only to prevent a new ranking
|
||||||
|
set from being created with this name.</dd>
|
||||||
|
<dt>RANK</dt><dd>Used to calculate the domain ranking for a given domain.
|
||||||
|
This affects the order they are stored in the index, and increases the odds they'll
|
||||||
|
even be considered within the time restrictions of the query.</dd>
|
||||||
|
<dt>BLOGS</dt><dd>Returns a fixed list of domains, configurable in <a href="/datasets">Datasets</a>.
|
||||||
|
Changes to this list will not be reflected in the index until the next time the index is rebuilt.</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
{{> control/partials/foot-includes }}
|
{{> control/partials/foot-includes }}
|
||||||
|
@ -11,17 +11,18 @@
|
|||||||
<form method="post" action="?act=create">
|
<form method="post" action="?act=create">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th><label for="name">Name</label></th>
|
||||||
<td>
|
<td>
|
||||||
<input pattern="\w+" type="text" value="{{name}}" id="name" name="name" />
|
<input pattern="\w+" type="text" value="{{name}}" id="name" name="name" style="text-transform: uppercase" />
|
||||||
<div>
|
<div>
|
||||||
<small class="text-muted">The name is how the ranking set is identified in the query parameters,
|
<small class="text-muted">Must be all letters.
|
||||||
|
The name is how the ranking set is identified in the query parameters,
|
||||||
and also decides the file name of the persisted ranking set definition. Keep it simple.</small>
|
and also decides the file name of the persisted ranking set definition. Keep it simple.</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Algorithm</th>
|
<th><label for="algorithm">Algorithm</label></th>
|
||||||
<td>
|
<td>
|
||||||
<select id="algorithm" name="algorithm">
|
<select id="algorithm" name="algorithm">
|
||||||
<option value="LINKS_PAGERANK">LINKS_PAGERANK</option>
|
<option value="LINKS_PAGERANK">LINKS_PAGERANK</option>
|
||||||
@ -38,7 +39,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th><label for="description">Description</label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
||||||
<div>
|
<div>
|
||||||
@ -47,15 +48,15 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Depth</th>
|
<th><label for="depth">Depth</label></th>
|
||||||
<td>
|
<td>
|
||||||
<input pattern="\d+" type="text" value="{{depth}}" id="depth" name="depth" />
|
<input pattern="\d+" type="text" value="{{depth}}" id="depth" name="depth" />
|
||||||
<div>
|
<div>
|
||||||
<small class="text-muted">Up to this number of domains are ranked, the rest are excluded.</small>
|
<small class="text-muted">Number. Up to this number of domains are ranked, the rest are excluded.</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><th colspan="2">Definition</th></tr>
|
<tr><th colspan="2"><label for="definition">Definition</label></th></tr>
|
||||||
<tr><td colspan="2">
|
<tr><td colspan="2">
|
||||||
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
||||||
<div>
|
<div>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<form method="post" action="?act=update">
|
<form method="post" action="?act=update">
|
||||||
<table class="table" id="update-form">
|
<table class="table" id="update-form">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th><label for="name">Name</label></th>
|
||||||
<td>
|
<td>
|
||||||
{{#if special}}<input type="hidden" name="name" value="{{name}}" />{{/if}}
|
{{#if special}}<input type="hidden" name="name" value="{{name}}" />{{/if}}
|
||||||
<input type="text" value="{{name}}" id="name" name="name" {{#if special}}disabled{{/if}} />
|
<input type="text" value="{{name}}" id="name" name="name" {{#if special}}disabled{{/if}} />
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Algorithm</th>
|
<th><label for="algorithm">Algorithm</label></th>
|
||||||
<td>
|
<td>
|
||||||
{{#if special}}<input type="hidden" name="algorithm" value="{{algorithm}}" />{{/if}}
|
{{#if special}}<input type="hidden" name="algorithm" value="{{algorithm}}" />{{/if}}
|
||||||
<select id="algorithm" name="algorithm" {{#if special}}disabled{{/if}}>
|
<select id="algorithm" name="algorithm" {{#if special}}disabled{{/if}}>
|
||||||
@ -44,7 +44,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th><label for="description">Description</label></th>
|
||||||
<td>
|
<td>
|
||||||
{{#if special}}<input type="hidden" name="description" value="{{description}}" />{{/if}}
|
{{#if special}}<input type="hidden" name="description" value="{{description}}" />{{/if}}
|
||||||
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
||||||
@ -54,15 +54,15 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Depth</th>
|
<th><label for="depth">Depth</label></th>
|
||||||
<td>
|
<td>
|
||||||
<input type="text" value="{{depth}}" id="depth" name="depth" />
|
<input type="text" value="{{depth}}" id="depth" name="depth" />
|
||||||
<div>
|
<div>
|
||||||
<small class="text-muted">Up to this number of domains are ranked, the rest are excluded.</small>
|
<small class="text-muted">Number. Up to this number of domains are ranked, the rest are excluded.</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><th colspan="2">Definition</th></tr>
|
<tr><th colspan="2"><label for="definition">Definition</label></th></tr>
|
||||||
<tr><td colspan="2">
|
<tr><td colspan="2">
|
||||||
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
Reference in New Issue
Block a user