mirror of
https://github.com/dokploy/dokploy.git
synced 2026-06-13 19:09:49 +00:00
feat: integrate dompurify and simple-icons for enhanced icon management
- Added `dompurify` for sanitizing SVG icons to prevent XSS vulnerabilities. - Introduced `simple-icons` for a collection of SVG icons, enhancing the icon selection feature. - Updated the `ShowIconSettings` component to utilize the new icon management logic. - Removed the obsolete `icons.json` file and replaced it with a new `bundled-icons.ts` file for better structure and maintainability. - Adjusted related API and component files to accommodate the new icon handling approach.
This commit is contained in:
@@ -1,25 +1,37 @@
|
|||||||
|
import DOMPurify from "dompurify";
|
||||||
import { Search, X } from "lucide-react";
|
import { Search, X } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Dropzone } from "@/components/ui/dropzone";
|
import { Dropzone } from "@/components/ui/dropzone";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import iconNames from "@/lib/icons.json";
|
import { type BundledIcon, bundledIcons } from "@/lib/bundled-icons";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
interface ShowIconSettingsProps {
|
interface ShowIconSettingsProps {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const svgToDataUrl = (icon: BundledIcon): string => {
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#${icon.hex}"><path d="${icon.path}"/></svg>`;
|
||||||
|
return `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
||||||
const [uploadedIcon, setUploadedIcon] = useState<string | null>(null);
|
const [uploadedIcon, setUploadedIcon] = useState<string | null>(null);
|
||||||
const [iconSearchQuery, setIconSearchQuery] = useState("");
|
const [iconSearchQuery, setIconSearchQuery] = useState("");
|
||||||
const [iconsToShow, setIconsToShow] = useState(24);
|
const [iconsToShow, setIconsToShow] = useState(24);
|
||||||
|
|
||||||
const popularIcons = (iconNames as string[]).sort();
|
const filteredIcons = useMemo(() => {
|
||||||
const filteredIcons = popularIcons.filter((icon) =>
|
if (!iconSearchQuery) return bundledIcons;
|
||||||
icon.toLowerCase().includes(iconSearchQuery.toLowerCase()),
|
const q = iconSearchQuery.toLowerCase();
|
||||||
);
|
return bundledIcons.filter(
|
||||||
|
(icon) =>
|
||||||
|
icon.title.toLowerCase().includes(q) ||
|
||||||
|
icon.slug.toLowerCase().includes(q),
|
||||||
|
);
|
||||||
|
}, [iconSearchQuery]);
|
||||||
|
|
||||||
const displayedIcons = filteredIcons.slice(0, iconsToShow);
|
const displayedIcons = filteredIcons.slice(0, iconsToShow);
|
||||||
const hasMoreIcons = filteredIcons.length > iconsToShow;
|
const hasMoreIcons = filteredIcons.length > iconsToShow;
|
||||||
|
|
||||||
@@ -30,7 +42,6 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { mutateAsync: updateApplication } =
|
const { mutateAsync: updateApplication } =
|
||||||
api.application.update.useMutation();
|
api.application.update.useMutation();
|
||||||
const { mutateAsync: fetchIcon } = api.application.fetchIcon.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.icon) {
|
if (data?.icon) {
|
||||||
@@ -44,26 +55,35 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
setIconsToShow(24);
|
setIconsToShow(24);
|
||||||
}, [iconSearchQuery]);
|
}, [iconSearchQuery]);
|
||||||
|
|
||||||
const handleIconSelect = async (iconName: string) => {
|
const handleIconSelect = async (icon: BundledIcon) => {
|
||||||
try {
|
try {
|
||||||
const result = await fetchIcon({ iconName });
|
const dataUrl = svgToDataUrl(icon);
|
||||||
setUploadedIcon(result.icon);
|
setUploadedIcon(dataUrl);
|
||||||
await updateApplication({
|
await updateApplication({
|
||||||
applicationId,
|
applicationId,
|
||||||
icon: result.icon,
|
icon: dataUrl,
|
||||||
});
|
});
|
||||||
toast.success("Icon saved successfully");
|
toast.success("Icon saved successfully");
|
||||||
await utils.application.one.invalidate({ applicationId });
|
await utils.application.one.invalidate({ applicationId });
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Error loading icon");
|
toast.error("Error saving icon");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanitizeSvg = (svgContent: string): string | null => {
|
||||||
|
const clean = DOMPurify.sanitize(svgContent, {
|
||||||
|
USE_PROFILES: { svg: true, svgFilters: true },
|
||||||
|
ADD_TAGS: ["use"],
|
||||||
|
});
|
||||||
|
if (!clean) return null;
|
||||||
|
return `data:image/svg+xml;base64,${btoa(clean)}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 pt-2.5">
|
<div className="flex flex-col gap-4 pt-2.5">
|
||||||
{uploadedIcon && (
|
{uploadedIcon && (
|
||||||
<div className="flex items-center gap-4 p-4 rounded-lg bg-background border">
|
<div className="flex items-center gap-4 p-4 rounded-lg bg-background border">
|
||||||
{/* biome-ignore lint/performance/noImgElement: uploaded icon is data URL; Next/Image not used for preview */}
|
{/* biome-ignore lint/performance/noImgElement: icon is data URL */}
|
||||||
<img
|
<img
|
||||||
src={uploadedIcon}
|
src={uploadedIcon}
|
||||||
alt="Uploaded icon"
|
alt="Uploaded icon"
|
||||||
@@ -89,7 +109,7 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
await utils.application.one.invalidate({
|
await utils.application.one.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Error removing icon");
|
toast.error("Error removing icon");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -118,26 +138,23 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-3">
|
||||||
{displayedIcons.map((iconName) => (
|
{displayedIcons.map((icon) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={iconName}
|
key={icon.slug}
|
||||||
onClick={() => handleIconSelect(iconName)}
|
onClick={() => handleIconSelect(icon)}
|
||||||
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:border-primary hover:bg-muted transition-colors group"
|
className="flex flex-col items-center gap-2 p-3 rounded-lg border hover:border-primary hover:bg-muted transition-colors group"
|
||||||
>
|
>
|
||||||
{/* biome-ignore lint/performance/noImgElement: external CDN URL and data URLs for icons; Next/Image not used for dynamic icon grid */}
|
<svg
|
||||||
<img
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
src={`https://cdn.svgporn.com/logos/${iconName}.svg`}
|
viewBox="0 0 24 24"
|
||||||
alt={iconName}
|
className="size-8 group-hover:scale-110 transition-transform"
|
||||||
className="size-8 object-contain group-hover:scale-110 transition-transform"
|
fill={`#${icon.hex}`}
|
||||||
onError={(e) => {
|
>
|
||||||
(
|
<path d={icon.path} />
|
||||||
e.target as HTMLImageElement
|
</svg>
|
||||||
).style.display = "none";
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="text-xs text-muted-foreground capitalize truncate w-full text-center">
|
<span className="text-xs text-muted-foreground capitalize truncate w-full text-center">
|
||||||
{iconName}
|
{icon.title}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -146,13 +163,9 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
<div className="flex justify-center mt-4">
|
<div className="flex justify-center mt-4">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() =>
|
onClick={() => setIconsToShow((prev) => prev + 24)}
|
||||||
setIconsToShow((prev) => prev + 24)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Load More (
|
Load More ({filteredIcons.length - iconsToShow} remaining)
|
||||||
{filteredIcons.length - iconsToShow}{" "}
|
|
||||||
remaining)
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -173,41 +186,52 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
const file = files[0];
|
const file = files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const fileToProcess: File = file;
|
|
||||||
|
|
||||||
const allowedTypes = [
|
const allowedTypes = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/jpg",
|
"image/jpg",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/svg+xml",
|
"image/svg+xml",
|
||||||
];
|
];
|
||||||
const fileExtension = fileToProcess.name
|
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||||
.split(".")
|
const allowedExtensions = ["jpg", "jpeg", "png", "svg"];
|
||||||
.pop()
|
|
||||||
?.toLowerCase();
|
|
||||||
const allowedExtensions = [
|
|
||||||
"jpg",
|
|
||||||
"jpeg",
|
|
||||||
"png",
|
|
||||||
"svg",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!allowedTypes.includes(fileToProcess.type) &&
|
!allowedTypes.includes(file.type) &&
|
||||||
!allowedExtensions.includes(
|
!allowedExtensions.includes(fileExtension || "")
|
||||||
fileExtension || "",
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
toast.error(
|
toast.error("Only JPG, JPEG, PNG, and SVG files are allowed");
|
||||||
"Only JPG, JPEG, PNG, and SVG files are allowed",
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileToProcess.size > 2 * 1024 * 1024) {
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
toast.error(
|
toast.error("Image size must be less than 2MB");
|
||||||
"Image size must be less than 2MB",
|
return;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
const isSvg =
|
||||||
|
file.type === "image/svg+xml" || fileExtension === "svg";
|
||||||
|
|
||||||
|
if (isSvg) {
|
||||||
|
const text = await file.text();
|
||||||
|
const sanitizedDataUrl = sanitizeSvg(text);
|
||||||
|
if (!sanitizedDataUrl) {
|
||||||
|
toast.error("Invalid SVG file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUploadedIcon(sanitizedDataUrl);
|
||||||
|
try {
|
||||||
|
await updateApplication({
|
||||||
|
applicationId,
|
||||||
|
icon: sanitizedDataUrl,
|
||||||
|
});
|
||||||
|
toast.success("Icon saved!");
|
||||||
|
await utils.application.one.invalidate({
|
||||||
|
applicationId,
|
||||||
|
});
|
||||||
|
} catch (_error) {
|
||||||
|
toast.error("Error saving icon");
|
||||||
|
setUploadedIcon(null);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,12 +248,12 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
await utils.application.one.invalidate({
|
await utils.application.one.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
toast.error("Error saving icon");
|
toast.error("Error saving icon");
|
||||||
setUploadedIcon(null);
|
setUploadedIcon(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(fileToProcess);
|
reader.readAsDataURL(file);
|
||||||
}}
|
}}
|
||||||
classNameWrapper="border-2 border-dashed border-border hover:border-primary bg-muted/30 hover:bg-muted/50 transition-all rounded-lg"
|
classNameWrapper="border-2 border-dashed border-border hover:border-primary bg-muted/30 hover:bg-muted/50 transition-all rounded-lg"
|
||||||
/>
|
/>
|
||||||
@@ -238,33 +262,6 @@ export const ShowIconSettings = ({ applicationId }: ShowIconSettingsProps) => {
|
|||||||
Supported formats: JPG, JPEG, PNG, SVG (max 2MB)
|
Supported formats: JPG, JPEG, PNG, SVG (max 2MB)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4 mt-6 border-t">
|
|
||||||
<div className="flex flex-col items-center gap-2 text-xs text-muted-foreground">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span>Icons by</span>
|
|
||||||
<a
|
|
||||||
href="https://github.com/gilbarbara/logos"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="hover:text-foreground transition-colors underline"
|
|
||||||
>
|
|
||||||
gilbarbara/logos
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span>Developer:</span>
|
|
||||||
<a
|
|
||||||
href="https://statsly.org/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="hover:text-foreground transition-colors underline"
|
|
||||||
>
|
|
||||||
Statsly
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -110,6 +110,7 @@
|
|||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"dockerode": "4.0.2",
|
"dockerode": "4.0.2",
|
||||||
|
"dompurify": "^3.3.3",
|
||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
"drizzle-orm": "0.45.1",
|
"drizzle-orm": "0.45.1",
|
||||||
"drizzle-zod": "0.8.3",
|
"drizzle-zod": "0.8.3",
|
||||||
@@ -163,6 +164,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/adm-zip": "^0.5.7",
|
"@types/adm-zip": "^0.5.7",
|
||||||
"@types/bcrypt": "5.0.2",
|
"@types/bcrypt": "5.0.2",
|
||||||
|
"@types/dompurify": "^3.2.0",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/lodash": "4.17.4",
|
"@types/lodash": "4.17.4",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
@@ -182,6 +184,7 @@
|
|||||||
"esbuild": "0.20.2",
|
"esbuild": "0.20.2",
|
||||||
"lint-staged": "^15.5.2",
|
"lint-staged": "^15.5.2",
|
||||||
"memfs": "^4.17.2",
|
"memfs": "^4.17.2",
|
||||||
|
"simple-icons": "^16.14.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"tsx": "^4.16.2",
|
"tsx": "^4.16.2",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
|
|||||||
@@ -1544,7 +1544,7 @@ const EnvironmentPage = (
|
|||||||
<img
|
<img
|
||||||
src={service.icon}
|
src={service.icon}
|
||||||
alt={service.name}
|
alt={service.name}
|
||||||
className="h-12 w-12 object-contain"
|
className="size-7 object-contain"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<GlobeIcon className="h-6 w-6" />
|
<GlobeIcon className="h-6 w-6" />
|
||||||
|
|||||||
@@ -948,35 +948,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fetchIcon: protectedProcedure
|
|
||||||
.input(z.object({ iconName: z.string() }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
const iconUrl = `https://cdn.svgporn.com/logos/${input.iconName}.svg`;
|
|
||||||
const response = await fetch(iconUrl);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: `Icon "${input.iconName}" not found`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const svgText = await response.text();
|
|
||||||
const base64Icon = `data:image/svg+xml;base64,${Buffer.from(svgText).toString("base64")}`;
|
|
||||||
|
|
||||||
return { icon: base64Icon };
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TRPCError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Failed to fetch icon: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
search: protectedProcedure
|
search: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
+7
-1
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": true,
|
"ignoreUnknown": true,
|
||||||
"includes": [
|
"includes": [
|
||||||
@@ -10,7 +15,8 @@
|
|||||||
"!**/drizzle/**",
|
"!**/drizzle/**",
|
||||||
"!node_modules/**",
|
"!node_modules/**",
|
||||||
"!packages/server/package.json"
|
"!packages/server/package.json"
|
||||||
]
|
],
|
||||||
|
"maxSize": 2097152
|
||||||
},
|
},
|
||||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||||
"linter": {
|
"linter": {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const findEnvironmentById = async (environmentId: string) => {
|
|||||||
applicationStatus: true,
|
applicationStatus: true,
|
||||||
description: true,
|
description: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
|
icon: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
|
|||||||
Generated
+30
@@ -305,6 +305,9 @@ importers:
|
|||||||
dockerode:
|
dockerode:
|
||||||
specifier: 4.0.2
|
specifier: 4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
|
dompurify:
|
||||||
|
specifier: ^3.3.3
|
||||||
|
version: 3.3.3
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: 16.4.5
|
specifier: 16.4.5
|
||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
@@ -459,6 +462,9 @@ importers:
|
|||||||
'@types/bcrypt':
|
'@types/bcrypt':
|
||||||
specifier: 5.0.2
|
specifier: 5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
'@types/dompurify':
|
||||||
|
specifier: ^3.2.0
|
||||||
|
version: 3.2.0
|
||||||
'@types/js-cookie':
|
'@types/js-cookie':
|
||||||
specifier: ^3.0.6
|
specifier: ^3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
@@ -516,6 +522,9 @@ importers:
|
|||||||
memfs:
|
memfs:
|
||||||
specifier: ^4.17.2
|
specifier: ^4.17.2
|
||||||
version: 4.56.10(tslib@2.8.1)
|
version: 4.56.10(tslib@2.8.1)
|
||||||
|
simple-icons:
|
||||||
|
specifier: ^16.14.0
|
||||||
|
version: 16.14.0
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.17
|
specifier: ^3.4.17
|
||||||
version: 3.4.19(tsx@4.16.2)(yaml@2.8.1)
|
version: 3.4.19(tsx@4.16.2)(yaml@2.8.1)
|
||||||
@@ -3968,6 +3977,10 @@ packages:
|
|||||||
'@types/dockerode@3.3.23':
|
'@types/dockerode@3.3.23':
|
||||||
resolution: {integrity: sha512-Lz5J+NFgZS4cEVhquwjIGH4oQwlVn2h7LXD3boitujBnzOE5o7s9H8hchEjoDK2SlRsJTogdKnQeiJgPPKLIEw==}
|
resolution: {integrity: sha512-Lz5J+NFgZS4cEVhquwjIGH4oQwlVn2h7LXD3boitujBnzOE5o7s9H8hchEjoDK2SlRsJTogdKnQeiJgPPKLIEw==}
|
||||||
|
|
||||||
|
'@types/dompurify@3.2.0':
|
||||||
|
resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==}
|
||||||
|
deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.
|
||||||
|
|
||||||
'@types/estree-jsx@1.0.5':
|
'@types/estree-jsx@1.0.5':
|
||||||
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
||||||
|
|
||||||
@@ -5059,6 +5072,9 @@ packages:
|
|||||||
dompurify@3.2.6:
|
dompurify@3.2.6:
|
||||||
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
|
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
|
||||||
|
|
||||||
|
dompurify@3.3.3:
|
||||||
|
resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==}
|
||||||
|
|
||||||
domutils@3.2.2:
|
domutils@3.2.2:
|
||||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||||
|
|
||||||
@@ -7501,6 +7517,10 @@ packages:
|
|||||||
simple-get@4.0.1:
|
simple-get@4.0.1:
|
||||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||||
|
|
||||||
|
simple-icons@16.14.0:
|
||||||
|
resolution: {integrity: sha512-2Nvs3jJpCfMWQerD4zdv91g/MpnWn81a7uhyAC0reuhrjmS2MtSmwIKwewOJR6Xe97ZmfltDntCDqKJIBawQOw==}
|
||||||
|
engines: {node: '>=0.12.18'}
|
||||||
|
|
||||||
sisteransi@1.0.5:
|
sisteransi@1.0.5:
|
||||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
@@ -12022,6 +12042,10 @@ snapshots:
|
|||||||
'@types/docker-modem': 3.0.6
|
'@types/docker-modem': 3.0.6
|
||||||
'@types/node': 24.10.13
|
'@types/node': 24.10.13
|
||||||
|
|
||||||
|
'@types/dompurify@3.2.0':
|
||||||
|
dependencies:
|
||||||
|
dompurify: 3.3.3
|
||||||
|
|
||||||
'@types/estree-jsx@1.0.5':
|
'@types/estree-jsx@1.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -13118,6 +13142,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/trusted-types': 2.0.7
|
'@types/trusted-types': 2.0.7
|
||||||
|
|
||||||
|
dompurify@3.3.3:
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/trusted-types': 2.0.7
|
||||||
|
|
||||||
domutils@3.2.2:
|
domutils@3.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-serializer: 2.0.0
|
dom-serializer: 2.0.0
|
||||||
@@ -15673,6 +15701,8 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
simple-concat: 1.0.1
|
simple-concat: 1.0.1
|
||||||
|
|
||||||
|
simple-icons@16.14.0: {}
|
||||||
|
|
||||||
sisteransi@1.0.5: {}
|
sisteransi@1.0.5: {}
|
||||||
|
|
||||||
slash@3.0.0: {}
|
slash@3.0.0: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user