Merge commit 'origin/master'
authorroot <root@cassidy.cs.princeton.edu>
Tue, 8 Mar 2011 13:35:48 +0000 (08:35 -0500)
committerroot <root@cassidy.cs.princeton.edu>
Tue, 8 Mar 2011 13:35:48 +0000 (08:35 -0500)
1  2 
sface/mainwindow.py
sface/screens/mainscreen.py

diff --combined sface/mainwindow.py
@@@ -11,7 -11,6 +11,7 @@@ from sface.logwindow import LogWindo
  from sface.rspecwindow import RSpecWindow
  from sface.screens.configscreen import ConfigScreen
  from sface.screens.mainscreen import MainScreen
 +from sface.screens.helpscreen import HelpScreen
  
  class Nav(QWidget):
      def __init__(self, parent=None):
          self.config = QLabel("", self)
          self.config.setAlignment(Qt.AlignRight)
          
 +        self.help = QLabel("", self)
 +        self.help.setAlignment(Qt.AlignRight)
 +
          hlayout = QHBoxLayout()
          hlayout.addWidget(self.title)
          hlayout.addStretch()
          hlayout.addWidget(QLabel("Go to: ", self))
          hlayout.addWidget(self.main)
          hlayout.addWidget(self.config)
 +        hlayout.addWidget(self.help)
          self.setLayout(hlayout)
  
      def setTitle(self, title):
          self.title.setText(title)
  
 -    def setLinks(self, main, config):
 +    def setLinks(self, main, config, help):
          self.main.setText(main)
          self.config.setText(config)
 +        self.help.setText(help)
  
  
  class Status(QLabel):
@@@ -79,20 -73,17 +79,20 @@@ class MainWindow(QWidget)
          self.pix = QLabel(self)
          self.config_screen = ConfigScreen(self)
          self.main_screen = MainScreen(self)
 +        self.help_screen = HelpScreen(self)
  
          self.screens = QStackedWidget(self)
          self.screens.addWidget(self.main_screen)
          self.screens.addWidget(self.config_screen)
 +        self.screens.addWidget(self.help_screen)
          self.screens.addWidget(self.pix)
          self.next_screen = None
  
          self.nav = Nav(self)
          self.nav.setTitle(self.main_screen.getTitleText())
          self.nav.setLinks(self.main_screen.getLinkText(),
 -                          self.config_screen.getLinkText())
 +                          self.config_screen.getLinkText(),
 +                          self.help_screen.getLinkText())
  
          self.status = Status(self)
          self.log = QLabel("<a href='showlog'>Show Log</a>", self)
          layout.addWidget(self.screens)
          layout.addLayout(hlayout)
          self.setLayout(layout)
-         self.resize(800, 600)
+         self.resize(800, 500)
  
 -        for link in (self.nav.main, self.nav.config):
 +        for link in (self.nav.main, self.nav.config, self.nav.help):
              self.connect(link, SIGNAL('linkActivated(QString)'),
                           self.animateToScreen)
  
  
      def showLogWindow(self, link):
          self.logWindow.show()
-         self.logWindow.resize(800, 400)
+         self.logWindow.resize(800, 200)
          self.logWindow.raise_()
          self.logWindow.activateWindow()
  
              self.next_screen = self.config_screen
          elif link == self.main_screen.name:
              self.next_screen = self.main_screen
 +        elif link == self.help_screen.name:
 +            self.next_screen = self.help_screen
  
          curr_screen = self.screens.currentWidget()
  
              self.toConfigScreen()
          elif self.next_screen == self.main_screen:
              self.toMainScreen()
 +        elif self.next_screen == self.help_screen:
 +            self.toHelpScreen()
  
      def toConfigScreen(self):
          self.screens.setCurrentWidget(self.config_screen)
          self.nav.setTitle(self.config_screen.getTitleText())
  
 +    def toHelpScreen(self):
 +        self.screens.setCurrentWidget(self.help_screen)
 +        self.nav.setTitle(self.help_screen.getTitleText())
 +
      def toMainScreen(self):
          self.screens.setCurrentWidget(self.main_screen)
          self.nav.setTitle(self.main_screen.getTitleText())
