diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
index b1e6d83482920b4dd18113814b6a6b31de373ce0..6381acd32d53f9e0a07e9a7c641a4977bc64e47e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java
@@ -31,7 +31,9 @@
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.table.AbstractTableModel;
 import org.openide.util.NbBundle.Messages;
+import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.datamodel.IngestJobInfo;
 import org.sleuthkit.datamodel.IngestModuleInfo;
@@ -47,6 +49,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
 
     private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName());
     private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED);
+    private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE);
+
     private List<IngestJobInfo> ingestJobs;
     private final List<IngestJobInfo> ingestJobsForSelectedDataSource = new ArrayList<>();
     private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel();
@@ -79,6 +83,16 @@ private void customizeComponents() {
                 refresh();
             }
         });
+        
+        Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> {
+            if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) {
+                return;
+            }
+                    
+            if (CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName())) {
+                refresh();
+            }
+        });
     }
 
     /**
@@ -110,9 +124,15 @@ public void setDataSource(DataSource selectedDataSource) {
      */
     private void refresh() {
         try {
-            SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
-            this.ingestJobs = skCase.getIngestJobs();
-            setDataSource(selectedDataSource);
+            if (Case.isCaseOpen()) {
+                SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+                this.ingestJobs = skCase.getIngestJobs();
+                setDataSource(selectedDataSource);
+            } else {
+                this.ingestJobs = new ArrayList<>();
+                setDataSource(null);
+            }
+            
         } catch (TskCoreException | NoCurrentCaseException ex) {
             logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
             JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
index fe998b0e576f0725efbfdb126f0f099b092b8c27..eb7affe9eee2d095e86f09304e40d64f11d2f116 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED
@@ -1,4 +1,6 @@
 CTL_DataSourceSummaryAction=Data Source Summary
+DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count
+DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type
 DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All
 DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated
 DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
index c2d8263056b6efef059ebd006fcfbae0183a8e69..dcbf9bb82d46367bd6a56124c640a723ef24cd27 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java
@@ -18,7 +18,8 @@
  */
 package org.sleuthkit.autopsy.casemodule.datasourcesummary;
 
-import java.util.ArrayList;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
@@ -43,6 +44,297 @@ final class DataSourceInfoUtilities {
 
     private static final Logger logger = Logger.getLogger(DataSourceInfoUtilities.class.getName());
 
+    /**
+     * Gets a count of files for a particular datasource where it is not a
+     * virtual directory and has a name.
+     *
+     * @param currentDataSource The datasource.
+     * @param additionalWhere   Additional sql where clauses.
+     * @param onError           The message to log on error.
+     *
+     * @return The count of files or null on error.
+     */
+    private static Long getCountOfFiles(DataSource currentDataSource, String additionalWhere, String onError) {
+        if (currentDataSource != null) {
+            try {
+                SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+                return skCase.countFilesWhere(
+                        "dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+                        + " AND name<>''"
+                        + " AND data_source_obj_id=" + currentDataSource.getId()
+                        + " AND " + additionalWhere);
+            } catch (TskCoreException | NoCurrentCaseException ex) {
+                logger.log(Level.WARNING, onError, ex);
+                //unable to get count of files for the specified types cell will be displayed as empty
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get count of files in a data source.
+     *
+     * @param currentDataSource The data source.
+     *
+     * @return The count.
+     */
+    static Long getCountOfFiles(DataSource currentDataSource) {
+        return getCountOfFiles(currentDataSource,
+                "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(),
+                "Unable to get count of files, providing empty results");
+    }
+
+    /**
+     * Get count of unallocated files in a data source.
+     *
+     * @param currentDataSource The data source.
+     *
+     * @return The count.
+     */
+    static Long getCountOfUnallocatedFiles(DataSource currentDataSource) {
+        return getCountOfFiles(currentDataSource,
+                "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+                + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(),
+                "Unable to get counts of unallocated files for datasource, providing empty results");
+    }
+
+    /**
+     * Get count of directories in a data source.
+     *
+     * @param currentDataSource The data source.
+     *
+     * @return The count.
+     */
+    static Long getCountOfDirectories(DataSource currentDataSource) {
+        return getCountOfFiles(currentDataSource,
+                "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+                + " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue(),
+                "Unable to get count of directories for datasource, providing empty results");
+    }
+
+    /**
+     * Get count of slack files in a data source.
+     *
+     * @param currentDataSource The data source.
+     *
+     * @return The count.
+     */
+    static Long getCountOfSlackFiles(DataSource currentDataSource) {
+        return getCountOfFiles(currentDataSource,
+                "type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(),
+                "Unable to get count of slack files for datasources, providing empty results");
+    }
+
+    /**
+     * An interface for handling a result set and returning a value.
+     */
+    private interface ResultSetHandler<T> {
+
+        T process(ResultSet resultset) throws SQLException;
+    }
+
+    /**
+     * Retrieves a result based on the provided query.
+     *
+     * @param query        The query.
+     * @param processor    The result set handler.
+     * @param errorMessage The error message to display if there is an error
+     *                     retrieving the resultset.
+     *
+     * @return The ResultSetHandler value or null if no ResultSet could be
+     *         obtained.
+     */
+    private static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) {
+        try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
+            ResultSet resultSet = dbQuery.getResultSet();
+            try {
+                return processor.process(resultSet);
+            } catch (SQLException ex) {
+                logger.log(Level.WARNING, errorMessage, ex);
+            }
+        } catch (TskCoreException | NoCurrentCaseException ex) {
+            logger.log(Level.WARNING, errorMessage, ex);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the size of unallocated files in a particular datasource.
+     *
+     * @param currentDataSource The data source.
+     *
+     * @return The size or null if the query could not be executed.
+     */
+    static Long getSizeOfUnallocatedFiles(DataSource currentDataSource) {
+        if (currentDataSource == null) {
+            return null;
+        }
+
+        final String valueParam = "value";
+        final String countParam = "count";
+        String query = "SELECT SUM(size) AS " + valueParam + ", COUNT(*) AS " + countParam
+                + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+                + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+                + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
+                + " AND name<>''"
+                + " AND data_source_obj_id=" + currentDataSource.getId();
+
+        ResultSetHandler<Long> handler = (resultSet) -> {
+            if (resultSet.next()) {
+                // ensure that there is an unallocated count result that is attached to this data source
+                long resultCount = resultSet.getLong(valueParam);
+                return (resultCount > 0) ? resultSet.getLong(valueParam) : null;
+            } else {
+                return null;
+            }
+        };
+        String errorMessage = "Unable to get size of unallocated files; returning null.";
+
+        return getBaseQueryResult(query, handler, errorMessage);
+    }
+
+    /**
+     * Retrieves counts for each artifact type in a data source.
+     *
+     * @param selectedDataSource The data source.
+     *
+     * @return A mapping of artifact type name to the counts or null if there
+     *         was an error executing the query.
+     */
+    static Map<String, Long> getCountsOfArtifactsByType(DataSource selectedDataSource) {
+        if (selectedDataSource == null) {
+            return Collections.emptyMap();
+        }
+
+        final String nameParam = "name";
+        final String valueParam = "value";
+        String query
+                = "SELECT bbt.display_name AS " + nameParam + ", COUNT(*) AS " + valueParam
+                + " FROM blackboard_artifacts bba "
+                + " INNER JOIN blackboard_artifact_types bbt ON bba.artifact_type_id = bbt.artifact_type_id"
+                + " WHERE bba.data_source_obj_id =" + selectedDataSource.getId()
+                + " GROUP BY bbt.display_name";
+
+        ResultSetHandler<Map<String, Long>> handler = (resultSet) -> {
+            Map<String, Long> toRet = new HashMap<>();
+            while (resultSet.next()) {
+                try {
+                    toRet.put(resultSet.getString(nameParam), resultSet.getLong(valueParam));
+                } catch (SQLException ex) {
+                    logger.log(Level.WARNING, "Failed to get a result pair from the result set.", ex);
+                }
+            }
+
+            return toRet;
+        };
+
+        String errorMessage = "Unable to get artifact type counts; returning null.";
+
+        return getBaseQueryResult(query, handler, errorMessage);
+    }
+
+    /**
+     * Generates a string which is a concatenation of the value received from
+     * the result set.
+     *
+     * @param query              The query.
+     * @param valueParam         The parameter for the value in the result set.
+     * @param separator          The string separator used in concatenation.
+     * @param errorMessage       The error message if the result set could not
+     *                           be received.
+     * @param singleErrorMessage The error message if a single result could not
+     *                           be obtained.
+     *
+     * @return The concatenated string or null if the query could not be
+     *         executed.
+     */
+    private static String getConcattedStringsResult(String query, String valueParam, String separator, String errorMessage, String singleErrorMessage) {
+        ResultSetHandler<String> handler = (resultSet) -> {
+            String toRet = "";
+            boolean first = true;
+            while (resultSet.next()) {
+                try {
+                    if (first) {
+                        first = false;
+                    } else {
+                        toRet += separator;
+                    }
+                    toRet += resultSet.getString(valueParam);
+                } catch (SQLException ex) {
+                    logger.log(Level.WARNING, singleErrorMessage, ex);
+                }
+            }
+
+            return toRet;
+        };
+
+        return getBaseQueryResult(query, handler, errorMessage);
+    }
+
+    /**
+     * Generates a concatenated string value based on the text value of a
+     * particular attribute in a particular artifact for a specific data source.
+     *
+     * @param dataSourceId    The data source id.
+     * @param artifactTypeId  The artifact type id.
+     * @param attributeTypeId The attribute type id.
+     *
+     * @return The concatenated value or null if the query could not be
+     *         executed.
+     */
+    private static String getConcattedAttrValue(long dataSourceId, int artifactTypeId, int attributeTypeId) {
+        final String valueParam = "concatted_attribute_value";
+        String query = "SELECT attr.value_text AS " + valueParam
+                + " FROM blackboard_artifacts bba "
+                + " INNER JOIN blackboard_attributes attr ON bba.artifact_id = attr.artifact_id "
+                + " WHERE bba.data_source_obj_id = " + dataSourceId
+                + " AND bba.artifact_type_id = " + artifactTypeId
+                + " AND attr.attribute_type_id = " + attributeTypeId;
+
+        String errorMessage = "Unable to execute query to retrieve concatted attribute values.";
+        String singleErrorMessage = "There was an error retrieving one of the results.  That result will be omitted from concatted value.";
+        String separator = ", ";
+        return getConcattedStringsResult(query, valueParam, separator, errorMessage, singleErrorMessage);
+    }
+
+    /**
+     * Retrieves the concatenation of operating system attributes for a
+     * particular data source.
+     *
+     * @param dataSource The data source.
+     *
+     * @return The concatenated value or null if the query could not be
+     *         executed.
+     */
+    static String getOperatingSystems(DataSource dataSource) {
+        if (dataSource == null) {
+            return null;
+        }
+
+        return getConcattedAttrValue(dataSource.getId(),
+                BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID(),
+                BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID());
+    }
+
+    /**
+     * Retrieves the concatenation of data source usage for a particular data
+     * source.
+     *
+     * @param dataSource The data source.
+     *
+     * @return The concatenated value or null if the query could not be
+     *         executed.
+     */
+    static String getDataSourceType(DataSource dataSource) {
+        if (dataSource == null) {
+            return null;
+        }
+
+        return getConcattedAttrValue(dataSource.getId(),
+                BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE.getTypeID(),
+                BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
+    }
+
     /**
      * Get a map containing the TSK_DATA_SOURCE_USAGE description attributes
      * associated with each data source in the current case.
@@ -147,39 +439,6 @@ static Map<Long, Long> getCountsOfTags() {
         }
     }
 
-    /**
-     * Get a map containing the names of operating systems joined in a comma
-     * seperated list to the Data Source they exist on in the current case. No
-     * item will exist in the map for data sources which do not contain
-     * TS_OS_INFO artifacts which have a program name.
-     *
-     * @return Collection which maps datasource id to a String which is a comma
-     *         seperated list of Operating system names found on the data
-     *         source.
-     */
-    static Map<Long, String> getOperatingSystems() {
-        Map<Long, String> osDetailMap = new HashMap<>();
-        try {
-            SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
-            ArrayList<BlackboardArtifact> osInfoArtifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO);
-            for (BlackboardArtifact osInfo : osInfoArtifacts) {
-                BlackboardAttribute programName = osInfo.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME));
-                if (programName != null) {
-                    String currentOsString = osDetailMap.get(osInfo.getDataSource().getId());
-                    if (currentOsString == null || currentOsString.isEmpty()) {
-                        currentOsString = programName.getValueString();
-                    } else {
-                        currentOsString = currentOsString + ", " + programName.getValueString();
-                    }
-                    osDetailMap.put(osInfo.getDataSource().getId(), currentOsString);
-                }
-            }
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.SEVERE, "Failed to load OS info artifacts.", ex);
-        }
-        return osDetailMap;
-    }
-
     /**
      * Get the number of files in the case database for the current data source
      * which have the specified mimetypes.
@@ -212,134 +471,6 @@ static Long getCountOfFilesForMimeTypes(DataSource currentDataSource, Set<String
         return null;
     }
 
-    /**
-     * Get a map containing the number of unallocated files in each data source
-     * in the current case.
-     *
-     * @return Collection which maps datasource id to a count for the number of
-     *         unallocated files in the datasource, will only contain entries
-     *         for datasources which have at least 1 unallocated file
-     */
-    static Map<Long, Long> getCountsOfUnallocatedFiles() {
-        try {
-            final String countUnallocatedFilesQuery = "data_source_obj_id, COUNT(*) AS value"
-                    + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
-                    + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
-                    + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
-                    + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
-            return getValuesMap(countUnallocatedFilesQuery);
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.WARNING, "Unable to get counts of unallocated files for all datasources, providing empty results", ex);
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Get a map containing the total size of unallocated files in each data
-     * source in the current case.
-     *
-     * @return Collection which maps datasource id to a total size in bytes of
-     *         unallocated files in the datasource, will only contain entries
-     *         for datasources which have at least 1 unallocated file
-     */
-    static Map<Long, Long> getSizeOfUnallocatedFiles() {
-        try {
-            final String countUnallocatedFilesQuery = "data_source_obj_id, sum(size) AS value"
-                    + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
-                    + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
-                    + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
-                    + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
-            return getValuesMap(countUnallocatedFilesQuery);
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.WARNING, "Unable to get size of unallocated files for all datasources, providing empty results", ex);
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Get a map containing the number of directories in each data source in the
-     * current case.
-     *
-     * @return Collection which maps datasource id to a count for the number of
-     *         directories in the datasource, will only contain entries for
-     *         datasources which have at least 1 directory
-     */
-    static Map<Long, Long> getCountsOfDirectories() {
-        try {
-            final String countDirectoriesQuery = "data_source_obj_id, COUNT(*) AS value"
-                    + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
-                    + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
-                    + " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()
-                    + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
-            return getValuesMap(countDirectoriesQuery);
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.WARNING, "Unable to get counts of directories for all datasources, providing empty results", ex);
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Get a map containing the number of slack files in each data source in the
-     * current case.
-     *
-     * @return Collection which maps datasource id to a count for the number of
-     *         slack files in the datasource, will only contain entries for
-     *         datasources which have at least 1 slack file
-     */
-    static Map<Long, Long> getCountsOfSlackFiles() {
-        try {
-            final String countSlackFilesQuery = "data_source_obj_id, COUNT(*) AS value"
-                    + " FROM tsk_files WHERE type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType()
-                    + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
-                    + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
-            return getValuesMap(countSlackFilesQuery);
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.WARNING, "Unable to get counts of slack files for all datasources, providing empty results", ex);
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Get a map containing maps which map artifact type to the number of times
-     * it exosts in each data source in the current case.
-     *
-     * @return Collection which maps datasource id to maps of artifact display
-     *         name to number of occurences in the datasource, will only contain
-     *         entries for artifacts which have at least one occurence in the
-     *         data source.
-     */
-    static Map<Long, Map<String, Long>> getCountsOfArtifactsByType() {
-        try {
-            final String countArtifactsQuery = "blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name AS label, COUNT(*) AS value"
-                    + " FROM blackboard_artifacts, blackboard_artifact_types"
-                    + " WHERE blackboard_artifacts.artifact_type_id = blackboard_artifact_types.artifact_type_id"
-                    + " GROUP BY blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name";
-            return getLabeledValuesMap(countArtifactsQuery);
-        } catch (TskCoreException | NoCurrentCaseException ex) {
-            logger.log(Level.WARNING, "Unable to get counts of all artifact types for all datasources, providing empty results", ex);
-            return Collections.emptyMap();
-        }
-    }
-
-    /**
-     * Helper method to execute a select query with a
-     * DataSourceLabeledValueCallback.
-     *
-     * @param query the portion of the query which should follow the SELECT
-     *
-     * @return a map of datasource object IDs to maps of String labels to the
-     *         values associated with them.
-     *
-     * @throws TskCoreException
-     * @throws NoCurrentCaseException
-     */
-    private static Map<Long, Map<String, Long>> getLabeledValuesMap(String query) throws TskCoreException, NoCurrentCaseException {
-        SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
-        DataSourceLabeledValueCallback callback = new DataSourceLabeledValueCallback();
-        skCase.getCaseDbAccessManager().select(query, callback);
-        return callback.getMapOfLabeledValues();
-    }
-
     /**
      * Helper method to execute a select query with a
      * DataSourceSingleValueCallback.
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form
index d3a17e0205ecda22e44a44edd7d0dd442269844b..9a82746da613c0df11acb31ecee348228fe5144f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form
@@ -70,18 +70,13 @@
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
       <SubComponents>
         <Component class="javax.swing.JTable" name="fileCountsByMimeTypeTable">
-          <Properties>
-            <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-              <Connection code="filesByMimeTypeTableModel" type="code"/>
-            </Property>
-          </Properties>
         </Component>
       </SubComponents>
     </Container>
     <Component class="javax.swing.JLabel" name="byMimeTypeLabel">
       <Properties>
         <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </Properties>
     </Component>
@@ -93,25 +88,20 @@
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
       <SubComponents>
         <Component class="javax.swing.JTable" name="fileCountsByCategoryTable">
-          <Properties>
-            <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-              <Connection code="filesByCategoryTableModel" type="code"/>
-            </Property>
-          </Properties>
         </Component>
       </SubComponents>
     </Container>
     <Component class="javax.swing.JLabel" name="byCategoryLabel">
       <Properties>
         <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </Properties>
     </Component>
     <Component class="javax.swing.JLabel" name="jLabel1">
       <Properties>
         <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+          <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
         </Property>
       </Properties>
     </Component>
@@ -123,15 +113,6 @@
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
       <SubComponents>
         <Component class="javax.swing.JTable" name="artifactCountsTable">
-          <Properties>
-            <Property name="autoCreateRowSorter" type="boolean" value="true"/>
-            <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
-              <Table columnCount="2" rowCount="0">
-                <Column editable="false" title="Result Type" type="java.lang.Object"/>
-                <Column editable="false" title="Count" type="java.lang.Object"/>
-              </Table>
-            </Property>
-          </Properties>
         </Component>
       </SubComponents>
     </Container>
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java
index 1d99f1c44520756d1efbaf41540299964387eb40..6bb6603c11520b86f4b6a0986d0e9a47c4863d58 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java
@@ -18,15 +18,12 @@
  */
 package org.sleuthkit.autopsy.casemodule.datasourcesummary;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import javax.swing.JLabel;
-import javax.swing.table.AbstractTableModel;
 import javax.swing.table.DefaultTableCellRenderer;
-import javax.swing.table.DefaultTableModel;
 import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.coreutils.FileTypeUtils;
 import org.sleuthkit.datamodel.DataSource;
 
@@ -34,69 +31,224 @@
  * Panel for displaying summary information on the known files present in the
  * specified DataSource
  */
+@Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type",
+    "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count",
+    "DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type",
+    "DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count",
+    "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type",
+    "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count"
+})
 class DataSourceSummaryCountsPanel extends javax.swing.JPanel {
 
+    // Result returned for a data model if no data found.
+    private static final Object[][] EMPTY_PAIRS = new Object[][]{};
+
+    // column headers for mime type table
+    private static final Object[] MIME_TYPE_COLUMN_HEADERS = new Object[]{
+        Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header(),
+        Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header()
+    };
+
+    // column headers for file by category table
+    private static final Object[] FILE_BY_CATEGORY_COLUMN_HEADERS = new Object[]{
+        Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header(),
+        Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header()
+    };
+
+    // column headers for artifact counts table
+    private static final Object[] ARTIFACT_COUNTS_COLUMN_HEADERS = new Object[]{
+        Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_type_header(),
+        Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_count_header()
+    };
+
     private static final long serialVersionUID = 1L;
-    private FilesByMimeTypeTableModel filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(null);
-    private FilesByCategoryTableModel filesByCategoryTableModel = new FilesByCategoryTableModel(null);
     private static final Logger logger = Logger.getLogger(DataSourceSummaryCountsPanel.class.getName());
-    private final Map<Long, Long> allFilesCountsMap;
-    private final Map<Long, Long> slackFilesCountsMap;
-    private final Map<Long, Long> directoriesCountsMap;
-    private final Map<Long, Long> unallocatedFilesCountsMap;
-    private final Map<Long, Map<String, Long>> artifactsByTypeCountsMap;
     private final DefaultTableCellRenderer rightAlignedRenderer = new DefaultTableCellRenderer();
 
+    private DataSource dataSource;
+
     /**
      * Creates new form DataSourceSummaryCountsPanel
      */
-    DataSourceSummaryCountsPanel(Map<Long, Long> fileCountsMap) {
-        this.allFilesCountsMap = fileCountsMap;
-        this.slackFilesCountsMap = DataSourceInfoUtilities.getCountsOfSlackFiles();
-        this.directoriesCountsMap = DataSourceInfoUtilities.getCountsOfDirectories();
-        this.unallocatedFilesCountsMap = DataSourceInfoUtilities.getCountsOfUnallocatedFiles();
-        this.artifactsByTypeCountsMap = DataSourceInfoUtilities.getCountsOfArtifactsByType();
+    DataSourceSummaryCountsPanel() {
         rightAlignedRenderer.setHorizontalAlignment(JLabel.RIGHT);
         initComponents();
         fileCountsByMimeTypeTable.getTableHeader().setReorderingAllowed(false);
         fileCountsByCategoryTable.getTableHeader().setReorderingAllowed(false);
+        artifactCountsTable.getTableHeader().setReorderingAllowed(false);
+        setDataSource(null);
     }
 
     /**
-     * Specify the DataSource to display file information for
+     * The datasource currently used as the model in this panel.
      *
-     * @param selectedDataSource the DataSource to display file information for
+     * @return The datasource currently being used as the model in this panel.
      */
-    void updateCountsTableData(DataSource selectedDataSource) {
-        filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(selectedDataSource);
-        fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel);
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    /**
+     * Sets datasource to visualize in the panel.
+     *
+     * @param dataSource The datasource to use in this panel.
+     */
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+        if (dataSource == null || !Case.isCaseOpen()) {
+            updateCountsTableData(EMPTY_PAIRS,
+                    EMPTY_PAIRS,
+                    EMPTY_PAIRS);
+        } else {
+            updateCountsTableData(getMimeTypeModel(dataSource),
+                    getFileCategoryModel(dataSource),
+                    getArtifactCountsModel(dataSource));
+        }
+
+    }
+
+    /**
+     * Specify the DataSource to display file information for.
+     *
+     * @param mimeTypeDataModel     The mime type data model.
+     * @param fileCategoryDataModel The file category data model.
+     * @param artifactDataModel     The artifact type data model.
+     */
+    private void updateCountsTableData(Object[][] mimeTypeDataModel, Object[][] fileCategoryDataModel, Object[][] artifactDataModel) {
+        fileCountsByMimeTypeTable.setModel(new NonEditableTableModel(mimeTypeDataModel, MIME_TYPE_COLUMN_HEADERS));
         fileCountsByMimeTypeTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
         fileCountsByMimeTypeTable.getColumnModel().getColumn(0).setPreferredWidth(130);
-        filesByCategoryTableModel = new FilesByCategoryTableModel(selectedDataSource);
-        fileCountsByCategoryTable.setModel(filesByCategoryTableModel);
+
+        fileCountsByCategoryTable.setModel(new NonEditableTableModel(fileCategoryDataModel, FILE_BY_CATEGORY_COLUMN_HEADERS));
         fileCountsByCategoryTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
         fileCountsByCategoryTable.getColumnModel().getColumn(0).setPreferredWidth(130);
-        updateArtifactCounts(selectedDataSource);
+
+        artifactCountsTable.setModel(new NonEditableTableModel(artifactDataModel, ARTIFACT_COUNTS_COLUMN_HEADERS));
+        artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230);
+        artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
+
         this.repaint();
     }
 
     /**
-     * Helper method to update the artifact specific counts by clearing the
-     * table and adding counts for the artifacts which exist in the selected
-     * data source.
+     * Determines the JTable data model for datasource mime types.
+     *
+     * @param dataSource The DataSource.
      *
-     * @param selectedDataSource the data source to display artifact counts for
+     * @return The model to be used with a JTable.
      */
-    private void updateArtifactCounts(DataSource selectedDataSource) {
-        ((DefaultTableModel) artifactCountsTable.getModel()).setRowCount(0);
-        if (selectedDataSource != null && artifactsByTypeCountsMap.get(selectedDataSource.getId()) != null) {
-            Map<String, Long> artifactCounts = artifactsByTypeCountsMap.get(selectedDataSource.getId());
-            for (String key : artifactCounts.keySet()) {
-                ((DefaultTableModel) artifactCountsTable.getModel()).addRow(new Object[]{key, artifactCounts.get(key)});
-            }
+    @Messages({
+        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images",
+        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos",
+        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio",
+        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents",
+        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables"
+    })
+    private static Object[][] getMimeTypeModel(DataSource dataSource) {
+        return new Object[][]{
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row(),
+                getCount(dataSource, FileTypeUtils.FileTypeCategory.IMAGE)},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row(),
+                getCount(dataSource, FileTypeUtils.FileTypeCategory.VIDEO)},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row(),
+                getCount(dataSource, FileTypeUtils.FileTypeCategory.AUDIO)},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row(),
+                getCount(dataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS)},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row(),
+                getCount(dataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE)}
+        };
+    }
+
+    /**
+     * Retrieves the counts of files of a particular mime type for a particular
+     * DataSource.
+     *
+     * @param dataSource The DataSource.
+     * @param category   The mime type category.
+     *
+     * @return The count.
+     */
+    private static Long getCount(DataSource dataSource, FileTypeUtils.FileTypeCategory category) {
+        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(dataSource, category.getMediaTypes());
+    }
+
+    /**
+     * Determines the JTable data model for datasource file categories.
+     *
+     * @param dataSource The DataSource.
+     *
+     * @return The model to be used with a JTable.
+     */
+    @Messages({
+        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All",
+        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated",
+        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated",
+        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack",
+        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory"
+    })
+    private static Object[][] getFileCategoryModel(DataSource selectedDataSource) {
+        Long fileCount = zeroIfNull(DataSourceInfoUtilities.getCountOfFiles(selectedDataSource));
+        Long unallocatedFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfUnallocatedFiles(selectedDataSource));
+        Long allocatedFiles = zeroIfNull(getAllocatedCount(fileCount, unallocatedFiles));
+        Long slackFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfSlackFiles(selectedDataSource));
+        Long directories = zeroIfNull(DataSourceInfoUtilities.getCountOfDirectories(selectedDataSource));
+
+        return new Object[][]{
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row(), fileCount},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row(), allocatedFiles},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row(), unallocatedFiles},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row(), slackFiles},
+            new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row(), directories}
+        };
+    }
+
+    /**
+     * Returns 0 if value is null.
+     *
+     * @param origValue The original value.
+     *
+     * @return The value or 0 if null.
+     */
+    private static Long zeroIfNull(Long origValue) {
+        return origValue == null ? 0 : origValue;
+    }
+
+    /**
+     * Safely gets the allocated files count.
+     *
+     * @param allFilesCount         The count of all files.
+     * @param unallocatedFilesCount The count of unallocated files.
+     *
+     * @return The count of allocated files.
+     */
+    private static long getAllocatedCount(Long allFilesCount, Long unallocatedFilesCount) {
+        if (allFilesCount == null) {
+            return 0;
+        } else if (unallocatedFilesCount == null) {
+            return allFilesCount;
+        } else {
+            return allFilesCount - unallocatedFilesCount;
         }
-        artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230);
-        artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
+    }
+
+    /**
+     * The counts of different artifact types found in a DataSource.
+     *
+     * @param selectedDataSource The DataSource.
+     *
+     * @return The JTable data model of counts of artifact types.
+     */
+    private static Object[][] getArtifactCountsModel(DataSource selectedDataSource) {
+        Map<String, Long> artifactMapping = DataSourceInfoUtilities.getCountsOfArtifactsByType(selectedDataSource);
+        if (artifactMapping == null) {
+            return EMPTY_PAIRS;
+        }
+
+        return artifactMapping.entrySet().stream()
+                .filter((entrySet) -> entrySet != null && entrySet.getKey() != null)
+                .sorted((a, b) -> a.getKey().compareTo(b.getKey()))
+                .map((entrySet) -> new Object[]{entrySet.getKey(), entrySet.getValue()})
+                .toArray(Object[][]::new);
     }
 
     /**
@@ -118,35 +270,16 @@ private void initComponents() {
         artifactCountsScrollPane = new javax.swing.JScrollPane();
         artifactCountsTable = new javax.swing.JTable();
 
-        fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel);
         fileCountsByMimeTypeScrollPane.setViewportView(fileCountsByMimeTypeTable);
 
         org.openide.awt.Mnemonics.setLocalizedText(byMimeTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byMimeTypeLabel.text")); // NOI18N
 
-        fileCountsByCategoryTable.setModel(filesByCategoryTableModel);
         fileCountsByCategoryScrollPane.setViewportView(fileCountsByCategoryTable);
 
         org.openide.awt.Mnemonics.setLocalizedText(byCategoryLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byCategoryLabel.text")); // NOI18N
 
         org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.jLabel1.text")); // NOI18N
 
-        artifactCountsTable.setAutoCreateRowSorter(true);
-        artifactCountsTable.setModel(new javax.swing.table.DefaultTableModel(
-            new Object [][] {
-
-            },
-            new String [] {
-                "Result Type", "Count"
-            }
-        ) {
-            boolean[] canEdit = new boolean [] {
-                false, false
-            };
-
-            public boolean isCellEditable(int rowIndex, int columnIndex) {
-                return canEdit [columnIndex];
-            }
-        });
         artifactCountsScrollPane.setViewportView(artifactCountsTable);
 
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
@@ -206,190 +339,4 @@ public boolean isCellEditable(int rowIndex, int columnIndex) {
     private javax.swing.JTable fileCountsByMimeTypeTable;
     private javax.swing.JLabel jLabel1;
     // End of variables declaration//GEN-END:variables
-
-    /**
-     * Table model for the files table model to display counts of specific file
-     * types by mime type found in the currently selected data source.
-     */
-    @Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type",
-        "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count"})
-    private class FilesByMimeTypeTableModel extends AbstractTableModel {
-
-        private static final long serialVersionUID = 1L;
-        private final DataSource currentDataSource;
-        private final List<String> columnHeaders = new ArrayList<>();
-        private static final int IMAGES_ROW_INDEX = 0;
-        private static final int VIDEOS_ROW_INDEX = 1;
-        private static final int AUDIO_ROW_INDEX = 2;
-        private static final int DOCUMENTS_ROW_INDEX = 3;
-        private static final int EXECUTABLES_ROW_INDEX = 4;
-
-        /**
-         * Create a FilesByMimeTypeTableModel for the speicified datasource.
-         *
-         * @param selectedDataSource the datasource which this
-         *                           FilesByMimeTypeTablemodel will represent
-         */
-        FilesByMimeTypeTableModel(DataSource selectedDataSource) {
-            columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header());
-            columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header());
-            currentDataSource = selectedDataSource;
-        }
-
-        @Override
-        public int getRowCount() {
-            //should be kept equal to the number of types we are displaying in the tables
-            return 5;
-        }
-
-        @Override
-        public int getColumnCount() {
-            return columnHeaders.size();
-        }
-
-        @Messages({
-            "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images",
-            "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos",
-            "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio",
-            "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents",
-            "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables"
-        })
-        @Override
-        public Object getValueAt(int rowIndex, int columnIndex) {
-            if (columnIndex == 0) {
-                switch (rowIndex) {
-                    case IMAGES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row();
-                    case VIDEOS_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row();
-                    case AUDIO_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row();
-                    case DOCUMENTS_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row();
-                    case EXECUTABLES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row();
-                    default:
-                        break;
-                }
-            } else if (columnIndex == 1) {
-                switch (rowIndex) {
-                    case 0:
-                        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes());
-                    case 1:
-                        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes());
-                    case 2:
-                        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes());
-                    case 3:
-                        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS.getMediaTypes());
-                    case 4:
-                        return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes());
-                    default:
-                        break;
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public String getColumnName(int column) {
-            return columnHeaders.get(column);
-        }
-    }
-
-    /**
-     * Table model for the files table model to display counts of specific file
-     * types by category found in the currently selected data source.
-     */
-    @Messages({"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type",
-        "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count"})
-    private class FilesByCategoryTableModel extends AbstractTableModel {
-
-        private static final long serialVersionUID = 1L;
-        private final DataSource currentDataSource;
-        private final List<String> columnHeaders = new ArrayList<>();
-        private static final int ALL_FILES_ROW_INDEX = 0;
-        private static final int ALLOCATED_FILES_ROW_INDEX = 1;
-        private static final int UNALLOCATED_FILES_ROW_INDEX = 2;
-        private static final int SLACK_FILES_ROW_INDEX = 3;
-        private static final int DIRECTORIES_ROW_INDEX = 4;
-        /**
-         * Create a FilesByCategoryTableModel for the speicified datasource.
-         *
-         * @param selectedDataSource the datasource which this
-         *                           FilesByCategoryTablemodel will represent
-         */
-        FilesByCategoryTableModel(DataSource selectedDataSource) {
-            columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header());
-            columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header());
-            currentDataSource = selectedDataSource;
-        }
-
-        @Override
-        public int getRowCount() {
-            //should be kept equal to the number of types we are displaying in the tables
-            return 5;
-        }
-
-        @Override
-        public int getColumnCount() {
-            return columnHeaders.size();
-        }
-
-        @Messages({
-            "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All",
-            "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated",
-            "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated",
-            "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack",
-            "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory"
-        })
-        @Override
-        public Object getValueAt(int rowIndex, int columnIndex) {
-            if (columnIndex == 0) {
-                switch (rowIndex) {
-                    case ALL_FILES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row();
-                    case ALLOCATED_FILES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row();
-                    case UNALLOCATED_FILES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row();
-                    case SLACK_FILES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row();
-                    case DIRECTORIES_ROW_INDEX:
-                        return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row();
-                    default:
-                        break;
-                }
-            } else if (columnIndex == 1 && currentDataSource != null) {
-                switch (rowIndex) {
-                    case 0:
-                        return allFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : allFilesCountsMap.get(currentDataSource.getId());
-                    case 1:
-                        //All files should be either allocated or unallocated as dir_flags only has two values so any file that isn't unallocated is allocated
-                        Long unallocatedFilesCount = unallocatedFilesCountsMap.get(currentDataSource.getId());
-                        Long allFilesCount = allFilesCountsMap.get(currentDataSource.getId());
-                        if (allFilesCount == null) {
-                            return 0;
-                        } else if (unallocatedFilesCount == null) {
-                            return allFilesCount;
-                        } else {
-                            return allFilesCount - unallocatedFilesCount;
-                        }
-                    case 2:
-                        return unallocatedFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : unallocatedFilesCountsMap.get(currentDataSource.getId());
-                    case 3:
-                        return slackFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : slackFilesCountsMap.get(currentDataSource.getId());
-                    case 4:
-                        return directoriesCountsMap.get(currentDataSource.getId()) == null ? 0 : directoriesCountsMap.get(currentDataSource.getId());
-                    default:
-                        break;
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public String getColumnName(int column) {
-            return columnHeaders.get(column);
-        }
-    }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java
index 1b898f49951a5a6d26cbe273f228aaa7ec537015..21caa2c4b85414f80ddf1a9c33d7426dab4082b1 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java
@@ -19,12 +19,12 @@
 package org.sleuthkit.autopsy.casemodule.datasourcesummary;
 
 import java.text.DecimalFormat;
-import java.util.Map;
-import java.util.HashMap;
 import java.util.logging.Level;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import javax.swing.table.DefaultTableModel;
+import org.apache.commons.lang3.StringUtils;
 import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.datamodel.DataSource;
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.TskCoreException;
@@ -36,23 +36,47 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel {
 
     //Because this panel was made using the gridbaglayout and netbean's Customize Layout tool it will be best to continue to modify it through that
     private static final long serialVersionUID = 1L;
-    private Map<Long, String> osDetailMap = new HashMap<>();
     private static final Integer SIZE_COVERSION_CONSTANT = 1000;
     private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##");
-    private final Map<Long, Long> unallocatedFilesSizeMap;
-    private final Map<Long, String> usageMap;
     private static final Logger logger = Logger.getLogger(DataSourceSummaryDetailsPanel.class.getName());
 
+    private DataSource dataSource;
+
     /**
      * Creates new form DataSourceSummaryDetailsPanel
      */
     @Messages({"DataSourceSummaryDetailsPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
         "DataSourceSummaryDetailsPanel.getDataSources.error.title=Load Failure"})
-    DataSourceSummaryDetailsPanel(Map<Long, String> usageMap) {
+    DataSourceSummaryDetailsPanel() {
         initComponents();
-        this.usageMap = usageMap;
-        this.unallocatedFilesSizeMap = DataSourceInfoUtilities.getSizeOfUnallocatedFiles();
-        osDetailMap = DataSourceInfoUtilities.getOperatingSystems();
+        setDataSource(null);
+    }
+
+    /**
+     * The datasource currently used as the model in this panel.
+     *
+     * @return The datasource currently being used as the model in this panel.
+     */
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    /**
+     * Sets datasource to visualize in the panel.
+     *
+     * @param dataSource The datasource to use in this panel.
+     */
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+
+        if (dataSource == null || !Case.isCaseOpen()) {
+            updateDetailsPanelData(null, null, null, null);
+        } else {
+            updateDetailsPanelData(dataSource,
+                    DataSourceInfoUtilities.getSizeOfUnallocatedFiles(dataSource),
+                    DataSourceInfoUtilities.getOperatingSystems(dataSource),
+                    DataSourceInfoUtilities.getDataSourceType(dataSource));
+        }
     }
 
     /**
@@ -60,75 +84,78 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel {
      *
      * @param selectedDataSource the DataSource to display details about.
      */
-    void updateDetailsPanelData(DataSource selectedDataSource) {
+    private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize, String osDetails, String usage) {
         clearTableValues();
         if (selectedDataSource != null) {
-            String sizeString = "";
-            String sectorSizeString = "";
-            String md5String = "";
-            String sha1String = "";
-            String sha256String = "";
-            String acquisitionDetailsString = "";
-            String imageTypeString = "";
-            String[] filePaths = new String[0];
-            String osDetailString = osDetailMap.get(selectedDataSource.getId()) == null ? "" : osDetailMap.get(selectedDataSource.getId());
-            String dataSourceTypeString = usageMap.get(selectedDataSource.getId()) == null ? "" : usageMap.get(selectedDataSource.getId());
+            unallocatedSizeValue.setText(getSizeString(unallocatedFilesSize));
+            operatingSystemValue.setText(StringUtils.isBlank(osDetails) ? "" : osDetails);
+            dataSourceUsageValue.setText(StringUtils.isBlank(usage) ? "" : usage);
+
+            timeZoneValue.setText(selectedDataSource.getTimeZone());
+            displayNameValue.setText(selectedDataSource.getName());
+            originalNameValue.setText(selectedDataSource.getName());
+            deviceIdValue.setText(selectedDataSource.getDeviceId());
+
             try {
-                acquisitionDetailsString = selectedDataSource.getAcquisitionDetails();
+                acquisitionDetailsTextArea.setText(selectedDataSource.getAcquisitionDetails());
             } catch (TskCoreException ex) {
                 logger.log(Level.WARNING, "Unable to get aquisition details for selected data source", ex);
             }
+
             if (selectedDataSource instanceof Image) {
-                imageTypeString = ((Image) selectedDataSource).getType().getName();
-                filePaths = ((Image) selectedDataSource).getPaths();
-                sizeString = getSizeString(selectedDataSource.getSize());
-                sectorSizeString = getSizeString(((Image) selectedDataSource).getSsize());
-                try {
-                    //older databases may have null as the hash values
-                    md5String = ((Image) selectedDataSource).getMd5();
-                    if (md5String == null) {
-                        md5String = "";
-                    }
-                } catch (TskCoreException ex) {
-                    logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex);
-                }
-                try {
-                    sha1String = ((Image) selectedDataSource).getSha1();
-                    if (sha1String == null) {
-                        sha1String = "";
-                    }
-                } catch (TskCoreException ex) {
-                    logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex);
-                }
-                try {
-                    sha256String = ((Image) selectedDataSource).getSha256();
-                    if (sha256String == null) {
-                        sha256String = "";
-                    }
-                } catch (TskCoreException ex) {
-                    logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex);
-                }
+                setFieldsForImage((Image) selectedDataSource);
+            }
+        }
+        updateFieldVisibility();
+        this.repaint();
+    }
+
+    /**
+     * Sets text fields for an image. This should be called after
+     * clearTableValues and before updateFieldVisibility to ensure the proper
+     * rendering.
+     *
+     * @param selectedImage The selected image.
+     */
+    private void setFieldsForImage(Image selectedImage) {
+        imageTypeValue.setText(selectedImage.getType().getName());
+        sizeValue.setText(getSizeString(selectedImage.getSize()));
+        sectorSizeValue.setText(getSizeString(selectedImage.getSsize()));
+
+        for (String path : selectedImage.getPaths()) {
+            ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
+        }
+
+        try {
+            //older databases may have null as the hash values
+            String md5String = selectedImage.getMd5();
+            if (md5String == null) {
+                md5String = "";
             }
-            displayNameValue.setText(selectedDataSource.getName());
-            originalNameValue.setText(selectedDataSource.getName());
-            deviceIdValue.setText(selectedDataSource.getDeviceId());
-            dataSourceUsageValue.setText(dataSourceTypeString);
-            operatingSystemValue.setText(osDetailString);
-            timeZoneValue.setText(selectedDataSource.getTimeZone());
-            acquisitionDetailsTextArea.setText(acquisitionDetailsString);
-            imageTypeValue.setText(imageTypeString);
-            sizeValue.setText(sizeString);
-            unallocatedSizeValue.setText(getSizeString(unallocatedFilesSizeMap.get(selectedDataSource.getId())));
-            sectorSizeValue.setText(sectorSizeString);
             md5HashValue.setText(md5String);
+        } catch (TskCoreException ex) {
+            logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex);
+        }
+
+        try {
+            String sha1String = selectedImage.getSha1();
+            if (sha1String == null) {
+                sha1String = "";
+            }
             sha1HashValue.setText(sha1String);
-            sha256HashValue.setText(sha256String);
-            for (String path : filePaths) {
-                ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
+        } catch (TskCoreException ex) {
+            logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex);
+        }
+
+        try {
+            String sha256String = selectedImage.getSha256();
+            if (sha256String == null) {
+                sha256String = "";
             }
+            sha256HashValue.setText(sha256String);
+        } catch (TskCoreException ex) {
+            logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex);
         }
