(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,
|
||||
ControlNodeService controlNodeService,
|
||||
ControlDomainRankingSetsService controlDomainRankingSetsService,
|
||||
ControlActorService controlActorService
|
||||
ControlActorService controlActorService,
|
||||
ControlErrorHandler errorHandler
|
||||
) throws IOException {
|
||||
|
||||
super(params);
|
||||
@ -81,6 +82,8 @@ public class ControlService extends Service {
|
||||
domainComplaintService.register();
|
||||
randomExplorationService.register();
|
||||
|
||||
errorHandler.register();
|
||||
|
||||
var indexRenderer = rendererFactory.renderer("control/index");
|
||||
var eventsRenderer = rendererFactory.renderer("control/sys/events");
|
||||
var serviceByIdRenderer = rendererFactory.renderer("control/sys/service-by-id");
|
||||
@ -106,6 +109,7 @@ public class ControlService extends Service {
|
||||
|
||||
Spark.get("/public/:resource", this::serveStatic);
|
||||
|
||||
|
||||
monitors.subscribe(this::logMonitorStateChange);
|
||||
|
||||
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.zaxxer.hikari.HikariDataSource;
|
||||
import nu.marginalia.control.ControlRendererFactory;
|
||||
import nu.marginalia.control.ControlValidationError;
|
||||
import nu.marginalia.control.Redirects;
|
||||
import nu.marginalia.db.DomainRankingSetsService;
|
||||
import spark.Request;
|
||||
@ -41,6 +42,7 @@ public class ControlDomainRankingSetsService {
|
||||
private Object alterSetModel(Request request, Response response) throws SQLException {
|
||||
final String act = request.queryParams("act");
|
||||
final String id = request.params("id");
|
||||
|
||||
if ("update".equals(act)) {
|
||||
domainRankingSetsService.upsert(new DomainRankingSetsService.DomainRankingSet(
|
||||
id,
|
||||
@ -54,18 +56,26 @@ public class ControlDomainRankingSetsService {
|
||||
else if ("delete".equals(act)) {
|
||||
var model = domainRankingSetsService.get(id).orElseThrow();
|
||||
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);
|
||||
return "";
|
||||
}
|
||||
else if ("create".equals(act)) {
|
||||
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(
|
||||
request.queryParams("name"),
|
||||
request.queryParams("name").toUpperCase(),
|
||||
request.queryParams("description"),
|
||||
DomainRankingSetsService.DomainSetAlgorithm.valueOf(request.queryParams("algorithm")),
|
||||
Integer.parseInt(request.queryParams("depth")),
|
||||
@ -74,7 +84,10 @@ public class ControlDomainRankingSetsService {
|
||||
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) {
|
||||
|
@ -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">
|
||||
<a href="/domain-ranking-sets/new" class="btn btn-primary">New Domain Ranking Set</a>
|
||||
</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>
|
||||
</body>
|
||||
{{> control/partials/foot-includes }}
|
||||
|
@ -11,17 +11,18 @@
|
||||
<form method="post" action="?act=create">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th><label for="name">Name</label></th>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Algorithm</th>
|
||||
<th><label for="algorithm">Algorithm</label></th>
|
||||
<td>
|
||||
<select id="algorithm" name="algorithm">
|
||||
<option value="LINKS_PAGERANK">LINKS_PAGERANK</option>
|
||||
@ -38,7 +39,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th><label for="description">Description</label></th>
|
||||
<td>
|
||||
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
||||
<div>
|
||||
@ -47,15 +48,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Depth</th>
|
||||
<th><label for="depth">Depth</label></th>
|
||||
<td>
|
||||
<input pattern="\d+" type="text" value="{{depth}}" id="depth" name="depth" />
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th colspan="2">Definition</th></tr>
|
||||
<tr><th colspan="2"><label for="definition">Definition</label></th></tr>
|
||||
<tr><td colspan="2">
|
||||
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
||||
<div>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<form method="post" action="?act=update">
|
||||
<table class="table" id="update-form">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th><label for="name">Name</label></th>
|
||||
<td>
|
||||
{{#if special}}<input type="hidden" name="name" value="{{name}}" />{{/if}}
|
||||
<input type="text" value="{{name}}" id="name" name="name" {{#if special}}disabled{{/if}} />
|
||||
@ -23,7 +23,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Algorithm</th>
|
||||
<th><label for="algorithm">Algorithm</label></th>
|
||||
<td>
|
||||
{{#if special}}<input type="hidden" name="algorithm" value="{{algorithm}}" />{{/if}}
|
||||
<select id="algorithm" name="algorithm" {{#if special}}disabled{{/if}}>
|
||||
@ -44,7 +44,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th><label for="description">Description</label></th>
|
||||
<td>
|
||||
{{#if special}}<input type="hidden" name="description" value="{{description}}" />{{/if}}
|
||||
<input type="text" value="{{description}}" id="description" name="description" {{#if special}}disabled{{/if}} />
|
||||
@ -54,15 +54,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Depth</th>
|
||||
<th><label for="depth">Depth</label></th>
|
||||
<td>
|
||||
<input type="text" value="{{depth}}" id="depth" name="depth" />
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><th colspan="2">Definition</th></tr>
|
||||
<tr><th colspan="2"><label for="definition">Definition</label></th></tr>
|
||||
<tr><td colspan="2">
|
||||
<textarea name="definition" id="definition" rows="10" style="width: 100%">{{definition}}</textarea>
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user