@@@ -4,6 -4,7 +4,6 @@@ from PyQt4.QtCore import 
  from PyQt4.QtGui import *
  
  from sfa.util.rspecHelper import RSpec
 -from sface.sfahelper import *
  from sface.config import config
  from sface.sfiprocess import SfiProcess
  from sface.screens.sfascreen import SfaScreen
@@@ -15,21 -16,6 +15,21 @@@ node_status = { "in": "Already Selected
                  "add": "To be Added",
                  "remove": "To be Removed"}
  
 +tag_status = { "in": "Already Set",
 +                "out": "Not Set",
 +                "add": "To be Added",
 +                "remove": "To be Removed"}
 +
 +default_tags = "Default tags"
 +settable_tags = ['delegations', 'initscript']
 +
 +def itemType(index):
 +    if index.parent().parent().isValid():
 +        return "tag"
 +    else:
 +        return "node"
 +
 +
  class NodeView(QTreeView):
      def __init__(self, parent):
          QTreeView.__init__(self, parent)
  #        self.setSelectionMode(self.MultiSelection)
          self.setAttribute(Qt.WA_MacShowFocusRect, 0)
          self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
 -        self.setToolTip("Double click on a row to change its status")
 +        self.setToolTip("Double click on a row to change its status.  Right click on a host to add a tag.")
  
      def mouseDoubleClickEvent(self, event):
          index = self.currentIndex()
          model = index.model()
 -        status_index = model.index(index.row(), 2, index.parent())
 +        status_index = model.index(index.row(), 1, index.parent())
          status_data = status_index.data().toString()
 -        hostname_index = model.index(index.row(), 1, index.parent())
 -        hostname_data = hostname_index.data().toString()
 -
 -        if status_data == node_status['in']:
 -            model.setData(status_index, QString(node_status['remove']))
 -        elif status_data == node_status['out']:
 -            model.setData(status_index, QString(node_status['add']))
 -        elif status_data in (node_status['add'], node_status['remove']):
 -            if hostname_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
 +        node_index = model.index(index.row(), 0, index.parent())
 +        node_data = node_index.data().toString()
 +
 +        if itemType(node_index) == "tag":
 +            data = node_index.data().toString()
 +            tagname, value = data.split(": ")
 +            if tagname not in settable_tags:
 +                # Pop up error msg
 +                QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
 +                return
 +            if status_data == tag_status['in']:
 +                model.setData(status_index, QString(tag_status['remove']))
 +            elif status_data == tag_status['add']:
 +                model.setData(status_index, QString(tag_status['out']))
 +            elif status_data == tag_status['remove']:
 +                model.setData(status_index, QString(tag_status['in']))
              else: model.setData(status_index, QString(node_status['out']))
 +        else:
 +            # This is a hostname
 +            if status_data == node_status['in']:
 +                model.setData(status_index, QString(node_status['remove']))
 +            elif status_data == node_status['out']:
 +                model.setData(status_index, QString(node_status['add']))
 +            elif status_data in (node_status['add'], node_status['remove']):
 +                if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
 +                else: model.setData(status_index, QString(node_status['out']))
 +
 +        model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
 +
 +    def mousePressEvent(self, event):
 +        QTreeView.mousePressEvent(self, event)
 +        if event.button() == Qt.LeftButton:
 +            return
  
 -        model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), hostname_index, hostname_index)
 +        # Right click
 +        index = self.currentIndex()
 +        model = index.model()
 +        status_index = model.index(index.row(), 1, index.parent())
 +        status_data = status_index.data().toString()
 +        node_index = model.index(index.row(), 0, index.parent())
 +        node_data = node_index.data().toString()
 +
 +        if itemType(node_index) == "node":
 +            # This is a hostname
 +            if status_data in (node_status['in'], node_status['add'], ""):
 +                # Pop up a dialog box for adding a new attribute
 +                tagname, ok = QInputDialog.getItem(self, "Add tag",
 +                                                   "Tag name:", settable_tags)
 +                if ok:
 +                    value, ok = QInputDialog.getText(self, "Add tag",
 +                                                     "Value for tag '%s'" % tagname)
 +                    if ok:
 +                        # Add a new row to the model for the tag
 +
 +                        # For testing with the QStandardItemModel
 +                        #nodeItem = model.itemFromIndex(index)
 +                        #tagstring = QString("%s: %s" % (tagname, value))
 +                        #tagItem = QStandardItem(tagstring)
 +                        #status = QStandardItem(QString(tag_status['add']))
 +                        #nodeItem.appendRow([tagItem, status])
 +
 +                        # We're using the QSortFilterProxyModel here
 +                        src_index = model.mapToSource(index)
 +                        src_model = src_index.model()
 +                        nodeItem = src_model.itemFromIndex(src_index)
 +                        tagstring = QString("%s: %s" % (tagname, value))
 +                        tagItem = QStandardItem(tagstring)
 +                        status = QStandardItem(QString(tag_status['add']))
 +                        nodeItem.appendRow([tagItem, status])
 +
 +            elif status_data in (node_status['out'], node_status['remove']):
 +                QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
 +                return
 +
 +        model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
  
      def currentChanged(self, current, previous):
          model = current.model()
 -        hostname_index = model.index(current.row(), 1, current.parent())
 -        hostname_data = hostname_index.data().toString()
 -        self.emit(SIGNAL('hostnameClicked(QString)'), hostname_data)
 +        node_index = model.index(current.row(), 0, current.parent())
 +        node_data = node_index.data().toString()
 +        self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
          
                  
  
