feat: add pi hole summary integration (#521)

* feat: add pi hole summary integration

* feat: add pi hole summary widget

* fix: type issues with integrations and integrationIds

* feat: add middleware for integrations and improve cache redis channel

* feat: add error boundary for widgets

* fix: broken lock file

* fix: format format issues

* fix: typecheck issue

* fix: deepsource issues

* fix: widget sandbox without error boundary

* chore: address pull request feedback

* chore: remove todo comment and created issue

* fix: format issues

* fix: deepsource issue
This commit is contained in:
Meier Lukas
2024-05-26 17:13:34 +02:00
committed by GitHub
parent 96c71aed6e
commit d57b771a17
45 changed files with 902 additions and 124 deletions

View File

@@ -0,0 +1,22 @@
import type { IntegrationSecretKind } from "@homarr/definitions";
import type { IntegrationSecret } from "./types";
export abstract class Integration {
constructor(
protected integration: {
id: string;
name: string;
url: string;
decryptedSecrets: IntegrationSecret[];
},
) {}
protected getSecretValue(kind: IntegrationSecretKind) {
const secret = this.integration.decryptedSecrets.find((secret) => secret.kind === kind);
if (!secret) {
throw new Error(`No secret of kind ${kind} was found`);
}
return secret.value;
}
}

View File

@@ -0,0 +1,6 @@
import type { IntegrationSecretKind } from "@homarr/definitions";
export interface IntegrationSecret {
kind: IntegrationSecretKind;
value: string;
}

View File

@@ -0,0 +1 @@
export { PiHoleIntegration } from "./pi-hole/pi-hole-integration";

View File

@@ -0,0 +1,5 @@
import type { DnsHoleSummary } from "./dns-hole-summary-types";
export interface DnsHoleSummaryIntegration {
getSummaryAsync(): Promise<DnsHoleSummary>;
}

View File

@@ -0,0 +1,6 @@
export interface DnsHoleSummary {
domainsBeingBlocked: number;
adsBlockedToday: number;
adsBlockedTodayPercentage: number;
dnsQueriesToday: number;
}

View File

@@ -0,0 +1,31 @@
import { Integration } from "../base/integration";
import type { DnsHoleSummaryIntegration } from "../interfaces/dns-hole-summary/dns-hole-summary-integration";
import type { DnsHoleSummary } from "../interfaces/dns-hole-summary/dns-hole-summary-types";
import { summaryResponseSchema } from "./pi-hole-types";
export class PiHoleIntegration extends Integration implements DnsHoleSummaryIntegration {
async getSummaryAsync(): Promise<DnsHoleSummary> {
const apiKey = super.getSecretValue("apiKey");
const response = await fetch(`${this.integration.url}/admin/api.php?summaryRaw&auth=${apiKey}`);
if (!response.ok) {
throw new Error(
`Failed to fetch summary for ${this.integration.name} (${this.integration.id}): ${response.statusText}`,
);
}
const result = summaryResponseSchema.safeParse(await response.json());
if (!result.success) {
throw new Error(
`Failed to parse summary for ${this.integration.name} (${this.integration.id}), most likely your api key is wrong: ${result.error.message}`,
);
}
return {
adsBlockedToday: result.data.ads_blocked_today,
adsBlockedTodayPercentage: result.data.ads_percentage_today,
domainsBeingBlocked: result.data.domains_being_blocked,
dnsQueriesToday: result.data.dns_queries_today,
};
}
}

View File

@@ -0,0 +1,9 @@
import { z } from "@homarr/validation";
export const summaryResponseSchema = z.object({
status: z.enum(["enabled", "disabled"]),
domains_being_blocked: z.number(),
ads_blocked_today: z.number(),
dns_queries_today: z.number(),
ads_percentage_today: z.number(),
});

View File

@@ -0,0 +1 @@
export * from "./interfaces/dns-hole-summary/dns-hole-summary-types";