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']
36 NODE_STATUS_COLUMN = 2
37 MEMBERSHIP_STATUS_COLUMN = 3
40 # maximum length of a name to display before clipping
44 if index.parent().parent().isValid():
50 class NodeView(QTreeView):
51 def __init__(self, parent):
52 QTreeView.__init__(self, parent)
54 self.setAnimated(True)
55 self.setItemsExpandable(True)
56 self.setRootIsDecorated(True)
57 self.setAlternatingRowColors(True)
58 # self.setSelectionMode(self.MultiSelection)
59 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
60 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
61 self.setToolTip("Double click on a row to change its status. Right click on a host to add a tag.")
63 def keyPressEvent(self, event):
64 if (event.key() == Qt.Key_Space):
65 self.toggleSelection()
67 QTreeView.keyPressEvent(self, event)
69 def mouseDoubleClickEvent(self, event):
70 self.toggleSelection()
72 def toggleSelection(self):
73 index = self.currentIndex()
75 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
76 status_data = status_index.data().toString()
77 node_index = model.index(index.row(), NAME_COLUMN, index.parent())
78 node_data = node_index.data().toString()
80 if itemType(node_index) == "tag":
81 data = node_index.data().toString()
82 tagname, value = data.split(": ")
83 if tagname not in settable_tags:
85 QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
87 if status_data == tag_status['in']:
88 model.setData(status_index, QString(tag_status['remove']))
89 elif status_data == tag_status['add']:
90 model.setData(status_index, QString(tag_status['out']))
91 elif status_data == tag_status['remove']:
92 model.setData(status_index, QString(tag_status['in']))
93 else: model.setData(status_index, QString(node_status['out']))
96 if status_data == node_status['in']:
97 model.setData(status_index, QString(node_status['remove']))
98 elif status_data == node_status['out']:
99 model.setData(status_index, QString(node_status['add']))
100 elif status_data in (node_status['add'], node_status['remove']):
101 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
102 else: model.setData(status_index, QString(node_status['out']))
104 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
106 def appendRow(self, parent, name, nodeStatus="", nodeType="", membership="", kind = ""):
107 # row: name nodeStatus nodeType membership kind
108 item = QStandardItem(QString(str(name)))
110 QStandardItem(QString(str(nodeType))),
111 QStandardItem(QString(str(nodeStatus))),
112 QStandardItem(QString(str(membership))),
113 QStandardItem(QString(str(kind)))]
114 parent.appendRow(row)
117 def mousePressEvent(self, event):
118 QTreeView.mousePressEvent(self, event)
119 if event.button() == Qt.LeftButton:
123 index = self.currentIndex()
124 model = index.model()
125 status_index = model.index(index.row(), 1, index.parent())
126 status_data = status_index.data().toString()
127 node_index = model.index(index.row(), 0, index.parent())
128 node_data = node_index.data().toString()
130 if itemType(node_index) == "node":
132 if status_data in (node_status['in'], node_status['add'], ""):
133 # Pop up a dialog box for adding a new attribute
134 tagname, ok = QInputDialog.getItem(self, "Add tag",
135 "Tag name:", settable_tags)
137 value, ok = QInputDialog.getText(self, "Add tag",
138 "Value for tag '%s'" % tagname)
140 # We're using the QSortFilterProxyModel here
141 src_index = model.mapToSource(index)
142 src_model = src_index.model()
143 nodeItem = src_model.itemFromIndex(src_index)
145 self.appendRow(nodeItem, "%s: %s" % (tagname, value), membership=tag_status['add'], kind="attribute")
147 elif status_data in (node_status['out'], node_status['remove']):
148 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
151 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
153 def currentChanged(self, current, previous):
154 model = current.model()
155 node_index = model.index(current.row(), 0, current.parent())
156 node_data = node_index.data().toString()
157 self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
161 class NodeNameDelegate(QStyledItemDelegate):
162 def __init__(self, parent):
163 QStyledItemDelegate.__init__(self, parent)
165 def displayText(self, value, locale):
166 data = str(QStyledItemDelegate.displayText(self, value, locale))
167 if (len(data)>NAME_MAX_LEN):
168 data = data[:(NAME_MAX_LEN-3)] + "..."
171 def paint(self, painter, option, index):
172 model = index.model()
173 data = str(self.displayText(index.data(), QLocale()))
174 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
175 status_data = status_index.data().toString()
177 fm = QFontMetrics(option.font)
178 rect = QRect(option.rect)
180 rect.setHeight(rect.height() - 2)
181 rect.setWidth(fm.width(QString(data)) + 6)
182 rect.setX(rect.x() + 5)
183 rect.setY(rect.y() - 1)
185 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
187 path = QPainterPath()
188 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
191 painter.setRenderHint(QPainter.Antialiasing)
193 if option.state & QStyle.State_Selected:
194 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
196 if itemType(index) == "node":
197 for x in node_status.keys():
198 if (node_status[x] == status_data) and (x in color_status):
199 painter.fillPath(path, color_status[x])
201 painter.setPen(QColor.fromRgb(0, 0, 0))
202 painter.drawText(rect, 0, QString(data))
205 for x in tag_status.keys():
206 if (tag_status[x] == status_data) and (x in color_status):
207 painter.fillPath(path, color_status[x])
209 painter.setPen(QColor.fromRgb(0, 0, 0))
210 painter.drawText(rect, 0, QString(data))
214 class NodeStatusDelegate(QStyledItemDelegate):
215 def __init__(self, parent):
216 QStyledItemDelegate.__init__(self, parent)
218 def paint(self, painter, option, index):
219 model = index.model()
220 nodestatus_index = model.index(index.row(), NODE_STATUS_COLUMN, index.parent())
221 nodestatus_data = nodestatus_index.data().toString()
223 fm = QFontMetrics(option.font)
224 rect = QRect(option.rect)
226 data = index.data().toString()
227 rect.setHeight(rect.height() - 2)
228 rect.setWidth(fm.width(QString(data)) + 6)
229 rect.setX(rect.x() + 5)
230 rect.setY(rect.y() - 1)
232 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
234 path = QPainterPath()
235 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
238 painter.setRenderHint(QPainter.Antialiasing)
240 if option.state & QStyle.State_Selected:
241 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
243 if (nodestatus_data == ""):
244 painter.setPen(QColor.fromRgb(0, 0, 0))
245 painter.drawText(rect, 0, QString(data))
246 elif (nodestatus_data == "boot"):
247 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
248 painter.setPen(QColor.fromRgb(0, 0, 0))
249 painter.drawText(rect, 0, QString(data))
251 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
252 painter.setPen(QColor.fromRgb(0, 0, 0))
253 painter.drawText(rect, 0, QString(data))
257 class NodeFilterProxyModel(QSortFilterProxyModel):
258 def __init__(self, parent=None):
259 QSortFilterProxyModel.__init__(self, parent)
260 self.hostname_filter_regex = None
261 self.nodestatus_filter = None
263 def setHostNameFilter(self, hostname):
264 self.hostname_filter_regex = QRegExp(hostname)
265 self.invalidateFilter()
267 def setNodeStatusFilter(self, status):
268 if (status == "all"):
269 self.nodestatus_filter = None
271 self.nodestatus_filter = status
272 self.invalidateFilter()
274 def filterAcceptsRow(self, sourceRow, source_parent):
275 kind_data = self.sourceModel().index(sourceRow, KIND_COLUMN, source_parent).data().toString()
276 if (kind_data == "node"):
277 if self.hostname_filter_regex:
278 name_data = self.sourceModel().index(sourceRow, NAME_COLUMN, source_parent).data().toString()
279 if (self.hostname_filter_regex.indexIn(name_data) < 0):
281 if self.nodestatus_filter:
282 nodestatus_data = self.sourceModel().index(sourceRow, NODE_STATUS_COLUMN, source_parent).data().toString()
283 if (nodestatus_data != self.nodestatus_filter):
287 class SliceWidget(QWidget):
288 def __init__(self, parent):
289 QWidget.__init__(self, parent)
291 self.network_names = []
292 self.process = SfiProcess(self)
294 self.slicename = QLabel("", self)
295 self.updateSliceName()
296 self.slicename.setScaledContents(False)
297 filterlabel = QLabel ("Filter: ", self)
298 filterbox = QComboBox(self)
299 filterbox.addItems(["all", "boot", "disabled", "reinstall", "safeboot"])
300 searchlabel = QLabel ("Search: ", self)
301 searchlabel.setScaledContents(False)
302 searchbox = QLineEdit(self)
303 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
305 toplayout = QHBoxLayout()
306 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
307 toplayout.addStretch()
308 toplayout.addWidget(filterlabel, 0, Qt.AlignRight)
309 toplayout.addWidget(filterbox, 0, Qt.AlignRight)
310 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
311 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
313 self.nodeView = NodeView(self)
314 self.nodeModel = QStandardItemModel(0, 4, self)
315 self.filterModel = NodeFilterProxyModel(self)
317 self.nodeNameDelegate = NodeNameDelegate(self)
318 self.nodeStatusDelegate = NodeStatusDelegate(self)
320 refresh = QPushButton("Refresh Slice Data", self)
321 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
322 renew = QPushButton("Renew Slice", self)
323 renew.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
324 submit = QPushButton("Submit", self)
325 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
327 bottomlayout = QHBoxLayout()
328 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
329 bottomlayout.addWidget(renew, 0, Qt.AlignLeft)
330 bottomlayout.addStretch()
331 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
333 layout = QVBoxLayout()
334 layout.addLayout(toplayout)
335 layout.addWidget(self.nodeView)
336 layout.addLayout(bottomlayout)
337 self.setLayout(layout)
338 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
340 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
341 self.connect(renew, SIGNAL('clicked()'), self.renew)
342 self.connect(submit, SIGNAL('clicked()'), self.submit_pg_compat)
343 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
344 self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
345 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
346 self.nodeSelectionChanged)
350 def submitFinished(self):
351 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
353 faultString = self.process.getFaultString()
355 self.setStatus("<font color='green'>Slice data submitted.</font>")
357 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
360 self.parent().signalAll("rspecUpdated")
362 def refreshResourcesFinished(self):
363 self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
365 faultString = self.process.getFaultString()
367 self.setStatus("Refreshing slice RSpec.")
368 self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
369 self.process.retrieveRspec()
371 self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
373 def refreshRSpecFinished(self):
374 self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
376 faultString = self.process.getFaultString()
378 self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
380 self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
383 self.parent().signalAll("rspecUpdated")
385 def setStatus(self, msg, timeout=None):
386 self.parent().setStatus(msg, timeout)
388 def checkRunningProcess(self):
389 if self.process.isRunning():
390 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
394 def search(self, search_string):
395 self.filterModel.setHostNameFilter(str(search_string))
397 def filter(self, filter_string):
398 self.filterModel.setNodeStatusFilter(str(filter_string))
400 def itemStatus(self, item):
401 statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
402 return str(statusItem.data(Qt.DisplayRole).toString())
404 def itemText(self, item):
405 return str(item.data(Qt.DisplayRole).toString())
407 # Recursively walk the tree, making changes to the RSpec
408 def process_subtree(self, rspec, resources, item, depth = 0):
410 model = self.nodeModel
414 elif depth == 2: # Hostname
415 hostname = self.itemText(item)
416 testbed = self.itemText(item.parent())
417 status = self.itemStatus(item)
418 if status == node_status['add']:
419 print "Add hostname: %s" % hostname
421 resource_node = resources.get_node_element(hostname)
423 if resource_node==None:
424 print "Error: Failed to find %s in resources rspec" % hostname
426 rspec.merge_node(resource_node, testbed)
427 rspec.add_slivers([{"hostname": str(hostname)}], testbed)
429 elif status == node_status['remove']:
430 print "Remove hostname: %s" % hostname
431 rspec.remove_slivers([{"hostname": str(hostname)}], testbed)
433 elif depth == 3: # Tag
434 tag, value = self.itemText(item).split(": ")
435 status = self.itemStatus(item)
436 tag = "%s" % tag # Prevent weird error from lxml
437 value = "%s" % value # Prevent weird error from lxml
438 node = self.itemText(item.parent())
439 testbed = self.itemText(item.parent().parent())
440 if status == tag_status['add']:
441 print "Add tag to (%s, %s): %s/%s " % (testbed, node, tag, value)
442 if node.startswith(default_tags):
443 rspec.add_default_sliver_attribute(tag, value, testbed)
445 rspec.add_sliver_attribute(node, tag, value, testbed)
447 elif status == tag_status['remove']:
448 print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
449 if node.startswith(default_tags):
450 rspec.remove_default_sliver_attribute(tag, value, testbed)
452 rspec.remove_sliver_attribute(node, tag, value, testbed)
455 children = item.rowCount()
456 for row in range(0, children):
457 status = self.process_subtree(rspec, resources, item.child(row), depth + 1)
458 change = change or status
463 if self.checkRunningProcess():
466 rspec = SfiData().getSliceRSpec()
467 resources = SfiData().getResourcesRSpec()
468 change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
471 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
474 # Several aggregates have issues with the <statistics> section in the
475 # rspec, so make sure it's not there.
476 stats_elems = rspec.xml.xpath("//statistics")
477 if len(stats_elems)>0:
478 stats_elem = stats_elems[0]
479 parent = stats_elem.xpath("..")[0]
480 parent.remove(stats_elem)
482 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
483 self.process.applyRSpec(rspec)
484 self.setStatus("Sending slice data (RSpec). This will take some time...")
486 # ProtoGENI-compatible submit. Contact each aggregate individually rather
487 # than using the slice manager.
488 # This code will be removed when ProtoGENI slicemanager is patched.
489 def submit_pg_compat(self):
490 if self.checkRunningProcess():
493 rspec = SfiData().getSliceRSpec()
494 resources = SfiData().getResourcesRSpec()
495 change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
498 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
501 # Several aggregates have issues with the <statistics> section in the
502 # rspec, so make sure it's not there.
503 stats_elems = rspec.xml.xpath("//statistics")
504 if len(stats_elems)>0:
505 stats_elem = stats_elems[0]
506 parent = stats_elem.xpath("..")[0]
507 parent.remove(stats_elem)
509 self.submit_aggSuccessCount = 0
510 self.submit_aggFailCount = 0
511 self.submit_rspec = rspec
512 self.connect(self.process, SIGNAL('finished()'), self.getVersionFinished)
513 self.process.getSliceMgrVersion()
514 self.setStatus("Getting aggregate directory...")
516 def getVersionFinished(self):
517 self.disconnect(self.process, SIGNAL('finished()'), self.getVersionFinished)
519 faultString = self.process.getFaultString()
521 peers = SfiData().getSliceMgrVersion()["peers"]
522 self.submit_aggs = [(key, peers[key]) for key in peers.keys()]
523 self.delete_aggs = [(key, peers[key]) for key in peers.keys() if key.startswith("emulab")]
524 str = "<font color='green'>Successfully retrieved agg list.</font> "
525 #self.submitNextAgg(str)
526 self.deleteNextAgg(str)
528 self.setStatus("<font color='red'>getSliceMgrVersion failed: %s</font>" % (faultString))
530 def deleteNextAgg(self, statusStr=""):
531 if (self.delete_aggs == []):
532 self.submitNextAgg(statusStr)
535 self.delete_agg = self.delete_aggs.pop()
537 urlParts = urlparse.urlsplit(self.delete_agg[1])
538 amPort = urlParts.port
539 amAddr = urlParts.hostname+urlParts.path
541 self.setStatus(statusStr + "Deleting slivers on %s..." % (self.delete_agg[0]))
543 self.connect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
544 self.process.deleteSlivers(aggAddr = amAddr, aggPort = amPort)
546 def submitNextAgg(self, statusStr=""):
547 if (self.submit_aggs == []):
548 self.setStatus(statusStr + "<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
549 (self.submit_aggSuccessCount,self.submit_aggSuccessCount+self.submit_aggFailCount))
550 QTimer.singleShot(2500, self.refresh)
553 self.submit_agg = self.submit_aggs.pop()
555 urlParts = urlparse.urlsplit(self.submit_agg[1])
556 amPort = urlParts.port
557 amAddr = urlParts.hostname+urlParts.path
559 self.setStatus(statusStr + "Submitting to %s..." % (self.submit_agg[0]))
561 self.connect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
562 self.process.applyRSpec(self.submit_rspec, aggAddr = amAddr, aggPort = amPort, saveObtained=False)
564 def submitNextAggFinished(self):
565 self.disconnect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
567 faultString = self.process.getFaultString()
569 self.submit_aggSuccessCount+=1
570 str = "<font color='green'>Succeeded on %s.</font> " % (self.submit_agg[0])
572 self.submit_aggFailCount+=1
573 str = "<font color='red'>Failed on %s.</font> " % (self.submit_agg[0]) # , faultString)
575 self.submitNextAgg(str)
577 def deleteNextAggFinished(self):
578 self.disconnect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
580 faultString = self.process.getFaultString()
582 str = "<font color='green'>Succeeded deleteslivers on %s.</font> " % (self.delete_agg[0])
584 str = "<font color='red'>Failed deleteslivers on %s.</font> " % (self.delete_agg[0]) # , faultString)
586 self.deleteNextAgg(str)
589 dlg = RenewWindow(parent=self)
593 if not config.getSlice():
594 self.setStatus("<font color='red'>Slice not set yet!</font>")
597 if self.process.isRunning():
598 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
601 self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
603 self.process.retrieveResources()
604 self.setStatus("Refreshing resources. This will take some time...")
606 def updateView(self):
607 global already_in_nodes
608 already_in_nodes = []
609 self.network_names = []
610 self.nodeModel.clear()
612 rspec = SfiData().getSliceRSpec()
616 resources = SfiData().getResourcesRSpec()
620 rootItem = self.nodeModel.invisibleRootItem()
621 networks = rspec.get_networks()
623 for network in resources.get_networks():
624 if not network in networks:
625 networks.append(network)
627 for network in networks:
628 self.network_names.append(network)
630 all_nodes = resources.get_nodes(network)
631 sliver_nodes = rspec.get_nodes_with_slivers(network)
633 available_nodes = [ node for node in all_nodes if node not in sliver_nodes ]
635 msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
636 networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
638 already_in_nodes += sliver_nodes
640 # Add default slice tags
641 self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
642 attrs = rspec.get_default_sliver_attributes(network)
643 for (name, value) in attrs:
644 tagstring = QString("%s: %s" % (name, value))
645 self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
647 for node in sliver_nodes:
648 self.nodeView.appendRow(networkItem,
650 nodeStatus=rspec.get_node_boot_state(node, network),
651 nodeType=rspec.get_node_sliver_type(node, network),
653 #get_node_element(node, network).attrib.get("boot_state",""),
654 #nodeType=rspec.get_node_element(node, network).attrib.get("sliver_type",""),
656 membership=node_status['in'],
659 attrs = rspec.get_sliver_attributes(node, network)
660 for (name, value) in attrs:
661 self.nodeView.appendRow(nodeItem,
662 "%s: %s" % (name, value),
663 membership=tag_status['in'],
666 for node in available_nodes:
667 self.nodeView.appendRow(networkItem,
669 #nodeStatus=resources.get_node_element(node, network).attrib.get("boot_state",""),
670 nodeStatus = resources.get_node_boot_state(node, network),
671 nodeType= resources.get_node_sliver_type(node, network),
672 membership=node_status['out'],
675 self.filterModel.setSourceModel(self.nodeModel)
676 self.filterModel.setDynamicSortFilter(True)
677 self.filterModel.sort(NAME_COLUMN)
679 headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
680 self.nodeModel.setHorizontalHeaderLabels(headers)
682 self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
683 self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
684 self.nodeView.setModel(self.filterModel)
685 self.nodeView.hideColumn(KIND_COLUMN)
686 self.nodeView.expandAll()
687 self.nodeView.resizeColumnToContents(0)
688 self.nodeView.collapseAll()
690 def updateSliceName(self):
691 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
693 def nodeSelectionChanged(self, hostname):
694 self.parent().nodeSelectionChanged(hostname)
696 class MainScreen(SfaScreen):
697 def __init__(self, parent):
698 SfaScreen.__init__(self, parent)
700 slice = SliceWidget(self)
701 self.init(slice, "Nodes", "OneLab SFA crawler")
703 def rspecUpdated(self):
704 self.mainwin.rspecWindow.updateView()
706 def configurationChanged(self):
707 self.widget.updateSliceName()
708 self.widget.updateView()
709 self.mainwin.rspecWindow.updateView()
711 def nodeSelectionChanged(self, hostname):
712 self.mainwin.nodeSelectionChanged(hostname)