multivalued columns in summary tables
authorVictor Kirhenshtein <victor@netxms.org>
Fri, 1 Sep 2017 13:25:13 +0000 (16:25 +0300)
committerVictor Kirhenshtein <victor@netxms.org>
Fri, 1 Sep 2017 13:25:13 +0000 (16:25 +0300)
22 files changed:
include/nms_util.h
src/java/client/netxms-client/src/main/java/org/netxms/client/Table.java
src/java/client/netxms-client/src/main/java/org/netxms/client/TableRow.java
src/java/client/netxms-client/src/main/java/org/netxms/client/datacollection/DciSummaryTable.java
src/java/client/netxms-client/src/main/java/org/netxms/client/datacollection/DciSummaryTableColumn.java
src/java/client/netxms-client/src/test/java/org/netxms/client/DataCollectionTest.java
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/dialogs/EditDciSummaryTableColumnDlg.java
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/propertypages/SummaryTableColumns.java
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/views/SummaryTable.java
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/SummaryTableWidget.java
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/SummaryTableContentProvider.java [moved from src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/TableContentProvider.java with 58% similarity]
src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/SummaryTableItemComparator.java [moved from webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/TableItemComparator.java with 73% similarity]
src/libnetxms/table.cpp
src/server/core/dcst.cpp
src/server/core/dctarget.cpp
src/server/include/nms_objects.h
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/dialogs/EditDciSummaryTableColumnDlg.java
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/propertypages/SummaryTableColumns.java
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/views/SummaryTable.java
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/SummaryTableWidget.java
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/SummaryTableContentProvider.java [moved from webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/TableContentProvider.java with 58% similarity]
webui/webapp/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/SummaryTableItemComparator.java [moved from src/java/netxms-eclipse/DataCollection/src/org/netxms/ui/eclipse/datacollection/widgets/internal/TableItemComparator.java with 73% similarity]

index 4d94c53..ae54597 100644 (file)
@@ -1328,14 +1328,14 @@ public:
    void set(const TCHAR *value, int status, UINT32 objectId) { safe_free(m_value); m_value = _tcsdup_ex(value); m_status = status; m_objectId = objectId; }
    void setPreallocated(TCHAR *value, int status, UINT32 objectId) { safe_free(m_value); m_value = value; m_status = status; m_objectId = objectId; }
 
-   const TCHAR *getValue() { return m_value; }
+   const TCHAR *getValue() const { return m_value; }
    void setValue(const TCHAR *value) { safe_free(m_value); m_value = _tcsdup_ex(value); }
    void setPreallocatedValue(TCHAR *value) { free(m_value); m_value = value; }
 
-   int getStatus() { return m_status; }
+   int getStatus() const { return m_status; }
    void setStatus(int status) { m_status = status; }
 
-   int getObjectId() { return m_objectId; }
+   int getObjectId() const { return m_objectId; }
    void setObjectId(UINT32 id) { m_objectId = id; }
 };
 
@@ -1347,6 +1347,7 @@ class TableRow
 private:
    ObjectArray<TableCell> *m_cells;
    UINT32 m_objectId;
+   int m_baseRow;
 
 public:
    TableRow(int columnCount);
@@ -1364,13 +1365,16 @@ public:
 
    void setStatus(int index, int status) { TableCell *c = m_cells->get(index); if (c != NULL) c->setStatus(status); }
 
-   const TCHAR *getValue(int index) { TableCell *c = m_cells->get(index); return (c != NULL) ? c->getValue() : NULL; }
-   int getStatus(int index) { TableCell *c = m_cells->get(index); return (c != NULL) ? c->getStatus() : -1; }
+   const TCHAR *getValue(int index) const { const TableCell *c = m_cells->get(index); return (c != NULL) ? c->getValue() : NULL; }
+   int getStatus(int index) const { const TableCell *c = m_cells->get(index); return (c != NULL) ? c->getStatus() : -1; }
 
-   UINT32 getObjectId() { return m_objectId; }
+   UINT32 getObjectId() const { return m_objectId; }
    void setObjectId(UINT32 id) { m_objectId = id; }
 
-   UINT32 getCellObjectId(int index) { TableCell *c = m_cells->get(index); return (c != NULL) ? c->getObjectId() : 0; }
+   int getBaseRow() const { return m_baseRow; }
+   void setBaseRow(int baseRow) { m_baseRow = baseRow; }
+
+   UINT32 getCellObjectId(int index) const { const TableCell *c = m_cells->get(index); return (c != NULL) ? c->getObjectId() : 0; }
    void setCellObjectId(int index, UINT32 id) { TableCell *c = m_cells->get(index); if (c != NULL) c->setObjectId(id); }
 };
 
@@ -1460,12 +1464,17 @@ public:
    void buildInstanceString(int row, TCHAR *buffer, size_t bufLen);
    int findRowByInstance(const TCHAR *instance);
 
-   UINT32 getObjectId(int row) { TableRow *r = m_data->get(row); return (r != NULL) ? r->getObjectId() : 0; }
-   void setObjectId(int row, UINT32 id) { TableRow *r = m_data->get(row); if (r != NULL) r->setObjectId(id); }
+   UINT32 getObjectId(int row) const { const TableRow *r = m_data->get(row); return (r != NULL) ? r->getObjectId() : 0; }
+   void setObjectIdAt(int row, UINT32 id) { TableRow *r = m_data->get(row); if (r != NULL) r->setObjectId(id); }
+   void setObjectId(UINT32 id) { setObjectIdAt(getNumRows() - 1, id); }
 
    void setCellObjectIdAt(int row, int col, UINT32 objectId);
    void setCellObjectId(int col, UINT32 objectId) { setCellObjectIdAt(getNumRows() - 1, col, objectId); }
-   UINT32 getCellObjectId(int row, int col) { TableRow *r = m_data->get(row); return (r != NULL) ? r->getCellObjectId(col) : 0; }
+   UINT32 getCellObjectId(int row, int col) const { const TableRow *r = m_data->get(row); return (r != NULL) ? r->getCellObjectId(col) : 0; }
+
+   void setBaseRowAt(int row, int baseRow);
+   void setBaseRow(int baseRow) { setBaseRowAt(getNumRows() - 1, baseRow); }
+   int getBaseRow(int row) const { const TableRow *r = m_data->get(row); return (r != NULL) ? r->getBaseRow() : 0; }
 
    static Table *createFromXML(const char *xml);
    TCHAR *createXML();
index 2b9abee..bde2a8f 100644 (file)
@@ -80,6 +80,8 @@ public class Table
                        if (extendedFormat)
                        {
                           row.setObjectId(msg.getFieldAsInt64(varId++));
+            if (msg.isFieldPresent(varId))
+               row.setBaseRow(msg.getFieldAsInt32(varId));
                           varId += 9;
                        }
                        for(int j = 0; j < columnCount; j++)
