Files
root cb073786b3 Initial commit: Werkzeuge-Sammlung
Enthält:
- rdp_client.py: RDP Client mit GUI und Monitor-Auswahl
- rdp.sh: Bash-basierter RDP Client
- teamleader_test/: Network Scanner Fullstack-App
- teamleader_test2/: Network Mapper CLI

Subdirectories mit eigenem Repo wurden ausgeschlossen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 09:39:24 +01:00

104 lines
2.9 KiB
JavaScript

const statusEl = document.querySelector('#status');
const svg = d3.select('#topology');
const width = 1000;
const height = 600;
const linkGroup = svg.append('g').attr('class', 'links');
const nodeGroup = svg.append('g').attr('class', 'nodes');
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.ip).distance(140))
.force('charge', d3.forceManyBody().strength(-200))
.force('center', d3.forceCenter(width / 2, height / 2));
function colorForNode(node) {
if (node.comment && node.comment.includes('gateway')) return '#ffb347';
if (node.comment && node.comment.includes('scanner')) return '#4db8ff';
return node.via_ssh ? '#7fbea6' : '#d4d4d4';
}
function render(data) {
const edges = data.edges.map(edge => ({
...edge,
source: edge.source,
target: edge.target,
}));
const link = linkGroup.selectAll('line').data(edges, d => `${d.source}|${d.target}|${d.relation}`);
link.join(
enter => enter.append('line').attr('stroke-width', 2),
update => update,
exit => exit.remove()
).attr('stroke', '#999');
const node = nodeGroup.selectAll('g').data(data.nodes, d => d.ip);
const nodeEnter = node.enter().append('g').call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
nodeEnter.append('circle').attr('r', 26);
nodeEnter.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '0.35em')
.text(d => d.ip);
nodeEnter.append('title');
const nodeMerged = nodeEnter.merge(node);
nodeMerged.select('circle').attr('fill', colorForNode);
nodeMerged.select('title').text(d => `${d.ip}\n${d.dns_name || 'no reverse host'}\nvia SSH: ${d.via_ssh}`);
node.exit().remove();
simulation.nodes(data.nodes).on('tick', ticked);
simulation.force('link').links(edges);
simulation.alpha(1).restart();
}
function ticked() {
linkGroup.selectAll('line')
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
nodeGroup.selectAll('g')
.attr('transform', d => `translate(${d.x},${d.y})`);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
async function refresh() {
try {
statusEl.textContent = 'Scanning local LAN…';
const response = await fetch('/api/scan');
if (!response.ok) {
throw new Error(`scan failed: ${response.status}`);
}
const payload = await response.json();
render(payload);
statusEl.textContent = `Last scanned ${new Date().toLocaleTimeString()}`;
} catch (error) {
statusEl.textContent = `Error: ${error.message}`;
}
}
document.querySelector('#refresh').addEventListener('click', refresh);
refresh();