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>
104 lines
2.9 KiB
JavaScript
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();
|