@@ -111,6 +113,8 @@ public class Table
          if (extendedFormat)
          {
             row.setObjectId(msg.getFieldAsInt64(varId++));
+            if (msg.isFieldPresent(varId))
+               row.setBaseRow(msg.getFieldAsInt32(varId));
             varId += 9;
          }
          for(int j = 0; j < columns.size(); j++)
index d0bbfea..ca18d2b 100644 (file)
@@ -30,6 +30,7 @@ public class TableRow
 {
    private List<TableCell> cells;
    private long objectId;
+   private int baseRow;
    
    /**
     * Create new row
@@ -39,6 +40,7 @@ public class TableRow
    public TableRow(int rowCount)
    {
       objectId = 0;
+      baseRow = -1;
       cells = new ArrayList<TableCell>(rowCount);
       for(int i = 0; i < rowCount; i++)
          cells.add(new TableCell(""));
@@ -52,6 +54,7 @@ public class TableRow
    public TableRow(TableRow src)
    {
       objectId = src.objectId;
+      baseRow = src.baseRow;
       cells = new ArrayList<TableCell>(src.cells.size());
       for(int i = 0; i < src.cells.size(); i++)
          cells.add(new TableCell(src.get(i)));
@@ -87,7 +90,8 @@ public class TableRow
       if (extendedFormat)
       {
          msg.setFieldInt32(varId++, (int)objectId);
-         varId += 9;
+         msg.setFieldInt32(varId++, baseRow);
+         varId += 8;
       }
       for(TableCell c : cells)
       {
@@ -118,6 +122,22 @@ public class TableRow
       this.objectId = objectId;
    }
 
+   /**
+    * @return the baseRow
+    */
+   public int getBaseRow()
+   {
+      return baseRow;
+   }
+
+   /**
+    * @param baseRow the baseRow to set
+    */
+   public void setBaseRow(int baseRow)
+   {
+      this.baseRow = baseRow;
+   }
+
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
@@ -126,6 +146,7 @@ public class TableRow
       return "TableRow{" +
               "cells=" + cells +
               ", objectId=" + objectId +
+              ", baseRow=" + baseRow +
               '}';
    }
 }
index 58ad241..8282454 100644 (file)
@@ -78,9 +78,9 @@ public class DciSummaryTable
                                String[] data = parts[i].split("\\^\\#\\^");
                                if (data.length == 2)
                                {
-                                       columns.add(new DciSummaryTableColumn(data[0], data[1], 0));
+                                       columns.add(new DciSummaryTableColumn(data[0], data[1], 0, ";"));
                                }
-                               else if (data.length == 3)
+                               else if (data.length >= 3)
                                {
                                        int flags;
                                        try
@@ -91,7 +91,7 @@ public class DciSummaryTable
                                        {
                                                flags = 0;
                                        }
-                                       columns.add(new DciSummaryTableColumn(data[0], data[1], flags));
+                                       columns.add(new DciSummaryTableColumn(data[0], data[1], flags, (data.length > 3) ? data[3] : ";"));
                                }
                        }
                }
@@ -125,6 +125,8 @@ public class DciSummaryTable
                        sb.append(c.getDciName());
                        sb.append("^#^");
                        sb.append(c.getFlags());
+         sb.append("^#^");
+         sb.append(c.getSeparator());
                }
                msg.setField(NXCPCodes.VID_COLUMNS, sb.toString());
        }
index 6ed9dbe..a077deb 100644 (file)
@@ -26,21 +26,25 @@ import org.netxms.base.NXCPMessage;
 public class DciSummaryTableColumn
 {
        public static int REGEXP_MATCH = 0x0001;
+   public static int MULTIVALUED = 0x0002;
        
        private String name;
        private String dciName;
        private int flags;
+       private String separator;
 
        /**
         * @param name The column name
         * @param dciName The dci name
         * @param flags The flags
+        * @param separator Separator for multivalued columns
         */
-       public DciSummaryTableColumn(String name, String dciName, int flags)
+       public DciSummaryTableColumn(String name, String dciName, int flags, String separator)
        {
                this.name = name;
                this.dciName = dciName;
                this.flags = flags;
+               this.separator = separator;
        }
        
        /**
@@ -53,6 +57,7 @@ public class DciSummaryTableColumn
                name = src.name;
                dciName = src.dciName;
                flags = src.flags;
+               separator = src.separator;
        }
        
        /**
@@ -64,6 +69,7 @@ public class DciSummaryTableColumn
           msg.setField(baseId, name);
       msg.setField(baseId + 1, dciName);
       msg.setFieldInt32(baseId + 2, flags);
+      msg.setField(baseId + 3, separator);
        }
 
        /**
@@ -99,6 +105,26 @@ public class DciSummaryTableColumn
        }
        
        /**
+    * Get separator for multivalued column
+    * 
+    * @return separator for multivalued column
+    */
+   public String getSeparator()
+   {
+      return separator;
+   }
+
+   /**
+    * Set separator for multivalued column
+    * 
+    * @param separator new separator for multivalued column
+    */
+   public void setSeparator(String separator)
+   {
+      this.separator = ((separator != null) && !separator.isEmpty()) ? separator : ";";
+   }
+
+   /**
         * @return true if match
         */
        public boolean isRegexpMatch()
@@ -117,6 +143,25 @@ public class DciSummaryTableColumn
                        flags &= ~REGEXP_MATCH;
        }
 
+   /**
+    * @return true if column is multivalued
+    */
+   public boolean isMultivalued()
+   {
+      return (flags & MULTIVALUED) != 0;
+   }
+   
+   /**
+    * @param enable true to set column as multivalued
+    */
+   public void setMultivalued(boolean enable)
+   {
+      if (enable)
+         flags |= MULTIVALUED;
+      else
+         flags &= ~MULTIVALUED;
+   }
+
        /**
         * @return the flags
         */
@@ -125,12 +170,12 @@ public class DciSummaryTableColumn
                return flags;
        }
 
-       @Override
-       public String toString() {
-               return "DciSummaryTableColumn{" +
-                               "name='" + name + '\'' +
-                               ", dciName='" + dciName + '\'' +
-                               ", flags=" + flags +
-                               '}';
-       }
+   /* (non-Javadoc)
+    * @see java.lang.Object#toString()
+    */
+   @Override
+   public String toString()
+   {
+      return "DciSummaryTableColumn [name=" + name + ", dciName=" + dciName + ", flags=" + flags + ", separator=" + separator + "]";
+   }
 }
index ad68cd2..5a96f6b 100644 (file)
@@ -146,14 +146,14 @@ public class DataCollectionTest extends AbstractSessionTest
                final NXCSession session = connect();
                
                DciSummaryTable t = new DciSummaryTable("test", "Test Table");
-               t.getColumns().add(new DciSummaryTableColumn("Idle", "System.CPU.Idle", 0));
-               t.getColumns().add(new DciSummaryTableColumn("I/O Wait", "System.CPU.IOWait", 0));
+               t.getColumns().add(new DciSummaryTableColumn("Idle", "System.CPU.Idle", 0, ";"));
+               t.getColumns().add(new DciSummaryTableColumn("I/O Wait", "System.CPU.IOWait", 0, ";"));
                
                int id = session.modifyDciSummaryTable(t);
                System.out.println("Assigned ID: " + id);
                t.setId(id);
 
