mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[pihole] Integration of API v6 (#19350)
* Initiating integration of API v6 Signed-off-by: clinique <gael@lhopital.org> * Adds enable / disable blocking Signed-off-by: clinique <gael@lhopital.org> * Correction of warnings Signed-off-by: clinique <gael@lhopital.org> * Finalizing API v6 for translated values of the handler. Code review corrections. Signed-off-by: gael@lhopital.org <gael@lhopital.org> * Adding missing Relative field. Signed-off-by: gael@lhopital.org <gael@lhopital.org> --------- Signed-off-by: clinique <gael@lhopital.org> Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
@@ -22,9 +22,10 @@ The Pi-hole Binding allows you to monitor Pi-hole statistics and control its fun
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|-----------------|---------|-------------------------------------------------------------------------------------------|---------|----------|----------|
|
||||
| hostname | text | Hostname or IP address of the device | N/A | yes | no |
|
||||
| hostname | text | URL (hostname or IP address) of the pihole web server | N/A | yes | no |
|
||||
| token | text | Token to access the device. To generate token go to `settings` > `API` > `Show API token` | N/A | yes | no |
|
||||
| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes |
|
||||
| refreshInterval | integer | Interval the device is polled in sec | 600 | no | yes |
|
||||
| serverVersion | text | Defines the API to be used with this server | v5 | no | no |
|
||||
|
||||
## Channels
|
||||
|
||||
|
||||
+1
-5
@@ -53,15 +53,11 @@ public class PiHoleActions implements ThingActions {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeUnit == null) {
|
||||
timeUnit = SECONDS;
|
||||
}
|
||||
|
||||
var local = handler;
|
||||
if (local == null) {
|
||||
return;
|
||||
}
|
||||
local.disableBlocking(timeUnit.toSeconds(time));
|
||||
local.disableBlocking((timeUnit == null ? SECONDS : timeUnit).toSeconds(time));
|
||||
}
|
||||
|
||||
public static void disableBlocking(@Nullable ThingActions actions, long time, @Nullable TimeUnit timeUnit)
|
||||
|
||||
+4
@@ -21,7 +21,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PiHoleConfiguration {
|
||||
public static final String API_V6 = "v6";
|
||||
public static final String API_V5 = "v5";
|
||||
|
||||
public String hostname = "";
|
||||
public String token = "";
|
||||
public int refreshIntervalSeconds = 600;
|
||||
public String serverVersion = API_V5;
|
||||
}
|
||||
|
||||
+13
-6
@@ -33,8 +33,10 @@ import java.util.concurrent.ScheduledFuture;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.pihole.internal.PiHoleBindingConstants.Channels.DisableEnable;
|
||||
import org.openhab.binding.pihole.internal.rest.AdminService;
|
||||
import org.openhab.binding.pihole.internal.rest.JettyAdminService;
|
||||
import org.openhab.binding.pihole.internal.rest.JettyAdminServiceV6;
|
||||
import org.openhab.binding.pihole.internal.rest.model.DnsStatistics;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
@@ -51,6 +53,8 @@ import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link PiHoleHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
@@ -58,21 +62,23 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
||||
public class PiHoleHandler extends BaseThingHandler {
|
||||
private static final int HTTP_DELAY_SECONDS = 1;
|
||||
private final Logger logger = LoggerFactory.getLogger(PiHoleHandler.class);
|
||||
private final Object lock = new Object();
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final HttpClient httpClient;
|
||||
private final Gson gson;
|
||||
|
||||
private @Nullable AdminService adminService;
|
||||
private @Nullable DnsStatistics dnsStatistics;
|
||||
private @Nullable ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
public PiHoleHandler(Thing thing, TimeZoneProvider timeZoneProvider, HttpClient httpClient) {
|
||||
public PiHoleHandler(Thing thing, TimeZoneProvider timeZoneProvider, HttpClient httpClient, Gson gson) {
|
||||
super(thing);
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.httpClient = httpClient;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,7 +107,11 @@ public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
||||
updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/handler.init.noToken");
|
||||
return;
|
||||
}
|
||||
adminService = new JettyAdminService(config.token, hostname, httpClient);
|
||||
|
||||
adminService = PiHoleConfiguration.API_V6.equals(config.serverVersion)
|
||||
? new JettyAdminServiceV6(config.token, hostname, httpClient, gson)
|
||||
: new JettyAdminService(config.token, hostname, httpClient, gson);
|
||||
|
||||
scheduledFuture = scheduler.scheduleWithFixedDelay(this::update, 0, config.refreshIntervalSeconds, SECONDS);
|
||||
|
||||
// do not set status here, the background task will do it.
|
||||
@@ -234,7 +244,6 @@ public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DnsStatistics> summary() throws PiHoleException {
|
||||
var local = adminService;
|
||||
if (local == null) {
|
||||
@@ -243,7 +252,6 @@ public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
||||
return local.summary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlocking(long seconds) throws PiHoleException {
|
||||
var local = adminService;
|
||||
if (local == null) {
|
||||
@@ -259,7 +267,6 @@ public class PiHoleHandler extends BaseThingHandler implements AdminService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlocking() throws PiHoleException {
|
||||
var local = adminService;
|
||||
if (local == null) {
|
||||
|
||||
+8
-3
@@ -29,6 +29,10 @@ import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link PiHoleHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
@@ -38,8 +42,10 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.pihole", service = ThingHandlerFactory.class)
|
||||
public class PiHoleHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(PI_HOLE_TYPE);
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
|
||||
@@ -60,9 +66,8 @@ public class PiHoleHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (PI_HOLE_TYPE.equals(thingTypeUID)) {
|
||||
return new PiHoleHandler(thing, timeZoneProvider, httpClientFactory.getCommonHttpClient());
|
||||
return new PiHoleHandler(thing, timeZoneProvider, httpClientFactory.getCommonHttpClient(), GSON);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-4
@@ -13,23 +13,41 @@
|
||||
package org.openhab.binding.pihole.internal.rest;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.openhab.binding.pihole.internal.PiHoleException;
|
||||
import org.openhab.binding.pihole.internal.rest.model.DnsStatistics;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
* @author Gaël L'hopital - Changed from 'interface' to abstract class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AdminService {
|
||||
public abstract class AdminService {
|
||||
protected static final long TIMEOUT_SECONDS = 10L;
|
||||
|
||||
protected final HttpClient client;
|
||||
protected final Gson gson;
|
||||
|
||||
public AdminService(HttpClient client, Gson gson) {
|
||||
this.client = client;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a summary of DNS statistics.
|
||||
*
|
||||
* @return An optional containing the DNS statistics.
|
||||
* @throws PiHoleException In case of error
|
||||
*/
|
||||
Optional<DnsStatistics> summary() throws PiHoleException;
|
||||
public abstract Optional<DnsStatistics> summary() throws PiHoleException;
|
||||
|
||||
/**
|
||||
* Disables blocking for a specified duration.
|
||||
@@ -37,12 +55,21 @@ public interface AdminService {
|
||||
* @param seconds The duration in seconds for which blocking should be disabled.
|
||||
* @throws PiHoleException In case of error
|
||||
*/
|
||||
void disableBlocking(long seconds) throws PiHoleException;
|
||||
public abstract void disableBlocking(long seconds) throws PiHoleException;
|
||||
|
||||
/**
|
||||
* Enables blocking.
|
||||
*
|
||||
* @throws PiHoleException In case of error
|
||||
*/
|
||||
void enableBlocking() throws PiHoleException;
|
||||
public abstract void enableBlocking() throws PiHoleException;
|
||||
|
||||
protected ContentResponse send(Request request) throws PiHoleException {
|
||||
try {
|
||||
return request.send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new PiHoleException(
|
||||
"Exception while sending request to Pi-hole. %s".formatted(e.getLocalizedMessage()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-26
@@ -16,39 +16,30 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.openhab.binding.pihole.internal.PiHoleException;
|
||||
import org.openhab.binding.pihole.internal.rest.model.DnsStatistics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JettyAdminService implements AdminService {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
private static final long TIMEOUT_SECONDS = 10L;
|
||||
public class JettyAdminService extends AdminService {
|
||||
private final Logger logger = LoggerFactory.getLogger(JettyAdminService.class);
|
||||
private final String token;
|
||||
private final URI baseUrl;
|
||||
private final HttpClient client;
|
||||
|
||||
public JettyAdminService(String token, URI baseUrl, HttpClient client) {
|
||||
this.token = token;
|
||||
private final URI baseUrl;
|
||||
private final String token;
|
||||
|
||||
public JettyAdminService(String token, URI baseUrl, HttpClient client, Gson gson) {
|
||||
super(client, gson);
|
||||
this.baseUrl = baseUrl;
|
||||
this.client = client;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,16 +49,7 @@ public class JettyAdminService implements AdminService {
|
||||
var request = client.newRequest(url).timeout(TIMEOUT_SECONDS, SECONDS);
|
||||
var response = send(request);
|
||||
var content = response.getContentAsString();
|
||||
return Optional.ofNullable(GSON.fromJson(content, DnsStatistics.class));
|
||||
}
|
||||
|
||||
private static ContentResponse send(Request request) throws PiHoleException {
|
||||
try {
|
||||
return request.send();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
throw new PiHoleException(
|
||||
"Exception while sending request to Pi-hole. %s".formatted(e.getLocalizedMessage()), e);
|
||||
}
|
||||
return Optional.ofNullable(gson.fromJson(content, DnsStatistics.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.pihole.internal.PiHoleException;
|
||||
import org.openhab.binding.pihole.internal.rest.model.DnsStatistics;
|
||||
import org.openhab.binding.pihole.internal.rest.model.GravityLastUpdated;
|
||||
import org.openhab.binding.pihole.internal.rest.model.Relative;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.Blocking;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.ConfigAnswer;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.DnsBlockingAnswer;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.HistoryClients;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.Password;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.SessionAnswer;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.SessionAnswer.Session;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.StatAnswer;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.StatAnswer.Gravity;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.StatAnswer.Queries;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.StatAnswer.Replies;
|
||||
import org.openhab.binding.pihole.internal.rest.model.v6.StatDatabaseSummary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JettyAdminServiceV6 extends AdminService {
|
||||
private final Logger logger = LoggerFactory.getLogger(JettyAdminServiceV6.class);
|
||||
private final URI authURI;
|
||||
private final URI statsSummaryURI;
|
||||
private final URI dnsBlockingURI;
|
||||
private final URI databaseSummaryURI;
|
||||
private final URI historyClientsURI;
|
||||
private final URI configURI;
|
||||
private final String tokenJson;
|
||||
|
||||
private @Nullable String sid;
|
||||
private @Nullable Instant sessionValidity;
|
||||
private @Nullable Instant midnightUtc;
|
||||
|
||||
public JettyAdminServiceV6(String token, URI baseUrl, HttpClient client, Gson gson) {
|
||||
super(client, gson);
|
||||
tokenJson = gson.toJson(new Password(token));
|
||||
|
||||
UriBuilder apiUriBuilder = UriBuilder.fromUri(baseUrl).path("api");
|
||||
authURI = apiUriBuilder.clone().path("auth").build();
|
||||
configURI = apiUriBuilder.clone().path("config").build();
|
||||
dnsBlockingURI = apiUriBuilder.clone().path("dns").path("blocking").build();
|
||||
historyClientsURI = apiUriBuilder.clone().path("history").path("clients").build();
|
||||
|
||||
UriBuilder statsUriBuilder = apiUriBuilder.clone().path("stats");
|
||||
statsSummaryURI = statsUriBuilder.clone().path("summary").build();
|
||||
databaseSummaryURI = statsUriBuilder.clone().path("database").path("summary").build();
|
||||
}
|
||||
|
||||
private String updateSid() throws PiHoleException {
|
||||
logger.debug("Get or update session ID");
|
||||
var request = client.newRequest(authURI).timeout(TIMEOUT_SECONDS, SECONDS).method(HttpMethod.POST)
|
||||
.header(HttpHeader.ACCEPT, "application/json").content(new StringContentProvider(tokenJson));
|
||||
var content = send(request).getContentAsString();
|
||||
|
||||
if (gson.fromJson(content, SessionAnswer.class) instanceof SessionAnswer answer) {
|
||||
Session session = answer.session();
|
||||
sid = session.sid();
|
||||
sessionValidity = Instant.now().plusSeconds(session.cautiousValidity());
|
||||
return session.sid();
|
||||
}
|
||||
|
||||
throw new PiHoleException("Error deserializing '%s'".formatted(content));
|
||||
}
|
||||
|
||||
private String getSid() throws PiHoleException {
|
||||
if (sid instanceof String localSid && sessionValidity instanceof Instant validUntil
|
||||
&& Instant.now().isBefore(validUntil)) {
|
||||
return localSid;
|
||||
}
|
||||
return updateSid();
|
||||
}
|
||||
|
||||
private long getTodayMidnight() {
|
||||
Instant now = Instant.now();
|
||||
Instant local = midnightUtc;
|
||||
|
||||
if (local == null || now.minus(1, ChronoUnit.DAYS).isAfter(local)) {
|
||||
local = now.truncatedTo(ChronoUnit.DAYS);
|
||||
midnightUtc = local;
|
||||
}
|
||||
|
||||
return local.getEpochSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DnsStatistics> summary() throws PiHoleException {
|
||||
logger.debug("Building the as if it was a v5 API");
|
||||
StatAnswer statAnswer = get(statsSummaryURI, StatAnswer.class);
|
||||
Gravity gravity = statAnswer.gravity();
|
||||
Queries statQueries = statAnswer.queries();
|
||||
Replies replies = statQueries.replies();
|
||||
|
||||
DnsBlockingAnswer blockingAnswer = get(dnsBlockingURI, DnsBlockingAnswer.class);
|
||||
|
||||
long todayMidnight = getTodayMidnight();
|
||||
StatDatabaseSummary statDatabase = get(databaseSummaryURI, StatDatabaseSummary.class, "from",
|
||||
Long.toString(todayMidnight), "until", Long.toString(todayMidnight + 24 * 60 * 60));
|
||||
|
||||
HistoryClients historyClients = get(historyClientsURI, HistoryClients.class, "N", "0");
|
||||
ConfigAnswer configAnswer = get(configURI, ConfigAnswer.class);
|
||||
Duration duration = Duration.between(gravity.instant(), Instant.now());
|
||||
Relative relative = new Relative((int) duration.toDaysPart(), duration.toHoursPart(), duration.toMinutesPart());
|
||||
|
||||
DnsStatistics translated = new DnsStatistics(gravity.domainsBeingBlocked(), statDatabase.sumQueries(),
|
||||
statDatabase.sumBlocked(), statDatabase.percentBlocked(), statQueries.uniqueDomains(),
|
||||
statQueries.forwarded(), statQueries.cached(), historyClients.clients().size(), null,
|
||||
statQueries.types().all(), replies.unknown(), replies.nodata(), replies.nxdomain(), replies.cname(),
|
||||
replies.ip(), replies.domain(), replies.rrname(), replies.servfail(), replies.refused(),
|
||||
replies.notimp(), replies.other(), replies.dnssec(), replies.none(), replies.blob(), replies.all(),
|
||||
configAnswer.config().misc().privacylevel(), blockingAnswer.blocking(), new GravityLastUpdated(
|
||||
configAnswer.config().files().gravity() != null, gravity.lastUpdate(), relative));
|
||||
|
||||
return Optional.of(translated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlocking(long seconds) throws PiHoleException {
|
||||
logger.debug("Disabling blocking for {} seconds", seconds);
|
||||
post(dnsBlockingURI, new Blocking(false, seconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlocking() throws PiHoleException {
|
||||
logger.debug("Enabling blocking");
|
||||
post(dnsBlockingURI, Blocking.BLOCK);
|
||||
}
|
||||
|
||||
private void post(URI targetURI, Object object) throws PiHoleException {
|
||||
var request = client.newRequest(targetURI).timeout(TIMEOUT_SECONDS, SECONDS).method(HttpMethod.POST)
|
||||
.header(HttpHeader.ACCEPT, "application/json").header("sid", getSid())
|
||||
.content(new StringContentProvider(gson.toJson(object)));
|
||||
send(request);
|
||||
}
|
||||
|
||||
private <T> T get(URI targetURI, Class<T> clazz, @Nullable Object... params) throws PiHoleException {
|
||||
var request = client.newRequest(targetURI).timeout(TIMEOUT_SECONDS, SECONDS)
|
||||
.header(HttpHeader.ACCEPT, "application/json").header("sid", getSid());
|
||||
|
||||
if (params.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("params count must be even");
|
||||
}
|
||||
for (int i = 0; i < params.length; i += 2) {
|
||||
if (params[i] instanceof String name && params[i + 1] instanceof String param) {
|
||||
request = request.param(name, param);
|
||||
} else {
|
||||
throw new IllegalArgumentException("parameters must be String");
|
||||
}
|
||||
}
|
||||
|
||||
var content = send(request).getContentAsString();
|
||||
T answer = gson.fromJson(content, clazz);
|
||||
if (answer != null) {
|
||||
return answer;
|
||||
}
|
||||
throw new PiHoleException("Error deserializing %s".formatted(content));
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record Blocking(boolean blocking, @Nullable Long timer) {
|
||||
public static Blocking BLOCK = new Blocking(true, null);
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record ConfigAnswer(Config config, double took) {
|
||||
|
||||
public record Temp(int limit, String unit) {
|
||||
}
|
||||
|
||||
public record Api(int maxSessions, boolean prettyJSON, String pwhash, String password, String totpSecret,
|
||||
String appPwhash, boolean appSudo, boolean cliPw, List<Object> excludeClients, List<Object> excludeDomains,
|
||||
int maxHistory, int maxClients, boolean clientHistoryGlobalMax, boolean allowDestructive, Temp temp) {
|
||||
}
|
||||
|
||||
public record Host(boolean force4, String iPv4, boolean force6, String iPv6) {
|
||||
}
|
||||
|
||||
public record Blocking(boolean active, String mode, String edns) {
|
||||
}
|
||||
|
||||
public record Blocking_1(boolean force4, String iPv4, boolean force6, String iPv6) {
|
||||
}
|
||||
|
||||
public record Cache(int size, int optimizer, int upstreamBlockedTTL) {
|
||||
}
|
||||
|
||||
public record Network(boolean parseARPcache, int expire) {
|
||||
}
|
||||
|
||||
public record Database(boolean dBimport, int maxDBdays, int dBinterval, boolean useWAL, Network network) {
|
||||
}
|
||||
|
||||
public record Debug(boolean database, boolean networking, boolean locks, boolean queries, boolean flags,
|
||||
boolean shmem, boolean gc, boolean arp, boolean regex, boolean api, boolean tls, boolean overtime,
|
||||
boolean status, boolean caps, boolean dnssec, boolean vectors, boolean resolver, boolean edns0,
|
||||
boolean clients, boolean aliasclients, boolean events, boolean helper, boolean config, boolean inotify,
|
||||
boolean webserver, boolean extra, boolean reserved, boolean ntp, boolean netlink, boolean all) {
|
||||
}
|
||||
|
||||
public record Dhcp(boolean active, String start, String end, String router, String netmask, String leaseTime,
|
||||
boolean ipv6, boolean rapidCommit, boolean multiDNS, boolean logging, boolean ignoreUnknownClients,
|
||||
List<String> hosts) {
|
||||
}
|
||||
|
||||
public record SpecialDomains(boolean mozillaCanary, boolean iCloudPrivateRelay, boolean designatedResolver) {
|
||||
}
|
||||
|
||||
public record Reply(Host host, Blocking_1 blocking) {
|
||||
}
|
||||
|
||||
public record RateLimit(int count, int interval) {
|
||||
}
|
||||
|
||||
public record Dns(List<String> upstreams, boolean cNAMEdeepInspect, boolean blockESNI, boolean edns0ecs,
|
||||
boolean ignoreLocalhost, boolean showDNSSEC, boolean analyzeOnlyAandAAAA, String piholePTR,
|
||||
String replyWhenBusy, int blockTTL, List<String> hosts, boolean domainNeeded, boolean expandHosts,
|
||||
String domain, boolean bogusPriv, boolean dnssec, String _interface, String hostRecord,
|
||||
String listeningMode, boolean queryLogging, List<Object> cnameRecords, int port, List<Object> revServers,
|
||||
Cache cache, Blocking blocking, SpecialDomains specialDomains, Reply reply, RateLimit rateLimit) {
|
||||
}
|
||||
|
||||
public record Files(String pid, String database, @Nullable String gravity, String gravityTmp, String macvendor,
|
||||
String pcap, Log log) {
|
||||
}
|
||||
|
||||
public record Interface(boolean boxed, String theme) {
|
||||
}
|
||||
|
||||
public record Ipv4(boolean active, String address) {
|
||||
}
|
||||
|
||||
public record Ipv6(boolean active, String address) {
|
||||
}
|
||||
|
||||
public record Log(String ftl, String dnsmasq, String webserver) {
|
||||
}
|
||||
|
||||
public record Check(boolean load, int shmem, int disk) {
|
||||
}
|
||||
|
||||
public record Misc(int privacylevel, int delayStartup, int nice, boolean addr2line, boolean etcDnsmasqD,
|
||||
List<Object> dnsmasqLines, boolean extraLogging, boolean readOnly, Check check) {
|
||||
}
|
||||
|
||||
public record Rtc(boolean set, String device, boolean utc) {
|
||||
}
|
||||
|
||||
public record Sync(boolean active, String server, int interval, int count, Rtc rtc) {
|
||||
}
|
||||
|
||||
public record Ntp(Ipv4 ipv4, Ipv6 ipv6, Sync sync) {
|
||||
}
|
||||
|
||||
public record Paths(String webroot, String webhome, String prefix) {
|
||||
}
|
||||
|
||||
public record Resolver(boolean resolveIPv4, boolean resolveIPv6, boolean networkNames, String refreshNames) {
|
||||
}
|
||||
|
||||
public record Session(int timeout, boolean restore) {
|
||||
}
|
||||
|
||||
public record Tls(String cert) {
|
||||
}
|
||||
|
||||
public record Webserver(String domain, String acl, String port, int threads, List<String> headers, boolean serveAll,
|
||||
Session session, Tls tls, Paths paths, Interface _interface, Api api) {
|
||||
}
|
||||
|
||||
public record Config(Dns dns, Dhcp dhcp, Ntp ntp, Resolver resolver, Database database, Webserver webserver,
|
||||
Files files, Misc misc, Debug debug) {
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record DnsBlockingAnswer(String blocking, Double timer, double took) {
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record HistoryAnswer(History history, double took) {
|
||||
public record History(double timestamp, int total, int cached, int blocked, int forwarded) {
|
||||
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record HistoryClients(Map<String, Client> clients, List<History> history, double took) {
|
||||
public record Client(String name, int total) {
|
||||
|
||||
}
|
||||
|
||||
public record History(double timestamp, Map<String, Integer> data) {
|
||||
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record Password(String password) {
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record SessionAnswer(Session session, double took) {
|
||||
public record Session(boolean valid, boolean totp, String sid, String csrf, int validity, String message) {
|
||||
public int cautiousValidity() {
|
||||
return validity / 3 * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record StatAnswer(Queries queries, Clients client, Gravity gravity, double took) {
|
||||
record Clients(int active, int total) {
|
||||
|
||||
}
|
||||
|
||||
public record Gravity(int domainsBeingBlocked, long lastUpdate) {
|
||||
public Instant instant() {
|
||||
return Instant.ofEpochSecond(lastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
public record Queries(int total, int blocked, double percentBlocked, int uniqueDomains, int forwarded, int cached,
|
||||
double frequency, Types types, Status status, Replies replies) {
|
||||
|
||||
}
|
||||
|
||||
public record Replies(@SerializedName("UNKNOWN") int unknown, //
|
||||
@SerializedName("NODATA") int nodata, //
|
||||
@SerializedName("NXDOMAIN") int nxdomain, //
|
||||
@SerializedName("CNAME") int cname, //
|
||||
@SerializedName("IP") int ip, //
|
||||
@SerializedName("DOMAIN") int domain, //
|
||||
@SerializedName("RRNAME") int rrname, //
|
||||
@SerializedName("SERVFAIL") int servfail, //
|
||||
@SerializedName("REFUSED") int refused, //
|
||||
@SerializedName("NOTIMP") int notimp, //
|
||||
@SerializedName("OTHER") int other, //
|
||||
@SerializedName("DNSSEC") int dnssec, //
|
||||
@SerializedName("NONE") int none, //
|
||||
@SerializedName("BLOB") int blob) {
|
||||
|
||||
public int all() {
|
||||
return unknown + nodata + nxdomain + cname + ip + domain + rrname + servfail + refused + notimp + other
|
||||
+ dnssec + none + blob;
|
||||
}
|
||||
}
|
||||
|
||||
public record Status(@SerializedName("UNKNOWN") int unknown, //
|
||||
@SerializedName("GRAVITY") int gravity, //
|
||||
@SerializedName("FORWARDED") int forwarded, //
|
||||
@SerializedName("CACHE") int cache, //
|
||||
@SerializedName("REGEX") int regex, //
|
||||
@SerializedName("DENYLIST") int denylist, //
|
||||
@SerializedName("EXTERNAL_BLOCKED_IP") int externalBlockedIp, //
|
||||
@SerializedName("EXTERNAL_BLOCKED_NULL") int externalBlockedNull, //
|
||||
@SerializedName("EXTERNAL_BLOCKED_NXRA") int externalBlockedNxra, //
|
||||
@SerializedName("GRAVITY_CNAME") int gravityCname, //
|
||||
@SerializedName("REGEX_CNAME") int regexCname, //
|
||||
@SerializedName("DENYLIST_CNAME") int denylistCname, //
|
||||
@SerializedName("RETRIED") int retried, //
|
||||
@SerializedName("RETRIED_DNSSEC") int retriedDnssec, //
|
||||
@SerializedName("IN_PROGRESS") int inProgress, //
|
||||
@SerializedName("DBBUSY") int dbbusy, //
|
||||
@SerializedName("SPECIAL_DOMAIN") int specialDomain, //
|
||||
@SerializedName("CACHE_STALE") int cacheStale, //
|
||||
@SerializedName("EXTERNAL_BLOCKED_EDE15") int externalBlockedEde15) {
|
||||
}
|
||||
|
||||
public record Types(@SerializedName("A") int a, //
|
||||
@SerializedName("AAAA") int aaaa, //
|
||||
@SerializedName("ANY") int any, //
|
||||
@SerializedName("SRV") int srv, //
|
||||
@SerializedName("SOA") int soa, //
|
||||
@SerializedName("PTR") int ptr, //
|
||||
@SerializedName("TXT") int txt, //
|
||||
@SerializedName("NAPTR") int naptr, //
|
||||
@SerializedName("MX") int mx, //
|
||||
@SerializedName("DS") int ds, //
|
||||
@SerializedName("RRSIG") int rrsig, //
|
||||
@SerializedName("DNSKEY") int dnskey, //
|
||||
@SerializedName("NS") int ns, //
|
||||
@SerializedName("SVCB") int svcb, //
|
||||
@SerializedName("HTTPS") int https, //
|
||||
@SerializedName("OTHER") int other) {
|
||||
|
||||
public int all() {
|
||||
return a + aaaa + any + srv + soa + ptr + txt + naptr + mx + ds + rrsig + dnskey + ns + svcb + https
|
||||
+ other;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pihole.internal.rest.model.v6;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record StatDatabaseSummary(int sumQueries, int sumBlocked, double percentBlocked, int totalClients,
|
||||
double took) {
|
||||
}
|
||||
@@ -123,8 +123,8 @@
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Hostname</label>
|
||||
<description>Hostname or IP address of the device</description>
|
||||
<label>Server URL</label>
|
||||
<description>URL (hostname or IP address) of the pihole web server</description>
|
||||
</parameter>
|
||||
<parameter name="token" type="text" required="true">
|
||||
<context>password</context>
|
||||
@@ -137,6 +137,15 @@
|
||||
<default>600</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="serverVersion" type="text">
|
||||
<label>Server Version</label>
|
||||
<description>Defines the API to be used with this server.</description>
|
||||
<default>v5</default>
|
||||
<options>
|
||||
<option value="v5">v5</option>
|
||||
<option value="v6">v6</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
|
||||
+8
-1
@@ -29,11 +29,18 @@ import org.openhab.binding.pihole.internal.rest.model.DnsStatistics;
|
||||
import org.openhab.binding.pihole.internal.rest.model.GravityLastUpdated;
|
||||
import org.openhab.binding.pihole.internal.rest.model.Relative;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* @author Martin Grzeslowski - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JettyAdminServiceTest {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
|
||||
String content = """
|
||||
{
|
||||
"domains_being_blocked": 131355,
|
||||
@@ -83,7 +90,7 @@ public class JettyAdminServiceTest {
|
||||
var token = "validToken";
|
||||
var baseUrl = URI.create("https://example.com");
|
||||
var client = mock(HttpClient.class);
|
||||
var adminService = new JettyAdminService(token, baseUrl, client);
|
||||
var adminService = new JettyAdminService(token, baseUrl, client, GSON);
|
||||
var dnsStatistics = new DnsStatistics(131355, // domains_being_blocked
|
||||
27459, // dns_queries_today
|
||||
2603, // ads_blocked_today
|
||||
|
||||
Reference in New Issue
Block a user