Add ad guard home (#937)

*  Add add guard home

*  Add request for blocked domains and fix request for blocked queries

* ♻️ PR feedback

*  Fix tests
This commit is contained in:
Manuel
2023-05-20 14:42:15 +02:00
committed by GitHub
parent 85dfb5bb58
commit fb52c4b003
15 changed files with 644 additions and 255 deletions

View File

@@ -1,20 +0,0 @@
import { ApiCheck, AssertionBuilder } from 'checkly/constructs';
const homepageApiCheck = new ApiCheck('homepage-api-check-1', {
name: 'Fetch Book List',
alertChannels: [],
degradedResponseTime: 10000,
maxResponseTime: 20000,
request: {
url: 'https://danube-web.shop/api/books',
method: 'GET',
followRedirects: true,
skipSSL: false,
assertions: [
AssertionBuilder.statusCode().equals(200),
AssertionBuilder.jsonBody('$[0].id').isNotNull(),
],
},
});
export default homepageApiCheck;

View File

@@ -1,11 +0,0 @@
import { test, expect, Page } from '@playwright/test';
// You can override the default Playwright test timeout of 30s
// test.setTimeout(60_000);
test('Checkly Homepage', async ({ page }: { page: Page }) => {
const response = await page.goto('https://danube-web.shop');
expect(response?.status()).toBeLessThan(400);
await expect(page).toHaveTitle(/Danube WebShop/);
await page.screenshot({ path: 'homepage.jpg' });
});

View File

@@ -77,7 +77,6 @@
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^13.0.0", "@next/bundle-analyzer": "^13.0.0",
"@next/eslint-plugin-next": "^13.0.0", "@next/eslint-plugin-next": "^13.0.0",
"@playwright/test": "^1.33.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@types/dockerode": "^3.3.9", "@types/dockerode": "^3.3.9",
@@ -110,7 +109,7 @@
"turbo": "latest", "turbo": "latest",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"video.js": "^8.0.3", "video.js": "^8.0.3",
"vitest": "^0.29.3", "vitest": "^0.31.1",
"vitest-fetch-mock": "^0.2.2" "vitest-fetch-mock": "^0.2.2"
}, },
"resolutions": { "resolutions": {

View File

@@ -1,5 +1,5 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import { Group, Select, SelectItem, Text } from '@mantine/core'; import { Group, Image, Select, SelectItem, Text } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
@@ -87,9 +87,14 @@ export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
}, },
{ {
value: 'pihole', value: 'pihole',
image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/pihole.png', image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/pi-hole.png',
label: 'PiHole', label: 'PiHole',
}, },
{
value: 'adGuardHome',
image: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/adguard-home.png',
label: 'AdGuard Home',
},
].filter((x) => Object.keys(integrationFieldProperties).includes(x.value)); ].filter((x) => Object.keys(integrationFieldProperties).includes(x.value));
const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => { const getNewProperties = (value: string | null): AppIntegrationPropertyType[] => {
@@ -133,11 +138,12 @@ export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
} }
icon={ icon={
form.values.integration?.type && ( form.values.integration?.type && (
<img <Image
src={data.find((x) => x.value === form.values.integration?.type)?.image} src={data.find((x) => x.value === form.values.integration?.type)?.image}
alt="integration" alt="integration"
width={20} width={20}
height={20} height={20}
fit="contain"
/> />
) )
} }
@@ -160,7 +166,7 @@ const SelectItemComponent = forwardRef<HTMLDivElement, ItemProps>(
({ image, label, description, ...others }: ItemProps, ref) => ( ({ image, label, description, ...others }: ItemProps, ref) => (
<div ref={ref} {...others}> <div ref={ref} {...others}>
<Group noWrap> <Group noWrap>
<img src={image} alt="integration icon" width={20} height={20} /> <Image src={image} alt="integration icon" width={20} height={20} fit="contain" />
<div> <div>
<Text size="sm">{label}</Text> <Text size="sm">{label}</Text>

View File

@@ -5,6 +5,8 @@ import { NextApiRequest, NextApiResponse } from 'next';
import { getConfig } from '../../../../tools/config/getConfig'; import { getConfig } from '../../../../tools/config/getConfig';
import { findAppProperty } from '../../../../tools/client/app-properties'; import { findAppProperty } from '../../../../tools/client/app-properties';
import { PiHoleClient } from '../../../../tools/server/sdk/pihole/piHole'; import { PiHoleClient } from '../../../../tools/server/sdk/pihole/piHole';
import { ConfigAppType } from '../../../../types/app';
import { AdGuard } from '../../../../tools/server/sdk/adGuard/adGuard';
const getQuerySchema = z.object({ const getQuerySchema = z.object({
status: z.enum(['enabled', 'disabled']), status: z.enum(['enabled', 'disabled']),
@@ -21,29 +23,50 @@ export const Post = async (request: NextApiRequest, response: NextApiResponse) =
return; return;
} }
const applicableApps = config.apps.filter((x) => x.integration?.type === 'pihole'); const applicableApps = config.apps.filter(
(x) => x.integration?.type && ['pihole', 'adGuardHome'].includes(x.integration?.type)
);
for (let i = 0; i < applicableApps.length; i += 1) { for (let i = 0; i < applicableApps.length; i += 1) {
const app = applicableApps[i]; const app = applicableApps[i];
const pihole = new PiHoleClient( if (app.integration?.type === 'pihole') {
app.url, await processPiHole(app, parseResult.data.status === 'disabled');
findAppProperty(app, 'password') return;
);
switch (parseResult.data.status) {
case 'enabled':
await pihole.enable();
break;
case 'disabled':
await pihole.disable();
break;
} }
await processAdGuard(app, parseResult.data.status === 'disabled');
} }
response.status(200).json({}); response.status(200).json({});
}; };
const processAdGuard = async (app: ConfigAppType, enable: boolean) => {
const adGuard = new AdGuard(
app.url,
findAppProperty(app, 'username'),
findAppProperty(app, 'password')
);
if (enable) {
await adGuard.disable();
return;
}
await adGuard.enable();
};
const processPiHole = async (app: ConfigAppType, enable: boolean) => {
const pihole = new PiHoleClient(app.url, findAppProperty(app, 'password'));
if (enable) {
await pihole.enable();
return;
}
await pihole.disable();
};
export default async (request: NextApiRequest, response: NextApiResponse) => { export default async (request: NextApiRequest, response: NextApiResponse) => {
if (request.method === 'POST') { if (request.method === 'POST') {
return Post(request, response); return Post(request, response);

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
import Consola from 'consola'; import Consola from 'consola';
import { getCookie } from 'cookies-next'; import { getCookie } from 'cookies-next';
import { NextApiRequest, NextApiResponse } from 'next'; import { NextApiRequest, NextApiResponse } from 'next';
@@ -5,12 +6,15 @@ import { findAppProperty } from '../../../../tools/client/app-properties';
import { getConfig } from '../../../../tools/config/getConfig'; import { getConfig } from '../../../../tools/config/getConfig';
import { PiHoleClient } from '../../../../tools/server/sdk/pihole/piHole'; import { PiHoleClient } from '../../../../tools/server/sdk/pihole/piHole';
import { AdStatistics } from '../../../../widgets/dnshole/type'; import { AdStatistics } from '../../../../widgets/dnshole/type';
import { AdGuard } from '../../../../tools/server/sdk/adGuard/adGuard';
export const Get = async (request: NextApiRequest, response: NextApiResponse) => { export const Get = async (request: NextApiRequest, response: NextApiResponse) => {
const configName = getCookie('config-name', { req: request }); const configName = getCookie('config-name', { req: request });
const config = getConfig(configName?.toString() ?? 'default'); const config = getConfig(configName?.toString() ?? 'default');
const applicableApps = config.apps.filter((x) => x.integration?.type === 'pihole'); const applicableApps = config.apps.filter(
(x) => x.integration?.type && ['pihole', 'adGuardHome'].includes(x.integration?.type)
);
const data: AdStatistics = { const data: AdStatistics = {
domainsBeingBlocked: 0, domainsBeingBlocked: 0,
@@ -26,21 +30,51 @@ export const Get = async (request: NextApiRequest, response: NextApiResponse) =>
const app = applicableApps[i]; const app = applicableApps[i];
try { try {
const piHole = new PiHoleClient(app.url, findAppProperty(app, 'password')); switch (app.integration?.type) {
case 'pihole': {
const piHole = new PiHoleClient(app.url, findAppProperty(app, 'password'));
const summary = await piHole.getSummary();
// eslint-disable-next-line no-await-in-loop data.domainsBeingBlocked += summary.domains_being_blocked;
const summary = await piHole.getSummary(); data.adsBlockedToday += summary.ads_blocked_today;
data.dnsQueriesToday += summary.dns_queries_today;
data.status.push({
status: summary.status,
appId: app.id,
});
adsBlockedTodayPercentageArr.push(summary.ads_percentage_today);
break;
}
case 'adGuardHome': {
const adGuard = new AdGuard(
app.url,
findAppProperty(app, 'username'),
findAppProperty(app, 'password')
);
data.domainsBeingBlocked += summary.domains_being_blocked; const stats = await adGuard.getStats();
data.adsBlockedToday += summary.ads_blocked_today; const status = await adGuard.getStatus();
data.dnsQueriesToday += summary.dns_queries_today; const countFilteredDomains = await adGuard.getCountFilteringDomains();
data.status.push({
status: summary.status, const blockedQueriesToday = stats.blocked_filtering.reduce((prev, sum) => prev + sum, 0);
appId: app.id, const queriesToday = stats.dns_queries.reduce((prev, sum) => prev + sum, 0);
}); data.adsBlockedToday = blockedQueriesToday;
adsBlockedTodayPercentageArr.push(summary.ads_percentage_today); data.domainsBeingBlocked += countFilteredDomains;
data.dnsQueriesToday += queriesToday;
data.status.push({
status: status.protection_enabled ? 'enabled' : 'disabled',
appId: app.id,
});
adsBlockedTodayPercentageArr.push((queriesToday / blockedQueriesToday) * 100);
break;
}
default: {
Consola.error(`Integration communication for app ${app.id} failed: unknown type`);
break;
}
}
} catch (err) { } catch (err) {
Consola.error(`Failed to communicate with PiHole at ${app.url}: ${err}`); Consola.error(`Failed to communicate with DNS hole at ${app.url}: ${err}`);
} }
} }

View File

@@ -78,7 +78,7 @@ describe('media-requests api', () => {
logSpy.mockRestore(); logSpy.mockRestore();
}); });
it('fetch and return requests in response', async () => { it('fetch and return requests in response with external url', async () => {
// Arrange // Arrange
const { req, res } = createMocks({ const { req, res } = createMocks({
method: 'GET', method: 'GET',
@@ -108,7 +108,16 @@ describe('media-requests api', () => {
}, },
}, },
], ],
} as ConfigType); widgets: [
{
id: 'hjeruijgrig',
type: 'media-requests-list',
properties: {
replaceLinksWithExternalHost: true,
},
},
],
} as unknown as ConfigType);
const logSpy = vi.spyOn(Consola, 'error'); const logSpy = vi.spyOn(Consola, 'error');
fetchMock.mockResponse((request) => { fetchMock.mockResponse((request) => {
@@ -287,6 +296,235 @@ describe('media-requests api', () => {
'https://image.tmdb.org/t/p/w600_and_h900_bestv2//hf4j0928gq543njgh8935nqh8.jpg', 'https://image.tmdb.org/t/p/w600_and_h900_bestv2//hf4j0928gq543njgh8935nqh8.jpg',
status: 2, status: 2,
type: 'movie', type: 'movie',
userLink: 'http://my-overseerr.external/users/1',
userName: 'Example User',
userProfilePicture: 'http://my-overseerr.external//os_logo_square.png',
},
]);
expect(logSpy).not.toHaveBeenCalled();
logSpy.mockRestore();
});
it('fetch and return requests in response with internal url', async () => {
// Arrange
const { req, res } = createMocks({
method: 'GET',
});
vi.mock('./../../../../tools/config/getConfig.ts', () => ({
get getConfig() {
return mockedGetConfig;
},
}));
mockedGetConfig.mockReturnValue({
apps: [
{
url: 'http://my-overseerr.local',
behaviour: {
externalUrl: 'http://my-overseerr.external',
},
integration: {
type: 'overseerr',
properties: [
{
field: 'apiKey',
type: 'private',
value: 'abc',
},
],
},
},
],
widgets: [
{
id: 'hjeruijgrig',
type: 'media-requests-list',
properties: {
replaceLinksWithExternalHost: false,
},
},
],
} as unknown as ConfigType);
const logSpy = vi.spyOn(Consola, 'error');
fetchMock.mockResponse((request) => {
if (request.url === 'http://my-overseerr.local/api/v1/request?take=25&skip=0&sort=added') {
return JSON.stringify({
pageInfo: { pages: 3, pageSize: 20, results: 42, page: 1 },
results: [
{
id: 44,
status: 2,
createdAt: '2023-04-06T19:38:45.000Z',
updatedAt: '2023-04-06T19:38:45.000Z',
type: 'movie',
is4k: false,
serverId: 0,
profileId: 4,
tags: [],
isAutoRequest: false,
media: {
downloadStatus: [],
downloadStatus4k: [],
id: 999,
mediaType: 'movie',
tmdbId: 99999999,
tvdbId: null,
imdbId: null,
status: 5,
status4k: 1,
createdAt: '2023-02-06T19:38:45.000Z',
updatedAt: '2023-02-06T20:00:04.000Z',
lastSeasonChange: '2023-08-06T19:38:45.000Z',
mediaAddedAt: '2023-05-14T06:30:34.000Z',
serviceId: 0,
serviceId4k: null,
externalServiceId: 32,
externalServiceId4k: null,
externalServiceSlug: '000000000000',
externalServiceSlug4k: null,
ratingKey: null,
ratingKey4k: null,
jellyfinMediaId: '0000',
jellyfinMediaId4k: null,
mediaUrl:
'http://your-jellyfin.local/web/index.html#!/details?id=mn8q2j4gq038g&context=home&serverId=jf83fj34gm340g',
serviceUrl: 'http://your-jellyfin.local/movie/0000',
},
seasons: [],
modifiedBy: {
permissions: 2,
warnings: [],
id: 1,
email: 'example-user@homarr.dev',
plexUsername: null,
jellyfinUsername: 'example-user',
username: null,
recoveryLinkExpirationDate: null,
userType: 3,
plexId: null,
jellyfinUserId: '00000000000000000',
jellyfinDeviceId: '111111111111111111',
jellyfinAuthToken: '2222222222222222222',
plexToken: null,
avatar: '/os_logo_square.png',
movieQuotaLimit: null,
movieQuotaDays: null,
tvQuotaLimit: null,
tvQuotaDays: null,
createdAt: '2022-07-03T19:53:08.000Z',
updatedAt: '2022-07-03T19:53:08.000Z',
requestCount: 34,
displayName: 'Example User',
},
requestedBy: {
permissions: 2,
warnings: [],
id: 1,
email: 'example-user@homarr.dev',
plexUsername: null,
jellyfinUsername: 'example-user',
username: null,
recoveryLinkExpirationDate: null,
userType: 3,
plexId: null,
jellyfinUserId: '00000000000000000',
jellyfinDeviceId: '111111111111111111',
jellyfinAuthToken: '2222222222222222222',
plexToken: null,
avatar: '/os_logo_square.png',
movieQuotaLimit: null,
movieQuotaDays: null,
tvQuotaLimit: null,
tvQuotaDays: null,
createdAt: '2022-07-03T19:53:08.000Z',
updatedAt: '2022-07-03T19:53:08.000Z',
requestCount: 34,
displayName: 'Example User',
},
seasonCount: 0,
},
],
});
}
if (request.url === 'http://my-overseerr.local/api/v1/movie/99999999') {
return JSON.stringify({
id: 0,
adult: false,
budget: 0,
genres: [
{
id: 18,
name: 'Dashboards',
},
],
relatedVideos: [],
originalLanguage: 'jp',
originalTitle: 'Homarrrr Movie',
popularity: 9.352,
productionCompanies: [],
productionCountries: [],
releaseDate: '2023-12-08',
releases: {
results: [],
},
revenue: 0,
spokenLanguages: [
{
english_name: 'Japanese',
iso_639_1: 'jp',
name: '日本語',
},
],
status: 'Released',
title: 'Homarr Movie',
video: false,
voteAverage: 9.999,
voteCount: 0,
backdropPath: '/mhjq8jr0qgrjnghnh.jpg',
homepage: '',
imdbId: 'tt0000000',
overview: 'A very cool movie',
posterPath: '/hf4j0928gq543njgh8935nqh8.jpg',
runtime: 97,
tagline: '',
credits: {},
collection: null,
externalIds: {
facebookId: null,
imdbId: null,
instagramId: null,
twitterId: null,
},
watchProviders: [],
keywords: [],
});
}
return Promise.reject(new Error(`Bad url: ${request.url}`));
});
// Act
await MediaRequestsRoute(req, res);
// Assert
expect(res._getStatusCode()).toBe(200);
expect(res.finished).toBe(true);
expect(JSON.parse(res._getData())).toEqual([
{
airDate: '2023-12-08',
backdropPath: 'https://image.tmdb.org/t/p/original//mhjq8jr0qgrjnghnh.jpg',
createdAt: '2023-04-06T19:38:45.000Z',
href: 'http://my-overseerr.local/movie/99999999',
id: 44,
name: 'Homarrrr Movie',
posterPath:
'https://image.tmdb.org/t/p/w600_and_h900_bestv2//hf4j0928gq543njgh8935nqh8.jpg',
status: 2,
type: 'movie',
userLink: 'http://my-overseerr.local/users/1', userLink: 'http://my-overseerr.local/users/1',
userName: 'Example User', userName: 'Example User',
userProfilePicture: 'http://my-overseerr.local//os_logo_square.png', userProfilePicture: 'http://my-overseerr.local//os_logo_square.png',

View File

@@ -51,7 +51,7 @@ const Get = async (request: NextApiRequest, response: NextApiResponse) => {
type: item.type, type: item.type,
name: genericItem.name, name: genericItem.name,
userName: item.requestedBy.displayName, userName: item.requestedBy.displayName,
userProfilePicture: constructAvatarUrl(app, item), userProfilePicture: constructAvatarUrl(appUrl, item),
userLink: `${appUrl}/users/${item.requestedBy.id}`, userLink: `${appUrl}/users/${item.requestedBy.id}`,
airDate: genericItem.airDate, airDate: genericItem.airDate,
status: item.status, status: item.status,
@@ -75,7 +75,7 @@ const Get = async (request: NextApiRequest, response: NextApiResponse) => {
return response.status(200).json(mediaRequests); return response.status(200).json(mediaRequests);
}; };
const constructAvatarUrl = (app: ConfigAppType, item: OverseerrResponseItem) => { const constructAvatarUrl = (appUrl: string, item: OverseerrResponseItem) => {
const isAbsolute = const isAbsolute =
item.requestedBy.avatar.startsWith('http://') || item.requestedBy.avatar.startsWith('https://'); item.requestedBy.avatar.startsWith('http://') || item.requestedBy.avatar.startsWith('https://');
@@ -83,7 +83,7 @@ const constructAvatarUrl = (app: ConfigAppType, item: OverseerrResponseItem) =>
return item.requestedBy.avatar; return item.requestedBy.avatar;
} }
return `${app.url}/${item.requestedBy.avatar}`; return `${appUrl}/${item.requestedBy.avatar}`;
}; };
const retrieveDetailsForItem = async ( const retrieveDetailsForItem = async (

View File

@@ -0,0 +1,41 @@
import { z } from 'zod';
export const adGuardApiStatsResponseSchema = z.object({
time_units: z.enum(['hours']),
top_queried_domains: z.array(z.record(z.string(), z.number())),
top_clients: z.array(z.record(z.string(), z.number())),
top_blocked_domains: z.array(z.record(z.string(), z.number())),
dns_queries: z.array(z.number()),
blocked_filtering: z.array(z.number()),
replaced_safebrowsing: z.array(z.number()),
replaced_parental: z.array(z.number()),
num_dns_queries: z.number().min(0),
num_blocked_filtering: z.number().min(0),
num_replaced_safebrowsing: z.number().min(0),
num_replaced_safesearch: z.number().min(0),
num_replaced_parental: z.number().min(0),
avg_processing_time: z.number().min(0).max(1),
});
export const adGuardApiStatusResponseSchema = z.object({
version: z.string(),
language: z.string(),
dns_addresses: z.array(z.string()),
dns_port: z.number().positive(),
http_port: z.number().positive(),
protection_disabled_duration: z.number(),
protection_enabled: z.boolean(),
dhcp_available: z.boolean(),
running: z.boolean(),
});
export const adGuardApiFilteringStatusSchema = z.object({
filters: z.array(z.object({
url: z.string().url(),
name: z.string(),
last_updated: z.string().optional(),
id: z.number().nonnegative(),
rules_count: z.number().nonnegative(),
enabled: z.boolean(),
})),
});

View File

@@ -0,0 +1,95 @@
import { trimStringEnding } from '../../../shared/strings';
import {
adGuardApiFilteringStatusSchema,
adGuardApiStatsResponseSchema,
adGuardApiStatusResponseSchema,
} from './adGuard.schema';
export class AdGuard {
private readonly baseHostName: string;
constructor(
hostname: string,
private readonly username: string,
private readonly password: string
) {
this.baseHostName = trimStringEnding(hostname, ['/#', '/']);
}
async getStats(): Promise<AdGuardStatsType> {
const response = await fetch(`${this.baseHostName}/control/stats`, {
headers: {
Authorization: `Basic ${this.getAuthorizationHeaderValue()}`,
},
});
const data = await response.json();
return adGuardApiStatsResponseSchema.parseAsync(data);
}
async getStatus() {
const response = await fetch(`${this.baseHostName}/control/status`, {
headers: {
Authorization: `Basic ${this.getAuthorizationHeaderValue()}`,
},
});
const data = await response.json();
return adGuardApiStatusResponseSchema.parseAsync(data);
}
async getCountFilteringDomains() {
const response = await fetch(`${this.baseHostName}/control/filtering/status`, {
headers: {
Authorization: `Basic ${this.getAuthorizationHeaderValue()}`,
},
});
const data = await response.json();
const schemaData = await adGuardApiFilteringStatusSchema.parseAsync(data);
return schemaData.filters
.filter((filter) => filter.enabled)
.reduce((sum, filter) => filter.rules_count + sum, 0);
}
async disable() {
await this.changeProtectionStatus(false);
}
async enable() {
await this.changeProtectionStatus(false);
}
private async changeProtectionStatus(newStatus: boolean, duration = 0) {
await fetch(`${this.baseHostName}/control/protection`, {
method: 'POST',
body: JSON.stringify({
enabled: newStatus,
duration,
}),
});
}
private getAuthorizationHeaderValue() {
return Buffer.from(`${this.username}:${this.password}`).toString('base64');
}
}
export type AdGuardStatsType = {
time_units: string;
top_queried_domains: { [key: string]: number }[];
top_clients: { [key: string]: number }[];
top_blocked_domains: { [key: string]: number }[];
dns_queries: number[];
blocked_filtering: number[];
replaced_safebrowsing: number[];
replaced_parental: number[];
num_dns_queries: number;
num_blocked_filtering: number;
num_replaced_safebrowsing: number;
num_replaced_safesearch: number;
num_replaced_parental: number;
avg_processing_time: number;
};

View File

@@ -1,10 +1,11 @@
import { trimStringEnding } from '../../../shared/strings';
import { PiHoleApiStatusChangeResponse, PiHoleApiSummaryResponse } from './piHole.type'; import { PiHoleApiStatusChangeResponse, PiHoleApiSummaryResponse } from './piHole.type';
export class PiHoleClient { export class PiHoleClient {
private readonly baseHostName: string; private readonly baseHostName: string;
constructor(hostname: string, private readonly apiToken: string) { constructor(hostname: string, private readonly apiToken: string) {
this.baseHostName = this.trimStringEnding(hostname, ['/admin/index.php', '/admin', '/']); this.baseHostName = trimStringEnding(hostname, ['/admin/index.php', '/admin', '/']);
} }
async getSummary() { async getSummary() {
@@ -60,15 +61,4 @@ export class PiHoleClient {
return json as PiHoleApiStatusChangeResponse; return json as PiHoleApiStatusChangeResponse;
} }
private trimStringEnding(original: string, toTrimIfExists: string[]) {
for (let i = 0; i < toTrimIfExists.length; i += 1) {
if (!original.endsWith(toTrimIfExists[i])) {
continue;
}
return original.substring(0, original.indexOf(toTrimIfExists[i]));
}
return original;
}
} }

View File

@@ -0,0 +1,10 @@
export const trimStringEnding = (original: string, toTrimIfExists: string[]) => {
for (let i = 0; i < toTrimIfExists.length; i += 1) {
if (!original.endsWith(toTrimIfExists[i])) {
continue;
}
return original.substring(0, original.indexOf(toTrimIfExists[i]));
}
return original;
};

View File

@@ -45,7 +45,8 @@ export type IntegrationType =
| 'plex' | 'plex'
| 'jellyfin' | 'jellyfin'
| 'nzbGet' | 'nzbGet'
| 'pihole'; | 'pihole'
| 'adGuardHome';
export type AppIntegrationType = { export type AppIntegrationType = {
type: IntegrationType | null; type: IntegrationType | null;
@@ -86,6 +87,7 @@ export const integrationFieldProperties: {
jellyfin: ['username', 'password'], jellyfin: ['username', 'password'],
plex: ['apiKey'], plex: ['apiKey'],
pihole: ['password'], pihole: ['password'],
adGuardHome: ['username', 'password'],
}; };
export type IntegrationFieldDefinitionType = { export type IntegrationFieldDefinitionType = {

View File

@@ -136,7 +136,7 @@ function DnsHoleSummaryWidgetTile({ widget }: DnsHoleSummaryWidgetProps) {
<Stack align="center" spacing="xs"> <Stack align="center" spacing="xs">
<IconSearch size={30} /> <IconSearch size={30} />
<div> <div>
<Text align="center">{formatNumber(data.dnsQueriesToday, 0)}</Text> <Text align="center">{formatNumber(data.dnsQueriesToday, 3)}</Text>
<Text align="center" lh={1.2} size="sm"> <Text align="center" lh={1.2} size="sm">
{t('card.metrics.queriesToday')} {t('card.metrics.queriesToday')}
</Text> </Text>

328
yarn.lock
View File

@@ -1561,22 +1561,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@playwright/test@npm:^1.33.0":
version: 1.33.0
resolution: "@playwright/test@npm:1.33.0"
dependencies:
"@types/node": "*"
fsevents: 2.3.2
playwright-core: 1.33.0
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: cec3215fc92c1cb9f5bfba357ea1cbe97b54979ab82f9d34a2287b1687cda5e0966b8ea7290dcd35416e18668e56d5781b6b8c4cec64baf12f3ae8dde0f68f5e
languageName: node
linkType: hard
"@polka/url@npm:^1.0.0-next.20": "@polka/url@npm:^1.0.0-next.20":
version: 1.0.0-next.21 version: 1.0.0-next.21
resolution: "@polka/url@npm:1.0.0-next.21" resolution: "@polka/url@npm:1.0.0-next.21"
@@ -2060,7 +2044,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/chai@npm:*, @types/chai@npm:^4.3.4": "@types/chai@npm:*, @types/chai@npm:^4.3.5":
version: 4.3.5 version: 4.3.5
resolution: "@types/chai@npm:4.3.5" resolution: "@types/chai@npm:4.3.5"
checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431
@@ -2560,34 +2544,46 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vitest/expect@npm:0.29.8": "@vitest/expect@npm:0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "@vitest/expect@npm:0.29.8" resolution: "@vitest/expect@npm:0.31.1"
dependencies: dependencies:
"@vitest/spy": 0.29.8 "@vitest/spy": 0.31.1
"@vitest/utils": 0.29.8 "@vitest/utils": 0.31.1
chai: ^4.3.7 chai: ^4.3.7
checksum: a80f9c352a979eb46690be2ea54b5ca391d3575b4053be80c1359325fb0cea913d6217f48d54e64ff5dda3b15bd7a6873a5f8128e8c098f7ebad1365d4065c5e checksum: 0d1e135ae753d913231eae830da00ee42afca53d354898fb43f97e82398dcf17298c02e9989dd6b19b9b2909989248ef76d203d63f6af6f9159dc96959ea654b
languageName: node languageName: node
linkType: hard linkType: hard
"@vitest/runner@npm:0.29.8": "@vitest/runner@npm:0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "@vitest/runner@npm:0.29.8" resolution: "@vitest/runner@npm:0.31.1"
dependencies: dependencies:
"@vitest/utils": 0.29.8 "@vitest/utils": 0.31.1
concordance: ^5.0.4
p-limit: ^4.0.0 p-limit: ^4.0.0
pathe: ^1.1.0 pathe: ^1.1.0
checksum: 8305370ff6c3fc6aea7189bd138ee4ff0e040a959c0fe6ab64bcb9e70ae5bf836b8dc058b1de288aa75c9d1cd648e5f112e7cd5691c03b7a1d32466d8bfc71a9 checksum: cc8702e21b799d5e941409cb2afe6d0e576b4f3ac99df4a1393a8cd11b57f6b0b06e756cc24e2739812d095fbfd0824e22e861dbd6a71769ca387d485ade6fb5
languageName: node languageName: node
linkType: hard linkType: hard
"@vitest/spy@npm:0.29.8": "@vitest/snapshot@npm:0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "@vitest/spy@npm:0.29.8" resolution: "@vitest/snapshot@npm:0.31.1"
dependencies: dependencies:
tinyspy: ^1.0.2 magic-string: ^0.30.0
checksum: 7b1607b696275bf94a497e92d7d10c466b9b3d08726bbedb3735bdf57f003763a9516e328af22746829526ce573f87eb6119ab64ce7db95794b2d220aa53b607 pathe: ^1.1.0
pretty-format: ^27.5.1
checksum: de05fa9136864f26f0804baf3ae8068f67de28015f29047329c84e67fb33be7305c9e52661b016e834d30f4081c136b3b6d8d4054c024a5d52b22a7f90fc4be0
languageName: node
linkType: hard
"@vitest/spy@npm:0.31.1":
version: 0.31.1
resolution: "@vitest/spy@npm:0.31.1"
dependencies:
tinyspy: ^2.1.0
checksum: 8b06cf25fcc028c16106ec82f4ceb84d6dfa04d06f651bca4738ce2b99796d1fc4e0c10319767240755eff8ede2bff9d31d5a901fe92828d319c65001581137b
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2604,15 +2600,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vitest/utils@npm:0.29.8": "@vitest/utils@npm:0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "@vitest/utils@npm:0.29.8" resolution: "@vitest/utils@npm:0.31.1"
dependencies: dependencies:
cli-truncate: ^3.1.0 concordance: ^5.0.4
diff: ^5.1.0
loupe: ^2.3.6 loupe: ^2.3.6
pretty-format: ^27.5.1 pretty-format: ^27.5.1
checksum: fa18cccb6ab5295e43a1a43b9c022f070646a893adb0561c50b3e0c39f05ea74cbf379aef22ef485ea9acbf2bb8f0a224d457fd4f16b9e1bf509c13052c7f08b checksum: 58016c185455e3814632cb77e37368c846bde5e342f8b4a66fa229bde64f455ca39abebc9c12e2483696ee38bc17b3c4300379f7a3b18d1087f24f474448a8d8
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2665,7 +2660,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"acorn@npm:^8.0.4, acorn@npm:^8.4.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2": "acorn@npm:^8.0.4, acorn@npm:^8.4.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2":
version: 8.8.2 version: 8.8.2
resolution: "acorn@npm:8.8.2" resolution: "acorn@npm:8.8.2"
bin: bin:
@@ -2777,13 +2772,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ansi-regex@npm:^6.0.1":
version: 6.0.1
resolution: "ansi-regex@npm:6.0.1"
checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169
languageName: node
linkType: hard
"ansi-styles@npm:^3.2.1": "ansi-styles@npm:^3.2.1":
version: 3.2.1 version: 3.2.1
resolution: "ansi-styles@npm:3.2.1" resolution: "ansi-styles@npm:3.2.1"
@@ -2809,13 +2797,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ansi-styles@npm:^6.0.0":
version: 6.2.1
resolution: "ansi-styles@npm:6.2.1"
checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9
languageName: node
linkType: hard
"ansicolors@npm:~0.3.2": "ansicolors@npm:~0.3.2":
version: 0.3.2 version: 0.3.2
resolution: "ansicolors@npm:0.3.2" resolution: "ansicolors@npm:0.3.2"
@@ -3132,6 +3113,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"blueimp-md5@npm:^2.10.0":
version: 2.19.0
resolution: "blueimp-md5@npm:2.19.0"
checksum: 28095dcbd2c67152a2938006e8d7c74c3406ba6556071298f872505432feb2c13241b0476644160ee0a5220383ba94cb8ccdac0053b51f68d168728f9c382530
languageName: node
linkType: hard
"brace-expansion@npm:^1.1.7": "brace-expansion@npm:^1.1.7":
version: 1.1.11 version: 1.1.11
resolution: "brace-expansion@npm:1.1.11" resolution: "brace-expansion@npm:1.1.11"
@@ -3532,16 +3520,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"cli-truncate@npm:^3.1.0":
version: 3.1.0
resolution: "cli-truncate@npm:3.1.0"
dependencies:
slice-ansi: ^5.0.0
string-width: ^5.0.0
checksum: c3243e41974445691c63f8b405df1d5a24049dc33d324fe448dc572e561a7b772ae982692900b1a5960901cc4fc7def25a629b9c69a4208ee89d12ab3332617a
languageName: node
linkType: hard
"cli-width@npm:^3.0.0": "cli-width@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "cli-width@npm:3.0.0" resolution: "cli-width@npm:3.0.0"
@@ -3683,6 +3661,22 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"concordance@npm:^5.0.4":
version: 5.0.4
resolution: "concordance@npm:5.0.4"
dependencies:
date-time: ^3.1.0
esutils: ^2.0.3
fast-diff: ^1.2.0
js-string-escape: ^1.0.1
lodash: ^4.17.15
md5-hex: ^3.0.1
semver: ^7.3.2
well-known-symbols: ^2.0.0
checksum: 749153ba711492feb7c3d2f5bb04c107157440b3e39509bd5dd19ee7b3ac751d1e4cd75796d9f702e0a713312dbc661421c68aa4a2c34d5f6d91f47e3a1c64a6
languageName: node
linkType: hard
"conf@npm:10.2.0": "conf@npm:10.2.0":
version: 10.2.0 version: 10.2.0
resolution: "conf@npm:10.2.0" resolution: "conf@npm:10.2.0"
@@ -3997,6 +3991,15 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"date-time@npm:^3.1.0":
version: 3.1.0
resolution: "date-time@npm:3.1.0"
dependencies:
time-zone: ^1.0.0
checksum: f9cfcd1b15dfeabab15c0b9d18eb9e4e2d9d4371713564178d46a8f91ad577a290b5178b80050718d02d9c0cf646f8a875011e12d1ed05871e9f72c72c8a8fe6
languageName: node
linkType: hard
"dayjs@npm:^1.11.7": "dayjs@npm:^1.11.7":
version: 1.11.7 version: 1.11.7
resolution: "dayjs@npm:1.11.7" resolution: "dayjs@npm:1.11.7"
@@ -4174,13 +4177,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"diff@npm:^5.1.0":
version: 5.1.0
resolution: "diff@npm:5.1.0"
checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90
languageName: node
linkType: hard
"dir-glob@npm:^3.0.1": "dir-glob@npm:^3.0.1":
version: 3.0.1 version: 3.0.1
resolution: "dir-glob@npm:3.0.1" resolution: "dir-glob@npm:3.0.1"
@@ -4337,13 +4333,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"eastasianwidth@npm:^0.2.0":
version: 0.2.0
resolution: "eastasianwidth@npm:0.2.0"
checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed
languageName: node
linkType: hard
"ejs@npm:^3.1.6, ejs@npm:^3.1.8": "ejs@npm:^3.1.6, ejs@npm:^3.1.8":
version: 3.1.9 version: 3.1.9
resolution: "ejs@npm:3.1.9" resolution: "ejs@npm:3.1.9"
@@ -4971,7 +4960,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"esutils@npm:^2.0.2": "esutils@npm:^2.0.2, esutils@npm:^2.0.3":
version: 2.0.3 version: 2.0.3
resolution: "esutils@npm:2.0.3" resolution: "esutils@npm:2.0.3"
checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87
@@ -5016,6 +5005,13 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-diff@npm:^1.2.0":
version: 1.3.0
resolution: "fast-diff@npm:1.3.0"
checksum: d22d371b994fdc8cce9ff510d7b8dc4da70ac327bcba20df607dd5b9cae9f908f4d1028f5fe467650f058d1e7270235ae0b8230809a262b4df587a3b3aa216c3
languageName: node
linkType: hard
"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9": "fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9":
version: 3.2.12 version: 3.2.12
resolution: "fast-glob@npm:3.2.12" resolution: "fast-glob@npm:3.2.12"
@@ -5278,7 +5274,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"fsevents@npm:2.3.2, fsevents@npm:~2.3.2": "fsevents@npm:~2.3.2":
version: 2.3.2 version: 2.3.2
resolution: "fsevents@npm:2.3.2" resolution: "fsevents@npm:2.3.2"
dependencies: dependencies:
@@ -5288,7 +5284,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"fsevents@patch:fsevents@2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>": "fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
version: 2.3.2 version: 2.3.2
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=df0bf1" resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=df0bf1"
dependencies: dependencies:
@@ -5745,7 +5741,6 @@ checkly@latest:
"@next/eslint-plugin-next": ^13.0.0 "@next/eslint-plugin-next": ^13.0.0
"@nivo/core": ^0.80.0 "@nivo/core": ^0.80.0
"@nivo/line": ^0.80.0 "@nivo/line": ^0.80.0
"@playwright/test": ^1.33.0
"@react-native-async-storage/async-storage": ^1.18.1 "@react-native-async-storage/async-storage": ^1.18.1
"@tabler/icons-react": ^2.18.0 "@tabler/icons-react": ^2.18.0
"@tanstack/query-async-storage-persister": ^4.27.1 "@tanstack/query-async-storage-persister": ^4.27.1
@@ -5806,7 +5801,7 @@ checkly@latest:
typescript: ^5.0.4 typescript: ^5.0.4
uuid: ^8.3.2 uuid: ^8.3.2
video.js: ^8.0.3 video.js: ^8.0.3
vitest: ^0.29.3 vitest: ^0.31.1
vitest-fetch-mock: ^0.2.2 vitest-fetch-mock: ^0.2.2
xml-js: ^1.6.11 xml-js: ^1.6.11
yarn: ^1.22.19 yarn: ^1.22.19
@@ -6245,13 +6240,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"is-fullwidth-code-point@npm:^4.0.0":
version: 4.0.0
resolution: "is-fullwidth-code-point@npm:4.0.0"
checksum: 8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d
languageName: node
linkType: hard
"is-function@npm:^1.0.1": "is-function@npm:^1.0.1":
version: 1.0.2 version: 1.0.2
resolution: "is-function@npm:1.0.2" resolution: "is-function@npm:1.0.2"
@@ -6606,6 +6594,13 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"js-string-escape@npm:^1.0.1":
version: 1.0.1
resolution: "js-string-escape@npm:1.0.1"
checksum: f11e0991bf57e0c183b55c547acec85bd2445f043efc9ea5aa68b41bd2a3e7d3ce94636cb233ae0d84064ba4c1a505d32e969813c5b13f81e7d4be12c59256fe
languageName: node
linkType: hard
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "js-tokens@npm:4.0.0" resolution: "js-tokens@npm:4.0.0"
@@ -6827,7 +6822,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"local-pkg@npm:^0.4.2": "local-pkg@npm:^0.4.3":
version: 0.4.3 version: 0.4.3
resolution: "local-pkg@npm:0.4.3" resolution: "local-pkg@npm:0.4.3"
checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55 checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55
@@ -6972,6 +6967,15 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"magic-string@npm:^0.30.0":
version: 0.30.0
resolution: "magic-string@npm:0.30.0"
dependencies:
"@jridgewell/sourcemap-codec": ^1.4.13
checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d
languageName: node
linkType: hard
"make-dir@npm:^3.0.0": "make-dir@npm:^3.0.0":
version: 3.1.0 version: 3.1.0
resolution: "make-dir@npm:3.1.0" resolution: "make-dir@npm:3.1.0"
@@ -7012,6 +7016,15 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"md5-hex@npm:^3.0.1":
version: 3.0.1
resolution: "md5-hex@npm:3.0.1"
dependencies:
blueimp-md5: ^2.10.0
checksum: 6799a19e8bdd3e0c2861b94c1d4d858a89220488d7885c1fa236797e367d0c2e5f2b789e05309307083503f85be3603a9686a5915568a473137d6b4117419cc2
languageName: node
linkType: hard
"media-typer@npm:0.3.0": "media-typer@npm:0.3.0":
version: 0.3.0 version: 0.3.0
resolution: "media-typer@npm:0.3.0" resolution: "media-typer@npm:0.3.0"
@@ -7253,7 +7266,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"mlly@npm:^1.1.0, mlly@npm:^1.2.0": "mlly@npm:^1.2.0":
version: 1.2.1 version: 1.2.1
resolution: "mlly@npm:1.2.1" resolution: "mlly@npm:1.2.1"
dependencies: dependencies:
@@ -8088,15 +8101,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"playwright-core@npm:1.33.0":
version: 1.33.0
resolution: "playwright-core@npm:1.33.0"
bin:
playwright: cli.js
checksum: 5fb7bda06a8b73b56b85b5a0b8f711211dde57a375d9379289e22239b2de879c6d93c8fdc9ba44b932bf100914ab1ca1a55697ad88440fdd0a39101fc020b77f
languageName: node
linkType: hard
"postcss@npm:8.4.14": "postcss@npm:8.4.14":
version: 8.4.14 version: 8.4.14
resolution: "postcss@npm:8.4.14" resolution: "postcss@npm:8.4.14"
@@ -8857,7 +8861,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": "semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8":
version: 7.5.1 version: 7.5.1
resolution: "semver@npm:7.5.1" resolution: "semver@npm:7.5.1"
dependencies: dependencies:
@@ -8961,16 +8965,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"slice-ansi@npm:^5.0.0":
version: 5.0.0
resolution: "slice-ansi@npm:5.0.0"
dependencies:
ansi-styles: ^6.0.0
is-fullwidth-code-point: ^4.0.0
checksum: 7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e
languageName: node
linkType: hard
"smart-buffer@npm:^4.2.0": "smart-buffer@npm:^4.2.0":
version: 4.2.0 version: 4.2.0
resolution: "smart-buffer@npm:4.2.0" resolution: "smart-buffer@npm:4.2.0"
@@ -9013,13 +9007,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"source-map@npm:^0.6.1":
version: 0.6.1
resolution: "source-map@npm:0.6.1"
checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2
languageName: node
linkType: hard
"split-ca@npm:^1.0.1": "split-ca@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "split-ca@npm:1.0.1" resolution: "split-ca@npm:1.0.1"
@@ -9085,7 +9072,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"std-env@npm:^3.3.1": "std-env@npm:^3.3.1, std-env@npm:^3.3.2":
version: 3.3.3 version: 3.3.3
resolution: "std-env@npm:3.3.3" resolution: "std-env@npm:3.3.3"
checksum: 6665f6d8bd63aae432d3eb9abbd7322847ad0d902603e6dce1e8051b4f42ceeb4f7f96a4faf70bb05ce65ceee2dc982502b701575c8a58b1bfad29f3dbb19f81 checksum: 6665f6d8bd63aae432d3eb9abbd7322847ad0d902603e6dce1e8051b4f42ceeb4f7f96a4faf70bb05ce65ceee2dc982502b701575c8a58b1bfad29f3dbb19f81
@@ -9126,17 +9113,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"string-width@npm:^5.0.0":
version: 5.1.2
resolution: "string-width@npm:5.1.2"
dependencies:
eastasianwidth: ^0.2.0
emoji-regex: ^9.2.2
strip-ansi: ^7.0.1
checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193
languageName: node
linkType: hard
"string.prototype.matchall@npm:^4.0.8": "string.prototype.matchall@npm:^4.0.8":
version: 4.0.8 version: 4.0.8
resolution: "string.prototype.matchall@npm:4.0.8" resolution: "string.prototype.matchall@npm:4.0.8"
@@ -9220,15 +9196,6 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"strip-ansi@npm:^7.0.1":
version: 7.0.1
resolution: "strip-ansi@npm:7.0.1"
dependencies:
ansi-regex: ^6.0.1
checksum: 257f78fa433520e7f9897722731d78599cb3fce29ff26a20a5e12ba4957463b50a01136f37c43707f4951817a75e90820174853d6ccc240997adc5df8f966039
languageName: node
linkType: hard
"strip-bom@npm:^3.0.0": "strip-bom@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "strip-bom@npm:3.0.0" resolution: "strip-bom@npm:3.0.0"
@@ -9252,7 +9219,7 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"strip-literal@npm:^1.0.0": "strip-literal@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "strip-literal@npm:1.0.1" resolution: "strip-literal@npm:1.0.1"
dependencies: dependencies:
@@ -9436,24 +9403,31 @@ checkly@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"tinybench@npm:^2.3.1": "time-zone@npm:^1.0.0":
version: 1.0.0
resolution: "time-zone@npm:1.0.0"
checksum: e46f5a69b8c236dcd8e91e29d40d4e7a3495ed4f59888c3f84ce1d9678e20461421a6ba41233509d47dd94bc18f1a4377764838b21b584663f942b3426dcbce8
languageName: node
linkType: hard
"tinybench@npm:^2.5.0":
version: 2.5.0 version: 2.5.0
resolution: "tinybench@npm:2.5.0" resolution: "tinybench@npm:2.5.0"
checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821 checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821
languageName: node languageName: node
linkType: hard linkType: hard
"tinypool@npm:^0.4.0": "tinypool@npm:^0.5.0":
version: 0.4.0 version: 0.5.0
resolution: "tinypool@npm:0.4.0" resolution: "tinypool@npm:0.5.0"
checksum: 8abcac9e784793499f1eeeace8290c026454b9d7338c74029ce6a821643bab8dcab7caeb4051e39006baf681d6a62d57c3319e9c0f6e2317a45ab0fdbd76ee26 checksum: 4e0dfd8f28666d541c1d92304222edc4613f05d74fe2243c8520d466a2cc6596011a7072c1c41a7de7522351b82fda07e8038198e8f43673d8d69401c5903f8c
languageName: node languageName: node
linkType: hard linkType: hard
"tinyspy@npm:^1.0.2": "tinyspy@npm:^2.1.0":
version: 1.1.1 version: 2.1.0
resolution: "tinyspy@npm:1.1.1" resolution: "tinyspy@npm:2.1.0"
checksum: 4ea908fdfddb92044c4454193ec543f5980ced0bd25c5b3d240a94c1511e47e765ad39cd13ae6d3370fb730f62038eedc357f55e4e239416e126bc418f0eee79 checksum: cb83c1f74a79dd5934018bad94f60a304a29d98a2d909ea45fc367f7b80b21b0a7d8135a2ce588deb2b3ba56c7c607258b2a03e6001d89e4d564f9a95cc6a81f
languageName: node languageName: node
linkType: hard linkType: hard
@@ -10020,19 +9994,19 @@ turbo@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"vite-node@npm:0.29.8": "vite-node@npm:0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "vite-node@npm:0.29.8" resolution: "vite-node@npm:0.31.1"
dependencies: dependencies:
cac: ^6.7.14 cac: ^6.7.14
debug: ^4.3.4 debug: ^4.3.4
mlly: ^1.1.0 mlly: ^1.2.0
pathe: ^1.1.0 pathe: ^1.1.0
picocolors: ^1.0.0 picocolors: ^1.0.0
vite: ^3.0.0 || ^4.0.0 vite: ^3.0.0 || ^4.0.0
bin: bin:
vite-node: vite-node.mjs vite-node: vite-node.mjs
checksum: b0981d4d63b1f373579eb9da69ca5af9123bf27c81ac246c541cdecf879ef4ef542e0b521cb6ceaafd5ead2cc3d243105d1fb8bf076953d42a6b2203607ce928 checksum: f70ffa3f6dcb4937cdc99f59bf360d42de83c556ba9a19eb1c3504ef20db4c1d1afa644d9a8e63240e851c0c95773b64c526bdb3eb4794b5e941ddcd57124aa9
languageName: node languageName: node
linkType: hard linkType: hard
@@ -10084,33 +10058,34 @@ turbo@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"vitest@npm:^0.29.3": "vitest@npm:^0.31.1":
version: 0.29.8 version: 0.31.1
resolution: "vitest@npm:0.29.8" resolution: "vitest@npm:0.31.1"
dependencies: dependencies:
"@types/chai": ^4.3.4 "@types/chai": ^4.3.5
"@types/chai-subset": ^1.3.3 "@types/chai-subset": ^1.3.3
"@types/node": "*" "@types/node": "*"
"@vitest/expect": 0.29.8 "@vitest/expect": 0.31.1
"@vitest/runner": 0.29.8 "@vitest/runner": 0.31.1
"@vitest/spy": 0.29.8 "@vitest/snapshot": 0.31.1
"@vitest/utils": 0.29.8 "@vitest/spy": 0.31.1
acorn: ^8.8.1 "@vitest/utils": 0.31.1
acorn: ^8.8.2
acorn-walk: ^8.2.0 acorn-walk: ^8.2.0
cac: ^6.7.14 cac: ^6.7.14
chai: ^4.3.7 chai: ^4.3.7
concordance: ^5.0.4
debug: ^4.3.4 debug: ^4.3.4
local-pkg: ^0.4.2 local-pkg: ^0.4.3
magic-string: ^0.30.0
pathe: ^1.1.0 pathe: ^1.1.0
picocolors: ^1.0.0 picocolors: ^1.0.0
source-map: ^0.6.1 std-env: ^3.3.2
std-env: ^3.3.1 strip-literal: ^1.0.1
strip-literal: ^1.0.0 tinybench: ^2.5.0
tinybench: ^2.3.1 tinypool: ^0.5.0
tinypool: ^0.4.0
tinyspy: ^1.0.2
vite: ^3.0.0 || ^4.0.0 vite: ^3.0.0 || ^4.0.0
vite-node: 0.29.8 vite-node: 0.31.1
why-is-node-running: ^2.2.2 why-is-node-running: ^2.2.2
peerDependencies: peerDependencies:
"@edge-runtime/vm": "*" "@edge-runtime/vm": "*"
@@ -10140,7 +10115,7 @@ turbo@latest:
optional: true optional: true
bin: bin:
vitest: vitest.mjs vitest: vitest.mjs
checksum: 203e33bf093fdb99a6832c905a6c78175bb15313e06e1dcfbeb010a0e3efb8ff0aba4d317efedb4de76bd0086691bbd2c4bc7d6631f60fb1634b96832cba144f checksum: b3f64a36102edc5b8594c085da648c838c0d275c620bd3b780624f936903b9c06579d6ef137fe9859e468f16deb8f154a50f009093119f9adb8b60ff1b7597ee
languageName: node languageName: node
linkType: hard linkType: hard
@@ -10200,6 +10175,13 @@ turbo@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"well-known-symbols@npm:^2.0.0":
version: 2.0.0
resolution: "well-known-symbols@npm:2.0.0"
checksum: 4f54bbc3012371cb4d228f436891b8e7536d34ac61a57541890257e96788608e096231e0121ac24d08ef2f908b3eb2dc0adba35023eaeb2a7df655da91415402
languageName: node
linkType: hard
"whatwg-encoding@npm:^2.0.0": "whatwg-encoding@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "whatwg-encoding@npm:2.0.0" resolution: "whatwg-encoding@npm:2.0.0"