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)
362 self.bottomlayout = bottomlayout
364 layout = QVBoxLayout()
365 layout.addLayout(toplayout)
366 layout.addWidget(self.nodeView)
367 layout.addLayout(bottomlayout)
368 self.setLayout(layout)
369 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
371 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
372 self.connect(renew, SIGNAL('clicked()'), self.renew)
373 self.connect(submit, SIGNAL('clicked()'), self.submit) # _pg_compat)
374 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
375 self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
376 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
377 self.nodeSelectionChanged)
381 def submitFinished(self):
382 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
384 faultString = self.process.getFaultString()
386 self.setStatus("<font color='green'>Slice data submitted.</font>")
388 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
391 self.parent().signalAll("rspecUpdated")
393 def deleteSliversFinished(self):
394 self.disconnect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
396 faultString = self.process.getFaultString()
398 self.setStatus("<font color='green'>Slice data submitted.</font>")
399 QTimer.singleShot(2500, self.refresh)
401 self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
403 def refreshResourcesFinished(self):
404 self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
406 faultString = self.process.getFaultString()
408 self.setStatus("Refreshing slice RSpec.")
409 self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
410 self.process.retrieveRspec()
412 self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
414 def refreshRSpecFinished(self):
415 self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
417 faultString = self.process.getFaultString()
419 self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
421 self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
424 self.parent().signalAll("rspecUpdated")
426 def setStatus(self, msg, timeout=None):
427 self.parent().setStatus(msg, timeout)
429 def checkRunningProcess(self):
430 if self.process.isRunning():
431 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
435 def search(self, search_string):
436 self.filterModel.setHostNameFilter(str(search_string))
438 def filter(self, filter_string):
439 self.filterModel.setNodeStatusFilter(str(filter_string))
441 def itemStatus(self, item):
442 statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
443 return str(statusItem.data(Qt.DisplayRole).toString())
445 def itemText(self, item):
446 return str(item.data(Qt.DisplayRole).toString())
448 # Recursively walk the tree, making changes to the RSpec
449 def process_subtree(self, rspec, rspec_node_names, resource_node_names, item, depth = 0):
454 model = self.nodeModel
458 elif depth == 2: # Hostname
459 hostname = self.itemText(item)
460 testbed = self.itemText(item.parent())
461 status = self.itemStatus(item)
462 if status == node_status['add']:
463 print "Add hostname: %s" % hostname
465 resource_node = resource_node_names.get(hostname, None)
467 if resource_node==None:
468 print "Error: Failed to find %s in resources rspec" % hostname
470 if not (hostname in rspec_node_names):
471 network_name = Xrn(resource_node['component_manager_id']).get_hrn()
472 rspec.version.add_network(network_name)
473 rspec.version.add_nodes([resource_node])
474 rspec.version.add_slivers([str(hostname)])
476 elif status == node_status['remove']:
477 print "Remove hostname: %s" % hostname
478 rspec.version.remove_slivers([str(hostname)])
480 elif depth == 3: # Tag
481 tag, value = self.itemText(item).split(": ")
482 status = self.itemStatus(item)
483 tag = "%s" % tag # Prevent weird error from lxml
484 value = "%s" % value # Prevent weird error from lxml
485 hostname = self.itemText(item.parent())
486 testbed = self.itemText(item.parent().parent())
487 if status == tag_status['add']:
488 print "Add tag to (%s, %s): %s/%s " % (testbed, hostname, tag, value)
489 if hostname.startswith(default_tags):
490 rspec.version.add_default_sliver_attribute(tag, value, testbed)
492 node = rspec_node_names.get(hostname, None)
494 rspec.version.add_sliver_attribute(node['component_id'], tag, value, testbed)
496 elif status == tag_status['remove']:
497 print "Remove tag from (%s, %s): %s/%s " % (testbed, hostname, tag, value)
498 if hostname.startswith(default_tags):
499 rspec.version.remove_default_sliver_attribute(tag, value, testbed)
501 node = rspec_node_names.get(hostname, None)
503 rspec.version.remove_sliver_attribute(node['component_id'], tag, value, testbed)
506 children = item.rowCount()
507 for row in range(0, children):
508 (c, ca, cr, cat, crt) = self.process_subtree(rspec, rspec_node_names, resource_node_names, item.child(row), depth + 1)
509 changeAdd = changeAdd + ca
510 changeRemove = changeRemove + cr
511 changeAddTag = changeAddTag + cat
512 changeRemoveTag = changeRemoveTag + crt
514 change = changeAdd + changeRemove + changeAddTag + changeRemoveTag
516 return (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag)
519 if self.checkRunningProcess():
522 rspec = SfiData().getSliceRSpec()
523 resources = SfiData().getResourcesRSpec()
525 resource_node_names = self.nodesByName(resources.version.get_nodes())
526 rspec_node_names = self.nodesByName(rspec.version.get_nodes_with_slivers())
528 (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
529 self.process_subtree(rspec, rspec_node_names, resource_node_names, self.nodeModel.invisibleRootItem())
532 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
535 if (changeRemove > 0) and (rspec.version.get_nodes_with_slivers() == []):
536 # removing the last sliver in a slice requires us to call deleteSlivers
537 self.connect(self.process, SIGNAL('finished()'), self.deleteSliversFinished)
538 self.process.deleteSlivers()
541 # Several aggregates have issues with the <statistics> section in the
542 # rspec, so make sure it's not there.
543 stats_elems = rspec.xml.xpath("//statistics")
544 if len(stats_elems)>0:
545 stats_elem = stats_elems[0]
546 parent = stats_elem.xpath("..")[0]
547 parent.remove(stats_elem)
549 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
550 self.process.applyRSpec(rspec)
551 self.setStatus("Sending slice data (RSpec). This will take some time...")
553 def submit_pg_compat(self):
554 if self.checkRunningProcess():
557 rspec = SfiData().getSliceRSpec()
558 resources = SfiData().getResourcesRSpec()
560 (change, changeAdd, changeRemove, changeAddTag, changeRemoveTag) = \
561 self.process_subtree(rspec, rspec, resources, self.nodeModel.invisibleRootItem())
564 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
567 dlg = ClientSliceManager(self)
568 dlg.submit_pg_compat(rspec)
571 self.setStatus("<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
572 (dlg.submit_aggSuccessCount,dlg.submit_aggSuccessCount+dlg.submit_aggFailCount))
573 QTimer.singleShot(2500, self.refresh)
576 dlg = RenewWindow(parent=self)
580 if not config.getSlice():
581 self.setStatus("<font color='red'>Slice not set yet!</font>")
584 if self.process.isRunning():
585 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
588 self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
590 self.process.retrieveResources()
591 self.setStatus("Refreshing resources. This will take some time...")
593 def nodesByNetwork(self, nodeList):
595 for node in nodeList:
596 network_name = Xrn(node['component_manager_id']).get_hrn()
598 net = netDict.get(network_name, [])
600 netDict[network_name] = net
606 def nodesByName(self, nodeList, nameDict=None):
609 for node in nodeList:
610 hostname = node.get("component_name", None)
611 if hostname and (not hostname in nameDict):
612 nameDict[hostname] = node
616 def updateView(self):
617 global already_in_nodes
618 already_in_nodes = []
619 self.network_names = []
620 self.nodeModel.clear()
622 rspec = SfiData().getSliceRSpec()
626 resources = SfiData().getResourcesRSpec()
630 rootItem = self.nodeModel.invisibleRootItem()
633 for network in rspec.version.get_networks():
634 network_name = network.get("name", None)
635 if (network_name != None) and (not network_name in networks):
636 networks.append(network_name)
637 for network in resources.version.get_networks():
638 network_name = network.get("name", None)
639 if (network_name != None) and (not network_name in networks):
640 networks.append(network_name)
642 resources_nodes = self.nodesByNetwork(resources.version.get_nodes())
643 rspec_nodes = self.nodesByNetwork(rspec.version.get_nodes_with_slivers())
645 for network in networks:
646 self.network_names.append(network)
648 all_nodes = resources_nodes.get(network, [])
649 sliver_nodes = rspec_nodes.get(network, [])
651 sliver_node_names = self.nodesByName(sliver_nodes)
653 available_nodes = [ node for node in all_nodes if node["component_name"] not in sliver_node_names ]
655 msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
656 networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
658 already_in_nodes += sliver_node_names.keys()
660 # Add default slice tags
661 nodeItem = self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
662 attrs = rspec.version.get_default_sliver_attributes(network)
664 name = attr.get("name", None)
665 value = attr.get("value", None)
666 tagstring = QString("%s: %s" % (name, value))
667 self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
669 for node in sliver_nodes:
671 if ("hardware_types" in node):
672 hardware_types = [x["name"] for x in node["hardware_types"]]
673 nodeType = ",".join(hardware_types)
674 nodeStatus = node.get("boot_state", "")
675 if nodeStatus == None:
677 nodeItem = self.nodeView.appendRow(networkItem,
678 node["component_name"],
679 nodeStatus=nodeStatus,
681 membership=node_status['in'],
684 attrs = rspec.version.get_sliver_attributes(node['component_id'], network)
686 name = attr.get("name", None)
687 value = attr.get("value", None)
688 self.nodeView.appendRow(nodeItem,
689 "%s: %s" % (name, value),
690 membership=tag_status['in'],
692 disk_images = node.get("disk_image", [])
693 for disk_image in disk_images:
694 name = disk_image.get("name", None)
695 self.noveView.appendRow(nodeItem, name,
696 membership=node_status['in'],
699 for node in available_nodes:
701 if ("hardware_types" in node):
702 hardware_types = [x["name"] for x in node["hardware_types"]]
703 nodeType = ",".join(hardware_types)
704 nodeStatus = node.get("boot_state", "")
705 if nodeStatus == None:
707 self.nodeView.appendRow(networkItem,
708 node["component_name"],
709 nodeStatus=nodeStatus,
711 membership=node_status['out'],
714 self.filterModel.setSourceModel(self.nodeModel)
715 self.filterModel.setDynamicSortFilter(True)
716 self.filterModel.sort(NAME_COLUMN)
718 headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
719 self.nodeModel.setHorizontalHeaderLabels(headers)
721 self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
722 self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
723 self.nodeView.setModel(self.filterModel)
724 self.nodeView.hideColumn(KIND_COLUMN)
725 self.nodeView.expandAll()
726 self.nodeView.resizeColumnToContents(0)
727 self.nodeView.collapseAll()
729 def updateSliceName(self):
730 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
732 def nodeSelectionChanged(self, hostname):
733 self.parent().nodeSelectionChanged(hostname)
735 class MainScreen(SfaScreen):
736 def __init__(self, parent):
737 SfaScreen.__init__(self, parent)
739 self.sliceWidget = SliceWidget(self)
740 self.init(self.sliceWidget, "Nodes", "OneLab SFA crawler")
742 def rspecUpdated(self):
743 self.mainwin.rspecWindow.updateView()
745 def configurationChanged(self):
746 self.widget.updateSliceName()
747 self.widget.updateView()
748 self.mainwin.rspecWindow.updateView()
750 def nodeSelectionChanged(self, hostname):
751 self.mainwin.nodeSelectionChanged(hostname)
753 def remoteSliceChanged(self):
754 # we're being notified the slice was changed remotely. Download a new
756 QTimer.singleShot(2500, self.sliceWidget.refresh)
758 def addMainScreenButton(self, caption, action):
759 # allows another screen to add a button to mainscreen
760 button = QPushButton(caption, self)
761 self.sliceWidget.bottomlayout.insertWidget(2, button)
762 self.sliceWidget.connect(button, SIGNAL('clicked()'), action)