add other device
This commit is contained in:
@@ -16,15 +16,18 @@ class MapItem(Base):
|
|||||||
- switch: Network switch
|
- switch: Network switch
|
||||||
- indoor_ap: Indoor access point
|
- indoor_ap: Indoor access point
|
||||||
- outdoor_ap: Outdoor access point
|
- outdoor_ap: Outdoor access point
|
||||||
|
- other_device: Other device (1 ethernet port)
|
||||||
|
- info: Information marker
|
||||||
|
|
||||||
Geometry:
|
Geometry:
|
||||||
- Point for devices (switches, APs)
|
- Point for devices (switches, APs, other devices, info markers)
|
||||||
- LineString for cables and wireless mesh
|
- LineString for cables and wireless mesh
|
||||||
|
|
||||||
Properties (JSONB):
|
Properties (JSONB):
|
||||||
- For cables: cable_type, name, notes, length_meters, start_device_id, end_device_id
|
- For cables: cable_type, name, notes, length_meters, start_device_id, end_device_id
|
||||||
- For devices: name, notes, port_count, connections (array of {cable_id, port_number})
|
- For devices: name, notes, port_count, connections (array of {cable_id, port_number})
|
||||||
- For wireless_mesh: name, notes, start_ap_id, end_ap_id
|
- For wireless_mesh: name, notes, start_ap_id, end_ap_id
|
||||||
|
- For info markers: name, notes, image (optional)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "map_items"
|
__tablename__ = "map_items"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
class MapItemBase(BaseModel):
|
class MapItemBase(BaseModel):
|
||||||
"""Base map item schema with common attributes."""
|
"""Base map item schema with common attributes."""
|
||||||
type: str = Field(..., description="Item type: cable, wireless_mesh, switch, indoor_ap, outdoor_ap")
|
type: str = Field(..., description="Item type: cable, wireless_mesh, switch, indoor_ap, outdoor_ap, other_device, info")
|
||||||
geometry: Dict[str, Any] = Field(..., description="GeoJSON geometry (Point or LineString)")
|
geometry: Dict[str, Any] = Field(..., description="GeoJSON geometry (Point or LineString)")
|
||||||
properties: Dict[str, Any] = Field(default_factory=dict, description="Item-specific properties")
|
properties: Dict[str, Any] = Field(default_factory=dict, description="Item-specific properties")
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
||||||
|
|
||||||
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
||||||
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'info'].includes(activeTool);
|
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'other_device', 'info'].includes(activeTool);
|
||||||
const isWirelessTool = activeTool === 'wireless_mesh';
|
const isWirelessTool = activeTool === 'wireless_mesh';
|
||||||
|
|
||||||
// Load all map items for snapping
|
// Load all map items for snapping
|
||||||
@@ -39,7 +39,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
// Find nearby device for snapping (exclude info markers)
|
// Find nearby device for snapping (exclude info markers)
|
||||||
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 5): any | null => {
|
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 5): any | null => {
|
||||||
const devices = allItems.filter(item =>
|
const devices = allItems.filter(item =>
|
||||||
['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type) &&
|
['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type) &&
|
||||||
item.geometry.type === 'Point'
|
item.geometry.type === 'Point'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
|
|
||||||
// Only add port_count and connections if it's not an info marker
|
// Only add port_count and connections if it's not an info marker
|
||||||
if (activeTool !== 'info') {
|
if (activeTool !== 'info') {
|
||||||
properties.port_count = activeTool === 'switch' ? 5 : activeTool === 'outdoor_ap' ? 1 : 4;
|
properties.port_count = activeTool === 'switch' ? 5 : (activeTool === 'outdoor_ap' || activeTool === 'other_device') ? 1 : 4;
|
||||||
properties.connections = [];
|
properties.connections = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function ItemContextMenu({ item, position, onClose, onUpdate }: ItemConte
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const isSwitch = item.type === 'switch';
|
const isSwitch = item.type === 'switch';
|
||||||
const isDevice = ['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type);
|
const isDevice = ['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type);
|
||||||
const hasConnections = item.properties.connections && item.properties.connections.length > 0;
|
const hasConnections = item.properties.connections && item.properties.connections.length > 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -88,6 +88,22 @@ const outdoorApIcon = new L.DivIcon({
|
|||||||
iconAnchor: [20, 40],
|
iconAnchor: [20, 40],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const otherDeviceIcon = new L.DivIcon({
|
||||||
|
html: `<div class="device-icon other-device-icon">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="4" y="6" width="16" height="12" rx="2" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<circle cx="12" cy="12" r="2"/>
|
||||||
|
<line x1="7" y1="9" x2="7" y2="9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="17" y1="9" x2="17" y2="9" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="7" y1="15" x2="7" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="17" y1="15" x2="17" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>`,
|
||||||
|
className: 'custom-device-marker',
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 40],
|
||||||
|
});
|
||||||
|
|
||||||
const infoIcon = new L.DivIcon({
|
const infoIcon = new L.DivIcon({
|
||||||
html: `<div class="info-marker-icon">
|
html: `<div class="info-marker-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
@@ -127,7 +143,7 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
console.log('Loaded items:', data);
|
console.log('Loaded items:', data);
|
||||||
// Log devices with their connections
|
// Log devices with their connections
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
if (['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type)) {
|
if (['switch', 'indoor_ap', 'outdoor_ap', 'other_device'].includes(item.type)) {
|
||||||
console.log(`Device ${item.type} (${item.id}): ${item.properties.connections?.length || 0} / ${item.properties.port_count} ports`);
|
console.log(`Device ${item.type} (${item.id}): ${item.properties.connections?.length || 0} / ${item.properties.port_count} ports`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -153,6 +169,8 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
return indoorApIcon;
|
return indoorApIcon;
|
||||||
case 'outdoor_ap':
|
case 'outdoor_ap':
|
||||||
return outdoorApIcon;
|
return outdoorApIcon;
|
||||||
|
case 'other_device':
|
||||||
|
return otherDeviceIcon;
|
||||||
case 'info':
|
case 'info':
|
||||||
return infoIcon;
|
return infoIcon;
|
||||||
default:
|
default:
|
||||||
@@ -379,7 +397,7 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
|
|
||||||
// Render devices and info markers
|
// Render devices and info markers
|
||||||
if (
|
if (
|
||||||
['switch', 'indoor_ap', 'outdoor_ap', 'info'].includes(item.type) &&
|
['switch', 'indoor_ap', 'outdoor_ap', 'other_device', 'info'].includes(item.type) &&
|
||||||
item.geometry.type === 'Point'
|
item.geometry.type === 'Point'
|
||||||
) {
|
) {
|
||||||
const [lng, lat] = item.geometry.coordinates;
|
const [lng, lat] = item.geometry.coordinates;
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ const DEVICE_TOOLS: ToolButton[] = [
|
|||||||
icon: 'outdoor_ap',
|
icon: 'outdoor_ap',
|
||||||
description: 'Outdoor Access Point',
|
description: 'Outdoor Access Point',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'other_device',
|
||||||
|
label: 'Other Device',
|
||||||
|
icon: 'other_device',
|
||||||
|
description: 'Other Device',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const INFO_TOOL: ToolButton = {
|
const INFO_TOOL: ToolButton = {
|
||||||
@@ -128,6 +134,19 @@ export function Toolbar({ readOnly = false }: ToolbarProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tool.icon === 'other_device') {
|
||||||
|
return (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style={{ color: '#6B7280' }}>
|
||||||
|
<rect x="4" y="6" width="16" height="12" rx="2" fill="none" stroke="currentColor" strokeWidth="2"/>
|
||||||
|
<circle cx="12" cy="12" r="2"/>
|
||||||
|
<line x1="7" y1="9" x2="7" y2="9" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<line x1="17" y1="9" x2="17" y2="9" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<line x1="7" y1="15" x2="7" y2="15" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<line x1="17" y1="15" x2="17" y2="15" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (tool.icon === 'info') {
|
if (tool.icon === 'info') {
|
||||||
return (
|
return (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style={{ color: '#6366F1' }}>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style={{ color: '#6366F1' }}>
|
||||||
|
|||||||
@@ -138,6 +138,11 @@
|
|||||||
color: #F59E0B;
|
color: #F59E0B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.other-device-icon {
|
||||||
|
border-color: #6B7280;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
/* Info marker icon */
|
/* Info marker icon */
|
||||||
.custom-info-marker {
|
.custom-info-marker {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type MapItemType = 'cable' | 'switch' | 'indoor_ap' | 'outdoor_ap' | 'wireless_mesh' | 'info';
|
export type MapItemType = 'cable' | 'switch' | 'indoor_ap' | 'outdoor_ap' | 'other_device' | 'wireless_mesh' | 'info';
|
||||||
|
|
||||||
export type CableType = 'fiber' | 'cat6' | 'cat6_poe';
|
export type CableType = 'fiber' | 'cat6' | 'cat6_poe';
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ export type DrawingTool =
|
|||||||
| 'switch'
|
| 'switch'
|
||||||
| 'indoor_ap'
|
| 'indoor_ap'
|
||||||
| 'outdoor_ap'
|
| 'outdoor_ap'
|
||||||
|
| 'other_device'
|
||||||
| 'wireless_mesh'
|
| 'wireless_mesh'
|
||||||
| 'info';
|
| 'info';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user