@@@ -138,67 -61,218 +138,67 @@@ class NodeNameDelegate(QStyledItemDeleg
  
      def paint(self, painter, option, index):
          model = index.model()
 -        data = "%s" % index.data().toString()
 -        status_index = model.index(index.row(), 2, index.parent())
 +        status_index = model.index(index.row(), 1, index.parent())
          status_data = status_index.data().toString()
  
 -        if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
 -            # default view
 -            QStyledItemDelegate.paint(self, painter, option, index)
 -            return
 -
          fm = QFontMetrics(option.font)
          rect = option.rect
 -        rect.setWidth(fm.width(QString(data)) + 8)
 +
 +        data = index.data().toString()
          rect.setHeight(rect.height() - 2)
 -        rect.setX(rect.x() + 4)
 -        x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width() 
 +        rect.setWidth(fm.width(QString(data)) + 6)
 +        rect.setX(rect.x() + 5)
 +        rect.setY(rect.y() - 1)
 +
 +        x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
  
          path = QPainterPath()
 -        path.addRoundedRect(x, y, w, h, 4, 4)
 +        path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
  
          painter.save()
          painter.setRenderHint(QPainter.Antialiasing)
 -        painter.drawRoundedRect(rect, 4, 4)
 -
 -        if status_data == node_status['in']: # already in the slice
 -            painter.fillPath(path, QColor.fromRgb(0, 250, 0))
 -            painter.setPen(QColor.fromRgb(0, 0, 0))
 -            painter.drawText(option.rect, 0, QString(data))
 -
 -        elif status_data == node_status['add']: # newly added to the slice
 -            painter.fillPath(path, QColor.fromRgb(0, 250, 0))
 -            painter.setPen(QColor.fromRgb(0, 0, 0))
 -            painter.drawText(option.rect, 0, QString(data))
 -            painter.drawRect(x + w + 10, y + 3, 10, 10)
 -            painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
 -
 -        elif status_data == node_status['remove']: # removed from the slice
 -            painter.fillPath(path, QColor.fromRgb(250, 0, 0))
 -            painter.setPen(QColor.fromRgb(0, 0, 0))
 -            painter.drawText(option.rect, 0, QString(data))
 -            painter.drawRect(x + w + 10, y + 3, 10, 10)
 -            painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
  
 -        painter.restore()
 +        if itemType(index) == "node":
 +            if status_data == node_status['in']: # already in the slice
 +                painter.fillPath(path, QColor("cyan"))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 +            elif status_data == node_status['add']: # newly added to the slice
 +                painter.fillPath(path, QColor.fromRgb(0, 250, 0))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -class TreeItem:
 -    def __init__(self, data, parent=None):
 -        self.parentItem = parent
 -        self.itemData = data
 -        self.childItems = []
 -
 -    def clear(self):
 -        for child in self.childItems:
 -            child.clear()
 -            del child
 -        del self.childItems
 -        self.childItems = []
 -
 -    def allChildItems(self):
 -        all = []
 -        for c in self.childItems:
 -            all.append(c)
 -            if c.childItems:
 -                for cc in c.childItems:
 -                    all.append(cc)
 -        return all
 -
 -    def appendChild(self, child):
 -        self.childItems.append(child)
 -
 -    def child(self, row):
 -        return self.childItems[row]
 -    
 -    def childCount(self):
 -        return len(self.childItems)
 -
 -    def childNumber(self):
 -        if self.parentItem:
 -            return self.parentItem.childItems.index(self)
 -        return 0
 -
 -    def columnCount(self):
 -        return len(self.itemData)
 -
 -    def data(self, column):
 -        return self.itemData[column]
 -
 -    def insertChildren(self, position, count, columns):
 -        if position < 0 or position > len(self.childItems):
 -            return False
 -        
 -        for row in range(count):
 -            data = self.data(columns)
 -            item = TreeItem(data, self)
 -            self.childItems.insert(position, item)
 +            elif status_data == node_status['remove']: # removed from the slice
 +                painter.fillPath(path, QColor.fromRgb(250, 0, 0))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -        return True
 +            else:
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -    def insertColumns(self, position, columns):
 -        if position < 0 or position > len(self.itemData):
 -            return False
 -
 -        for column in range(columns):
 -            self.itemData.insert(position, QVariant())
 -        
 -        for child in self.childItems:
 -            child.insertColumns(position, columns)
 -        
 -        return True
 -
 -    def setData(self, column, value):
 -        if column < 0 or column >= len(self.itemData):
 -            return False
 -
 -        self.itemData[column] = value
 -        return True
 -    
 -    def parent(self):
 -        return self.parentItem
 -
 -
 -class NodeModel(QAbstractItemModel):
 -    def __init__(self, parent):
 -        QAbstractItemModel.__init__(self, parent)
 -        self.__initRoot()
 -
 -    def clear(self):
 -        self.rootItem.clear()
 -        self.__initRoot()
 -
 -    def __initRoot(self):
 -        self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
 -
 -    def getItem(self, index):
 -        if index.isValid():
 -            item = index.internalPointer()
 -            if isinstance(item, TreeItem):
 -                return item
 -        return self.rootItem
 -
 -    def headerData(self, section, orientation, role):
 -        if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
 -            return self.rootItem.data(section)
 -        return QVariant()
 -
 -    def index(self, row, column, parent):
 -        if not self.hasIndex(row, column, parent):
 -            return QModelIndex()
 -
 -        parentItem = self.getItem(parent)
 -        childItem = parentItem.child(row)
 -        if childItem:
 -            return self.createIndex(row, column, childItem)
          else:
 -            return QModelIndex()
 -
 -    def insertColumns(self, position, columns, parent):
 -        self.beginInsertColumns(parent, position, position + columns -1)
 -        ret = self.rootItem.insertColumns(position, columns)
 -        self.endInsertColumns()
 -        return ret
 +            if status_data == tag_status['in']: # already in the slice
 +                painter.fillPath(path, QColor("cyan"))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -    def insertRows(self, position, rows, parent):
 -        parentItem = self.getItem(parent)
 -        self.beginInsertRows(parent, position, position + rows -1)
 -        ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
 -        self.endInsertRows()
 -        return ret
 +            elif status_data == tag_status['add']: # newly added to the slice
 +                painter.fillPath(path, QColor.fromRgb(0, 250, 0))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -    def parent(self, index):
 -        if not index.isValid():
 -            return QModelIndex()
 +            elif status_data == tag_status['remove']: # removed from the slice
 +                painter.fillPath(path, QColor.fromRgb(250, 0, 0))
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 -        childItem = self.getItem(index)
 -        parentItem = childItem.parent()
 -        if parentItem is self.rootItem:
 -            return QModelIndex()
 -
 -        return self.createIndex(parentItem.childNumber(), 0, parentItem)
 -
 -    def rowCount(self, parent=QModelIndex()):
 -        parentItem = self.getItem(parent)
 -        return parentItem.childCount()
 -
 -    def columnCount(self, parent=None):
 -        return self.rootItem.columnCount()
 -
 -    def data(self, index, role):
 -        if not index.isValid():
 -            return QVariant()
 -
 -        if role != Qt.DisplayRole and role != Qt.EditRole:
 -            return QVariant()
 -
 -        item = self.getItem(index)
 -        return item.data(index.column())
 -
 -    def flags(self, index):
 -        if not index.isValid():
 -            return 0
 -        return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
 -
 -    def setData(self, index, value, role):
 -        if role != Qt.EditRole:
 -            return False
 -
 -        item = self.getItem(index)
 -        ret = item.setData(index.column(), value)
 -        if ret:
 -            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
 -        return ret
 +            else:
 +                painter.setPen(QColor.fromRgb(0, 0, 0))
 +                painter.drawText(option.rect, 0, QString(data))
  
 +        painter.restore()
  
  class SliceWidget(QWidget):
      def __init__(self, parent):
          toplayout.addWidget(searchbox, 0, Qt.AlignRight)
  
          self.nodeView = NodeView(self)
 -        self.nodeModel = NodeModel(self)
 +        self.nodeModel = QStandardItemModel(0, 2, self)
          self.filterModel = QSortFilterProxyModel(self) # enable filtering
  
          self.nodeNameDelegate = NodeNameDelegate(self)
          rspec_file = config.getSliceRSpecFile()
          if os.path.exists(rspec_file):
              xml = open(rspec_file).read()
 -            return xml
 +            return RSpec(xml)
          return None
  
      def setStatus(self, msg, timeout=None):
          filters = networks + [str(filter_string)]
          self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
  
 +    def itemStatus(self, item):
 +        statusItem = item.parent().child(item.row(), 1)
 +        return statusItem.data(Qt.DisplayRole).toString()
 +
 +    def itemText(self, item):
 +        return item.data(Qt.DisplayRole).toString()
 +
 +    # Recursively walk the tree, making changes to the RSpec
 +    def process_subtree(self, rspec, item, depth = 0):
 +        change = False
 +        model = self.nodeModel
 +
 +        if depth in [0, 1]:
 +            pass
 +        elif depth == 2: # Hostname
 +            hostname = self.itemText(item)
 +            testbed = self.itemText(item.parent())
 +            status = self.itemStatus(item)
 +            if status == node_status['add']:
 +                print "Add hostname: %s" % hostname
 +                rspec.add_sliver(hostname, testbed)
 +                change = True
 +            elif status == node_status['remove']:
 +                print "Remove hostname: %s" % hostname
 +                rspec.remove_sliver(hostname, testbed)
 +                change = True
 +        elif depth == 3: # Tag
 +            tag, value = self.itemText(item).split(": ")
 +            status = self.itemStatus(item)
 +            tag = "%s" % tag     # Prevent weird error from lxml
 +            value = "%s" % value # Prevent weird error from lxml
 +            node = self.itemText(item.parent())
 +            testbed = self.itemText(item.parent().parent())
 +            if status == tag_status['add']:
 +                print "Add tag to (%s, %s): %s/%s " % (testbed, node, tag, value)
 +                if node.startsWith(default_tags):
 +                    rspec.add_default_sliver_attribute(tag, value, testbed)
 +                else:
 +                    rspec.add_sliver_attribute(node, tag, value, testbed)
 +                change = True
 +            elif status == tag_status['remove']:
 +                print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
 +                if node.startsWith(default_tags):
 +                    rspec.remove_default_sliver_attribute(tag, value, testbed)
 +                else:
 +                    rspec.remove_sliver_attribute(node, tag, value, testbed)
 +                change = True
 +
 +        children = item.rowCount()
 +        for row in range(0, children):
 +            status = self.process_subtree(rspec, item.child(row), depth + 1)
 +            change = change or status
 +
 +        return change
 +
      def submit(self):
          if self.checkRunningProcess():
              return
  
 -        rspec = RSpec(self.readSliceRSpec())
 -        
 -        no_change = True
 -        all_child = self.nodeModel.rootItem.allChildItems()
 -        for c in all_child:
 -            testbed, hostname, status = c.itemData
 -            if isinstance(status, QVariant):
 -                status = status.toString()
 -
 -            if status == node_status['add']:
 -                rspec.add_sliver(hostname)
 -                no_change = False
 -            elif str(status) == node_status['remove']:
 -                rspec.remove_sliver(hostname)
 -                no_change = False
 +        rspec = self.readSliceRSpec()
 +        change = self.process_subtree(rspec, self.nodeModel.invisibleRootItem())
  
 -        if no_change:
 +        if nochange:
              self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
              return
  
          already_in_nodes = []
          self.network_names = []
          self.nodeModel.clear()
 -        
 -        rspec_string = self.readSliceRSpec()
 -        if not rspec_string:
 +
 +        rspec = self.readSliceRSpec()
 +        if not rspec:
              return None
  
 -        networks = rspec_get_networks(rspec_string)
 +        rootItem = self.nodeModel.invisibleRootItem()
 +        networks = sorted(rspec.get_network_list())
          for network in networks:
              self.network_names.append(network)
 -            networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
  
 -            all_nodes = rspec_get_nodes_from_network(rspec_string, network)
 -            sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
 +            all_nodes = rspec.get_node_list(network)
 +            sliver_nodes = rspec.get_sliver_list(network)
              available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
  
 +            networkItem = QStandardItem(QString(network))
 +            msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
 +            rootItem.appendRow([networkItem, QStandardItem(QString(msg))])
 +
              already_in_nodes += sliver_nodes
  
 +            # Add default slice tags
 +            nodeItem = QStandardItem(QString("%s for %s" % (default_tags, network)))
 +            statusItem = QStandardItem(QString(""))
 +            networkItem.appendRow([nodeItem, statusItem])
 +            attrs = rspec.get_default_sliver_attributes(network)
 +            for (name, value) in attrs:
 +                    tagstring = QString("%s: %s" % (name, value))
 +                    tagItem = QStandardItem(tagstring)
 +                    status = QStandardItem(QString(tag_status['in']))
 +                    nodeItem.appendRow([tagItem, status])
 +
              for node in sliver_nodes:
 -                nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
 -                networkItem.appendChild(nodeItem)
 +                nodeItem = QStandardItem(QString(node))
 +                statusItem = QStandardItem(QString(node_status['in']))
 +                networkItem.appendRow([nodeItem, statusItem])
  
 -            for node in available_nodes:
 -                nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
 -                networkItem.appendChild(nodeItem)
 +                attrs = rspec.get_sliver_attributes(node, network)
 +                for (name, value) in attrs:
 +                    tagstring = QString("%s: %s" % (name, value))
 +                    tagItem = QStandardItem(tagstring)
 +                    statusItem = QStandardItem(QString(tag_status['in']))
 +                    nodeItem.appendRow([tagItem, statusItem])
  
 -            self.nodeModel.rootItem.appendChild(networkItem)
 +            for node in available_nodes:
 +                nodeItem = QStandardItem(QString(node))
 +                statusItem = QStandardItem(QString(node_status['out']))
 +                networkItem.appendRow([nodeItem, statusItem])
  
          self.filterModel.setSourceModel(self.nodeModel)
          self.filterModel.setFilterKeyColumn(-1)
          self.filterModel.setDynamicSortFilter(True)
  
 -        self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
 +        headers = QStringList() << "Hostname or Tag" << "Status"
 +        self.nodeModel.setHorizontalHeaderLabels(headers)
 +
 +        self.nodeView.setItemDelegateForColumn(0, self.nodeNameDelegate)
          self.nodeView.setModel(self.filterModel)
          self.nodeView.expandAll()
 -        self.nodeView.resizeColumnToContents(1)
 +        self.nodeView.resizeColumnToContents(0)
 +        self.nodeView.collapseAll()
  
      def updateSliceName(self):
          self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
@@@ -451,7 -457,7 +451,7 @@@ class MainScreen(SfaScreen)
          SfaScreen.__init__(self, parent)
  
          slice = SliceWidget(self)
-         self.init(slice, "Main Window", "OneLab Federation GUI")
+         self.init(slice, "Main Window", "OneLab SFA crawler")
  
      def rspecUpdated(self):
          self.mainwin.rspecWindow.updateView()