-               t.getColumns().add(new DciSummaryTableColumn("System", "^System\\.CPU\\.Sys.*", DciSummaryTableColumn.REGEXP_MATCH));
+               t.getColumns().add(new DciSummaryTableColumn("System", "^System\\.CPU\\.Sys.*", DciSummaryTableColumn.REGEXP_MATCH, ";"));
                session.modifyDciSummaryTable(t);
                
                List<DciSummaryTableDescriptor> list = session.listDciSummaryTables();
@@ -197,16 +197,16 @@ public class DataCollectionTest extends AbstractSessionTest
 
       // single instance
       List<DciSummaryTableColumn> columns = new ArrayList<DciSummaryTableColumn>();
-      columns.add(new DciSummaryTableColumn("Usage", "System.CPU.Usage", 0));
-      columns.add(new DciSummaryTableColumn("I/O Wait", "System.CPU.IOWait", 0));
+      columns.add(new DciSummaryTableColumn("Usage", "System.CPU.Usage", 0, ";"));
+      columns.add(new DciSummaryTableColumn("I/O Wait", "System.CPU.IOWait", 0, ";"));
 
       Table result = session.queryAdHocDciSummaryTable(2, columns, AggregationFunction.AVERAGE, new Date(System.currentTimeMillis() - 86400000), new Date(), false);
       printTable(result);
       
       // multi instance
       columns.clear();
-      columns.add(new DciSummaryTableColumn("Free %", "FileSystem\\.FreePerc\\(.*\\)", DciSummaryTableColumn.REGEXP_MATCH));
-      columns.add(new DciSummaryTableColumn("Free bytes", "FileSystem\\.Free\\(.*\\)", DciSummaryTableColumn.REGEXP_MATCH));
+      columns.add(new DciSummaryTableColumn("Free %", "FileSystem\\.FreePerc\\(.*\\)", DciSummaryTableColumn.REGEXP_MATCH, ";"));
+      columns.add(new DciSummaryTableColumn("Free bytes", "FileSystem\\.Free\\(.*\\)", DciSummaryTableColumn.REGEXP_MATCH, ";"));
 
       result = session.queryAdHocDciSummaryTable(2, columns, AggregationFunction.LAST, null, null, true);
       printTable(result);
index 0181cf1..1cc766b 100644 (file)
@@ -20,6 +20,8 @@ package org.netxms.ui.eclipse.datacollection.dialogs;
 
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
@@ -40,6 +42,8 @@ public class EditDciSummaryTableColumnDlg extends Dialog
        private LabeledText name;
        private LabeledText dciName;
        private Button checkRegexpMatch;
