feat: per-node custom color styling (border, background, icon)
- Add resolveNodeColors() utility merging type defaults with per-node overrides - Default colors per node type (cyan=isp/router/lxc/ap, green=switch/nas, purple=server/vm, orange=proxmox, amber=iot, gray=generic) - Remove glowColor prop from BaseNode — colors now come from node data - ProxmoxGroupNode uses resolveNodeColors for group border/header/icon - NodeModal: Appearance section with 3 color swatches (border, background, icon) — click swatch to open native color picker; Reset to defaults button - custom_colors persisted as JSON in DB (backend model + schemas + migration) - 7 new unit tests for resolveNodeColors covering all node types + partial overrides
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Handle, Position, NodeResizer, type NodeProps, type Node } from '@xyflow/react'
|
||||
import { Layers } from 'lucide-react'
|
||||
import type { NodeData, NodeStatus } from '@/types'
|
||||
import { resolveNodeColors } from '@/utils/nodeColors'
|
||||
import { BaseNode } from './BaseNode'
|
||||
|
||||
const STATUS_COLORS: Record<NodeStatus, string> = {
|
||||
@@ -10,18 +11,18 @@ const STATUS_COLORS: Record<NodeStatus, string> = {
|
||||
unknown: '#8b949e',
|
||||
}
|
||||
|
||||
const GLOW = '#ff6e00'
|
||||
|
||||
export function ProxmoxGroupNode(props: NodeProps<Node<NodeData>>) {
|
||||
const { data, selected } = props
|
||||
const colors = resolveNodeColors(data)
|
||||
|
||||
// Render as a regular node when container mode is disabled
|
||||
if (data.container_mode === false) {
|
||||
return <BaseNode {...props} icon={Layers} glowColor={GLOW} />
|
||||
return <BaseNode {...props} icon={Layers} />
|
||||
}
|
||||
|
||||
const statusColor = STATUS_COLORS[data.status]
|
||||
const isOnline = data.status === 'online'
|
||||
const glow = colors.border
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -29,36 +30,36 @@ export function ProxmoxGroupNode(props: NodeProps<Node<NodeData>>) {
|
||||
minWidth={220}
|
||||
minHeight={160}
|
||||
isVisible={selected}
|
||||
lineStyle={{ borderColor: GLOW, opacity: 0.6 }}
|
||||
handleStyle={{ borderColor: GLOW, backgroundColor: '#21262d' }}
|
||||
lineStyle={{ borderColor: glow, opacity: 0.6 }}
|
||||
handleStyle={{ borderColor: glow, backgroundColor: '#21262d' }}
|
||||
/>
|
||||
|
||||
{/* Group border */}
|
||||
<div
|
||||
className="w-full h-full rounded-xl border-2 flex flex-col overflow-hidden"
|
||||
style={{
|
||||
borderColor: selected ? GLOW : isOnline ? `${GLOW}66` : '#30363d',
|
||||
background: isOnline ? `${GLOW}08` : '#0d111766',
|
||||
borderColor: selected ? glow : isOnline ? `${glow}66` : '#30363d',
|
||||
background: isOnline ? `${colors.background}cc` : `${colors.background}aa`,
|
||||
boxShadow: isOnline
|
||||
? `0 0 20px ${GLOW}1a, inset 0 0 40px ${GLOW}08`
|
||||
? `0 0 20px ${glow}1a, inset 0 0 40px ${glow}08`
|
||||
: selected
|
||||
? `0 0 12px ${GLOW}33`
|
||||
? `0 0 12px ${glow}33`
|
||||
: 'none',
|
||||
}}
|
||||
>
|
||||
{/* Header bar */}
|
||||
<div
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 shrink-0"
|
||||
style={{ background: isOnline ? `${GLOW}18` : '#161b2288', borderBottom: `1px solid ${isOnline ? `${GLOW}33` : '#30363d'}` }}
|
||||
style={{ background: isOnline ? `${glow}18` : '#161b2288', borderBottom: `1px solid ${isOnline ? `${glow}33` : '#30363d'}` }}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center w-5 h-5 rounded-md shrink-0"
|
||||
style={{ color: isOnline ? GLOW : '#8b949e', background: '#161b22' }}
|
||||
style={{ color: isOnline ? colors.icon : '#8b949e', background: '#161b22' }}
|
||||
>
|
||||
<Layers size={12} />
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<span className="text-[11px] font-semibold leading-tight truncate" style={{ color: isOnline ? GLOW : '#c9d1d9' }}>
|
||||
<span className="text-[11px] font-semibold leading-tight truncate" style={{ color: isOnline ? glow : '#c9d1d9' }}>
|
||||
{data.label}
|
||||
</span>
|
||||
{data.ip && (
|
||||
|
||||
Reference in New Issue
Block a user