@@ -22,6 +22,7 @@ let dragHandle = null;
|
|||||||
let dragOrig = null;
|
let dragOrig = null;
|
||||||
|
|
||||||
let nextShapeZ = 0;
|
let nextShapeZ = 0;
|
||||||
|
let copyBuffer = null;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
canvas = document.getElementById('networkCanvas');
|
canvas = document.getElementById('networkCanvas');
|
||||||
@@ -49,6 +50,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
window.addEventListener('resize', () => { resizeCanvas(); renderNetwork(); });
|
window.addEventListener('resize', () => { resizeCanvas(); renderNetwork(); });
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
|
||||||
|
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
||||||
|
if (selectedNodeId) copyNode(selectedNodeId);
|
||||||
|
else if (selectedShapeId) copyShape(selectedShapeId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
|
||||||
|
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
||||||
|
if (copyBuffer) pasteItem();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (e.key === 'Delete') {
|
if (e.key === 'Delete') {
|
||||||
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
||||||
if (selectedNodeId) deleteSelectedNode(selectedNodeId);
|
if (selectedNodeId) deleteSelectedNode(selectedNodeId);
|
||||||
@@ -301,6 +315,57 @@ async function deleteSelectedShape(id) {
|
|||||||
loadNetworkData();
|
loadNetworkData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyNode(id) {
|
||||||
|
const n = nodes.find(x => x.id == id);
|
||||||
|
if (!n) return;
|
||||||
|
copyBuffer = { type: 'node', data: { ...n } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyShape(id) {
|
||||||
|
const s = shapes.find(x => x.id == id);
|
||||||
|
if (!s) return;
|
||||||
|
copyBuffer = { type: 'shape', data: { ...s } };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pasteItem() {
|
||||||
|
if (!copyBuffer) return;
|
||||||
|
const offset = 30;
|
||||||
|
if (copyBuffer.type === 'node') {
|
||||||
|
const d = copyBuffer.data;
|
||||||
|
await apiFetch('nodes', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
label: d.label + ' (copy)',
|
||||||
|
ip_address: d.ip_address,
|
||||||
|
node_type: d.node_type,
|
||||||
|
status: d.status,
|
||||||
|
group_name: d.group_name,
|
||||||
|
pos_x: (parseFloat(d.pos_x) || 100) + offset,
|
||||||
|
pos_y: (parseFloat(d.pos_y) || 100) + offset
|
||||||
|
})
|
||||||
|
});
|
||||||
|
loadNetworkData();
|
||||||
|
} else if (copyBuffer.type === 'shape') {
|
||||||
|
const d = copyBuffer.data;
|
||||||
|
await apiFetch('shapes', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
label: d.label + ' (copy)',
|
||||||
|
shape_type: d.shape_type,
|
||||||
|
pos_x: (parseFloat(d.pos_x) || 100) + offset,
|
||||||
|
pos_y: (parseFloat(d.pos_y) || 100) + offset,
|
||||||
|
width: d.width || 200,
|
||||||
|
height: d.height || 150,
|
||||||
|
color: d.color || '#1e3a5f',
|
||||||
|
border_color: d.border_color || '#3b82f6',
|
||||||
|
opacity: parseFloat(d.opacity) || 0.15,
|
||||||
|
z_index: nextShapeZ++
|
||||||
|
})
|
||||||
|
});
|
||||||
|
loadNetworkData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showConfirm(msg) {
|
function showConfirm(msg) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const modalEl = document.getElementById('confirmModal');
|
const modalEl = document.getElementById('confirmModal');
|
||||||
@@ -381,7 +446,7 @@ const NODE_FA_ICONS = {
|
|||||||
server: { icon: '\uf233', color: '#8b5cf6' },
|
server: { icon: '\uf233', color: '#8b5cf6' },
|
||||||
router: { icon: '\uf4d8', color: '#f59e0b' },
|
router: { icon: '\uf4d8', color: '#f59e0b' },
|
||||||
firewall: { icon: '\uf6ed', color: '#ef4444' },
|
firewall: { icon: '\uf6ed', color: '#ef4444' },
|
||||||
switch: { icon: '\uf2d1', color: '#06b6d4' },
|
switch: { icon: '\uf0e8', color: '#06b6d4' },
|
||||||
cloud: { icon: '\uf0c2', color: '#22c55e' },
|
cloud: { icon: '\uf0c2', color: '#22c55e' },
|
||||||
endpoint: { icon: '\uf109', color: '#ec4899' },
|
endpoint: { icon: '\uf109', color: '#ec4899' },
|
||||||
other: { icon: '\uf111', color: '#6b7280' }
|
other: { icon: '\uf111', color: '#6b7280' }
|
||||||
@@ -501,7 +566,7 @@ function drawCanvasNode(n) {
|
|||||||
|
|
||||||
// Draw icon using Font Awesome
|
// Draw icon using Font Awesome
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.font = '500 22px "Font Awesome 6 Free", "FontAwesome", "Font Awesome 5 Free", sans-serif';
|
ctx.font = '900 22px "Font Awesome 6 Free", "FontAwesome", "Font Awesome 5 Free"';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillStyle = n.color;
|
ctx.fillStyle = n.color;
|
||||||
|
|||||||
Reference in New Issue
Block a user