Merge remote-tracking branch 'upstream/cvt-graph-view' into 957-api

# Conflicts:
#	Core/src/org/sleuthkit/autopsy/communications/CommunicationsGraph.java
This commit is contained in:
millmanorama 2018-03-08 16:09:33 +01:00
commit aa462abff3
6 changed files with 104 additions and 70 deletions

View File

@ -4,7 +4,7 @@
<AuxValues> <AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
@ -92,4 +92,4 @@
</Properties> </Properties>
</Component> </Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -19,8 +19,14 @@
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.awt.Dimension;
import java.awt.Font;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JTabbedPane;
import javax.swing.LayoutStyle;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ProxyLookup; import org.openide.util.lookup.ProxyLookup;
@ -83,48 +89,46 @@ public final class CVTTopComponent extends TopComponent {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
browseVisualizeTabPane = new javax.swing.JTabbedPane(); browseVisualizeTabPane = new JTabbedPane();
accountsBrowser = new org.sleuthkit.autopsy.communications.AccountsBrowser(); accountsBrowser = new AccountsBrowser();
vizPanel = new org.sleuthkit.autopsy.communications.VisualizationPanel(); vizPanel = new VisualizationPanel();
filtersPane = new org.sleuthkit.autopsy.communications.FiltersPanel(); filtersPane = new FiltersPanel();
browseVisualizeTabPane.setFont(new java.awt.Font("Tahoma", 0, 18)); // NOI18N browseVisualizeTabPane.setFont(new Font("Tahoma", 0, 18)); // NOI18N
browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
browseVisualizeTabPane.addTab(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N
filtersPane.setMinimumSize(new java.awt.Dimension(256, 495)); filtersPane.setMinimumSize(new Dimension(256, 495));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); GroupLayout layout = new GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6) .addGap(6, 6, 6)
.addComponent(filtersPane, javax.swing.GroupLayout.PREFERRED_SIZE, 265, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(filtersPane, GroupLayout.PREFERRED_SIZE, 265, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseVisualizeTabPane, javax.swing.GroupLayout.DEFAULT_SIZE, 786, Short.MAX_VALUE) .addComponent(browseVisualizeTabPane, GroupLayout.PREFERRED_SIZE, 786, Short.MAX_VALUE)
.addContainerGap()) .addContainerGap())
); );
layout.setVerticalGroup( layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6) .addGap(6, 6, 6)
.addComponent(filtersPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(filtersPane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(5, 5, 5)) .addGap(5, 5, 5))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(browseVisualizeTabPane) .addComponent(browseVisualizeTabPane)
.addContainerGap()) .addContainerGap())
); );
browseVisualizeTabPane.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N browseVisualizeTabPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.communications.AccountsBrowser accountsBrowser; private AccountsBrowser accountsBrowser;
private javax.swing.JTabbedPane browseVisualizeTabPane; private JTabbedPane browseVisualizeTabPane;
private org.sleuthkit.autopsy.communications.FiltersPanel filtersPane; private FiltersPanel filtersPane;
private org.sleuthkit.autopsy.communications.VisualizationPanel vizPanel; private VisualizationPanel vizPanel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
@Override @Override
@ -142,7 +146,7 @@ public final class CVTTopComponent extends TopComponent {
* *
* Re-applying the filters means we will lose the selection... * Re-applying the filters means we will lose the selection...
*/ */
filtersPane.updateAndApplyFilters(); filtersPane.updateAndApplyFilters(true);
} }
@Override @Override
@ -156,14 +160,24 @@ public final class CVTTopComponent extends TopComponent {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* Extension of ProxyLookup that exposes the ability to change the Lookups
* delegated to.
*
*/
final private static class ProxyLookupImpl extends ProxyLookup { final private static class ProxyLookupImpl extends ProxyLookup {
ProxyLookupImpl(Lookup... l) { ProxyLookupImpl(Lookup... lookups) {
super(l); super(lookups);
} }
protected void changeLookups(Lookup... l) { /**
setLookups(l); * Set the Lookups delegated to by this lookup.
*
* @param lookups The new Lookups to delegate to.
*/
protected void changeLookups(Lookup... lookups) {
setLookups(lookups);
} }
} }
} }

