mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-10 13:39:07 -06:00
feat: add ProjectOverview and ProjectNetworkGraph components for enhanced app visualization
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"@xyflow/react": "^12.10.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bufferutil": "^4.0.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
|
||||
import projectService from "@/server/services/project.service";
|
||||
import AppTable from "./apps-table";
|
||||
import ProjectOverview from "./project-overview";
|
||||
import appService from "@/server/services/app.service";
|
||||
import PageTitle from "@/components/custom/page-title";
|
||||
import ProjectBreadcrumbs from "./project-breadcrumbs";
|
||||
@@ -36,7 +36,7 @@ export default async function AppsPage({
|
||||
{UserGroupUtils.sessionCanCreateNewAppsForProject(session, params.projectId) &&
|
||||
<CreateProjectActions projectId={projectId} />}
|
||||
</PageTitle>
|
||||
<AppTable session={session} app={relevantApps} projectId={project.id} />
|
||||
<ProjectOverview session={session} apps={relevantApps} projectId={project.id} />
|
||||
<ProjectBreadcrumbs project={project} />
|
||||
</div>
|
||||
)
|
||||
|
||||
227
src/app/project/[projectId]/project-network-graph.tsx
Normal file
227
src/app/project/[projectId]/project-network-graph.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
'use client';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { ReactFlow, Background, Controls, Node, Edge, MarkerType, Handle, Position } from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { App, AppDomain, AppPort } from '@prisma/client';
|
||||
import { Globe, Network, Lock, Cloud, Shield, ArrowDown } from 'lucide-react';
|
||||
import PodStatusIndicator from '@/components/custom/pod-status-indicator';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface AppWithRelations extends App {
|
||||
appPorts: AppPort[];
|
||||
appDomains: AppDomain[];
|
||||
}
|
||||
|
||||
interface ProjectNetworkGraphProps {
|
||||
apps: AppWithRelations[];
|
||||
}
|
||||
|
||||
const PolicyIcon = ({ policy, type, ports }: { policy: string, type: 'ingress' | 'egress', ports: string }) => {
|
||||
let Icon = Globe;
|
||||
let color = type === 'egress' ? 'text-blue-500' : 'text-green-500';
|
||||
let title = policy;
|
||||
|
||||
switch (policy) {
|
||||
case 'ALLOW_ALL':
|
||||
Icon = Globe;
|
||||
color = 'text-green-500';
|
||||
break;
|
||||
case 'NAMESPACE_ONLY':
|
||||
Icon = Network;
|
||||
color = 'text-blue-500';
|
||||
break;
|
||||
case 'DENY_ALL':
|
||||
Icon = Lock;
|
||||
color = 'text-red-500';
|
||||
break;
|
||||
case 'INTERNET_ONLY':
|
||||
Icon = Cloud;
|
||||
color = 'text-orange-500';
|
||||
break;
|
||||
default:
|
||||
Icon = Shield;
|
||||
color = 'text-gray-500';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2'>
|
||||
{/*<div className={`p-1 bg-white rounded-full border shadow-sm ${color}`} title={`${type}: ${title}`}>
|
||||
<div className=' flex gap-1 items-center'>
|
||||
<ArrowDown size={13} className='text-gray-500' />
|
||||
</div>
|
||||
</div>*/}
|
||||
<div className={`p-1 bg-white rounded-full border shadow-sm ${color}`} title={`${type}: ${title}`}>
|
||||
<div className=' flex gap-1 items-center'>
|
||||
<Icon size={16} />
|
||||
</div>
|
||||
</div>
|
||||
{ports && type === 'ingress' && <div className={`p-1 px-2 bg-white rounded-full border shadow-sm text-xs text-gray-500`} title={`${type}: ${title}`}>
|
||||
{ports}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AppNode = ({ data }: { data: any }) => {
|
||||
return (
|
||||
<div className="relative bg-white border border-slate-300 rounded-md p-4 min-w-[150px] shadow-sm text-center cursor-pointer hover:border-slate-400 transition-colors">
|
||||
<Handle type="target" position={Position.Top} className="!bg-transparent !border-0" />
|
||||
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 z-10">
|
||||
<PolicyIcon policy={data.ingressPolicy} ports={data.ports} type="ingress" />
|
||||
</div>
|
||||
|
||||
<div className="font-semibold text-sm mt-2 mb-2 flex gap-3 items-center justify-center">
|
||||
<PodStatusIndicator appId={data.appId} /> <p>{data.label}</p>
|
||||
</div>
|
||||
|
||||
<div className="absolute -bottom-3 left-1/2 -translate-x-1/2 z-10">
|
||||
<PolicyIcon policy={data.egressPolicy} ports={data.ports} type="egress" />
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Bottom} className="!bg-transparent !border-0" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const nodeTypes = {
|
||||
appNode: AppNode,
|
||||
};
|
||||
|
||||
const Legend = () => {
|
||||
return (
|
||||
<div className="mt-4 p-4 border rounded-md bg-slate-50 text-sm">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-1 text-xs uppercase text-slate-500 pb-2">Node Layout</h4>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-8 h-8 border border-slate-300 rounded bg-white relative mx-1">
|
||||
<div className="absolute -top-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-slate-200 rounded-full border border-slate-300"></div>
|
||||
</div>
|
||||
<span>Top Icon: <strong>Ingress Policy</strong> (Incoming traffic)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 border border-slate-300 rounded bg-white relative mx-1">
|
||||
<div className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-slate-200 rounded-full border border-slate-300"></div>
|
||||
</div>
|
||||
<span>Bottom Icon: <strong>Egress Policy</strong> (Outgoing traffic)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-1 text-xs uppercase text-slate-500 pb-2">Network Policy Types</h4>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe size={16} className="text-green-500" />
|
||||
<span>Allow All</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Network size={16} className="text-blue-500" />
|
||||
<span>Project Only</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Cloud size={16} className="text-orange-500" />
|
||||
<span>Internet Only</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Lock size={16} className="text-red-500" />
|
||||
<span>Deny All</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ProjectNetworkGraph({ apps }: ProjectNetworkGraphProps) {
|
||||
const router = useRouter();
|
||||
const { nodes, edges } = useMemo(() => {
|
||||
const nodes: Node[] = [];
|
||||
const edges: Edge[] = [];
|
||||
|
||||
const radius = 200;
|
||||
const centerX = 400;
|
||||
const centerY = 300;
|
||||
|
||||
// Check if we need an Internet node
|
||||
const hasInternetAccess = apps.some(app => app.appDomains.length > 0);
|
||||
if (hasInternetAccess) {
|
||||
nodes.push({
|
||||
id: 'INTERNET',
|
||||
position: { x: centerX, y: centerY - radius - 150 },
|
||||
data: { label: 'Internet' },
|
||||
style: { background: '#e0e0e0', border: '1px solid #777', padding: 10, borderRadius: '50%', width: 100, height: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 'bold' },
|
||||
type: 'input', // It's a source only
|
||||
});
|
||||
}
|
||||
|
||||
apps.forEach((app, index) => {
|
||||
// Circular layout
|
||||
const angle = (index / apps.length) * 2 * Math.PI;
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
const y = centerY + radius * Math.sin(angle);
|
||||
|
||||
|
||||
const ports = Array.from(new Set([
|
||||
...app.appDomains,
|
||||
...app.appPorts
|
||||
].map(d => d.port))).join(', ');
|
||||
|
||||
nodes.push({
|
||||
id: app.id,
|
||||
position: { x, y },
|
||||
data: {
|
||||
label: app.name,
|
||||
ingressPolicy: app.ingressNetworkPolicy,
|
||||
egressPolicy: app.egressNetworkPolicy,
|
||||
appId: app.id,
|
||||
ports
|
||||
},
|
||||
type: 'appNode',
|
||||
});
|
||||
|
||||
// Edge from Internet to App
|
||||
if (app.appDomains.length > 0) {
|
||||
const hostnames = app.appDomains.map(d => d.hostname).join(', ');
|
||||
edges.push({
|
||||
id: `INTERNET-${app.id}`,
|
||||
source: 'INTERNET',
|
||||
target: app.id,
|
||||
label: `${hostnames}`,
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
animated: true,
|
||||
style: { stroke: '#000' },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes, edges };
|
||||
}, [apps]);
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div style={{ height: 600, border: '1px solid #eee', borderRadius: 8 }}>
|
||||
<ReactFlow
|
||||
defaultNodes={nodes}
|
||||
defaultEdges={edges}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
elementsSelectable={false}
|
||||
onNodeClick={(event, node) => {
|
||||
if (node.id !== 'INTERNET') {
|
||||
router.push(`/project/app/${node.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* <Background />
|
||||
<Controls />*/}
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<Legend />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
src/app/project/[projectId]/project-overview.tsx
Normal file
30
src/app/project/[projectId]/project-overview.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import AppTable from "./apps-table";
|
||||
import ProjectNetworkGraph from "./project-network-graph";
|
||||
import { App } from "@prisma/client";
|
||||
import { UserSession } from "@/shared/model/sim-session.model";
|
||||
|
||||
interface ProjectOverviewProps {
|
||||
apps: any[]; // Using any to avoid complex type imports, as we know the data structure is correct
|
||||
session: UserSession;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export default function ProjectOverview({ apps, session, projectId }: ProjectOverviewProps) {
|
||||
return (
|
||||
<Tabs defaultValue="table" className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="table">Table View</TabsTrigger>
|
||||
<TabsTrigger value="graph">Network Graph</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="table">
|
||||
<AppTable session={session} app={apps} projectId={projectId} />
|
||||
</TabsContent>
|
||||
<TabsContent value="graph">
|
||||
<ProjectNetworkGraph apps={apps} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -74,6 +74,10 @@ class AppService {
|
||||
where: {
|
||||
projectId
|
||||
},
|
||||
include: {
|
||||
appPorts: true,
|
||||
appDomains: true
|
||||
},
|
||||
orderBy: {
|
||||
name: 'asc'
|
||||
}
|
||||
|
||||
116
yarn.lock
116
yarn.lock
@@ -2956,12 +2956,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
|
||||
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
|
||||
|
||||
"@types/d3-drag@^3.0.7":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02"
|
||||
integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-ease@^3.0.0":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
|
||||
integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
|
||||
|
||||
"@types/d3-interpolate@^3.0.1":
|
||||
"@types/d3-interpolate@*", "@types/d3-interpolate@^3.0.1", "@types/d3-interpolate@^3.0.4":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
|
||||
integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
|
||||
@@ -2980,6 +2987,11 @@
|
||||
dependencies:
|
||||
"@types/d3-time" "*"
|
||||
|
||||
"@types/d3-selection@*", "@types/d3-selection@^3.0.10":
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3"
|
||||
integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==
|
||||
|
||||
"@types/d3-shape@^3.1.0":
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72"
|
||||
@@ -2997,6 +3009,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
|
||||
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
|
||||
|
||||
"@types/d3-transition@^3.0.8":
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706"
|
||||
integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-zoom@^3.0.8":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b"
|
||||
integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==
|
||||
dependencies:
|
||||
"@types/d3-interpolate" "*"
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/debug@4.1.7":
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
|
||||
@@ -3242,6 +3269,30 @@
|
||||
resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0.tgz#275fb8f6e14afa6e8a0c05d4ebc94523ff775396"
|
||||
integrity sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==
|
||||
|
||||
"@xyflow/react@^12.10.0":
|
||||
version "12.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.10.0.tgz#d2924cb38074e8e6141643dd8bd7a0666aaac868"
|
||||
integrity sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==
|
||||
dependencies:
|
||||
"@xyflow/system" "0.0.74"
|
||||
classcat "^5.0.3"
|
||||
zustand "^4.4.0"
|
||||
|
||||
"@xyflow/system@0.0.74":
|
||||
version "0.0.74"
|
||||
resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.74.tgz#4bc01af020504387c88a3b13ac6d426b47cba845"
|
||||
integrity sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==
|
||||
dependencies:
|
||||
"@types/d3-drag" "^3.0.7"
|
||||
"@types/d3-interpolate" "^3.0.4"
|
||||
"@types/d3-selection" "^3.0.10"
|
||||
"@types/d3-transition" "^3.0.8"
|
||||
"@types/d3-zoom" "^3.0.8"
|
||||
d3-drag "^3.0.0"
|
||||
d3-interpolate "^3.0.1"
|
||||
d3-selection "^3.0.0"
|
||||
d3-zoom "^3.0.0"
|
||||
|
||||
abab@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||
@@ -3907,6 +3958,11 @@ class-variance-authority@^0.7.1:
|
||||
dependencies:
|
||||
clsx "^2.1.1"
|
||||
|
||||
classcat@^5.0.3:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.5.tgz#8c209f359a93ac302404a10161b501eba9c09c77"
|
||||
integrity sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==
|
||||
|
||||
client-only@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
|
||||
@@ -4129,7 +4185,20 @@ csstype@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
d3-ease@^3.0.1:
|
||||
"d3-dispatch@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||
|
||||
"d3-drag@2 - 3", d3-drag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-selection "3"
|
||||
|
||||
"d3-ease@1 - 3", d3-ease@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||
@@ -4139,7 +4208,7 @@ d3-ease@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
|
||||
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
@@ -4162,6 +4231,11 @@ d3-scale@^4.0.2:
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||
|
||||
d3-shape@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||
@@ -4183,11 +4257,33 @@ d3-shape@^3.1.0:
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
d3-timer@^3.0.1:
|
||||
"d3-timer@1 - 3", d3-timer@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||
|
||||
"d3-transition@2 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
d3-dispatch "1 - 3"
|
||||
d3-ease "1 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-timer "1 - 3"
|
||||
|
||||
d3-zoom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
|
||||
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-drag "2 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-selection "2 - 3"
|
||||
d3-transition "2 - 3"
|
||||
|
||||
damerau-levenshtein@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
@@ -8649,6 +8745,11 @@ use-sidecar@^1.1.2:
|
||||
detect-node-es "^1.1.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
use-sync-external-store@^1.2.2:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
|
||||
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
@@ -9034,6 +9135,13 @@ zod@^3.23.8:
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
|
||||
zustand@^4.4.0:
|
||||
version "4.5.7"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.7.tgz#7d6bb2026a142415dd8be8891d7870e6dbe65f55"
|
||||
integrity sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==
|
||||
dependencies:
|
||||
use-sync-external-store "^1.2.2"
|
||||
|
||||
zustand@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.1.tgz#2bdca5e4be172539558ce3974fe783174a48fdcf"
|
||||
|
||||
Reference in New Issue
Block a user