-        updateFieldVisibility();
-        this.repaint();
     }
 
     /**
@@ -147,7 +174,7 @@ void updateDetailsPanelData(DataSource selectedDataSource) {
         "DataSourceSummaryDetailsPanel.units.terabytes= TB",
         "DataSourceSummaryDetailsPanel.units.petabytes= PB"
     })
-    private String getSizeString(Long size) {
+    private static String getSizeString(Long size) {
         if (size == null) {
             return "";
         }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form
index 72c45cb5a7fa8551fe403c2c580f71e040519721..272fc9b041d2f743485f269ab50f9f3c877b4981 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form
@@ -68,6 +68,11 @@
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
       <SubComponents>
         <Container class="javax.swing.JTabbedPane" name="dataSourceTabbedPane">
+          <AuxValues>
+            <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="dataSourceSummaryTabbedPane"/>
+            <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
+            <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
+          </AuxValues>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
               <JSplitPaneConstraints position="right"/>
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
index 3c68b7b3edf348dd3497d50ab56c5103d191d729..e1197fa83a809d9fa249ab47ad1d3e7b1a67203d 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
@@ -27,7 +27,6 @@
 import java.util.Set;
 import javax.swing.event.ListSelectionEvent;
 import org.openide.util.NbBundle.Messages;
-import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
 import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason;
@@ -41,10 +40,8 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
 
     private static final long serialVersionUID = 1L;
     private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED);
-    private final DataSourceSummaryCountsPanel countsPanel;
-    private final DataSourceSummaryDetailsPanel detailsPanel;
     private final DataSourceBrowser dataSourcesPanel;
-    private final IngestJobInfoPanel ingestHistoryPanel;
+    private final DataSourceSummaryTabbedPane dataSourceSummaryTabbedPane;
 
     /**
      * Creates new form DataSourceSummaryDialog for displaying a summary of the
@@ -61,21 +58,14 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
         super(owner, Bundle.DataSourceSummaryDialog_window_title(), true);
         Map<Long, String> usageMap = DataSourceInfoUtilities.getDataSourceTypes();
         Map<Long, Long> fileCountsMap = DataSourceInfoUtilities.getCountsOfFiles();
-        countsPanel = new DataSourceSummaryCountsPanel(fileCountsMap);
-        detailsPanel = new DataSourceSummaryDetailsPanel(usageMap);
         dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
-        ingestHistoryPanel = new IngestJobInfoPanel();
+        dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
         initComponents();
         dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
-        dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel);
-        dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel);
-        dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel);
         dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
             if (!e.getValueIsAdjusting()) {
                 DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource();
-                countsPanel.updateCountsTableData(selectedDataSource);
-                detailsPanel.updateDetailsPanelData(selectedDataSource);
-                ingestHistoryPanel.setDataSource(selectedDataSource);
+                dataSourceSummaryTabbedPane.setDataSource(selectedDataSource);
                 this.repaint();
             }
         });
@@ -116,7 +106,7 @@ private void initComponents() {
 
         closeButton = new javax.swing.JButton();
         dataSourceSummarySplitPane = new javax.swing.JSplitPane();
-        dataSourceTabbedPane = new javax.swing.JTabbedPane();
+        javax.swing.JTabbedPane dataSourceTabbedPane = dataSourceSummaryTabbedPane;
 
         org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryDialog.class, "DataSourceSummaryDialog.closeButton.text")); // NOI18N
         closeButton.addActionListener(new java.awt.event.ActionListener() {
@@ -173,6 +163,5 @@ void selectDataSource(Long dataSourceId) {
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JButton closeButton;
     private javax.swing.JSplitPane dataSourceSummarySplitPane;
-    private javax.swing.JTabbedPane dataSourceTabbedPane;
     // End of variables declaration//GEN-END:variables
 }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1d15a6b0dfa4385129a4ccebfe5b8ba64fc71f3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java
@@ -0,0 +1,74 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.datasourcesummary;
+
+import javax.swing.JTabbedPane;
+import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
+import org.sleuthkit.datamodel.DataSource;
+
+/**
+ * A tabbed pane showing the summary of a data source including tabs of:
+ * DataSourceSummaryCountsPanel, DataSourceSummaryDetailsPanel, and
+ * IngestJobInfoPanel.
+ */
+public class DataSourceSummaryTabbedPane extends JTabbedPane {
+
+    private static final long serialVersionUID = 1L;
+    
+    private final DataSourceSummaryCountsPanel countsPanel;
+    private final DataSourceSummaryDetailsPanel detailsPanel;
+    private final IngestJobInfoPanel ingestHistoryPanel;
+
+    private DataSource dataSource = null;
+
+    /**
+     * Constructs a tabbed pane showing the summary of a data source.
+     */
+    public DataSourceSummaryTabbedPane() {
+        countsPanel = new DataSourceSummaryCountsPanel();
+        detailsPanel = new DataSourceSummaryDetailsPanel();
+        ingestHistoryPanel = new IngestJobInfoPanel();
+        
+        addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel);
+        addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel);
+        addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel);
+    }
+
+    /**
+     * The datasource currently used as the model in this panel.
+     *
+     * @return The datasource currently being used as the model in this panel.
+     */
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    /**
+     * Sets datasource to visualize in the tabbed panel.
+     *
+     * @param dataSource The datasource to use in this panel.
+     */
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+
+        detailsPanel.setDataSource(dataSource);
+        countsPanel.setDataSource(dataSource);
+        ingestHistoryPanel.setDataSource(dataSource);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ed965e2abcb841072db0ca891f094037ff7614c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java
@@ -0,0 +1,37 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.casemodule.datasourcesummary;
+
+import javax.swing.table.DefaultTableModel;
+
+/**
+ * A Table model where cells are not editable.
+ */
+class NonEditableTableModel extends DefaultTableModel {
+    private static final long serialVersionUID = 1L;
+    
+    NonEditableTableModel(Object[][] data, Object[] columnNames) {
+        super(data, columnNames);
+    }
+
+    @Override
+    public boolean isCellEditable(int row, int column) {
+        return false;
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED
new file mode 100644
index 0000000000000000000000000000000000000000..8a629e17a9fef2e1a8d04e445af887cbf78ac329
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED
@@ -0,0 +1 @@
+DataSourceSummaryResultViewer_title=Summary
diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b1b3cb127db37b613f5a7c331e10d90f9c2513f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java
@@ -0,0 +1,137 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2020 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.resultviewers.summary;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.util.logging.Level;
+import javax.swing.SwingUtilities;
+import org.openide.explorer.ExplorerManager;
+import org.openide.nodes.Node;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryTabbedPane;
+import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
+import org.sleuthkit.autopsy.corecomponents.AbstractDataResultViewer;
+import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+
+/**
+ * A tabular result viewer that displays a summary of the selected Data Source.
+ */
+@ServiceProvider(service = DataResultViewer.class)
+@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
+public class DataSourceSummaryResultViewer extends AbstractDataResultViewer {
+
+    private static final long serialVersionUID = 1L;
+    private static final Logger LOGGER = Logger.getLogger(DataSourceSummaryResultViewer.class.getName());
+
+    private final String title;
+
+    /**
+     * Constructs a tabular result viewer that displays a summary of the
+     * selected Data Source.
+     */
+    public DataSourceSummaryResultViewer() {
+        this(null);
+    }
+
+    /**
+     * Constructs a tabular result viewer that displays a summary of the
+     * selected Data Source.
+     *
+     * @param explorerManager The explorer manager of the ancestor top
+     *                        component.
+     *
+     */
+    @Messages({
+        "DataSourceSummaryResultViewer_title=Summary"
+    })
+    public DataSourceSummaryResultViewer(ExplorerManager explorerManager) {
+        this(explorerManager, Bundle.DataSourceSummaryResultViewer_title());
+    }
+
+    /**
+     * Constructs a tabular result viewer that displays a summary of the
+     * selected Data Source.
+     *
+     * @param explorerManager The explorer manager of the ancestor top
+     *                        component.
+     * @param title           The title.
+     *
+     */
+    public DataSourceSummaryResultViewer(ExplorerManager explorerManager, String title) {
+        super(explorerManager);
+        this.title = title;
+        initComponents();
+    }
+
+    @Override
+    public DataResultViewer createInstance() {
+        return new DataSourceSummaryResultViewer();
+    }
+
+    @Override
+    public boolean isSupported(Node node) {
+        return getDataSource(node) != null;
+    }
+
+    /**
+     * Returns the datasource attached to the node or null if none can be found.
+     *
+     * @param node The node to search.
+     *
+     * @return The datasource or null if not found.
+     */
+    private DataSource getDataSource(Node node) {
+        return node == null ? null : node.getLookup().lookup(DataSource.class);
+    }
+
+    @Override
+    @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
+    public void setNode(Node node) {
+        if (!SwingUtilities.isEventDispatchThread()) {
+            LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread.");
+            return;
+        }
+
+        DataSource dataSource = getDataSource(node);
+
+        this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        try {
+            summaryPanel.setDataSource(dataSource);
+        } finally {
+            this.setCursor(null);
+        }
+    }
+
+    @Override
+    public String getTitle() {
+        return title;
+    }
+
+    private void initComponents() {
+        summaryPanel = new DataSourceSummaryTabbedPane();
+        setLayout(new BorderLayout());
+        add(summaryPanel, BorderLayout.CENTER);
+    }
+
+    private DataSourceSummaryTabbedPane summaryPanel;
+}