View File

@ -193,19 +193,22 @@ final class CommunicationsGraph extends mxGraph {
final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value; final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID()); scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
scopes.put("size", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5)); scopes.put("relationships", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5));
scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/" scopes.put("iconFileName", CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/"
+ Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType()))); + Utils.getIconFileName(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey)); scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey));
scopes.put("MARKER_PIN_URL", MARKER_PIN_URL); scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell)); scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell));
scopes.put("LOCK_URL", LOCK_URL); scopes.put("LOCK_URL", LOCK_URL);
scopes.put("device_id", adiKey.getAccountDeviceInstance().getDeviceId());
labelMustache.execute(stringWriter, scopes); labelMustache.execute(stringWriter, scopes);
return stringWriter.toString(); return stringWriter.toString();
} else { } else {
return ((mxICell) cell).getId(); final mxICell edge = (mxICell) cell;
final long count = (long) edge.getValue();
return"<html>"+ edge.getId() + "<br>" + count + (count == 1 ? " relationship" : " relationships")+"</html>";
} }
} }
@ -222,27 +225,20 @@ final class CommunicationsGraph extends mxGraph {
private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) { private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) {
final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance(); final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance();
final String name =// accountDeviceInstance.getDeviceId() + ":" + final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
accountDeviceInstance.getAccount().getTypeSpecificID();
final mxCell vertex = nodeMap.computeIfAbsent(name, vertexName -> { final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10; double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10;
mxCell newVertex = (mxCell) insertVertex( mxCell newVertex = (mxCell) insertVertex(
getDefaultParent(), getDefaultParent(),
vertexName, accountDeviceInstanceKey, name, accountDeviceInstanceKey,
Math.random() * 400, Math.random() * 400,
Math.random() * 400, Math.random() * 400,
size, size,
size); size);
return newVertex; return newVertex;
}); });
// final mxCellState state = getView().getState(vertex, true);
//
// getView().updateLabel(state);
// getView().updateLabelBounds(state);
// getView().updateBoundingBox(state);
return vertex; return vertex;
} }
@ -253,12 +249,11 @@ final class CommunicationsGraph extends mxGraph {
Object[] edgesBetween = getEdgesBetween(vertex1, vertex2); Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
mxCell edge; mxCell edge;
if (edgesBetween.length == 0) { if (edgesBetween.length == 0) {
final String edgeName = vertex1.getId() + " <-> " + vertex2.getId(); final String edgeName = vertex1.getId() + " - " + vertex2.getId();
edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2, edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
"strokeWidth=" + (Math.log(relSources) + 1)); "strokeWidth=" + (Math.log(relSources) + 1));
} else { } else {
edge = (mxCell) edgesBetween[0]; edge = (mxCell) edgesBetween[0];
// ((Collection<Content>) edge.getValue()).addAll(relSources);
edge.setStyle("strokeWidth=" + (Math.log(relSources) + 1)); edge.setStyle("strokeWidth=" + (Math.log(relSources) + 1));
} }
return edge; return edge;
@ -308,9 +303,9 @@ final class CommunicationsGraph extends mxGraph {
progress.progress(++i); progress.progress(++i);
} }
Set<AccountDeviceInstance> accountIDs = relatedAccounts.keySet(); Set<AccountDeviceInstance> accounts = relatedAccounts.keySet();
Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accountIDs, currentFilter); Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter);
int total = relationshipCounts.size(); int total = relationshipCounts.size();
int k = 0; int k = 0;

View File

