5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
8 #from sfa.util.rspecHelper import RSpec
9 from sfa.util.xrn import Xrn
10 from sface.config import config
11 from sface.sfirenew import RenewWindow
12 from sface.sfiprocess import SfiProcess
13 from sface.screens.sfascreen import SfaScreen
14 from sface.sfidata import SfiData
16 from sface.clislicemgr import ClientSliceManager
20 node_status = { "in": "Already Selected",
21 "out": "Not Selected",
23 "remove": "To be Removed"}
25 tag_status = { "in": "Already Set",
28 "remove": "To be Removed"}
30 color_status = { "in": QColor.fromRgb(0, 250, 250),
31 "add": QColor.fromRgb(0, 250, 0),
32 "remove": QColor.fromRgb(250, 0, 0) }
34 default_tags = "Default tags"
35 settable_tags = ['delegations', 'initscript']
39 NODE_STATUS_COLUMN = 2
40 MEMBERSHIP_STATUS_COLUMN = 3
43 # maximum length of a name to display before clipping
47 if index.parent().parent().isValid():
53 class NodeView(QTreeView):
54 def __init__(self, parent):
55 QTreeView.__init__(self, parent)
57 self.setAnimated(True)
58 self.setItemsExpandable(True)
59 self.setRootIsDecorated(True)
60 self.setAlternatingRowColors(True)
61 # self.setSelectionMode(self.MultiSelection)
62 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
63 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
64 self.setToolTip("Double click on a row to change its status. Right click on a host to add a tag.")
66 def keyPressEvent(self, event):
67 if (event.key() == Qt.Key_Space):
68 self.toggleSelection()
70 QTreeView.keyPressEvent(self, event)
72 def mouseDoubleClickEvent(self, event):
73 self.toggleSelection()
75 def toggleSelection(self):
76 index = self.currentIndex()
80 # probably no rspec downloaded yet
83 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
84 status_data = status_index.data().toString()
85 node_index = model.index(index.row(), NAME_COLUMN, index.parent())
86 node_data = node_index.data().toString()
88 if itemType(node_index) == "tag":
89 data = node_index.data().toString()
90 tagname, value = data.split(": ")
91 if tagname not in settable_tags:
93 QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
95 if status_data == tag_status['in']:
96 model.setData(status_index, QString(tag_status['remove']))
97 elif status_data == tag_status['add']:
98 model.setData(status_index, QString(tag_status['out']))
99 elif status_data == tag_status['remove']:
100 model.setData(status_index, QString(tag_status['in']))
101 else: model.setData(status_index, QString(node_status['out']))
104 if status_data == node_status['in']:
105 model.setData(status_index, QString(node_status['remove']))
106 elif status_data == node_status['out']:
107 model.setData(status_index, QString(node_status['add']))
108 elif status_data in (node_status['add'], node_status['remove']):
109 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
110 else: model.setData(status_index, QString(node_status['out']))
112 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
114 def appendRow(self, parent, name, nodeStatus="", nodeType="", membership="", kind = ""):
115 # row: name nodeStatus nodeType membership kind
116 item = QStandardItem(QString(str(name)))
118 QStandardItem(QString(str(nodeType))),
119 QStandardItem(QString(str(nodeStatus))),
120 QStandardItem(QString(str(membership))),
121 QStandardItem(QString(str(kind)))]
122 parent.appendRow(row)
125 def mousePressEvent(self, event):
126 QTreeView.mousePressEvent(self, event)
127 if event.button() == Qt.LeftButton:
131 index = self.currentIndex()
132 model = index.model()
135 # probably no rspec downloaded yet
138 status_index = model.index(index.row(), 1, index.parent())
139 status_data = status_index.data().toString()
140 node_index = model.index(index.row(), 0, index.parent())
141 node_data = node_index.data().toString()
143 if itemType(node_index) == "node":
145 if status_data in (node_status['in'], node_status['add'], ""):
146 # Pop up a dialog box for adding a new attribute
147 tagname, ok = QInputDialog.getItem(self, "Add tag",
148 "Tag name:", settable_tags)
150 value, ok = QInputDialog.getText(self, "Add tag",
151 "Value for tag '%s'" % tagname)
153 # We're using the QSortFilterProxyModel here
154 src_index = model.mapToSource(index)
155 src_model = src_index.model()
156 nodeItem = src_model.itemFromIndex(src_index)
158 self.appendRow(nodeItem, "%s: %s" % (tagname, value), membership=tag_status['add'], kind="attribute")
160 elif status_data in (node_status['out'], node_status['remove']):
161 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
164 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
166 def currentChanged(self, current, previous):
167 model = current.model()
168 node_index = model.index(current.row(), 0, current.parent())
169 node_data = node_index.data().toString()
170 self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
174 class NodeNameDelegate(QStyledItemDelegate):
175 def __init__(self, parent):
176 QStyledItemDelegate.__init__(self, parent)
178 def displayText(self, value, locale):
180 data = str(QStyledItemDelegate.displayText(self, value, locale))
181 except UnicodeEncodeError:
182 data = "<UnicodeDecodeError when generating displaytext>"
183 if (len(data)>NAME_MAX_LEN):
184 data = data[:(NAME_MAX_LEN-3)] + "..."
187 def paint(self, painter, option, index):
188 model = index.model()
189 data = str(self.displayText(index.data(), QLocale()))
190 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
191 status_data = status_index.data().toString()
193 fm = QFontMetrics(option.font)
194 rect = QRect(option.rect)
196 rect.setHeight(rect.height() - 2)
197 rect.setWidth(fm.width(QString(data)) + 6)
198 rect.setX(rect.x() + 5)
199 rect.setY(rect.y() - 1)
201 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
203 path = QPainterPath()
204 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
207 painter.setRenderHint(QPainter.Antialiasing)
209 if option.state & QStyle.State_Selected:
210 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
212 if itemType(index) == "node":
213 for x in node_status.keys():
214 if (node_status[x] == status_data) and (x in color_status):
215 painter.fillPath(path, color_status[x])
217 painter.setPen(QColor.fromRgb(0, 0, 0))
218 painter.drawText(rect, 0, QString(data))
221 for x in tag_status.keys():
222 if (tag_status[x] == status_data) and (x in color_status):
223 painter.fillPath(path, color_status[x])
225 painter.setPen(QColor.fromRgb(0, 0, 0))
226 painter.drawText(rect, 0, QString(data))
230 class NodeStatusDelegate(QStyledItemDelegate):
231 def __init__(self, parent):
232 QStyledItemDelegate.__init__(self, parent)
234 def paint(self, painter, option, index):
235 model = index.model()
236 nodestatus_index = model.index(index.row(), NODE_STATUS_COLUMN, index.parent())
237 nodestatus_data = nodestatus_index.data().toString()
239 fm = QFontMetrics(option.font)
240 rect = QRect(option.rect)
242 data = index.data().toString()
243 rect.setHeight(rect.height() - 2)
244 rect.setWidth(fm.width(QString(data)) + 6)
245 rect.setX(rect.x() + 5)
246 rect.setY(rect.y() - 1)
248 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
250 path = QPainterPath()
251 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
254 painter.setRenderHint(QPainter.Antialiasing)
256 if option.state & QStyle.State_Selected:
257 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
259 if (nodestatus_data == ""):
260 painter.setPen(QColor.fromRgb(0, 0, 0))
261 painter.drawText(rect, 0, QString(data))
262 elif (nodestatus_data == "boot"):
263 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
264 painter.setPen(QColor.fromRgb(0, 0, 0))
265 painter.drawText(rect, 0, QString(data))
267 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
268 painter.setPen(QColor.fromRgb(0, 0, 0))
269 painter.drawText(rect, 0, QString(data))
273 class NodeFilterProxyModel(QSortFilterProxyModel):
274 def __init__(self, parent=None):
275 QSortFilterProxyModel.__init__(self, parent)
276 self.hostname_filter_regex = None
277 self.nodestatus_filter = None
279 def setHostNameFilter(self, hostname):
280 self.hostname_filter_regex = QRegExp(hostname)
281 self.invalidateFilter()
283 def setNodeStatusFilter(self, status):
284 if (status == "all"):
285 self.nodestatus_filter = None
287 self.nodestatus_filter = status
288 self.invalidateFilter()
290 def filterAcceptsRow(self, sourceRow, source_parent):
291 kind_data = self.sourceModel().index(sourceRow, KIND_COLUMN, source_parent).data().toString()
292 if (kind_data == "node"):
293 if self.hostname_filter_regex:
294 name_data = self.sourceModel().index(sourceRow, NAME_COLUMN, source_parent).data().toString()
295 if (self.hostname_filter_regex.indexIn(name_data) < 0):
297 if self.nodestatus_filter:
298 nodestatus_data = self.sourceModel().index(sourceRow, NODE_STATUS_COLUMN, source_parent).data().toString()
299 if (nodestatus_data != self.nodestatus_filter):
303 def lessThan(self, left, right):
304 l_str = str(left.data().toString())
305 r_str = str(right.data().toString())
307 # make sure default_tags appears before everything else
308 if l_str.startswith(default_tags):
311 if r_str.startswith(default_tags):
314 return (l_str < r_str)
317 class SliceWidget(QWidget):
318 def __init__(self, parent):
319 QWidget.__init__(self, parent)
321 self.network_names = []
322 self.process = SfiProcess(self)
324 self.slicename = QLabel("", self)
325 self.updateSliceName()
326 self.slicename.setScaledContents(False)
327 filterlabel = QLabel ("Filter: ", self)
328 filterbox = QComboBox(self)
329 filterbox.addItems(["all", "boot", "disabled", "reinstall", "safeboot"])
330 searchlabel = QLabel ("Search: ", self)
331 searchlabel.setScaledContents(False)
332 searchbox = QLineEdit(self)
333 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
335 toplayout = QHBoxLayout()
336 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
337 toplayout.addStretch()
338 toplayout.addWidget(filterlabel, 0, Qt.AlignRight)
339 toplayout.addWidget(filterbox, 0, Qt.AlignRight)
340 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
341 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
343 self.nodeView = NodeView(self)
344 self.nodeModel = QStandardItemModel(0, 4, self)
345 self.filterModel = NodeFilterProxyModel(self)
347 self.nodeNameDelegate = NodeNameDelegate(self)
348 self.nodeStatusDelegate = NodeStatusDelegate(self)
350 refresh = QPushButton("Refresh Slice Data", self)
351 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
352 renew = QPushButton("Renew Slice", self)
353 renew.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
354 submit = QPushButton("Submit", self)
355 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
357 bottomlayout = QHBoxLayout()
358 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
359 bottomlayout.addWidget(renew, 0, Qt.AlignLeft)
360 bottomlayout.addStretch()
361 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
363 layout = QVBoxLayout()
364 layout.addLayout(toplayout)
365 layout.addWidget(self.nodeView)
366 layout.addLayout(bottomlayout)
367 self.setLayout(layout)
368 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
370 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
371 self.connect(renew, SIGNAL('clicked()'), self.renew)
372 self.connect(submit, SIGNAL('clicked()'), self.submit) # _pg_compat)
373 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
374 self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
375 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
376 self.nodeSelectionChanged)
380 def submitFinished(self):
381 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
383 faultString = self.process.getFaultString()
385 self.setStatus("<font color='green'>Slice data submitted.</font>")
387 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
390 self.parent().signalAll("rspecUpdated")
392 def deleteSliversFinished(self):
393 self.disconnect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
395 faultString = self.process.getFaultString()
397 self.setStatus("<font color='green'>Slice data submitted.</font>")
398 QTimer.singleShot(2500, self.refresh)
400 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
402 def refreshResourcesFinished(self):
403 self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
405 faultString = self.process.getFaultString()
407 self.setStatus("Refreshing slice RSpec.")
408 self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
409 self.process.retrieveRspec()
411 self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
413 def refreshRSpecFinished(self):
414 self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
416 faultString = self.process.getFaultString()
418 self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
420 self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
423 self.parent().signalAll("rspecUpdated")
425 def setStatus(self, msg, timeout=None):
426 self.parent().setStatus(msg, timeout)
428 def checkRunningProcess(self):
429 if self.process.isRunning():
430 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
434 def search(self, search_string):
435 self.filterModel.setHostNameFilter(str(search_string))
437 def filter(self, filter_string):
438 self.filterModel.setNodeStatusFilter(str(filter_string))
440 def itemStatus(self, item):
441 statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
442 return str(statusItem.data(Qt.DisplayRole).toString())
444 def itemText(self, item):
445 return str(item.data(Qt.DisplayRole).toString())
447 # Recursively walk the tree, making changes to the RSpec
448 def process_subtree(self, rspec, rspec_node_names, resource_node_names, item, depth = 0):
453 model = self.nodeModel
457 elif depth == 2: # Hostname
458 hostname = self.itemText(item)
459 testbed = self.itemText(item.parent())
460 status = self.itemStatus(item)
461 if status == node_status['add']:
462 print "Add hostname: %s" % hostname
464 resource_node = resource_node_names.get(hostname, None)
466 if resource_node==None:
467 print "Error: Failed to find %s in resources rspec" % hostname
469 if not (hostname in rspec_node_names):
470 network_name = Xrn(resource_node['component_manager_id']).get_hrn()
471 rspec.version.add_network(network_name)
472 rspec.version.add_nodes([resource_node])
473 rspec.version.add_slivers([str(hostname)])
475 elif status == node_status['remove']:
476 print "Remove hostname: %s" % hostname
477 rspec.version.remove_slivers([str(hostname)])
479 elif depth == 3: # Tag
480 tag, value = self.itemText(item).split(": ")
481 status = self.itemStatus(item)
482 tag = "%s" % tag # Prevent weird error from lxml
483 value = "%s" % value # Prevent weird error from lxml
484 hostname = self.itemText(item.parent())
485 testbed = self.itemText(item.parent().parent())
486 if status == tag_status['add']:
487 print "Add tag to (%s, %s): %s/%s " % (testbed, hostname, tag, value)
488 if hostname.startswith(default_tags):
489 rspec.version.add_default_sliver_attribute(tag, value, testbed)
491 node = rspec_node_names.get(hostname, None)
493 rspec.version.add_sliver_attribute(node['component_id'], tag, value, testbed)
495 elif status == tag_status['remove']:
496 print "Remove tag from (%s, %s): %s/%s " % (testbed, hostname, tag, value)
497 if hostname.startswith(default_tags):
498 rspec.version.remove_default_sliver_attribute(tag, value, testbed)
500 node = rspec_node_names.get(hostname, None)
502 rspec.version.remove_sliver_attribute(node['component_id'], tag, value, testbed)
505 children = item.rowCount()
506 for row in range(0, children):
507 (c, ca, cr, cat, crt) = self.process_subtree(rspec, rspec_node_names, resource_node_names, item.child(row), depth + 1)
508 changeAdd = changeAdd + ca
509 changeRemove = changeRemove + cr
510 changeAddTag = changeAddTag + cat
511 changeRemoveTag = changeRemoveTag + crt
513 change = changeAdd + changeRemove + changeAddTag + changeRemoveTag
515 return (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag)
518 if self.checkRunningProcess():
521 rspec = SfiData().getSliceRSpec()
522 resources = SfiData().getResourcesRSpec()
524 resource_node_names = self.nodesByName(resources.version.get_nodes())
525 rspec_node_names = self.nodesByName(rspec.version.get_nodes_with_slivers())
527 (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
528 self.process_subtree(rspec, rspec_node_names, resource_node_names, self.nodeModel.invisibleRootItem())
531 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
534 if (changeRemove > 0) and (rspec.version.get_nodes_with_slivers() == []):
535 # removing the last sliver in a slice requires us to call deleteSlivers
536 self.connect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
537 self.process.deleteSlivers()
540 # Several aggregates have issues with the <statistics> section in the
541 # rspec, so make sure it's not there.
542 stats_elems = rspec.xml.xpath("//statistics")
543 if len(stats_elems)>0:
544 stats_elem = stats_elems[0]
545 parent = stats_elem.xpath("..")[0]
546 parent.remove(stats_elem)
548 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
549 self.process.applyRSpec(rspec)
550 self.setStatus("Sending slice data (RSpec). This will take some time...")
552 def submit_pg_compat(self):
553 if self.checkRunningProcess():
556 rspec = SfiData().getSliceRSpec()
557 resources = SfiData().getResourcesRSpec()
559 (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
560 self.process_subtree(rspec, rspec, resources, self.nodeModel.invisibleRootItem())
563 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
566 dlg = ClientSliceManager(self)
567 dlg.submit_pg_compat(rspec)
570 self.setStatus("<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
571 (dlg.submit_aggSuccessCount,dlg.submit_aggSuccessCount+dlg.submit_aggFailCount))
572 QTimer.singleShot(2500, self.refresh)
575 dlg = RenewWindow(parent=self)
579 if not config.getSlice():
580 self.setStatus("<font color='red'>Slice not set yet!</font>")
583 if self.process.isRunning():
584 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
587 self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
589 self.process.retrieveResources()
590 self.setStatus("Refreshing resources. This will take some time...")
592 def nodesByNetwork(self, nodeList):
594 for node in nodeList:
595 network_name = Xrn(node['component_manager_id']).get_hrn()
597 net = netDict.get(network_name, [])
599 netDict[network_name] = net
605 def nodesByName(self, nodeList, nameDict=None):
608 for node in nodeList:
609 hostname = node.get("component_name", None)
610 if hostname and (not hostname in nameDict):
611 nameDict[hostname] = node
615 def updateView(self):
616 global already_in_nodes
617 already_in_nodes = []
618 self.network_names = []
619 self.nodeModel.clear()
621 rspec = SfiData().getSliceRSpec()
625 resources = SfiData().getResourcesRSpec()
629 rootItem = self.nodeModel.invisibleRootItem()
632 for network in rspec.version.get_networks():
633 network_name = network.get("name", None)
634 if (network_name != None) and (not network_name in networks):
635 networks.append(network_name)
636 for network in resources.version.get_networks():
637 network_name = network.get("name", None)
638 if (network_name != None) and (not network_name in networks):
639 networks.append(network_name)
641 resources_nodes = self.nodesByNetwork(resources.version.get_nodes())
642 rspec_nodes = self.nodesByNetwork(rspec.version.get_nodes_with_slivers())
644 for network in networks:
645 self.network_names.append(network)
647 all_nodes = resources_nodes.get(network, [])
648 sliver_nodes = rspec_nodes.get(network, [])
650 sliver_node_names = self.nodesByName(sliver_nodes)
652 available_nodes = [ node for node in all_nodes if node["component_name"] not in sliver_node_names ]
654 msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
655 networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
657 already_in_nodes += sliver_node_names.keys()
659 # Add default slice tags
660 nodeItem = self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
661 attrs = rspec.version.get_default_sliver_attributes(network)
663 name = attr.get("name", None)
664 value = attr.get("value", None)
665 tagstring = QString("%s: %s" % (name, value))
666 self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
668 for node in sliver_nodes:
670 if ("hardware_types" in node):
671 hardware_types = [x["name"] for x in node["hardware_types"]]
672 nodeType = ",".join(hardware_types)
673 nodeStatus = node.get("boot_state", "")
674 if nodeStatus == None:
676 nodeItem = self.nodeView.appendRow(networkItem,
677 node["component_name"],
678 nodeStatus=nodeStatus,
680 membership=node_status['in'],
683 attrs = rspec.version.get_sliver_attributes(node['component_id'], network)
685 name = attr.get("name", None)
686 value = attr.get("value", None)
687 self.nodeView.appendRow(nodeItem,
688 "%s: %s" % (name, value),
689 membership=tag_status['in'],
691 disk_images = node.get("disk_image", [])
692 for disk_image in disk_images:
693 name = disk_image.get("name", None)
694 self.noveView.appendRow(nodeItem, name,
695 membership=node_status['in'],
698 for node in available_nodes:
700 if ("hardware_types" in node):
701 hardware_types = [x["name"] for x in node["hardware_types"]]
702 nodeType = ",".join(hardware_types)
703 nodeStatus = node.get("boot_state", "")
704 if nodeStatus == None:
706 self.nodeView.appendRow(networkItem,
707 node["component_name"],
708 nodeStatus=nodeStatus,
710 membership=node_status['out'],
713 self.filterModel.setSourceModel(self.nodeModel)
714 self.filterModel.setDynamicSortFilter(True)
715 self.filterModel.sort(NAME_COLUMN)
717 headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
718 self.nodeModel.setHorizontalHeaderLabels(headers)
720 self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
721 self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
722 self.nodeView.setModel(self.filterModel)
723 self.nodeView.hideColumn(KIND_COLUMN)
724 self.nodeView.expandAll()
725 self.nodeView.resizeColumnToContents(0)
726 self.nodeView.collapseAll()
728 def updateSliceName(self):
729 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
731 def nodeSelectionChanged(self, hostname):
732 self.parent().nodeSelectionChanged(hostname)
734 class MainScreen(SfaScreen):
735 def __init__(self, parent):
736 SfaScreen.__init__(self, parent)
738 self.sliceWidget = SliceWidget(self)
739 self.init(self.sliceWidget, "Nodes", "OneLab SFA crawler")
741 def rspecUpdated(self):
742 self.mainwin.rspecWindow.updateView()
744 def configurationChanged(self):
745 self.widget.updateSliceName()
746 self.widget.updateView()
747 self.mainwin.rspecWindow.updateView()
749 def nodeSelectionChanged(self, hostname):
750 self.mainwin.nodeSelectionChanged(hostname)
752 def remoteSliceChanged(self):
753 # we're being notified the slice was changed remotely. Download a new
755 QTimer.singleShot(2500, self.sliceWidget.refresh)