mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-05-03 15:59:18 -05:00
feat: enhance ProjectNetworkGraph layout and add tab navigation in ProjectOverview
This commit is contained in:
@@ -135,28 +135,36 @@ export default function ProjectNetworkGraph({ apps }: ProjectNetworkGraphProps)
|
|||||||
const nodes: Node[] = [];
|
const nodes: Node[] = [];
|
||||||
const edges: Edge[] = [];
|
const edges: Edge[] = [];
|
||||||
|
|
||||||
const radius = 200;
|
// Separate apps with domains and without domains
|
||||||
const centerX = 400;
|
const appsWithDomains = apps.filter(app => app.appDomains.length > 0);
|
||||||
const centerY = 300;
|
const appsWithoutDomains = apps.filter(app => app.appDomains.length === 0);
|
||||||
|
|
||||||
|
const nodeSpacing = 250; // Horizontal spacing between nodes
|
||||||
|
const rowSpacing = 150; // Vertical spacing between rows
|
||||||
|
const internetY = 100;
|
||||||
|
const firstRowY = internetY + 200;
|
||||||
|
const secondRowY = firstRowY + rowSpacing;
|
||||||
|
|
||||||
// Check if we need an Internet node
|
// Check if we need an Internet node
|
||||||
const hasInternetAccess = apps.some(app => app.appDomains.length > 0);
|
const hasInternetAccess = appsWithDomains.length > 0;
|
||||||
|
const internetX = (Math.max(appsWithDomains.length, appsWithoutDomains.length) * nodeSpacing) / 2;
|
||||||
|
|
||||||
if (hasInternetAccess) {
|
if (hasInternetAccess) {
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: 'INTERNET',
|
id: 'INTERNET',
|
||||||
position: { x: centerX, y: centerY - radius - 150 },
|
position: { x: internetX, y: internetY },
|
||||||
data: { label: 'Internet' },
|
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' },
|
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
|
type: 'input', // It's a source only
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
apps.forEach((app, index) => {
|
// First row: Apps with domains (internet accessible)
|
||||||
// Circular layout
|
appsWithDomains.forEach((app, index) => {
|
||||||
const angle = (index / apps.length) * 2 * Math.PI;
|
const totalWidth = (appsWithDomains.length - 1) * nodeSpacing;
|
||||||
const x = centerX + radius * Math.cos(angle);
|
const startX = internetX - (totalWidth / 2);
|
||||||
const y = centerY + radius * Math.sin(angle);
|
const x = startX + (index * nodeSpacing);
|
||||||
|
const y = firstRowY;
|
||||||
|
|
||||||
const ports = Array.from(new Set([
|
const ports = Array.from(new Set([
|
||||||
...app.appDomains,
|
...app.appDomains,
|
||||||
@@ -178,20 +186,45 @@ export default function ProjectNetworkGraph({ apps }: ProjectNetworkGraphProps)
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Edge from Internet to App
|
// Edge from Internet to App
|
||||||
if (app.appDomains.length > 0) {
|
const hostnames = app.appDomains.map(d => d.hostname).join(', ');
|
||||||
const hostnames = app.appDomains.map(d => d.hostname).join(', ');
|
edges.push({
|
||||||
edges.push({
|
id: `INTERNET-${app.id}`,
|
||||||
id: `INTERNET-${app.id}`,
|
source: 'INTERNET',
|
||||||
source: 'INTERNET',
|
target: app.id,
|
||||||
target: app.id,
|
label: `${hostnames}`,
|
||||||
label: `${hostnames}`,
|
markerEnd: {
|
||||||
markerEnd: {
|
type: MarkerType.ArrowClosed,
|
||||||
type: MarkerType.ArrowClosed,
|
},
|
||||||
},
|
animated: true,
|
||||||
animated: true,
|
style: { stroke: '#000' },
|
||||||
style: { stroke: '#000' },
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
// Second row: Apps without domains (not internet accessible)
|
||||||
|
appsWithoutDomains.forEach((app, index) => {
|
||||||
|
const totalWidth = (appsWithoutDomains.length - 1) * nodeSpacing;
|
||||||
|
const startX = internetX - (totalWidth / 2);
|
||||||
|
const x = startX + (index * nodeSpacing);
|
||||||
|
const y = secondRowY;
|
||||||
|
|
||||||
|
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,
|
||||||
|
app,
|
||||||
|
ports
|
||||||
|
},
|
||||||
|
type: 'appNode',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { nodes, edges };
|
return { nodes, edges };
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import AppTable from "./apps-table";
|
|||||||
import ProjectNetworkGraph from "./project-network-graph";
|
import ProjectNetworkGraph from "./project-network-graph";
|
||||||
import { App } from "@prisma/client";
|
import { App } from "@prisma/client";
|
||||||
import { UserSession } from "@/shared/model/sim-session.model";
|
import { UserSession } from "@/shared/model/sim-session.model";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
interface ProjectOverviewProps {
|
interface ProjectOverviewProps {
|
||||||
apps: any[]; // Using any to avoid complex type imports, as we know the data structure is correct
|
apps: any[]; // Using any to avoid complex type imports, as we know the data structure is correct
|
||||||
@@ -13,8 +14,16 @@ interface ProjectOverviewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ProjectOverview({ apps, session, projectId }: ProjectOverviewProps) {
|
export default function ProjectOverview({ apps, session, projectId }: ProjectOverviewProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const currentTab = searchParams.get('tab') || 'table';
|
||||||
|
|
||||||
|
const handleTabChange = (value: string) => {
|
||||||
|
router.push(`?tab=${value}`, { scroll: false });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="table" className="w-full">
|
<Tabs value={currentTab} onValueChange={handleTabChange} className="w-full">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="table">Table View</TabsTrigger>
|
<TabsTrigger value="table">Table View</TabsTrigger>
|
||||||
<TabsTrigger value="graph">Network Graph</TabsTrigger>
|
<TabsTrigger value="graph">Network Graph</TabsTrigger>
|
||||||
|
|||||||
Reference in New Issue
Block a user