Add initial metrics explorer
Some checks failed
CI / Go tests (push) Has been cancelled
CI / More Go tests (push) Has been cancelled
CI / Go tests with previous Go version (push) Has been cancelled
CI / UI tests (push) Has been cancelled
CI / Go tests on Windows (push) Has been cancelled
CI / Mixins tests (push) Has been cancelled
CI / Build Prometheus for common architectures (0) (push) Has been cancelled
CI / Build Prometheus for common architectures (1) (push) Has been cancelled
CI / Build Prometheus for common architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (0) (push) Has been cancelled
CI / Build Prometheus for all architectures (1) (push) Has been cancelled
CI / Build Prometheus for all architectures (10) (push) Has been cancelled
CI / Build Prometheus for all architectures (11) (push) Has been cancelled
CI / Build Prometheus for all architectures (2) (push) Has been cancelled
CI / Build Prometheus for all architectures (3) (push) Has been cancelled
CI / Build Prometheus for all architectures (4) (push) Has been cancelled
CI / Build Prometheus for all architectures (5) (push) Has been cancelled
CI / Build Prometheus for all architectures (6) (push) Has been cancelled
CI / Build Prometheus for all architectures (7) (push) Has been cancelled
CI / Build Prometheus for all architectures (8) (push) Has been cancelled
CI / Build Prometheus for all architectures (9) (push) Has been cancelled
CI / Check generated parser (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / fuzzing (push) Has been cancelled
CI / codeql (push) Has been cancelled
CI / Report status of build Prometheus for all architectures (push) Has been cancelled
CI / Publish main branch artifacts (push) Has been cancelled
CI / Publish release artefacts (push) Has been cancelled
CI / Publish UI on npm Registry (push) Has been cancelled

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-08-28 21:14:21 +02:00
parent 4ccadd5453
commit bc48d19d8c
5 changed files with 393 additions and 46 deletions

View file

@ -24,12 +24,14 @@
"@mantine/dates": "^7.11.2",
"@mantine/hooks": "^7.11.2",
"@mantine/notifications": "^7.11.2",
"@nexucis/fuzzy": "^0.5.1",
"@nexucis/kvsearch": "^0.9.1",
"@prometheus-io/codemirror-promql": "^0.50.0-rc.1",
"@reduxjs/toolkit": "^2.2.1",
"@tabler/icons-react": "^2.47.0",
"@tanstack/react-query": "^5.22.2",
"@types/lodash": "^4.17.7",
"@types/sanitize-html": "^2.13.0",
"@uiw/react-codemirror": "^4.21.22",
"dayjs": "^1.11.10",
"lodash": "^4.17.21",
@ -38,6 +40,7 @@
"react-infinite-scroll-component": "^6.1.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"sanitize-html": "^2.13.0",
"uplot": "^1.6.30",
"uplot-react": "^1.2.2",
"use-query-params": "^2.2.1"

View file

@ -0,0 +1,6 @@
// Result type for /api/v1/alerts endpoint.
// See: https://prometheus.io/docs/prometheus/latest/querying/api/#querying-target-metadata
export type MetadataResult = Record<
string,
{ type: string; help: string; unit: string }[]
>;

View file

@ -5,6 +5,7 @@ import {
InputBase,
Loader,
Menu,
Modal,
rem,
useComputedColorScheme,
} from "@mantine/core";
@ -13,11 +14,12 @@ import {
PromQLExtension,
newCompleteStrategy,
} from "@prometheus-io/codemirror-promql";
import { FC, useEffect, useMemo, useState } from "react";
import { FC, Suspense, useEffect, useMemo, useRef, useState } from "react";
import CodeMirror, {
EditorState,
EditorView,
Prec,
ReactCodeMirrorRef,
highlightSpecialChars,
keymap,
placeholder,
@ -62,6 +64,8 @@ import {
import { useAPIQuery } from "../../api/api";
import { notifications } from "@mantine/notifications";
import { useSettings } from "../../state/settingsSlice";
import MetricsExplorer from "./MetricsExplorer/MetricsExplorer";
import ErrorBoundary from "../../components/ErrorBoundary";
const promqlExtension = new PromQLExtension();
@ -165,6 +169,11 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
}
}, [formatResult, formatError]);
const cmRef = useRef<ReactCodeMirrorRef>(null);
const [showMetricsExplorer, setShowMetricsExplorer] = useState(false);
// TODO: Implement query history.
// This is just a placeholder until query history is implemented, so disable the linter warning.
const queryHistory = useMemo<string[]>(() => [], []);
@ -214,6 +223,7 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
leftSection={
<IconSearch style={{ width: rem(14), height: rem(14) }} />
}
onClick={() => setShowMetricsExplorer(true)}
>
Explore metrics
</Menu.Item>
@ -248,6 +258,7 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
value={expr}
onChange={setExpr}
autoFocus
ref={cmRef}
extensions={[
baseTheme,
highlightSpecialChars(),
@ -305,6 +316,35 @@ const ExpressionInput: FC<ExpressionInputProps> = ({
<Button variant="primary" onClick={() => executeQuery(expr)}>
Execute
</Button>
<Modal
size="95%"
opened={showMetricsExplorer}
onClose={() => setShowMetricsExplorer(false)}
title="Explore metrics"
>
<ErrorBoundary key={location.pathname} title="Error showing metrics">
<Suspense fallback={<Loader />}>
<MetricsExplorer
metricNames={metricNames}
insertText={(text: string) => {
if (cmRef.current && cmRef.current.view) {
const view = cmRef.current.view;
view.dispatch(
view.state.update({
changes: {
from: view.state.selection.ranges[0].from,
to: view.state.selection.ranges[0].to,
insert: text,
},
})
);
}
}}
close={() => setShowMetricsExplorer(false)}
/>
</Suspense>
</ErrorBoundary>
</Modal>
</Group>
);
};

View file

@ -0,0 +1,176 @@
import { FC, useState } from "react";
import { useSuspenseAPIQuery } from "../../../api/api";
import { MetadataResult } from "../../../api/responseTypes/metadata";
import {
ActionIcon,
Alert,
Anchor,
Group,
Stack,
Table,
TextInput,
} from "@mantine/core";
import React from "react";
import { Fuzzy } from "@nexucis/fuzzy";
import sanitizeHTML from "sanitize-html";
import { IconCopy, IconTerminal, IconZoomIn } from "@tabler/icons-react";
const fuz = new Fuzzy({
pre: '<b style="color: rgb(0, 102, 191)">',
post: "</b>",
shouldSort: true,
});
const sanitizeOpts = {
allowedTags: ["b"],
allowedAttributes: { b: ["style"] },
};
type MetricsExplorerProps = {
metricNames: string[];
insertText: (text: string) => void;
close: () => void;
};
const getSearchMatches = (input: string, expressions: string[]) =>
fuz.filter(input.replace(/ /g, ""), expressions, {
pre: '<b style="color: rgb(0, 102, 191)">',
post: "</b>",
});
const MetricsExplorer: FC<MetricsExplorerProps> = ({
metricNames,
insertText,
close,
}) => {
// const metricMeta = promAPI.useFetchAPI<MetricMetadata>(`/api/v1/metadata`);
// Fetch the alerting rules data.
const { data } = useSuspenseAPIQuery<MetadataResult>({
path: `/metadata`,
});
const [selectedMetric, setSelectedMetric] = useState<string | null>(null);
const [filterText, setFilterText] = useState<string>("");
const getMeta = (m: string) =>
data.data[m.replace(/(_count|_sum|_bucket)$/, "")] || [
{ help: "unknown", type: "unknown", unit: "unknown" },
];
if (selectedMetric !== null) {
return (
<Alert>
TODO: The labels explorer for a metric still needs to be implemented.
<br />
<br />
<Anchor fz="1em" onClick={() => setSelectedMetric(null)}>
Back to metrics list
</Anchor>
</Alert>
);
}
return (
<Stack>
<TextInput
title="Filter by text"
placeholder="Enter text to filter metric names by..."
value={filterText}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setFilterText(e.target.value)
}
autoFocus
/>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Metric</Table.Th>
<Table.Th>Type</Table.Th>
<Table.Th>Help</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{(filterText === ""
? metricNames.map((m) => ({ original: m, rendered: m }))
: getSearchMatches(filterText, metricNames)
).map((m) => (
<Table.Tr key={m.original}>
<Table.Td>
<Group justify="space-between">
<div
dangerouslySetInnerHTML={{
__html: sanitizeHTML(m.rendered, sanitizeOpts),
}}
/>
<Group gap="xs">
<ActionIcon
size="sm"
color="gray"
variant="light"
title="Explore metric"
onClick={() => {
setSelectedMetric(m.original);
}}
>
<IconZoomIn
style={{ width: "70%", height: "70%" }}
stroke={1.5}
/>
</ActionIcon>
<ActionIcon
size="sm"
color="gray"
variant="light"
title="Insert at cursor"
onClick={() => {
insertText(m.original);
close();
}}
>
<IconTerminal
style={{ width: "70%", height: "70%" }}
stroke={1.5}
/>
</ActionIcon>
<ActionIcon
size="sm"
color="gray"
variant="light"
title="Copy to clipboard"
onClick={() => {
navigator.clipboard.writeText(m.original);
}}
>
<IconCopy
style={{ width: "70%", height: "70%" }}
stroke={1.5}
/>
</ActionIcon>
</Group>
</Group>
</Table.Td>
<Table.Td c="cyan.9" fs="italic" px="lg">
{getMeta(m.original).map((meta, idx) => (
<React.Fragment key={idx}>
{meta.type}
<br />
</React.Fragment>
))}
</Table.Td>
<Table.Td c="pink.9">
{getMeta(m.original).map((meta, idx) => (
<React.Fragment key={idx}>
{meta.help}
<br />
</React.Fragment>
))}
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Stack>
);
};
export default MetricsExplorer;

212
web/ui/package-lock.json generated
View file

@ -121,12 +121,14 @@
"@mantine/dates": "^7.11.2",
"@mantine/hooks": "^7.11.2",
"@mantine/notifications": "^7.11.2",
"@nexucis/fuzzy": "^0.5.1",
"@nexucis/kvsearch": "^0.9.1",
"@prometheus-io/codemirror-promql": "^0.50.0-rc.1",
"@reduxjs/toolkit": "^2.2.1",
"@tabler/icons-react": "^2.47.0",
"@tanstack/react-query": "^5.22.2",
"@types/lodash": "^4.17.7",
"@types/sanitize-html": "^2.13.0",
"@uiw/react-codemirror": "^4.21.22",
"dayjs": "^1.11.10",
"lodash": "^4.17.21",
@ -135,6 +137,7 @@
"react-infinite-scroll-component": "^6.1.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"sanitize-html": "^2.13.0",
"uplot": "^1.6.30",
"uplot-react": "^1.2.2",
"use-query-params": "^2.2.1"
@ -430,9 +433,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz",
"integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
"integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -479,13 +482,13 @@
}
},
"node_modules/@babel/generator": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
"version": "7.25.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
"integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.0",
"@babel/types": "^7.25.4",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@ -731,13 +734,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
"integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.2"
"@babel/types": "^7.25.4"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -996,17 +999,17 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
"integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/generator": "^7.25.4",
"@babel/parser": "^7.25.4",
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.2",
"@babel/types": "^7.25.4",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1024,9 +1027,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz",
"integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2189,6 +2192,12 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@nexucis/fuzzy": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.5.1.tgz",
"integrity": "sha512-+swL9itqBe1rx5Pr8ihaIS7STOeFI90HpOFF8y/3wo3ryTxKs0Hf4xc+wiA4yi9nrY4wo3VC8HJOxNiekSBE4w==",
"license": "MIT"
},
"node_modules/@nexucis/kvsearch": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@nexucis/kvsearch/-/kvsearch-0.9.1.tgz",
@ -2198,12 +2207,6 @@
"@nexucis/fuzzy": "^0.5.1"
}
},
"node_modules/@nexucis/kvsearch/node_modules/@nexucis/fuzzy": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@nexucis/fuzzy/-/fuzzy-0.5.1.tgz",
"integrity": "sha512-+swL9itqBe1rx5Pr8ihaIS7STOeFI90HpOFF8y/3wo3ryTxKs0Hf4xc+wiA4yi9nrY4wo3VC8HJOxNiekSBE4w==",
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2726,6 +2729,15 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@types/sanitize-html": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz",
"integrity": "sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==",
"license": "MIT",
"dependencies": {
"htmlparser2": "^8.0.0"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
@ -3338,9 +3350,9 @@
}
},
"node_modules/browserslist": {
"version": "4.23.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
"integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"dev": true,
"funding": [
{
@ -3358,9 +3370,9 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001640",
"electron-to-chromium": "^1.4.820",
"node-releases": "^2.0.14",
"caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
},
"bin": {
@ -3440,9 +3452,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001645",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001645.tgz",
"integrity": "sha512-GFtY2+qt91kzyMk6j48dJcwJVq5uTkk71XxE3RtScx7XWRLsO7bU44LOFkOZYR8w9YMS0UhPSYpN/6rAMImmLw==",
"version": "1.0.30001653",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz",
"integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==",
"dev": true,
"funding": [
{
@ -3765,7 +3777,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -3827,6 +3838,61 @@
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@ -3870,6 +3936,18 @@
"dev": true,
"peer": true
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -3931,7 +4009,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -4608,6 +4685,25 @@
"dev": true,
"peer": true
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -4826,6 +4922,15 @@
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@ -5820,7 +5925,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
@ -5883,10 +5987,11 @@
"peer": true
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
"dev": true
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true,
"license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
@ -6032,6 +6137,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse-srcset": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
"license": "MIT"
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -6078,7 +6189,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@ -6176,7 +6286,6 @@
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -6861,6 +6970,20 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/sanitize-html": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz",
"integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==",
"license": "MIT",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
}
},
"node_modules/sass": {
"version": "1.69.5",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",
@ -6967,7 +7090,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}