5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
8 #from sfa.util.rspecHelper import RSpec
9 from sface.config import config
10 from sface.sfirenew import RenewWindow
11 from sface.sfiprocess import SfiProcess
12 from sface.screens.sfascreen import SfaScreen
13 from sface.sfidata import SfiData
17 node_status = { "in": "Already Selected",
18 "out": "Not Selected",
20 "remove": "To be Removed"}
22 tag_status = { "in": "Already Set",
25 "remove": "To be Removed"}
27 color_status = { "in": QColor.fromRgb(0, 250, 250),
28 "add": QColor.fromRgb(0, 250, 0),
29 "remove": QColor.fromRgb(250, 0, 0) }
31 default_tags = "Default tags"
32 settable_tags = ['delegations', 'initscript']
35 NODE_STATUS_COLUMN = 1
36 MEMBERSHIP_STATUS_COLUMN = 2
39 # maximum length of a name to display before clipping
43 if index.parent().parent().isValid():
49 class NodeView(QTreeView):
50 def __init__(self, parent):
51 QTreeView.__init__(self, parent)
53 self.setAnimated(True)
54 self.setItemsExpandable(True)
55 self.setRootIsDecorated(True)
56 self.setAlternatingRowColors(True)
57 # self.setSelectionMode(self.MultiSelection)
58 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
59 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
60 self.setToolTip("Double click on a row to change its status. Right click on a host to add a tag.")
62 def keyPressEvent(self, event):
63 if (event.key() == Qt.Key_Space):
64 self.toggleSelection()
66 QTreeView.keyPressEvent(self, event)
68 def mouseDoubleClickEvent(self, event):
69 self.toggleSelection()
71 def toggleSelection(self):
72 index = self.currentIndex()
74 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
75 status_data = status_index.data().toString()
76 node_index = model.index(index.row(), NAME_COLUMN, index.parent())
77 node_data = node_index.data().toString()
79 if itemType(node_index) == "tag":
80 data = node_index.data().toString()
81 tagname, value = data.split(": ")
82 if tagname not in settable_tags:
84 QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
86 if status_data == tag_status['in']:
87 model.setData(status_index, QString(tag_status['remove']))
88 elif status_data == tag_status['add']:
89 model.setData(status_index, QString(tag_status['out']))
90 elif status_data == tag_status['remove']:
91 model.setData(status_index, QString(tag_status['in']))
92 else: model.setData(status_index, QString(node_status['out']))
95 if status_data == node_status['in']:
96 model.setData(status_index, QString(node_status['remove']))
97 elif status_data == node_status['out']:
98 model.setData(status_index, QString(node_status['add']))
99 elif status_data in (node_status['add'], node_status['remove']):
100 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
101 else: model.setData(status_index, QString(node_status['out']))
103 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
105 def mousePressEvent(self, event):
106 QTreeView.mousePressEvent(self, event)
107 if event.button() == Qt.LeftButton:
111 index = self.currentIndex()
112 model = index.model()
113 status_index = model.index(index.row(), 1, index.parent())
114 status_data = status_index.data().toString()
115 node_index = model.index(index.row(), 0, index.parent())
116 node_data = node_index.data().toString()
118 if itemType(node_index) == "node":
120 if status_data in (node_status['in'], node_status['add'], ""):
121 # Pop up a dialog box for adding a new attribute
122 tagname, ok = QInputDialog.getItem(self, "Add tag",
123 "Tag name:", settable_tags)
125 value, ok = QInputDialog.getText(self, "Add tag",
126 "Value for tag '%s'" % tagname)
128 # Add a new row to the model for the tag
130 # For testing with the QStandardItemModel
131 #nodeItem = model.itemFromIndex(index)
132 #tagstring = QString("%s: %s" % (tagname, value))
133 #tagItem = QStandardItem(tagstring)
134 #status = QStandardItem(QString(tag_status['add']))
135 #nodeItem.appendRow([tagItem, status])
137 # We're using the QSortFilterProxyModel here
138 src_index = model.mapToSource(index)
139 src_model = src_index.model()
140 nodeItem = src_model.itemFromIndex(src_index)
141 tagstring = QString("%s: %s" % (tagname, value))
142 tagItem = QStandardItem(tagstring)
143 status = QStandardItem(QString(tag_status['add']))
144 nodeItem.appendRow([tagItem, QStandardItem(QString("")), status])
146 elif status_data in (node_status['out'], node_status['remove']):
147 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
150 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
152 def currentChanged(self, current, previous):
153 model = current.model()
154 node_index = model.index(current.row(), 0, current.parent())
155 node_data = node_index.data().toString()
156 self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
160 class NodeNameDelegate(QStyledItemDelegate):
161 def __init__(self, parent):
162 QStyledItemDelegate.__init__(self, parent)
164 def displayText(self, value, locale):
165 data = str(QStyledItemDelegate.displayText(self, value, locale))
166 if (len(data)>NAME_MAX_LEN):
167 data = data[:(NAME_MAX_LEN-3)] + "..."
170 def paint(self, painter, option, index):
171 model = index.model()
172 data = str(self.displayText(index.data(), QLocale()))
173 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
174 status_data = status_index.data().toString()
176 fm = QFontMetrics(option.font)
177 rect = QRect(option.rect)
179 rect.setHeight(rect.height() - 2)
180 rect.setWidth(fm.width(QString(data)) + 6)
181 rect.setX(rect.x() + 5)
182 rect.setY(rect.y() - 1)
184 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
186 path = QPainterPath()
187 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
190 painter.setRenderHint(QPainter.Antialiasing)
192 if option.state & QStyle.State_Selected:
193 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
195 if itemType(index) == "node":
196 for x in node_status.keys():
197 if (node_status[x] == status_data) and (x in color_status):
198 painter.fillPath(path, color_status[x])
200 painter.setPen(QColor.fromRgb(0, 0, 0))
201 painter.drawText(rect, 0, QString(data))
204 for x in tag_status.keys():
205 if (tag_status[x] == status_data) and (x in color_status):
206 painter.fillPath(path, color_status[x])
208 painter.setPen(QColor.fromRgb(0, 0, 0))
209 painter.drawText(rect, 0, QString(data))
213 class NodeStatusDelegate(QStyledItemDelegate):
214 def __init__(self, parent):
215 QStyledItemDelegate.__init__(self, parent)
217 def paint(self, painter, option, index):
218 model = index.model()
219 nodestatus_index = model.index(index.row(), NODE_STATUS_COLUMN, index.parent())
220 nodestatus_data = nodestatus_index.data().toString()
222 fm = QFontMetrics(option.font)
223 rect = QRect(option.rect)
225 data = index.data().toString()
226 rect.setHeight(rect.height() - 2)
227 rect.setWidth(fm.width(QString(data)) + 6)
228 rect.setX(rect.x() + 5)
229 rect.setY(rect.y() - 1)
231 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
233 path = QPainterPath()
234 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
237 painter.setRenderHint(QPainter.Antialiasing)
239 if option.state & QStyle.State_Selected:
240 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
242 if (nodestatus_data == ""):
243 painter.setPen(QColor.fromRgb(0, 0, 0))
244 painter.drawText(rect, 0, QString(data))
245 elif (nodestatus_data == "boot"):
246 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
247 painter.setPen(QColor.fromRgb(0, 0, 0))
248 painter.drawText(rect, 0, QString(data))
250 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
251 painter.setPen(QColor.fromRgb(0, 0, 0))
252 painter.drawText(rect, 0, QString(data))
256 class NodeFilterProxyModel(QSortFilterProxyModel):
257 def __init__(self, parent=None):
258 QSortFilterProxyModel.__init__(self, parent)
259 self.hostname_filter_regex = None
260 self.nodestatus_filter = None
262 def setHostNameFilter(self, hostname):
263 self.hostname_filter_regex = QRegExp(hostname)
264 self.invalidateFilter()
266 def setNodeStatusFilter(self, status):
267 if (status == "all"):
268 self.nodestatus_filter = None
270 self.nodestatus_filter = status
271 self.invalidateFilter()
273 def filterAcceptsRow(self, sourceRow, source_parent):
274 kind_data = self.sourceModel().index(sourceRow, KIND_COLUMN, source_parent).data().toString()
275 if (kind_data == "node"):
276 if self.hostname_filter_regex:
277 name_data = self.sourceModel().index(sourceRow, NAME_COLUMN, source_parent).data().toString()
278 if (self.hostname_filter_regex.indexIn(name_data) < 0):
280 if self.nodestatus_filter:
281 nodestatus_data = self.sourceModel().index(sourceRow, NODE_STATUS_COLUMN, source_parent).data().toString()
282 if (nodestatus_data != self.nodestatus_filter):
286 class SliceWidget(QWidget):
287 def __init__(self, parent):
288 QWidget.__init__(self, parent)
290 self.network_names = []
291 self.process = SfiProcess(self)
293 self.slicename = QLabel("", self)
294 self.updateSliceName()
295 self.slicename.setScaledContents(False)
296 filterlabel = QLabel ("Filter: ", self)
297 filterbox = QComboBox(self)
298 filterbox.addItems(["all", "boot", "disabled", "reinstall", "safeboot"])
299 searchlabel = QLabel ("Search: ", self)
300 searchlabel.setScaledContents(False)
301 searchbox = QLineEdit(self)
302 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
304 toplayout = QHBoxLayout()
305 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
306 toplayout.addStretch()
307 toplayout.addWidget(filterlabel, 0, Qt.AlignRight)
308 toplayout.addWidget(filterbox, 0, Qt.AlignRight)
309 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
310 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
312 self.nodeView = NodeView(self)
313 self.nodeModel = QStandardItemModel(0, 4, self)
314 self.filterModel = NodeFilterProxyModel(self)
316 self.nodeNameDelegate = NodeNameDelegate(self)
317 self.nodeStatusDelegate = NodeStatusDelegate(self)
319 refresh = QPushButton("Refresh Slice Data", self)
320 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
321 renew = QPushButton("Renew Slice", self)
322 renew.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
323 submit = QPushButton("Submit", self)
324 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
326 bottomlayout = QHBoxLayout()
327 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
328 bottomlayout.addWidget(renew, 0, Qt.AlignLeft)
329 bottomlayout.addStretch()
330 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
332 layout = QVBoxLayout()
333 layout.addLayout(toplayout)
334 layout.addWidget(self.nodeView)
335 layout.addLayout(bottomlayout)
336 self.setLayout(layout)
337 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
339 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
340 self.connect(renew, SIGNAL('clicked()'), self.renew)
341 self.connect(submit, SIGNAL('clicked()'), self.submit_pg_compat)
342 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
343 self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
344 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
345 self.nodeSelectionChanged)
349 def submitFinished(self):
350 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
352 faultString = self.process.getFaultString()
354 self.setStatus("<font color='green'>Slice data submitted.</font>")
356 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
359 self.parent().signalAll("rspecUpdated")
361 def refreshResourcesFinished(self):
362 self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
364 faultString = self.process.getFaultString()
366 self.setStatus("Refreshing slice RSpec.")
367 self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
368 self.process.retrieveRspec()
370 self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
372 def refreshRSpecFinished(self):
373 self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
375 faultString = self.process.getFaultString()
377 self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
379 self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
382 self.parent().signalAll("rspecUpdated")
384 def setStatus(self, msg, timeout=None):
385 self.parent().setStatus(msg, timeout)
387 def checkRunningProcess(self):
388 if self.process.isRunning():
389 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
393 def search(self, search_string):
394 self.filterModel.setHostNameFilter(str(search_string))
396 def filter(self, filter_string):
397 self.filterModel.setNodeStatusFilter(str(filter_string))
399 def itemStatus(self, item):
400 statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
401 return str(statusItem.data(Qt.DisplayRole).toString())
403 def itemText(self, item):
404 return str(item.data(Qt.DisplayRole).toString())
406 # Recursively walk the tree, making changes to the RSpec
407 def process_subtree(self, rspec, resources, item, depth = 0):
409 model = self.nodeModel
413 elif depth == 2: # Hostname
414 hostname = self.itemText(item)
415 testbed = self.itemText(item.parent())
416 status = self.itemStatus(item)
417 if status == node_status['add']:
418 print "Add hostname: %s" % hostname
420 resource_node = resources.get_node_element(hostname)
422 if resource_node==None:
423 print "Error: Failed to find %s in resources rspec" % hostname
425 rspec.merge_node(resource_node, testbed)
426 rspec.add_slivers([{"hostname": str(hostname)}], testbed)
428 elif status == node_status['remove']:
429 print "Remove hostname: %s" % hostname
430 rspec.remove_slivers([{"hostname": str(hostname)}], testbed)
432 elif depth == 3: # Tag
433 tag, value = self.itemText(item).split(": ")
434 status = self.itemStatus(item)
435 tag = "%s" % tag # Prevent weird error from lxml
436 value = "%s" % value # Prevent weird error from lxml
437 node = self.itemText(item.parent())
438 testbed = self.itemText(item.parent().parent())
439 if status == tag_status['add']:
440 print "Add tag to (%s, %s): %s/%s " % (testbed, node, tag, value)
441 if node.startswith(default_tags):
442 rspec.add_default_sliver_attribute(tag, value, testbed)
444 rspec.add_sliver_attribute(node, tag, value, testbed)
446 elif status == tag_status['remove']:
447 print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
448 if node.startswith(default_tags):
449 rspec.remove_default_sliver_attribute(tag, value, testbed)
451 rspec.remove_sliver_attribute(node, tag, value, testbed)
454 children = item.rowCount()
455 for row in range(0, children):
456 status = self.process_subtree(rspec, resources, item.child(row), depth + 1)
457 change = change or status
462 if self.checkRunningProcess():
465 rspec = SfiData().getSliceRSpec()
466 resources = SfiData().getResourcesRSpec()
467 change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
470 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
473 # Several aggregates have issues with the <statistics> section in the
474 # rspec, so make sure it's not there.
475 stats_elems = rspec.xml.xpath("//statistics")
476 if len(stats_elems)>0:
477 stats_elem = stats_elems[0]
478 parent = stats_elem.xpath("..")[0]
479 parent.remove(stats_elem)
481 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
482 self.process.applyRSpec(rspec)
483 self.setStatus("Sending slice data (RSpec). This will take some time...")
485 # ProtoGENI-compatible submit. Contact each aggregate individually rather
486 # than using the slice manager.
487 # This code will be removed when ProtoGENI slicemanager is patched.
488 def submit_pg_compat(self):
489 if self.checkRunningProcess():
492 rspec = SfiData().getSliceRSpec()
493 resources = SfiData().getResourcesRSpec()
494 change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
497 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
500 # Several aggregates have issues with the <statistics> section in the
501 # rspec, so make sure it's not there.
502 stats_elems = rspec.xml.xpath("//statistics")
503 if len(stats_elems)>0:
504 stats_elem = stats_elems[0]
505 parent = stats_elem.xpath("..")[0]
506 parent.remove(stats_elem)
508 self.submit_aggSuccessCount = 0
509 self.submit_aggFailCount = 0
510 self.submit_rspec = rspec
511 self.connect(self.process, SIGNAL('finished()'), self.getVersionFinished)
512 self.process.getSliceMgrVersion()
513 self.setStatus("Getting aggregate directory...")
515 def getVersionFinished(self):
516 self.disconnect(self.process, SIGNAL('finished()'), self.getVersionFinished)
518 faultString = self.process.getFaultString()
520 peers = SfiData().getSliceMgrVersion()["peers"]
521 self.submit_aggs = [(key, peers[key]) for key in peers.keys()]
522 self.delete_aggs = [(key, peers[key]) for key in peers.keys() if key.startswith("emulab")]
523 str = "<font color='green'>Successfully retrieved agg list.</font> "
524 #self.submitNextAgg(str)
525 self.deleteNextAgg(str)
527 self.setStatus("<font color='red'>getSliceMgrVersion failed: %s</font>" % (faultString))
529 def deleteNextAgg(self, statusStr=""):
530 if (self.delete_aggs == []):
531 self.submitNextAgg(statusStr)
534 self.delete_agg = self.delete_aggs.pop()
536 urlParts = urlparse.urlsplit(self.delete_agg[1])
537 amPort = urlParts.port
538 amAddr = urlParts.hostname+urlParts.path
540 self.setStatus(statusStr + "Deleting slivers on %s..." % (self.delete_agg[0]))
542 self.connect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
543 self.process.deleteSlivers(aggAddr = amAddr, aggPort = amPort)
545 def submitNextAgg(self, statusStr=""):
546 if (self.submit_aggs == []):
547 self.setStatus(statusStr + "<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
548 (self.submit_aggSuccessCount,self.submit_aggSuccessCount+self.submit_aggFailCount))
549 QTimer.singleShot(2500, self.refresh)
552 self.submit_agg = self.submit_aggs.pop()
554 urlParts = urlparse.urlsplit(self.submit_agg[1])
555 amPort = urlParts.port
556 amAddr = urlParts.hostname+urlParts.path
558 self.setStatus(statusStr + "Submitting to %s..." % (self.submit_agg[0]))
560 self.connect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
561 self.process.applyRSpec(self.submit_rspec, aggAddr = amAddr, aggPort = amPort, saveObtained=False)
563 def submitNextAggFinished(self):
564 self.disconnect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
566 faultString = self.process.getFaultString()
568 self.submit_aggSuccessCount+=1
569 str = "<font color='green'>Succeeded on %s.</font> " % (self.submit_agg[0])
571 self.submit_aggFailCount+=1
572 str = "<font color='red'>Failed on %s.</font> " % (self.submit_agg[0]) # , faultString)
574 self.submitNextAgg(str)
576 def deleteNextAggFinished(self):
577 self.disconnect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
579 faultString = self.process.getFaultString()
581 str = "<font color='green'>Succeeded deleteslivers on %s.</font> " % (self.delete_agg[0])
583 str = "<font color='red'>Failed deleteslivers on %s.</font> " % (self.delete_agg[0]) # , faultString)
585 self.deleteNextAgg(str)
588 dlg = RenewWindow(parent=self)
592 if not config.getSlice():
593 self.setStatus("<font color='red'>Slice not set yet!</font>")
596 if self.process.isRunning():
597 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
600 self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
602 self.process.retrieveResources()
603 self.setStatus("Refreshing resources. This will take some time...")
605 def updateView(self):
606 global already_in_nodes
607 already_in_nodes = []
608 self.network_names = []
609 self.nodeModel.clear()
611 rspec = SfiData().getSliceRSpec()
615 resources = SfiData().getResourcesRSpec()
619 rootItem = self.nodeModel.invisibleRootItem()
620 networks = rspec.get_networks()
622 for network in resources.get_networks():
623 if not network in networks:
624 networks.append(network)
626 for network in networks:
627 self.network_names.append(network)
629 all_nodes = resources.get_nodes(network)
630 sliver_nodes = rspec.get_nodes_with_slivers(network)
632 available_nodes = [ node for node in all_nodes if node not in sliver_nodes ]
634 networkItem = QStandardItem(QString(network))
635 msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
636 rootItem.appendRow([networkItem, QStandardItem(QString("")), QStandardItem(QString(msg)), QStandardItem(QString("network"))])
638 already_in_nodes += sliver_nodes
640 # Add default slice tags
641 nodeItem = QStandardItem(QString("%s for %s" % (default_tags, network)))
642 statusItem = QStandardItem(QString(""))
643 nodeStatus = QStandardItem(QString(""))
644 networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("defaults"))])
645 attrs = rspec.get_default_sliver_attributes(network)
646 for (name, value) in attrs:
647 tagstring = QString("%s: %s" % (name, value))
648 tagItem = QStandardItem(tagstring)
649 status = QStandardItem(QString(tag_status['in']))
650 nodeStatus = QStandardItem(QString(""))
651 nodeItem.appendRow([tagItem, nodeStatus, status, QStandardItem(QString("attribute"))])
653 for node in sliver_nodes:
654 nodeItem = QStandardItem(QString(node))
655 statusItem = QStandardItem(QString(node_status['in']))
656 nodeStatus = QStandardItem(QString(rspec.get_node_element(node, network).attrib.get("boot_state","")))
657 networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
659 attrs = rspec.get_sliver_attributes(node, network)
660 for (name, value) in attrs:
661 tagstring = QString("%s: %s" % (name, value))
662 tagItem = QStandardItem(tagstring)
663 statusItem = QStandardItem(QString(tag_status['in']))
664 nodeStatus = QStandardItem(QString(""))
665 nodeItem.appendRow([tagItem, nodeStatus, statusItem, QStandardItem(QString("attribute"))])
667 for node in available_nodes:
668 nodeItem = QStandardItem(QString(node))
669 statusItem = QStandardItem(QString(node_status['out']))
670 nodeStatus = QStandardItem(QString(resources.get_node_element(node, network).attrib.get("boot_state","")))
671 networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
673 self.filterModel.setSourceModel(self.nodeModel)
674 self.filterModel.setDynamicSortFilter(True)
676 headers = QStringList() << "Hostname or Tag" << "Node Status" << "Membership Status" << "Kind"
677 self.nodeModel.setHorizontalHeaderLabels(headers)
679 self.nodeView.setItemDelegateForColumn(0, self.nodeNameDelegate)
680 self.nodeView.setItemDelegateForColumn(1, self.nodeStatusDelegate)
681 self.nodeView.setModel(self.filterModel)
682 self.nodeView.hideColumn(KIND_COLUMN)
683 self.nodeView.expandAll()
684 self.nodeView.resizeColumnToContents(0)
685 self.nodeView.collapseAll()
687 def updateSliceName(self):
688 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
690 def nodeSelectionChanged(self, hostname):
691 self.parent().nodeSelectionChanged(hostname)
693 class MainScreen(SfaScreen):
694 def __init__(self, parent):
695 SfaScreen.__init__(self, parent)
697 slice = SliceWidget(self)
698 self.init(slice, "Nodes", "OneLab SFA crawler")
700 def rspecUpdated(self):
701 self.mainwin.rspecWindow.updateView()
703 def configurationChanged(self):
704 self.widget.updateSliceName()
705 self.widget.updateView()
706 self.mainwin.rspecWindow.updateView()
708 def nodeSelectionChanged(self, hostname):
709 self.mainwin.nodeSelectionChanged(hostname)