+   private Button checkMultivalued;
+   private LabeledText separator;
 
        /**
         * @param parentShell
@@ -88,6 +92,27 @@ public class EditDciSummaryTableColumnDlg extends Dialog
       checkRegexpMatch.setText(Messages.get().EditDciSummaryTableColumnDlg_UseRegExp);
       checkRegexpMatch.setSelection(column.isRegexpMatch());
       
+      checkMultivalued = new Button(dialogArea, SWT.CHECK);
+      checkMultivalued.setText("&Multivalued column");
+      checkMultivalued.setSelection(column.isMultivalued());
+      checkMultivalued.addSelectionListener(new SelectionAdapter() {
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            separator.setEnabled(checkMultivalued.getSelection());
+         }
+      });
+      
+      separator = new LabeledText(dialogArea, SWT.NONE);
+      separator.setLabel("&Separator for multiple values");
+      separator.getTextControl().setTextLimit(15);
+      gd = new GridData();
+      gd.horizontalAlignment = SWT.FILL;
+      gd.grabExcessHorizontalSpace = true;
+      separator.setLayoutData(gd);
+      separator.setText(column.getSeparator());
+      separator.setEnabled(column.isMultivalued());
+      
                return dialogArea;
        }
 
@@ -110,6 +135,8 @@ public class EditDciSummaryTableColumnDlg extends Dialog
                column.setName(name.getText());
                column.setDciName(dciName.getText());
                column.setRegexpMatch(checkRegexpMatch.getSelection());
+      column.setMultivalued(checkMultivalued.getSelection());
+      column.setSeparator(separator.getText().trim());
                super.okPressed();
        }
 }
index daedf54..f8b52be 100644 (file)
@@ -479,7 +479,7 @@ public class SummaryTableColumns extends PropertyPage
         */
        private void addColumn()
        {
-               DciSummaryTableColumn column = new DciSummaryTableColumn("", "", 0); //$NON-NLS-1$ //$NON-NLS-2$
+               DciSummaryTableColumn column = new DciSummaryTableColumn("", "", 0, ";"); //$NON-NLS-1$ //$NON-NLS-2$
                EditDciSummaryTableColumnDlg dlg = new EditDciSummaryTableColumnDlg(getShell(), column);
                if (dlg.open() == Window.OK)
                {
@@ -503,7 +503,7 @@ public class SummaryTableColumns extends PropertyPage
                        List<DciSummaryTableColumn> select = new ArrayList<DciSummaryTableColumn>();
                        for (DciValue item : selection)
                        {
-                       DciSummaryTableColumn column = new DciSummaryTableColumn(item.getDescription(), item.getName(), 0);
+                       DciSummaryTableColumn column = new DciSummaryTableColumn(item.getDescription(), item.getName(), 0, ";");
                        select.add(column);
                        columns.add(column);
                        }
index 364860a..f72348a 100644 (file)
@@ -141,7 +141,7 @@ public class SummaryTable extends ViewPart
        @Override
        public void setFocus()
        {
-               viewer.getViewer().getTable().setFocus();
+               viewer.getViewer().getControl().setFocus();
        }
        
        /**
index babb16c..16253f5 100644 (file)
@@ -40,7 +40,7 @@ import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Menu;
-import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TreeColumn;
 import org.eclipse.ui.IViewPart;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
@@ -53,8 +53,8 @@ import org.netxms.ui.eclipse.console.resources.GroupMarkers;
 import org.netxms.ui.eclipse.datacollection.Activator;
 import org.netxms.ui.eclipse.datacollection.Messages;
 import org.netxms.ui.eclipse.datacollection.views.helpers.ObjectSelectionProvider;
-import org.netxms.ui.eclipse.datacollection.widgets.internal.TableContentProvider;
-import org.netxms.ui.eclipse.datacollection.widgets.internal.TableItemComparator;
+import org.netxms.ui.eclipse.datacollection.widgets.internal.SummaryTableContentProvider;
+import org.netxms.ui.eclipse.datacollection.widgets.internal.SummaryTableItemComparator;
 import org.netxms.ui.eclipse.datacollection.widgets.internal.TableLabelProvider;
 import org.netxms.ui.eclipse.jobs.ConsoleJob;
 import org.netxms.ui.eclipse.objectbrowser.api.ObjectContextMenu;
@@ -64,7 +64,7 @@ import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 import org.netxms.ui.eclipse.tools.MessageDialogHelper;
 import org.netxms.ui.eclipse.tools.ViewRefreshController;
 import org.netxms.ui.eclipse.tools.WidgetHelper;
-import org.netxms.ui.eclipse.widgets.SortableTableViewer;
+import org.netxms.ui.eclipse.widgets.SortableTreeViewer;
 
 /**
  * DCI summary table display widget
@@ -74,7 +74,7 @@ public class SummaryTableWidget extends Composite
    private int tableId;
    private long baseObjectId;
    private IViewPart viewPart;
-   private SortableTableViewer viewer;
+   private SortableTreeViewer viewer;
    private TableLabelProvider labelProvider;
    private Action actionExportToCsv;
    private Action actionUseMultipliers;
@@ -82,7 +82,7 @@ public class SummaryTableWidget extends Composite
    private Action actionShowObjectDetails;
    private ViewRefreshController refreshController;
    private boolean useMultipliers = true;
-   private TableColumn currentColumn = null;
+   private TreeColumn currentColumn = null;
    private ObjectSelectionProvider objectSelectionProvider;
 
    /**
@@ -104,8 +104,8 @@ public class SummaryTableWidget extends Composite
       
       setLayout(new FillLayout());
 
-      viewer = new SortableTableViewer(this, SWT.FULL_SELECTION | SWT.MULTI);
-      viewer.setContentProvider(new TableContentProvider());
+      viewer = new SortableTreeViewer(this, SWT.FULL_SELECTION | SWT.MULTI);
+      viewer.setContentProvider(new SummaryTableContentProvider());
       labelProvider = new TableLabelProvider();
       labelProvider.setUseMultipliers(useMultipliers);
       viewer.setLabelProvider(labelProvider);
@@ -133,7 +133,7 @@ public class SummaryTableWidget extends Composite
          }
       });
       
-      viewer.getTable().addMouseListener(new MouseListener() {
+      viewer.getTree().addMouseListener(new MouseListener() {
          @Override
          public void mouseUp(MouseEvent e)
          {
@@ -302,29 +302,30 @@ public class SummaryTableWidget extends Composite
          viewer.createColumns(names, widths, 0, SWT.UP);
          final IDialogSettings settings = Activator.getDefault().getDialogSettings();
          final String key = viewPart.getViewSite().getId() + ".SummaryTable." + Integer.toString(tableId); //$NON-NLS-1$
-         WidgetHelper.restoreTableViewerSettings(viewer, settings, key);
+         WidgetHelper.restoreTreeViewerSettings(viewer, settings, key);
          String value = settings.get(key + ".useMultipliers"); //$NON-NLS-1$
          if (value != null)
             useMultipliers = Boolean.parseBoolean(value);
          labelProvider.setUseMultipliers(useMultipliers);
-         viewer.getTable().addDisposeListener(new DisposeListener() {
+         viewer.getControl().addDisposeListener(new DisposeListener() {
             @Override
             public void widgetDisposed(DisposeEvent e)
             {
-               WidgetHelper.saveTableViewerSettings(viewer, settings, key);
+               WidgetHelper.saveTreeViewerSettings(viewer, settings, key);
                settings.put(key + ".useMultipliers", useMultipliers); //$NON-NLS-1$
             }
          });
-         viewer.setComparator(new TableItemComparator(table.getColumnDataTypes()));
+         viewer.setComparator(new SummaryTableItemComparator(table));
       }
       labelProvider.setColumnDataTypes(table.getColumnDataTypes());
       viewer.setInput(table);
+      viewer.expandAll();
    }
 
    /**
     * @return the viewer
     */
-   public SortableTableViewer getViewer()
+   public SortableTreeViewer getViewer()
    {
       return viewer;
    }
@@ -20,7 +20,7 @@ package org.netxms.ui.eclipse.datacollection.widgets.internal;
 
 import java.util.ArrayList;
 import java.util.List;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.Viewer;
 import org.netxms.client.NXCSession;
 import org.netxms.client.Table;
@@ -33,7 +33,7 @@ import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 /**
  * Content provider for NetXMS table viewer
  */
-public class TableContentProvider implements IStructuredContentProvider
+public class SummaryTableContentProvider implements ITreeContentProvider
 {
        private Table table = null;
        
@@ -54,7 +54,7 @@ public class TableContentProvider implements IStructuredContentProvider
        }
 
        /* (non-Javadoc)
-        * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+        * @see org.eclipse.jface.viewers.ITreeContentProvider#getElements(java.lang.Object)
         */
        @Override
        public Object[] getElements(Object inputElement)
@@ -66,12 +66,63 @@ public class TableContentProvider implements IStructuredContentProvider
                TableRow[] rows = table.getAllRows();
                for(int i = 0; i < rows.length; i++)
                {
-                  list.add(new RowWrapper(rows[i]));              
+                  if (rows[i].getBaseRow() == -1)
+                  {
+                     list.add(new RowWrapper(rows[i], i, null));
+                  }
+                  else
+                  {
+                     RowWrapper p = findParent(list, rows[i].getBaseRow());
+                     if (p != null)
+                        p.add(new RowWrapper(rows[i], i, p));
+                  }
                }
                
                return list.toArray();
        }
 
+       /**
+        * Find parent row
+        * 
+        * @param list row list
+        * @param id parent ID
+        * @return parent row
+        */
+       private RowWrapper findParent(List<RowWrapper> list, int id)
+       {
+          for(RowWrapper r : list)
+             if (r.id == id)
+                return r;
+          return null;
+       }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
+    */
+   @Override
+   public Object[] getChildren(Object parentElement)
+   {
+      return ((RowWrapper)parentElement).children.toArray();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
+    */
+   @Override
+   public Object getParent(Object element)
+   {
+      return ((RowWrapper)element).parent;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
+    */
+   @Override
+   public boolean hasChildren(Object element)
+   {
+      return !((RowWrapper)element).children.isEmpty();
+   }
+       
        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IContentProvider#dispose()
         */
@@ -85,9 +136,20 @@ public class TableContentProvider implements IStructuredContentProvider
         */
        private class RowWrapper extends TableRow implements ObjectWrapper
        {
-      protected RowWrapper(TableRow src)
+          protected RowWrapper parent;
+          protected int id;
+          protected List<RowWrapper> children = new ArrayList<RowWrapper>(0);
+          
+      protected RowWrapper(TableRow src, int id, RowWrapper parent)
       {
          super(src);
+         this.id = id;
+         this.parent = parent;
+      }
+      
+      protected void add(RowWrapper r)
+      {
+         children.add(r);
       }
 
       @Override
@@ -21,23 +21,26 @@ package org.netxms.ui.eclipse.datacollection.widgets.internal;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.swt.SWT;
+import org.netxms.client.Table;
 import org.netxms.client.TableRow;
 import org.netxms.client.datacollection.DataCollectionItem;
-import org.netxms.ui.eclipse.widgets.SortableTableViewer;
+import org.netxms.ui.eclipse.widgets.SortableTreeViewer;
 
 /**
  * Comparator for table items
  */
-public class TableItemComparator extends ViewerComparator
+public class SummaryTableItemComparator extends ViewerComparator
 {
+   private Table table;
        private int[] formats;
        
        /**
         * 
         */
-       public TableItemComparator(int[] formats)
+       public SummaryTableItemComparator(Table table)
        {
-               this.formats = formats;
+          this.table = table;
+               this.formats = table.getColumnDataTypes();
        }
 
        /* (non-Javadoc)
@@ -46,13 +49,13 @@ public class TableItemComparator extends ViewerComparator
        @Override
        public int compare(Viewer viewer, Object e1, Object e2)
        {
-               final int column = (Integer)((SortableTableViewer) viewer).getTable().getSortColumn().getData("ID"); //$NON-NLS-1$
+               final int column = (Integer)((SortableTreeViewer) viewer).getTree().getSortColumn().getData("ID"); //$NON-NLS-1$
                final int format = (column < formats.length) ? formats[column] : DataCollectionItem.DT_STRING;
-               
-               final String value1 = ((TableRow)e1).get(column).getValue();
-               final String value2 = ((TableRow)e2).get(column).getValue();
-               
-               int result;
+
+      final String value1 = getCellValue((TableRow)e1, column);
+      final String value2 = getCellValue((TableRow)e2, column);
+      
+      int result;
                switch(format)
                {
                        case DataCollectionItem.DT_INT:
@@ -70,8 +73,15 @@ public class TableItemComparator extends ViewerComparator
                                result = value1.compareToIgnoreCase(value2);
                                break;
                }
-               
-               return (((SortableTableViewer)viewer).getTable().getSortDirection() == SWT.UP) ? result : -result;
+               return (((SortableTreeViewer)viewer).getTree().getSortDirection() == SWT.UP) ? result : -result;
+       }
+       
+       private String getCellValue(TableRow r, int column)
+       {
+      String value = r.get(column).getValue();
+      if (((value == null) || value.isEmpty()) && (r.getBaseRow() != -1))
+        return table.getCellValue(r.getBaseRow(), column);
+      return value;
        }
        
        /**
index 02cada8..3aa0312 100644 (file)
@@ -37,6 +37,7 @@ TableRow::TableRow(int columnCount)
    for(int i = 0; i < columnCount; i++)
       m_cells->add(new TableCell());
    m_objectId = 0;
+   m_baseRow = -1;
 }
 
 /**
@@ -48,6 +49,7 @@ TableRow::TableRow(TableRow *src)
    for(int i = 0; i < src->m_cells->size(); i++)
       m_cells->add(new TableCell(src->m_cells->get(i)));
    m_objectId = src->m_objectId;
+   m_baseRow = src->m_baseRow;
 }
 
 /**
@@ -200,7 +202,8 @@ static void StartElement(void *userData, const char *name, const char **attrs)
       if (ps->state == XML_STATE_DATA)
       {
          ps->table->addRow();
-         ps->table->setObjectId(ps->table->getNumRows() - 1, XMLGetAttrInt(attrs, "objectId", DEFAULT_OBJECT_ID));
+         ps->table->setObjectId(XMLGetAttrInt(attrs, "objectId", DEFAULT_OBJECT_ID));
+         ps->table->setBaseRow(XMLGetAttrInt(attrs, "baseRow", -1));
          ps->column = 0;
                   ps->state = XML_STATE_ROW;
       }
@@ -364,10 +367,21 @@ TCHAR *Table::createXML()
    for(i = 0; i < m_data->size(); i++)
    {
       UINT32 objectId = m_data->get(i)->getObjectId();
+      int baseRow = m_data->get(i)->getBaseRow();
       if (objectId != DEFAULT_OBJECT_ID)
-         xml.appendFormattedString(_T("<tr objectId=\"%u\">\r\n"), objectId);
+      {
+         if (baseRow != -1)
+            xml.appendFormattedString(_T("<tr objectId=\"%u\" baseRow=\"%d\">\r\n"), objectId, baseRow);
+         else
+            xml.appendFormattedString(_T("<tr objectId=\"%u\">\r\n"), objectId);
+      }
       else
-         xml.append(_T("<tr>\r\n"));
+      {
+         if (baseRow != -1)
+            xml.appendFormattedString(_T("<tr baseRow=\"%d\">\r\n"), baseRow);
+         else
+            xml.append(_T("<tr>\r\n"));
+      }
       for(int j = 0; j < m_columns->size(); j++)
       {
          int status = m_data->get(i)->getStatus(j);
@@ -458,6 +472,8 @@ void Table::createFromMessage(NXCPMessage *msg)
       if (m_extendedFormat)
       {
          row->setObjectId(msg->getFieldAsUInt32(dwId++));
+         if (msg->isFieldExist(dwId))
+            row->setBaseRow(msg->getFieldAsInt32(dwId));
          dwId += 9;
       }
       for(int j = 0; j < columns; j++)
@@ -518,7 +534,8 @@ int Table::fillMessage(NXCPMessage &msg, int offset, int rowLimit)
       if (m_extendedFormat)
       {
                        msg.setField(id++, r->getObjectId());
-         id += 9;
+         msg.setField(id++, r->getBaseRow());
+         id += 8;
       }
                for(int col = 0; col < m_columns->size(); col++)
                {
@@ -763,6 +780,18 @@ void Table::setCellObjectIdAt(int row, int col, UINT32 objectId)
 }
 
 /**
+ * Set base row for given row
+ */
+void Table::setBaseRowAt(int row, int baseRow)
+{
+   TableRow *r = m_data->get(row);
+   if (r != NULL)
+   {
+      r->setBaseRow(baseRow);
+   }
+}
+
+/**
  * Add all rows from another table.
  * Identical table format assumed.
  *
index 571ffda..917fa79 100644 (file)
@@ -109,6 +109,10 @@ SummaryTableColumn::SummaryTableColumn(NXCPMessage *msg, UINT32 baseId)
    msg->getFieldAsString(baseId, m_name, MAX_DB_STRING);
    msg->getFieldAsString(baseId + 1, m_dciName, MAX_PARAM_NAME);
    m_flags = msg->getFieldAsUInt32(baseId + 2);
+   if (msg->isFieldExist(baseId + 3))
+      msg->getFieldAsString(baseId + 3, m_separator, 16);
+   else
+      _tcscpy(m_separator, _T(";"));
 }
 
 /**
@@ -124,7 +128,9 @@ void SummaryTableColumn::createExportRecord(String &xml, int id)
    xml.appendPreallocated(EscapeStringForXML(m_dciName, -1));
    xml.append(_T("</dci>\n\t\t\t\t\t<flags>"));
    xml.append(m_flags);
-   xml.append(_T("</flags>\n\t\t\t\t</column>\n"));
+   xml.append(_T("</flags>\n\t\t\t\t\t<separator>\n"));
+   xml.append(m_separator);
+   xml.append(_T("</separator>\n\t\t\t\t</column>\n"));
 }
 
 /**
@@ -142,6 +148,17 @@ SummaryTableColumn::SummaryTableColumn(TCHAR *configStr)
       {
          *opt = 0;
          opt += 3;
+         TCHAR *sep = _tcsstr(opt, _T("^#^"));
+         if (sep != NULL)
+         {
+            *sep = 0;
+            sep += 3;
+            nx_strncpy(m_separator, sep, 16);
+         }
+         else
+         {
+            _tcscpy(m_separator, _T(";"));
+         }
          m_flags = _tcstoul(opt, NULL, 10);
       }
       else
@@ -212,7 +229,7 @@ SummaryTable::SummaryTable(INT32 id, DB_RESULT hResult)
          m_filter = NXSLCompileAndCreateVM(m_filterSource, errorText, 1024, new NXSL_ServerEnv);
          if (m_filter == NULL)
          {
-            DbgPrintf(4, _T("Error compiling filter script for DCI summary table: %s"), errorText);
+            nxlog_debug(4, _T("Error compiling filter script for DCI summary table: %s"), errorText);
          }
       }
       else
@@ -251,7 +268,7 @@ SummaryTable::SummaryTable(INT32 id, DB_RESULT hResult)
  */
 SummaryTable *SummaryTable::loadFromDB(INT32 id, UINT32 *rcc)
 {
-   DbgPrintf(4, _T("Loading configuration for DCI summary table %d"), id);
+   nxlog_debug(4, _T("Loading configuration for DCI summary table %d"), id);
    SummaryTable *table = NULL;
    DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
    DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT title,flags,guid,menu_path,node_filter,columns FROM dci_summary_tables WHERE id=?"));
@@ -283,7 +300,7 @@ SummaryTable *SummaryTable::loadFromDB(INT32 id, UINT32 *rcc)
       *rcc = RCC_DB_FAILURE;
    }
    DBConnectionPoolReleaseConnection(hdb);
-   DbgPrintf(4, _T("SummaryTable::loadFromDB(%d): object=%p, rcc=%d"), id, table, (int)*rcc);
+   nxlog_debug(4, _T("SummaryTable::loadFromDB(%d): object=%p, rcc=%d"), id, table, (int)*rcc);
    return table;
 }
 
@@ -294,7 +311,7 @@ SummaryTable::~SummaryTable()
 {
    delete m_columns;
    delete m_filter;
-   safe_free(m_filterSource);
+   free(m_filterSource);
 }
 
 /**
@@ -308,7 +325,7 @@ bool SummaryTable::filter(DataCollectionTarget *object)
    bool result = true;
    m_filter->setGlobalVariable(_T("$object"), object->createNXSLObject());
    if (object->getObjectClass() == OBJECT_NODE)
-      m_filter->setGlobalVariable(_T("$node"), new NXSL_Value(new NXSL_Object(&g_nxslNodeClass, object)));
+      m_filter->setGlobalVariable(_T("$node"), object->createNXSLObject());
    if (m_filter->run())
    {
       NXSL_Value *value = m_filter->getResult();
@@ -319,7 +336,7 @@ bool SummaryTable::filter(DataCollectionTarget *object)
    }
    else
    {
-      DbgPrintf(4, _T("Error executing filter script for DCI summary table: %s"), m_filter->getErrorText());
+      nxlog_debug(4, _T("Error executing filter script for DCI summary table: %s"), m_filter->getErrorText());
    }
    return result;
 }
@@ -458,6 +475,8 @@ static TCHAR *BuildColumnList(ConfigEntry *root)
       s.append(c->getSubEntryValue(_T("dci")));
       s.append(_T("^#^"));
       s.append(c->getSubEntryValueAsUInt(_T("flags")));
+      s.append(_T("^#^"));
+      s.append(c->getSubEntryValue(_T("separator")));
    }
    delete columns;
    return _tcsdup((const TCHAR *)s);
index 6406d3f..2c06cae 100644 (file)
@@ -873,7 +873,7 @@ void DataCollectionTarget::getDciValuesSummary(SummaryTable *tableDefinition, Ta
                   tableData->addRow();
                   tableData->set(0, m_name);
                   tableData->set(1, instance);
-                  tableData->setObjectId(tableData->getNumRows() - 1, m_id);
+                  tableData->setObjectId(m_id);
                }
             }
             else
@@ -882,7 +882,7 @@ void DataCollectionTarget::getDciValuesSummary(SummaryTable *tableDefinition, Ta
                {
                   tableData->addRow();
                   tableData->set(0, m_name);
-                  tableData->setObjectId(tableData->getNumRows() - 1, m_id);
+                  tableData->setObjectId(m_id);
                   rowAdded = true;
                }
                row = tableData->getNumRows() - 1;
@@ -892,7 +892,27 @@ void DataCollectionTarget::getDciValuesSummary(SummaryTable *tableDefinition, Ta
             tableData->getColumnDefinitions()->get(i + offset)->setDataType(((DCItem *)object)->getDataType());
             if (tableDefinition->getAggregationFunction() == F_LAST)
             {
-               tableData->setAt(row, i + offset, ((DCItem *)object)->getLastValue());
+               if (tc->m_flags & COLUMN_DEFINITION_MULTIVALUED)
+               {
+                  StringList *values = String(((DCItem *)object)->getLastValue()).split(tc->m_separator);
+                  tableData->setAt(row, i + offset, values->get(0));
+                  for(int r = 1; r < values->size(); r++)
+                  {
+                     if (row + r >= tableData->getNumRows())
+                     {
+                        tableData->addRow();
+                        tableData->setObjectId(m_id);
+                        tableData->setBaseRow(row);
+                     }
+                     tableData->setAt(row + r, i + offset, values->get(r));
+                     tableData->setStatusAt(row + r, i + offset, ((DCItem *)object)->getThresholdSeverity());
+                     tableData->setCellObjectIdAt(row + r, i + offset, object->getId());
+                  }
+               }
+               else
+               {
+                  tableData->setAt(row, i + offset, ((DCItem *)object)->getLastValue());
+               }
             }
             else
             {
index 2e8ec48..0c7c5c8 100644 (file)
@@ -394,6 +394,7 @@ public:
  * Summary table column flags
  */
 #define COLUMN_DEFINITION_REGEXP_MATCH    0x0001
+#define COLUMN_DEFINITION_MULTIVALUED     0x0002
 
 /**
  * Column definition for DCI summary table
@@ -404,6 +405,7 @@ public:
    TCHAR m_name[MAX_DB_STRING];
    TCHAR m_dciName[MAX_PARAM_NAME];
    UINT32 m_flags;
+   TCHAR m_separator[16];
 
    SummaryTableColumn(NXCPMessage *msg, UINT32 baseId);
    SummaryTableColumn(TCHAR *configStr);
index 0181cf1..1cc766b 100644 (file)
@@ -20,6 +20,8 @@ package org.netxms.ui.eclipse.datacollection.dialogs;
 
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
@@ -40,6 +42,8 @@ public class EditDciSummaryTableColumnDlg extends Dialog
        private LabeledText name;
        private LabeledText dciName;
        private Button checkRegexpMatch;
+   private Button checkMultivalued;
+   private LabeledText separator;
 
        /**
         * @param parentShell
@@ -88,6 +92,27 @@ public class EditDciSummaryTableColumnDlg extends Dialog
       checkRegexpMatch.setText(Messages.get().EditDciSummaryTableColumnDlg_UseRegExp);
       checkRegexpMatch.setSelection(column.isRegexpMatch());
       
+      checkMultivalued = new Button(dialogArea, SWT.CHECK);
+      checkMultivalued.setText("&Multivalued column");
+      checkMultivalued.setSelection(column.isMultivalued());
+      checkMultivalued.addSelectionListener(new SelectionAdapter() {
+         @Override
+         public void widgetSelected(SelectionEvent e)
+         {
+            separator.setEnabled(checkMultivalued.getSelection());
+         }
+      });
+      
+      separator = new LabeledText(dialogArea, SWT.NONE);
+      separator.setLabel("&Separator for multiple values");
+      separator.getTextControl().setTextLimit(15);
+      gd = new GridData();
+      gd.horizontalAlignment = SWT.FILL;
+      gd.grabExcessHorizontalSpace = true;
+      separator.setLayoutData(gd);
+      separator.setText(column.getSeparator());
+      separator.setEnabled(column.isMultivalued());
+      
                return dialogArea;
        }
 
@@ -110,6 +135,8 @@ public class EditDciSummaryTableColumnDlg extends Dialog
                column.setName(name.getText());
                column.setDciName(dciName.getText());
                column.setRegexpMatch(checkRegexpMatch.getSelection());
+      column.setMultivalued(checkMultivalued.getSelection());
+      column.setSeparator(separator.getText().trim());
                super.okPressed();
        }
 }
index daedf54..f8b52be 100644 (file)
@@ -479,7 +479,7 @@ public class SummaryTableColumns extends PropertyPage
         */
        private void addColumn()
        {
-               DciSummaryTableColumn column = new DciSummaryTableColumn("", "", 0); //$NON-NLS-1$ //$NON-NLS-2$
+               DciSummaryTableColumn column = new DciSummaryTableColumn("", "", 0, ";"); //$NON-NLS-1$ //$NON-NLS-2$
                EditDciSummaryTableColumnDlg dlg = new EditDciSummaryTableColumnDlg(getShell(), column);
                if (dlg.open() == Window.OK)
                {
@@ -503,7 +503,7 @@ public class SummaryTableColumns extends PropertyPage
                        List<DciSummaryTableColumn> select = new ArrayList<DciSummaryTableColumn>();
                        for (DciValue item : selection)
                        {
-                       DciSummaryTableColumn column = new DciSummaryTableColumn(item.getDescription(), item.getName(), 0);
+                       DciSummaryTableColumn column = new DciSummaryTableColumn(item.getDescription(), item.getName(), 0, ";");
                        select.add(column);
                        columns.add(column);
                        }
index 364860a..f72348a 100644 (file)
@@ -141,7 +141,7 @@ public class SummaryTable extends ViewPart
        @Override
        public void setFocus()
        {
-               viewer.getViewer().getTable().setFocus();
+               viewer.getViewer().getControl().setFocus();
        }
        
        /**
index babb16c..16253f5 100644 (file)
@@ -40,7 +40,7 @@ import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Menu;
-import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TreeColumn;
 import org.eclipse.ui.IViewPart;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
@@ -53,8 +53,8 @@ import org.netxms.ui.eclipse.console.resources.GroupMarkers;
 import org.netxms.ui.eclipse.datacollection.Activator;
 import org.netxms.ui.eclipse.datacollection.Messages;
 import org.netxms.ui.eclipse.datacollection.views.helpers.ObjectSelectionProvider;
-import org.netxms.ui.eclipse.datacollection.widgets.internal.TableContentProvider;
-import org.netxms.ui.eclipse.datacollection.widgets.internal.TableItemComparator;
+import org.netxms.ui.eclipse.datacollection.widgets.internal.SummaryTableContentProvider;
+import org.netxms.ui.eclipse.datacollection.widgets.internal.SummaryTableItemComparator;
 import org.netxms.ui.eclipse.datacollection.widgets.internal.TableLabelProvider;
 import org.netxms.ui.eclipse.jobs.ConsoleJob;
 import org.netxms.ui.eclipse.objectbrowser.api.ObjectContextMenu;
@@ -64,7 +64,7 @@ import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 import org.netxms.ui.eclipse.tools.MessageDialogHelper;
 import org.netxms.ui.eclipse.tools.ViewRefreshController;
 import org.netxms.ui.eclipse.tools.WidgetHelper;
-import org.netxms.ui.eclipse.widgets.SortableTableViewer;
+import org.netxms.ui.eclipse.widgets.SortableTreeViewer;
 
 /**
  * DCI summary table display widget
@@ -74,7 +74,7 @@ public class SummaryTableWidget extends Composite
    private int tableId;
    private long baseObjectId;
    private IViewPart viewPart;
-   private SortableTableViewer viewer;
+   private SortableTreeViewer viewer;
    private TableLabelProvider labelProvider;
    private Action actionExportToCsv;
    private Action actionUseMultipliers;
@@ -82,7 +82,7 @@ public class SummaryTableWidget extends Composite
    private Action actionShowObjectDetails;
    private ViewRefreshController refreshController;
    private boolean useMultipliers = true;
-   private TableColumn currentColumn = null;
+   private TreeColumn currentColumn = null;
    private ObjectSelectionProvider objectSelectionProvider;
 
    /**
@@ -104,8 +104,8 @@ public class SummaryTableWidget extends Composite
       
       setLayout(new FillLayout());
 
-      viewer = new SortableTableViewer(this, SWT.FULL_SELECTION | SWT.MULTI);
-      viewer.setContentProvider(new TableContentProvider());
+      viewer = new SortableTreeViewer(this, SWT.FULL_SELECTION | SWT.MULTI);
+      viewer.setContentProvider(new SummaryTableContentProvider());
       labelProvider = new TableLabelProvider();
       labelProvider.setUseMultipliers(useMultipliers);
       viewer.setLabelProvider(labelProvider);
@@ -133,7 +133,7 @@ public class SummaryTableWidget extends Composite
          }
       });
       
-      viewer.getTable().addMouseListener(new MouseListener() {
+      viewer.getTree().addMouseListener(new MouseListener() {
          @Override
          public void mouseUp(MouseEvent e)
          {
@@ -302,29 +302,30 @@ public class SummaryTableWidget extends Composite
          viewer.createColumns(names, widths, 0, SWT.UP);
          final IDialogSettings settings = Activator.getDefault().getDialogSettings();
          final String key = viewPart.getViewSite().getId() + ".SummaryTable." + Integer.toString(tableId); //$NON-NLS-1$
-         WidgetHelper.restoreTableViewerSettings(viewer, settings, key);
+         WidgetHelper.restoreTreeViewerSettings(viewer, settings, key);
          String value = settings.get(key + ".useMultipliers"); //$NON-NLS-1$
          if (value != null)
             useMultipliers = Boolean.parseBoolean(value);
          labelProvider.setUseMultipliers(useMultipliers);
-         viewer.getTable().addDisposeListener(new DisposeListener() {
+         viewer.getControl().addDisposeListener(new DisposeListener() {
             @Override
             public void widgetDisposed(DisposeEvent e)
             {
-               WidgetHelper.saveTableViewerSettings(viewer, settings, key);
+               WidgetHelper.saveTreeViewerSettings(viewer, settings, key);
                settings.put(key + ".useMultipliers", useMultipliers); //$NON-NLS-1$
             }
          });
-         viewer.setComparator(new TableItemComparator(table.getColumnDataTypes()));
+         viewer.setComparator(new SummaryTableItemComparator(table));
       }
       labelProvider.setColumnDataTypes(table.getColumnDataTypes());
       viewer.setInput(table);
+      viewer.expandAll();
    }
 
    /**
     * @return the viewer
     */
-   public SortableTableViewer getViewer()
+   public SortableTreeViewer getViewer()
    {
       return viewer;
    }
@@ -20,7 +20,7 @@ package org.netxms.ui.eclipse.datacollection.widgets.internal;
 
 import java.util.ArrayList;
 import java.util.List;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.Viewer;
 import org.netxms.client.NXCSession;
 import org.netxms.client.Table;
@@ -33,7 +33,7 @@ import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 /**
  * Content provider for NetXMS table viewer
  */
-public class TableContentProvider implements IStructuredContentProvider
+public class SummaryTableContentProvider implements ITreeContentProvider
 {
        private Table table = null;
        
@@ -54,7 +54,7 @@ public class TableContentProvider implements IStructuredContentProvider
        }
 
        /* (non-Javadoc)
-        * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
+        * @see org.eclipse.jface.viewers.ITreeContentProvider#getElements(java.lang.Object)
         */
        @Override
        public Object[] getElements(Object inputElement)
@@ -66,12 +66,63 @@ public class TableContentProvider implements IStructuredContentProvider
                TableRow[] rows = table.getAllRows();
                for(int i = 0; i < rows.length; i++)
                {
-                  list.add(new RowWrapper(rows[i]));              
+                  if (rows[i].getBaseRow() == -1)
+                  {
+                     list.add(new RowWrapper(rows[i], i, null));
+                  }
+                  else
+                  {
+                     RowWrapper p = findParent(list, rows[i].getBaseRow());
+                     if (p != null)
+                        p.add(new RowWrapper(rows[i], i, p));
+                  }
                }
                
                return list.toArray();
        }
 
+       /**
+        * Find parent row
+        * 
+        * @param list row list
+        * @param id parent ID
+        * @return parent row
+        */
+       private RowWrapper findParent(List<RowWrapper> list, int id)
+       {
+          for(RowWrapper r : list)
+             if (r.id == id)
+                return r;
+          return null;
+       }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
+    */
+   @Override
+   public Object[] getChildren(Object parentElement)
+   {
+      return ((RowWrapper)parentElement).children.toArray();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
+    */
+   @Override
+   public Object getParent(Object element)
+   {
+      return ((RowWrapper)element).parent;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
+    */
+   @Override
+   public boolean hasChildren(Object element)
+   {
+      return !((RowWrapper)element).children.isEmpty();
+   }
+       
        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IContentProvider#dispose()
         */
@@ -85,9 +136,20 @@ public class TableContentProvider implements IStructuredContentProvider
         */
        private class RowWrapper extends TableRow implements ObjectWrapper
        {
-      protected RowWrapper(TableRow src)
+          protected RowWrapper parent;
+          protected int id;
+          protected List<RowWrapper> children = new ArrayList<RowWrapper>(0);
+          
+      protected RowWrapper(TableRow src, int id, RowWrapper parent)
       {
          super(src);
+         this.id = id;
+         this.parent = parent;
+      }
+      
+      protected void add(RowWrapper r)
+      {
+         children.add(r);
       }
 
       @Override
@@ -21,23 +21,26 @@ package org.netxms.ui.eclipse.datacollection.widgets.internal;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.swt.SWT;
+import org.netxms.client.Table;
 import org.netxms.client.TableRow;
 import org.netxms.client.datacollection.DataCollectionItem;
-import org.netxms.ui.eclipse.widgets.SortableTableViewer;
+import org.netxms.ui.eclipse.widgets.SortableTreeViewer;
 
 /**
  * Comparator for table items
  */
-public class TableItemComparator extends ViewerComparator
+public class SummaryTableItemComparator extends ViewerComparator
 {
+   private Table table;
        private int[] formats;
        
        /**
         * 
         */
-       public TableItemComparator(int[] formats)
+       public SummaryTableItemComparator(Table table)
        {
-               this.formats = formats;
+          this.table = table;
+               this.formats = table.getColumnDataTypes();
        }
 
        /* (non-Javadoc)
@@ -46,13 +49,13 @@ public class TableItemComparator extends ViewerComparator
        @Override
        public int compare(Viewer viewer, Object e1, Object e2)
        {
-               final int column = (Integer)((SortableTableViewer) viewer).getTable().getSortColumn().getData("ID"); //$NON-NLS-1$
+               final int column = (Integer)((SortableTreeViewer) viewer).getTree().getSortColumn().getData("ID"); //$NON-NLS-1$
                final int format = (column < formats.length) ? formats[column] : DataCollectionItem.DT_STRING;
-               
-               final String value1 = ((TableRow)e1).get(column).getValue();
-               final String value2 = ((TableRow)e2).get(column).getValue();
-               
-               int result;
+
+      final String value1 = getCellValue((TableRow)e1, column);
+      final String value2 = getCellValue((TableRow)e2, column);
+      
+      int result;
                switch(format)
                {
                        case DataCollectionItem.DT_INT:
@@ -70,8 +73,15 @@ public class TableItemComparator extends ViewerComparator
                                result = value1.compareToIgnoreCase(value2);
                                break;
                }
-               
-               return (((SortableTableViewer)viewer).getTable().getSortDirection() == SWT.UP) ? result : -result;
+               return (((SortableTreeViewer)viewer).getTree().getSortDirection() == SWT.UP) ? result : -result;
+       }
+       
+       private String getCellValue(TableRow r, int column)
+       {
+      String value = r.get(column).getValue();
+      if (((value == null) || value.isEmpty()) && (r.getBaseRow() != -1))
+        return table.getCellValue(r.getBaseRow(), column);
+      return value;
        }
        
        /**