@ -61,8 +61,15 @@ final public class FiltersPanel extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName()); private static final Logger logger = Logger.getLogger(FiltersPanel.class.getName());
/**
* Map from Account.Type to the checkbox for that account type's filter.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private final Map<Account.Type, JCheckBox> accountTypeMap = new HashMap<>(); private final Map<Account.Type, JCheckBox> accountTypeMap = new HashMap<>();
/**
* Map from datasource device id to the checkbox for that datasource.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private final Map<String, JCheckBox> devicesMap = new HashMap<>(); private final Map<String, JCheckBox> devicesMap = new HashMap<>();
@ -70,6 +77,11 @@ final public class FiltersPanel extends JPanel {
* Listens to ingest events to enable refresh button * Listens to ingest events to enable refresh button
*/ */
private final PropertyChangeListener ingestListener; private final PropertyChangeListener ingestListener;
/**
* Flag that indicates the UI is not up-sto-date with respect to the case DB
* and it should be refreshed (by reapplying the filters).
*/
private boolean needsRefresh; private boolean needsRefresh;
/** /**
@ -78,10 +90,16 @@ final public class FiltersPanel extends JPanel {
* results) * results)
*/ */
private final ItemListener validationListener; private final ItemListener validationListener;
private boolean deviceAccountTypeEnabled = false;
@NbBundle.Messages({"refreshText=Refresh Results", /**
"applyText=Apply"}) * Is the device account type filter enabled or not. It should be enabled
* when the Table/Brows mode is active and disabled when the visualization
* is active. Initially false since the browse/table mode is active
* initially.
*/
private boolean deviceAccountTypeEnabled;
@NbBundle.Messages({"refreshText=Refresh Results", "applyText=Apply"})
public FiltersPanel() { public FiltersPanel() {
initComponents(); initComponents();
deviceRequiredLabel.setVisible(false); deviceRequiredLabel.setVisible(false);
@ -102,8 +120,7 @@ final public class FiltersPanel extends JPanel {
updateTimeZone(); updateTimeZone();
validationListener = itemEvent -> validateFilters(); validationListener = itemEvent -> validateFilters();
updateFilters(); updateFilters(true);
setAllDevicesSelected(true);
UserPreferences.addChangeListener(preferenceChangeEvent -> { UserPreferences.addChangeListener(preferenceChangeEvent -> {
if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)) { if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)) {
updateTimeZone(); updateTimeZone();
@ -118,7 +135,7 @@ final public class FiltersPanel extends JPanel {
if (null != eventData if (null != eventData
&& eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()
&& eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { && eventData.getBlackboardArtifactType().getTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
updateFilters(); updateFilters(false);
needsRefresh = true; needsRefresh = true;
validateFilters(); validateFilters();
} }
@ -150,8 +167,8 @@ final public class FiltersPanel extends JPanel {
/** /**
* Update the filter widgets, and apply them. * Update the filter widgets, and apply them.
*/ */
void updateAndApplyFilters() { void updateAndApplyFilters(boolean initialState) {
updateFilters(); updateFilters(initialState);
applyFilters(); applyFilters();
} }
@ -162,9 +179,9 @@ final public class FiltersPanel extends JPanel {
/** /**
* Updates the filter widgets to reflect he data sources/types in the case. * Updates the filter widgets to reflect he data sources/types in the case.
*/ */
private void updateFilters() { private void updateFilters(boolean initialState) {
updateAccountTypeFilter(); updateAccountTypeFilter();
updateDeviceFilter(); updateDeviceFilter(initialState);
} }
@Override @Override
@ -221,7 +238,7 @@ final public class FiltersPanel extends JPanel {
/** /**
* Populate the devices filter widgets * Populate the devices filter widgets
*/ */
private void updateDeviceFilter() { private void updateDeviceFilter(boolean initialState) {
try { try {
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase(); final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
@ -229,13 +246,12 @@ final public class FiltersPanel extends JPanel {
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
//store the device id in the map, but display a datasource name in the UI. //store the device id in the map, but display a datasource name in the UI.
devicesMap.computeIfAbsent(dataSource.getDeviceId(), ds -> { devicesMap.computeIfAbsent(dataSource.getDeviceId(), ds -> {
final JCheckBox jCheckBox = new JCheckBox(dsName, false); final JCheckBox jCheckBox = new JCheckBox(dsName, initialState);
jCheckBox.addItemListener(validationListener); jCheckBox.addItemListener(validationListener);
devicesPane.add(jCheckBox); devicesPane.add(jCheckBox);
return jCheckBox; return jCheckBox;
}); });
}; }
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
logger.log(Level.WARNING, "Communications Visualization Tool opened with no open case.", ex); logger.log(Level.WARNING, "Communications Visualization Tool opened with no open case.", ex);
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
@ -538,6 +554,21 @@ final public class FiltersPanel extends JPanel {
return new DateRangeFilter(start, end); return new DateRangeFilter(start, end);
} }
/**
* Enable or disable the device account type filter. The filter should be
* disabled for the browse/table mode and enabled for the visualization.
*
* @param enable True to enable the device account type filter, False to
* disable it.
*/
void setDeviceAccountTypeEnabled(boolean enable) {
deviceAccountTypeEnabled = enable;
JCheckBox deviceCheckbox = accountTypeMap.get(Account.Type.DEVICE);
if (deviceCheckbox != null) {
deviceCheckbox.setEnabled(deviceAccountTypeEnabled);
}
}
/** /**
* Set the selection state of all the account type check boxes * Set the selection state of all the account type check boxes
* *
@ -569,6 +600,7 @@ final public class FiltersPanel extends JPanel {
private void setAllSelected(Map<?, JCheckBox> map, boolean selected) { private void setAllSelected(Map<?, JCheckBox> map, boolean selected) {
map.values().forEach(box -> box.setSelected(selected)); map.values().forEach(box -> box.setSelected(selected));
} }
private void unCheckAllAccountTypesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unCheckAllAccountTypesButtonActionPerformed private void unCheckAllAccountTypesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_unCheckAllAccountTypesButtonActionPerformed
setAllAccountTypesSelected(false); setAllAccountTypesSelected(false);
}//GEN-LAST:event_unCheckAllAccountTypesButtonActionPerformed }//GEN-LAST:event_unCheckAllAccountTypesButtonActionPerformed
@ -620,12 +652,4 @@ final public class FiltersPanel extends JPanel {
private final javax.swing.JButton unCheckAllAccountTypesButton = new javax.swing.JButton(); private final javax.swing.JButton unCheckAllAccountTypesButton = new javax.swing.JButton();
private final javax.swing.JButton unCheckAllDevicesButton = new javax.swing.JButton(); private final javax.swing.JButton unCheckAllDevicesButton = new javax.swing.JButton();
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
void setDeviceAccountTypeEnabled(boolean enableDeviceAccountType) {
deviceAccountTypeEnabled = enableDeviceAccountType;
JCheckBox deviceCheckbox = accountTypeMap.get(Account.Type.DEVICE);
if (deviceCheckbox != null) {
deviceCheckbox.setEnabled(deviceAccountTypeEnabled);
}
}
} }

View File

@ -1,3 +1,3 @@
<html> <html>
<div style="font-size:{{size}}px;">{{#pinned}}<img style="vertical-align: middle;" height={{size}} width={{size}} src={{MARKER_PIN_URL}}>{{/pinned}}{{#locked}}<img style="vertical-align: middle;"height={{size}} width={{size}} src={{LOCK_URL}}>{{/locked}}<img style="vertical-align: middle;" height={{size}} width={{size}} src={{iconFileName}}>{{accountName}}</div> <div style="font-size:{{size}}px;">{{#pinned}}<img style="vertical-align: middle;" height={{size}} width={{size}} src={{MARKER_PIN_URL}}>{{/pinned}}{{#locked}}<img style="vertical-align: middle;"height={{size}} width={{size}} src={{LOCK_URL}}>{{/locked}}<img style="vertical-align: middle;" height={{size}} width={{size}} src={{iconFileName}}>{{accountName}} {{#device_id}}<br>data source: {{device_id}}{{/device_id}}</div>
</html> </html>

View File

@ -728,7 +728,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider
mxICell target = (mxICell) graph.getModel().getTerminal(cell, false); mxICell target = (mxICell) graph.getModel().getTerminal(cell, false);
AccountDeviceInstanceKey account2 = (AccountDeviceInstanceKey) target.getValue(); AccountDeviceInstanceKey account2 = (AccountDeviceInstanceKey) target.getValue();
try { try {
final List<Content> relationshipSources1 = commsManager.getRelationshipSources(account1.getAccountDeviceInstance(), final List<Content> relationshipSources1 = commsManager.getRelationshipSources(
account1.getAccountDeviceInstance(),
account2.getAccountDeviceInstance(), account2.getAccountDeviceInstance(),
currentFilter); currentFilter);
relationshipSources.addAll(relationshipSources1); relationshipSources.addAll(relationshipSources1);