feat(web/mazenet): port-drag edges, context menus, delete actions

This commit is contained in:
2026-04-20 19:26:49 -04:00
parent 0401cccd1d
commit 6db5842a28
8 changed files with 320 additions and 34 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { Trash2 } from 'lucide-react';
import type { Net, MazeNode, Edge, PendingChange } from './types';
export type Selection =
@@ -14,9 +15,12 @@ interface Props {
edges: Edge[];
pending: PendingChange[];
onClose?: () => void;
onDeleteNet?: (id: string) => void;
onDeleteNode?: (id: string) => void;
onDeleteEdge?: (id: string) => void;
}
const Inspector: React.FC<Props> = ({ selection, nets, nodes, edges, pending, onClose }) => {
const Inspector: React.FC<Props> = ({ selection, nets, nodes, edges, pending, onClose, onDeleteNet, onDeleteNode, onDeleteEdge }) => {
const net = selection?.type === 'net' ? nets.find((n) => n.id === selection.id) : undefined;
const node = selection?.type === 'node' ? nodes.find((n) => n.id === selection.id) : undefined;
const edge = selection?.type === 'edge' ? edges.find((e) => e.id === selection.id) : undefined;
@@ -40,36 +44,63 @@ const Inspector: React.FC<Props> = ({ selection, nets, nodes, edges, pending, on
{!selection && <div className="inspector-empty">SELECT AN ELEMENT</div>}
{net && (
<div className="kvs">
<div className="k">KIND</div> <div className="v">{net.kind.toUpperCase()}</div>
<div className="k">LABEL</div> <div className="v">{net.label}</div>
<div className="k">CIDR</div> <div className="v">{net.cidr}</div>
<div className="k">MEMBERS</div> <div className="v">
{nodes.filter((n) => n.netId === net.id).map((n) => n.name).join(', ') || '—'}
<>
<div className="kvs">
<div className="k">KIND</div> <div className="v">{net.kind.toUpperCase()}</div>
<div className="k">LABEL</div> <div className="v">{net.label}</div>
<div className="k">CIDR</div> <div className="v">{net.cidr}</div>
<div className="k">MEMBERS</div> <div className="v">
{nodes.filter((n) => n.netId === net.id).map((n) => n.name).join(', ') || '—'}
</div>
</div>
</div>
{net.kind !== 'internet' && onDeleteNet && (
<button type="button" className="maze-btn ghost" onClick={() => onDeleteNet(net.id)}>
<Trash2 size={12} /> DELETE NET
</button>
)}
</>
)}
{node && (
<div className="kvs">
<div className="k">KIND</div> <div className="v">{node.kind === 'observed' ? 'OBSERVED' : 'DECKY'}</div>
<div className="k">NAME</div> <div className="v">{node.name}</div>
<div className="k">ARCHETYPE</div> <div className="v">{node.archetype}</div>
<div className="k">NET</div> <div className="v">{nets.find((nn) => nn.id === node.netId)?.label ?? node.netId}</div>
<div className="k">SERVICES</div> <div className="v">{node.services.join(', ') || '—'}</div>
<div className="k">STATUS</div> <div className="v">{node.status.toUpperCase()}</div>
</div>
<>
<div className="kvs">
<div className="k">KIND</div> <div className="v">{node.kind === 'observed' ? 'OBSERVED' : 'DECKY'}</div>
<div className="k">NAME</div> <div className="v">{node.name}</div>
<div className="k">ARCHETYPE</div> <div className="v">{node.archetype}</div>
<div className="k">NET</div> <div className="v">{nets.find((nn) => nn.id === node.netId)?.label ?? node.netId}</div>
<div className="k">SERVICES</div> <div className="v">{node.services.join(', ') || '—'}</div>
<div className="k">STATUS</div> <div className="v">{node.status.toUpperCase()}</div>
</div>
{onDeleteNode && (
<button
type="button"
className="maze-btn ghost"
disabled={node.kind === 'observed'}
title={node.kind === 'observed' ? 'observed entity — not a deployed decky' : 'delete decky'}
onClick={() => node.kind === 'decky' && onDeleteNode(node.id)}
>
<Trash2 size={12} /> DELETE NODE
</button>
)}
</>
)}
{edge && (
<div className="kvs">
<div className="k">FROM</div> <div className="v">{nodes.find((n) => n.id === edge.from)?.name ?? edge.from}</div>
<div className="k">TO</div> <div className="v">{nodes.find((n) => n.id === edge.to)?.name ?? edge.to}</div>
<div className="k">TRAFFIC</div> <div className="v">{edge.traffic.toUpperCase()}</div>
{edge.label && (<>
<div className="k">LABEL</div> <div className="v">{edge.label}</div>
</>)}
</div>
<>
<div className="kvs">
<div className="k">FROM</div> <div className="v">{nodes.find((n) => n.id === edge.from)?.name ?? edge.from}</div>
<div className="k">TO</div> <div className="v">{nodes.find((n) => n.id === edge.to)?.name ?? edge.to}</div>
<div className="k">TRAFFIC</div> <div className="v">{edge.traffic.toUpperCase()}</div>
{edge.label && (<>
<div className="k">LABEL</div> <div className="v">{edge.label}</div>
</>)}
</div>
{onDeleteEdge && (
<button type="button" className="maze-btn ghost" onClick={() => onDeleteEdge(edge.id)}>
<Trash2 size={12} /> REMOVE EDGE
</button>
)}
</>
)}
<div>