poor: Move everything into the QMainWindow and add basic UI
parent
87935c7e3a
commit
3cbb14c1af
@ -1,160 +1,275 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Get topologies
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider
|
from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider, OspfFileParseError
|
||||||
|
|
||||||
|
|
||||||
ref_topo_file = 'reference.ospf'
|
ref_topo_file = 'reference.ospf'
|
||||||
if len(sys.argv) > 1:
|
|
||||||
ref_topo_file = sys.argv[1]
|
|
||||||
|
|
||||||
ref_topo = OspfFileTopologyProvider(ref_topo_file).get_topology()
|
|
||||||
|
|
||||||
def get_empty_topology():
|
|
||||||
from birdvisu.topo_v3 import TopologyV3
|
|
||||||
topo = TopologyV3()
|
|
||||||
topo.freeze()
|
|
||||||
return topo
|
|
||||||
|
|
||||||
if len(sys.argv) > 2 and sys.argv[2] == '--no-bird':
|
|
||||||
cur_topo = get_empty_topology()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
cur_topo = BirdSocketTopologyProvider(instance='ospf1', area=0).get_topology()
|
|
||||||
except OSError as e:
|
|
||||||
# I know this is not how you print exceptions.
|
|
||||||
print('Cannot get topology from BIRD: {e}')
|
|
||||||
print('Will provide an empty topology.')
|
|
||||||
cur_topo = get_empty_topology()
|
|
||||||
|
|
||||||
# Create combined topology
|
|
||||||
|
|
||||||
from birdvisu.topo_v3 import TopologyV3
|
from birdvisu.topo_v3 import TopologyV3
|
||||||
|
|
||||||
combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo)
|
|
||||||
combined_topology.freeze()
|
|
||||||
|
|
||||||
# Annotate it
|
|
||||||
|
|
||||||
from birdvisu.topo_v3 import VertexID
|
from birdvisu.topo_v3 import VertexID
|
||||||
from birdvisu.annotations import AnnotatedTopology, AnnotatorID
|
from birdvisu.annotations import AnnotatedTopology, AnnotatorID
|
||||||
from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree
|
from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
annot_topo = AnnotatedTopology(combined_topology)
|
|
||||||
annotators = [
|
|
||||||
AnnotatorID(TopologyDifference),
|
|
||||||
AnnotatorID(ShortestPathTree, (VertexID(
|
|
||||||
family=None,
|
|
||||||
is_router=True,
|
|
||||||
address=None,
|
|
||||||
router_id=int(IPv4Address('172.23.100.10')),
|
|
||||||
dr_id=None,
|
|
||||||
discriminator=None
|
|
||||||
), 'current')),
|
|
||||||
]
|
|
||||||
for ann_id in annotators:
|
|
||||||
annot_topo.run_annotator(ann_id)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
|
|
||||||
# Show it
|
|
||||||
|
|
||||||
#from birdvisu.visualisation import annotators
|
|
||||||
#from birdvisu import maps_new
|
|
||||||
|
|
||||||
# annotators.create_qgritems does not like being run without Qt initialization.
|
|
||||||
from PySide6 import QtCore, QtGui, QtWidgets
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
app = QtWidgets.QApplication([])
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
#annotated_topology = maps_new.annotate_topology(combined_topology,
|
|
||||||
# # A semi-canonical set of annotators:
|
|
||||||
# [
|
|
||||||
# annotators.extract_positions,
|
|
||||||
# annotators.random_position,
|
|
||||||
# annotators.assign_brushes,
|
|
||||||
# annotators.create_qgritems,
|
|
||||||
# ]
|
|
||||||
# )
|
|
||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
shapes = dict()
|
|
||||||
nei = defaultdict(lambda: [])
|
|
||||||
|
|
||||||
class MyGraphicsRectItem(QtWidgets.QGraphicsRectItem):
|
class MyGraphicsRectItem(QtWidgets.QGraphicsRectItem):
|
||||||
#def __init__(self, *a, **kwa):
|
def __init__(self, nei, shapes, *a, **kwa):
|
||||||
# return super().__init__(*a, **kwa)
|
self.nei = nei
|
||||||
|
self.shapes = shapes
|
||||||
|
return super().__init__(*a, **kwa)
|
||||||
#def itemChange(self, change, val):
|
#def itemChange(self, change, val):
|
||||||
# return super().itemChange(change, val)
|
# return super().itemChange(change, val)
|
||||||
def mouseMoveEvent(self, evt):
|
def mouseMoveEvent(self, evt):
|
||||||
vtxid = self.data(0)
|
vtxid = self.data(0)
|
||||||
for e in nei[vtxid]:
|
for e in self.nei[vtxid]:
|
||||||
x1 = self.x()
|
x1 = self.x()
|
||||||
y1 = self.y()
|
y1 = self.y()
|
||||||
other = e.source if e.source != vtxid else e.target
|
other = e.source if e.source != vtxid else e.target
|
||||||
x2 = shapes[other].x()
|
x2 = self.shapes[other].x()
|
||||||
y2 = shapes[other].y()
|
y2 = self.shapes[other].y()
|
||||||
qlinef = QtCore.QLineF(x1, y1, x2, y2)
|
qlinef = QtCore.QLineF(x1, y1, x2, y2)
|
||||||
shapes[e].setLine(qlinef)
|
self.shapes[e].setLine(qlinef)
|
||||||
return super().mouseMoveEvent(evt)
|
return super().mouseMoveEvent(evt)
|
||||||
|
|
||||||
for k, v in annot_topo.topology.vertices.items():
|
from enum import Enum, auto
|
||||||
size = 30 if k.is_router else 10
|
from PySide6.QtCore import Slot
|
||||||
x, y = randint(0, 1920), randint(0, 1080)
|
from birdvisu.ospfsock import BirdSocketConnection
|
||||||
shape = MyGraphicsRectItem(-size/2, -size/2, size, size)
|
|
||||||
shape.setPos(x,y)
|
class BirdTopologyLoader(QtWidgets.QDialog):
|
||||||
# TODO:brush
|
def __init__(self, *a, **kwa):
|
||||||
label_text = str(IPv4Address(k.router_id)) if k.is_router else str(k.address) # Surprisingly works for all the possible addresses.
|
super().__init__(*a, **kwa)
|
||||||
label = QtWidgets.QGraphicsSimpleTextItem(label_text, parent=shape)
|
self.setModal(True)
|
||||||
label.setY(size*0.8)
|
self.result_ = (None, None, None)
|
||||||
text_width = label.boundingRect().width()
|
outer = QtWidgets.QVBoxLayout(self)
|
||||||
label.setX(-text_width/2)
|
# Area
|
||||||
shape.setData(0, k)
|
inner = QtWidgets.QHBoxLayout()
|
||||||
shape.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
|
inner.addWidget(QtWidgets.QLabel('Area:'))
|
||||||
shapes[k] = shape
|
line = QtWidgets.QLineEdit(self)
|
||||||
|
line.setText('-1')
|
||||||
for e in annot_topo.topology.edges:
|
self.line = line
|
||||||
start = shapes[e.source].pos()
|
inner.addWidget(line)
|
||||||
end = shapes[e.target].pos()
|
outer.addLayout(inner)
|
||||||
qlinef = QtCore.QLineF(start, end)
|
|
||||||
line = QtWidgets.QGraphicsLineItem(qlinef)
|
inner = QtWidgets.QHBoxLayout()
|
||||||
line.setData(0, e)
|
inner.addWidget(QtWidgets.QLabel('Instance:'))
|
||||||
nei[e.source].append(e)
|
combo = QtWidgets.QComboBox()
|
||||||
nei[e.target].append(e)
|
combo.addItems(self.get_instances())
|
||||||
shapes[e] = line
|
self.combo = combo
|
||||||
|
inner.addWidget(combo)
|
||||||
# Render the widget
|
outer.addLayout(inner)
|
||||||
|
|
||||||
scene = QtWidgets.QGraphicsScene()
|
inner = QtWidgets.QHBoxLayout()
|
||||||
|
inner.addWidget(QtWidgets.QLabel('Protocol:'))
|
||||||
#for tagsrc in [
|
combo = QtWidgets.QComboBox()
|
||||||
# annotated_topology.router_annotations.values(),
|
combo.addItems(['Guess!', 'OSPFv2', 'OSPFv3'])
|
||||||
# annotated_topology.network_annotations.values(),
|
self.combo2 = combo
|
||||||
# annotated_topology.link_annotations.values(),
|
inner.addWidget(combo)
|
||||||
# ]:
|
outer.addLayout(inner)
|
||||||
# for taglist in tagsrc:
|
|
||||||
# assert len(taglist) > 0
|
buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
|
||||||
# assert isinstance(taglist[-1], QtWidgets.QGraphicsItem)
|
buttons.accepted.connect(self.saveResult)
|
||||||
# scene.addItem(taglist[-1])
|
buttons.accepted.connect(self.accept)
|
||||||
|
buttons.rejected.connect(self.reject)
|
||||||
for sh in shapes.values(): scene.addItem(sh)
|
outer.addWidget(buttons)
|
||||||
|
|
||||||
view = QtWidgets.QGraphicsView(scene)
|
@Slot()
|
||||||
view.setDragMode(view.DragMode.ScrollHandDrag)
|
def saveResult(self):
|
||||||
#view.show()
|
try:
|
||||||
|
area = int(self.line.text())
|
||||||
|
except ValueError:
|
||||||
main_window = QtWidgets.QMainWindow()
|
area = int(IPv4Address(self.line.text()))
|
||||||
main_window.setCentralWidget(view)
|
if area == -1: area = None
|
||||||
|
inst = self.combo.currentText()
|
||||||
menu = main_window.menuBar().addMenu('Hello')
|
proto = {
|
||||||
act = QtGui.QAction('Hi')
|
'OSPFv2': 2,
|
||||||
#act.setToolTip('Howdy')
|
'OSPFv3': 3,
|
||||||
menu.addAction(act)
|
'Guess!': None,
|
||||||
|
}[self.combo2.currentText()]
|
||||||
|
self.result_ = (area, inst, proto)
|
||||||
|
|
||||||
|
def get_instances(self):
|
||||||
|
# Our code is stupid, so we initialise a BirdSocketTopologyProvider just to get instances and then drop it.
|
||||||
|
prov = BirdSocketTopologyProvider()
|
||||||
|
bird = BirdSocketConnection()
|
||||||
|
return prov.find_running_ospf(bird)
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
class Tool(Enum):
|
||||||
|
MoveTopology = auto()
|
||||||
|
ShortestPath = auto()
|
||||||
|
|
||||||
|
def __init__(self, *a, **kwa):
|
||||||
|
super().__init__(*a, **kwa)
|
||||||
|
self.ref_topo_provider = None
|
||||||
|
self.cur_topo_provider = None
|
||||||
|
self.annotators = self.get_annotators()
|
||||||
|
self.annot_topo = None
|
||||||
|
self.highlighter = None
|
||||||
|
self.tool = self.Tool.MoveTopology
|
||||||
|
self.scene = QtWidgets.QGraphicsScene()
|
||||||
|
self.view = QtWidgets.QGraphicsView(self.scene)
|
||||||
|
self.view.setDragMode(self.view.DragMode.ScrollHandDrag)
|
||||||
|
self.setCentralWidget(self.view)
|
||||||
|
self.statusbar = self.statusBar()
|
||||||
|
self.statusbar.showMessage('Hello!')
|
||||||
|
|
||||||
|
self.menubar = self.menuBar()
|
||||||
|
mode_menu = self.menubar.addMenu('&Mode')
|
||||||
|
short_path_act = QtGui.QAction("Sh. path &DAG", self)
|
||||||
|
short_path_act.triggered.connect(self.shortestPathMode)
|
||||||
|
mode_menu.addAction(short_path_act)
|
||||||
|
|
||||||
|
topo_menu = self.menubar.addMenu('&Topology')
|
||||||
|
open_ref_act = QtGui.QAction("&Load reference", self)
|
||||||
|
open_ref_act.triggered.connect(self.openRefTopology)
|
||||||
|
topo_menu.addAction(open_ref_act)
|
||||||
|
|
||||||
|
cur_topo_menu = topo_menu.addMenu("Load ¤t")
|
||||||
|
running_bird_act = QtGui.QAction('&BIRD', self)
|
||||||
|
running_bird_act.triggered.connect(self.curTopologyFromBird)
|
||||||
|
cur_topo_menu.addAction(running_bird_act)
|
||||||
|
from_file_act = QtGui.QAction('&from file', self)
|
||||||
|
from_file_act.triggered.connect(self.curTopologyFromFile)
|
||||||
|
cur_topo_menu.addAction(from_file_act)
|
||||||
|
|
||||||
|
refresh_act = QtGui.QAction("&Refresh", self)
|
||||||
|
refresh_act.triggered.connect(self.refreshTopologies)
|
||||||
|
topo_menu.addAction(refresh_act)
|
||||||
|
|
||||||
|
def get_annotators(self):
|
||||||
|
return [
|
||||||
|
AnnotatorID(TopologyDifference),
|
||||||
|
AnnotatorID(ShortestPathTree, (VertexID(
|
||||||
|
family=None,
|
||||||
|
is_router=True,
|
||||||
|
address=None,
|
||||||
|
router_id=int(IPv4Address('172.23.100.10')),
|
||||||
|
dr_id=None,
|
||||||
|
discriminator=None
|
||||||
|
), 'current')),
|
||||||
|
]
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def shortestPathMode(self):
|
||||||
|
self.tool = self.Tool.ShortestPath
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def openRefTopology(self):
|
||||||
|
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open reference topology', '.', 'OSPF files(*.ospf);;All files(*)')[0]
|
||||||
|
if filename == '': return # Do nothing
|
||||||
|
self.ref_topo_provider = OspfFileTopologyProvider(filename)
|
||||||
|
try:
|
||||||
|
ref_topo = self.ref_topo_provider.get_topology()
|
||||||
|
except OspfFileParseError as e:
|
||||||
|
warning = QtWidgets.QMessageBox.critical(self, "Bad reference topology", f"The reference topology seems to be malformed: {e}.\nPlease select a valid reference topology.")
|
||||||
|
self.ref_topo_provider = None
|
||||||
|
return
|
||||||
|
self.refreshTopologies()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def curTopologyFromBird(self):
|
||||||
|
loader = BirdTopologyLoader(self)
|
||||||
|
loader.exec()
|
||||||
|
area, instance, version = loader.result_
|
||||||
|
self.cur_topo_provider = BirdSocketTopologyProvider(instance=instance, area=area, version=version)
|
||||||
|
try:
|
||||||
|
cur_topo = self.cur_topo_provider.get_topology()
|
||||||
|
except OspfFileParseError as e:
|
||||||
|
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
|
||||||
|
self.cur_topo_provider = None
|
||||||
|
return
|
||||||
|
self.refreshTopologies()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def curTopologyFromFile(self):
|
||||||
|
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open current topology', '.', 'OSPF files(*.ospf);;All files(*)')[0]
|
||||||
|
if filename == '': return # Do nothing
|
||||||
|
self.cur_topo_provider = OspfFileTopologyProvider(filename)
|
||||||
|
try:
|
||||||
|
cur_topo = self.cur_topo_provider.get_topology()
|
||||||
|
except OspfFileParseError as e:
|
||||||
|
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
|
||||||
|
self.cur_topo_provider = None
|
||||||
|
return
|
||||||
|
self.refreshTopologies()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def refreshTopologies(self):
|
||||||
|
# Pre-checks:
|
||||||
|
msg = ''
|
||||||
|
if self.ref_topo_provider is None: msg += 'Please select reference topology. '
|
||||||
|
if self.cur_topo_provider is None: msg += 'Please select current topology. '
|
||||||
|
if msg:
|
||||||
|
self.statusbar.showMessage(msg)
|
||||||
|
# Nothing more to do.
|
||||||
|
return
|
||||||
|
# We just drop anything we had before, since we will be re-reading all the files.
|
||||||
|
try:
|
||||||
|
ref_topo = self.ref_topo_provider.get_topology()
|
||||||
|
except OspfFileParseError as e:
|
||||||
|
warning = QtWidgets.QMessageBox.critical(self, "Bad reference topology", f"The reference topology seems to be malformed: {e}.\nPlease select a valid reference topology.")
|
||||||
|
self.ref_topo_provider = None
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cur_topo = self.cur_topo_provider.get_topology()
|
||||||
|
except OspfFileParseError as e:
|
||||||
|
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
|
||||||
|
self.cur_topo_provider = None
|
||||||
|
return
|
||||||
|
self.scene.clear()
|
||||||
|
combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo)
|
||||||
|
combined_topology.freeze()
|
||||||
|
self.annot_topo = AnnotatedTopology(combined_topology)
|
||||||
|
for ann_id in self.annotators:
|
||||||
|
self.annot_topo.run_annotator(ann_id)
|
||||||
|
# Draw it
|
||||||
|
self.set_current_highlighter()
|
||||||
|
self.ad_hoc_draw_visu()
|
||||||
|
|
||||||
|
def set_current_highlighter(self):
|
||||||
|
# TODO!
|
||||||
|
self.highlighter = self.annotators[-1]
|
||||||
|
|
||||||
|
def ad_hoc_draw_visu(self):
|
||||||
|
shapes = dict()
|
||||||
|
self.nei = defaultdict(lambda: [])
|
||||||
|
for k, v in self.annot_topo.topology.vertices.items():
|
||||||
|
size = 30 if k.is_router else 10
|
||||||
|
x, y = randint(0, 1920), randint(0, 1080)
|
||||||
|
shape = MyGraphicsRectItem(self.nei, shapes, -size/2, -size/2, size, size)
|
||||||
|
shape.setPos(x,y)
|
||||||
|
# TODO:brush
|
||||||
|
label_text = str(IPv4Address(k.router_id)) if k.is_router else str(k.address) # Surprisingly works for all the possible addresses.
|
||||||
|
label = QtWidgets.QGraphicsSimpleTextItem(label_text, parent=shape)
|
||||||
|
label.setY(size*0.8)
|
||||||
|
text_width = label.boundingRect().width()
|
||||||
|
label.setX(-text_width/2)
|
||||||
|
shape.setData(0, k)
|
||||||
|
shape.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
|
||||||
|
shapes[k] = shape
|
||||||
|
|
||||||
|
for e in self.annot_topo.topology.edges:
|
||||||
|
start = shapes[e.source].pos()
|
||||||
|
end = shapes[e.target].pos()
|
||||||
|
qlinef = QtCore.QLineF(start, end)
|
||||||
|
line = QtWidgets.QGraphicsLineItem(qlinef)
|
||||||
|
line.setData(0, e)
|
||||||
|
self.nei[e.source].append(e)
|
||||||
|
self.nei[e.target].append(e)
|
||||||
|
shapes[e] = line
|
||||||
|
if self.highlighter is not None and self.highlighter in self.annot_topo.annotations:
|
||||||
|
ann = self.annot_topo.annotations[self.highlighter]
|
||||||
|
for shk in ann.for_vertex.keys() | ann.for_edge.keys():
|
||||||
|
shapes[shk].setPen(QtGui.QPen(QtGui.QColor('blue')))
|
||||||
|
for sh in shapes.values(): self.scene.addItem(sh)
|
||||||
|
|
||||||
|
main_window = MainWindow()
|
||||||
main_window.show()
|
main_window.show()
|
||||||
|
|
||||||
app.exec()
|
app.exec()
|
||||||
|
Loading…
Reference in New Issue