mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 13:09:00 +00:00
(control) New view for domains
Still a work in progress, but at this point it's possible to use for viewing domains
This commit is contained in:
parent
2f38c95886
commit
74e25370ca
@ -2,16 +2,18 @@ package nu.marginalia.control;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.inject.Inject;
|
||||
import nu.marginalia.service.ServiceMonitors;
|
||||
import nu.marginalia.control.actor.ControlActorService;
|
||||
import nu.marginalia.control.app.svc.*;
|
||||
import nu.marginalia.control.node.svc.ControlNodeActionsService;
|
||||
import nu.marginalia.control.node.svc.ControlFileStorageService;
|
||||
import nu.marginalia.control.node.svc.ControlNodeActionsService;
|
||||
import nu.marginalia.control.node.svc.ControlNodeService;
|
||||
import nu.marginalia.control.sys.svc.*;
|
||||
import nu.marginalia.model.gson.GsonFactory;
|
||||
import nu.marginalia.screenshot.ScreenshotService;
|
||||
import nu.marginalia.service.server.*;
|
||||
import nu.marginalia.service.ServiceMonitors;
|
||||
import nu.marginalia.service.server.BaseServiceParams;
|
||||
import nu.marginalia.service.server.Service;
|
||||
import nu.marginalia.service.server.StaticResources;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import spark.Request;
|
||||
@ -19,7 +21,7 @@ import spark.Response;
|
||||
import spark.Spark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
|
||||
public class ControlService extends Service {
|
||||
|
||||
@ -56,6 +58,7 @@ public class ControlService extends Service {
|
||||
ControlDomainRankingSetsService controlDomainRankingSetsService,
|
||||
ControlActorService controlActorService,
|
||||
AbortedProcessService abortedProcessService,
|
||||
DomainsManagementService domainsManagementService,
|
||||
ControlErrorHandler errorHandler
|
||||
) throws IOException {
|
||||
|
||||
@ -84,6 +87,7 @@ public class ControlService extends Service {
|
||||
apiKeyService.register();
|
||||
domainComplaintService.register();
|
||||
randomExplorationService.register();
|
||||
domainsManagementService.register();
|
||||
|
||||
errorHandler.register();
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
package nu.marginalia.control.app.model;
|
||||
|
||||
public record DomainModel(int id,
|
||||
String name,
|
||||
String ip,
|
||||
int nodeAffinity,
|
||||
double rank,
|
||||
boolean blacklisted) {
|
||||
public boolean isIndexed() {
|
||||
return nodeAffinity > 0;
|
||||
}
|
||||
|
||||
public DomainAffinityState getAffinityState() {
|
||||
if (nodeAffinity < 0) {
|
||||
return DomainAffinityState.Known;
|
||||
}
|
||||
else if (nodeAffinity == 0) {
|
||||
return DomainAffinityState.Scheduled;
|
||||
}
|
||||
else {
|
||||
return DomainAffinityState.Assigned;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DomainAffinityState {
|
||||
Assigned("The domain has been assigned to a node."),
|
||||
Scheduled("The domain will be assigned to the next crawling node."),
|
||||
Known("The domain is known but not yet scheduled for crawling.");
|
||||
|
||||
private final String desc;
|
||||
DomainAffinityState(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package nu.marginalia.control.app.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public record DomainSearchResultModel(String query,
|
||||
String affinity,
|
||||
String field,
|
||||
Map<String, Boolean> selectedAffinity,
|
||||
Map<String, Boolean> selectedField,
|
||||
int page,
|
||||
boolean hasNext,
|
||||
boolean hasPrevious,
|
||||
List<DomainModel> results)
|
||||
{
|
||||
public Integer getNextPage() {
|
||||
if (!hasNext) return null;
|
||||
return page + 1;
|
||||
}
|
||||
|
||||
public Integer getPreviousPage() {
|
||||
if (!hasPrevious) return null;
|
||||
return page - 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package nu.marginalia.control.app.svc;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import nu.marginalia.control.ControlRendererFactory;
|
||||
import nu.marginalia.control.app.model.DomainModel;
|
||||
import nu.marginalia.control.app.model.DomainSearchResultModel;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Spark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DomainsManagementService {
|
||||
|
||||
private final HikariDataSource dataSource;
|
||||
private final ControlRendererFactory rendererFactory;
|
||||
|
||||
@Inject
|
||||
public DomainsManagementService(HikariDataSource dataSource,
|
||||
ControlRendererFactory rendererFactory
|
||||
) {
|
||||
this.dataSource = dataSource;
|
||||
this.rendererFactory = rendererFactory;
|
||||
}
|
||||
|
||||
public void register() throws IOException {
|
||||
|
||||
var domainsViewRenderer = rendererFactory.renderer("control/app/domains");
|
||||
|
||||
Spark.get("/domains", this::getDomains, domainsViewRenderer::render);
|
||||
|
||||
}
|
||||
|
||||
private DomainSearchResultModel getDomains(Request request, Response response) throws SQLException {
|
||||
List<DomainModel> ret = new ArrayList<>();
|
||||
|
||||
String filterRaw = Objects.requireNonNullElse(request.queryParams("filter"), "*");
|
||||
|
||||
String filter;
|
||||
if (filterRaw.isBlank()) filter = "%";
|
||||
else filter = filterRaw.replace('*', '%');
|
||||
|
||||
int page = Integer.parseInt(Objects.requireNonNullElse(request.queryParams("page"), "0"));
|
||||
boolean hasMore = false;
|
||||
int count = 10;
|
||||
|
||||
String field = Objects.requireNonNullElse(request.queryParams("field"), "domain");
|
||||
Map<String, Boolean> selectedField = Map.of(field, true);
|
||||
|
||||
String affinity = Objects.requireNonNullElse(request.queryParams("affinity"), "all");
|
||||
Map<String, Boolean> selectedAffinity = Map.of(affinity, true);
|
||||
|
||||
|
||||
try (var conn = dataSource.getConnection();
|
||||
var stmt = conn.prepareStatement("""
|
||||
SELECT EC_DOMAIN.ID,
|
||||
DOMAIN_NAME,
|
||||
NODE_AFFINITY,
|
||||
`RANK`,
|
||||
IP,
|
||||
EC_DOMAIN_BLACKLIST.URL_DOMAIN IS NOT NULL AS BLACKLISTED
|
||||
FROM WMSA_prod.EC_DOMAIN
|
||||
LEFT JOIN WMSA_prod.EC_DOMAIN_BLACKLIST ON DOMAIN_NAME = EC_DOMAIN_BLACKLIST.URL_DOMAIN
|
||||
|
||||
"""
|
||||
+ // listen, I wouldn't worry about it
|
||||
(switch (field) {
|
||||
case "domain" -> "WHERE DOMAIN_NAME LIKE ?";
|
||||
case "ip" -> "WHERE IP LIKE ?";
|
||||
case "id" -> "WHERE EC_DOMAIN.ID = ?";
|
||||
default -> "WHERE DOMAIN_NAME LIKE ?";
|
||||
})
|
||||
+ " " +
|
||||
(switch (affinity) {
|
||||
case "assigned" -> "AND NODE_AFFINITY > 0";
|
||||
case "scheduled" -> "AND NODE_AFFINITY = 0";
|
||||
case "known" -> "AND NODE_AFFINITY < 0";
|
||||
default -> "";
|
||||
}) +
|
||||
|
||||
"""
|
||||
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
"""
|
||||
))
|
||||
{
|
||||
stmt.setString(1, filter);
|
||||
stmt.setInt(2, count + 1);
|
||||
stmt.setInt(3, count * page);
|
||||
|
||||
try (var rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
if (ret.size() == count) {
|
||||
hasMore = true;
|
||||
break;
|
||||
}
|
||||
ret.add(new DomainModel(
|
||||
rs.getInt("ID"),
|
||||
rs.getString("DOMAIN_NAME"),
|
||||
rs.getString("IP"),
|
||||
rs.getInt("NODE_AFFINITY"),
|
||||
Math.round(100 * rs.getDouble("RANK"))/100.,
|
||||
rs.getBoolean("BLACKLISTED")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DomainSearchResultModel(filterRaw,
|
||||
affinity,
|
||||
field,
|
||||
selectedAffinity,
|
||||
selectedField,
|
||||
page,
|
||||
hasMore,
|
||||
page > 0,
|
||||
ret);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Control Service</title>
|
||||
{{> control/partials/head-includes }}
|
||||
</head>
|
||||
<body>
|
||||
{{> control/partials/nav}}
|
||||
<div class="container">
|
||||
<h1 class="my-3">Domains</h1>
|
||||
|
||||
<table class="table">
|
||||
<form method="get">
|
||||
<tr>
|
||||
<td>
|
||||
<select name="field" class="form-select" aria-label="Select Field">
|
||||
<option value="domain" {{#if selectedField.domain}}selected{{/if}}>Domain Name</option>
|
||||
<option value="id" {{#if selectedField.id}}selected{{/if}}>Domain ID</option>
|
||||
<option value="ip" {{#if selectedField.ip}}selected{{/if}}>IP</option>
|
||||
</select>
|
||||
</td>
|
||||
<td colspan="3"><input type="text" name="filter" class="form-control" placeholder="Domain" value="{{query}}"></td>
|
||||
<td>
|
||||
<select name="affinity" class="form-select" aria-label="Select Node Affinity">
|
||||
<option value="all" {{#if selectedAffinity.all}}selected{{/if}}>-</option>
|
||||
<option value="known" {{#if selectedAffinity.known}}selected{{/if}}>Known</option>
|
||||
<option value="scheduled" {{#if selectedAffinity.scheduled}}selected{{/if}}>Scheduled</option>
|
||||
<option value="assigned" {{#if selectedAffinity.assigned}}selected{{/if}}>Assigned</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><button type="submit" class="btn btn-primary">Search</button></td>
|
||||
</tr>
|
||||
</form>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>ID</th>
|
||||
<th>Node Affinity</th>
|
||||
<th>Rank</th>
|
||||
<th>IP</th>
|
||||
<th>Blacklisted</th>
|
||||
</tr>
|
||||
{{#each results}}
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{id}}</td>
|
||||
<td title="{{affinityState.desc}}">{{affinityState}} ({{nodeAffinity}})</td>
|
||||
<td>{{rank}}</td>
|
||||
<td>{{ip}}</td>
|
||||
<td>{{#if blacklisted}}✓{{/if}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{#unless results}}
|
||||
<tr>
|
||||
<td colspan="5">No results found</td>
|
||||
</tr>
|
||||
{{/unless}}
|
||||
<tr>
|
||||
<td>
|
||||
{{#if hasPrevious}}
|
||||
<a href="?page={{previousPage}}&filter={{query}}&field={{field}}&affinity={{affinity}}">Previous</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td colspan="3"></td>
|
||||
<td>
|
||||
{{#if hasNext}}
|
||||
<a href="?page={{nextPage}}&filter={{query}}&field={{field}}&affinity={{affinity}}">Next</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
{{> control/partials/foot-includes }}
|
||||
</html>
|
@ -23,6 +23,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
{{/unless}}
|
||||
<li class="nav-item"><a class="nav-link" href="/domains">Domains</a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false">Index Nodes</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
Loading…
Reference in New Issue
Block a user