diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED new file mode 100644 index 0000000000..2e019a0248 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/Bundle.properties-MERGED @@ -0,0 +1,2 @@ +DataSourceUserActivitySummary_getRecentAccounts_calllogMessage=Call Log +DataSourceUserActivitySummary_getRecentAccounts_emailMessage=Email Message diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java index 9a60bd17e7..4480b207e1 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java @@ -22,6 +22,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; @@ -35,6 +36,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; +import org.sleuthkit.datamodel.BlackboardAttribute.Type; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TskData.TSK_FS_META_FLAG_ENUM; @@ -224,8 +226,8 @@ final class DataSourceInfoUtilities { */ private DataSourceInfoUtilities() { } - - /** + + /** * Create a Map of lists of artifacts sorted by the given attribute. * * @param skCase SleuthkitCase instance. @@ -275,12 +277,12 @@ final class DataSourceInfoUtilities { List artifactList = new ArrayList<>(); for (List mapArtifactList : sortedMap.values()) { - + if (maxCount == 0 || (artifactList.size() + mapArtifactList.size()) <= maxCount) { artifactList.addAll(mapArtifactList); continue; } - + if (maxCount == artifactList.size()) { break; } @@ -357,4 +359,64 @@ final class DataSourceInfoUtilities { } } } + + /** + * Retrieves attribute from artifact if exists. Returns null if attribute is + * null or underlying call throws exception. + * + * @param artifact The artifact. + * @param attributeType The attribute type to retrieve from the artifact. + * + * @return The attribute or null if could not be received. + */ + private static BlackboardAttribute getAttributeOrNull(BlackboardArtifact artifact, Type attributeType) { + try { + return artifact.getAttribute(attributeType); + } catch (TskCoreException ex) { + return null; + } + } + + /** + * Retrieves the string value of a certain attribute type from an artifact. + * + * @param artifact The artifact. + * @param attributeType The attribute type. + * + * @return The 'getValueString()' value or null if the attribute or String + * could not be retrieved. + */ + static String getStringOrNull(BlackboardArtifact artifact, Type attributeType) { + BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType); + return (attr == null) ? null : attr.getValueString(); + } + + /** + * Retrieves the long value of a certain attribute type from an artifact. + * + * @param artifact The artifact. + * @param attributeType The attribute type. + * + * @return The 'getValueLong()' value or null if the attribute could not be + * retrieved. + */ + static Long getLongOrNull(BlackboardArtifact artifact, Type attributeType) { + BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType); + return (attr == null) ? null : attr.getValueLong(); + } + + /** + * Retrieves the long value of a certain attribute type from an artifact and + * converts to date (seconds since epoch). + * + * @param artifact The artifact. + * @param attributeType The attribute type. + * + * @return The date determined from the 'getValueLong()' as seconds from + * epoch or null if the attribute could not be retrieved or is 0. + */ + static Date getDateOrNull(BlackboardArtifact artifact, Type attributeType) { + Long longVal = getLongOrNull(artifact, attributeType); + return (longVal == null || longVal == 0) ? null : new Date(longVal * 1000); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java deleted file mode 100644 index cc97b62389..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2020 Basis Technology Corp. - * Contact: carrier sleuthkit 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.datasourcesummary.datamodel; - -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.sleuthkit.datamodel.DataSource; - -/** - * Provides summary information about top domains in a datasource. At this time, - * the data being provided is fictitious and is done as a placeholder. - */ -public class DataSourceTopDomainsSummary { - - private static final long SLEEP_TIME = 5000; - - /** - * A function to calculate a result from 2 parameters. - */ - interface Function2 { - - O apply(A1 a1, A2 a2); - } - - /** - * Gets a list of recent domains based on the datasource. - * - * @param dataSource The datasource to query for recent domains. - * @param count The max count of items to return. - * - * @return The list of items retrieved from the database. - * - * @throws InterruptedException - */ - public List getRecentDomains(DataSource dataSource, int count) throws InterruptedException { - Thread.sleep(SLEEP_TIME); - final String dId = Long.toString(dataSource.getId()); - final Function2 getId = (s, idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx); - return IntStream.range(0, count) - .mapToObj(num -> new TopDomainsResult( - getId.apply("domain", num), - getId.apply("url", num), - (long) num, - new Date(((long) num) * 1000 * 60 * 60 * 24) - )) - .collect(Collectors.toList()); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceUserActivitySummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceUserActivitySummary.java new file mode 100644 index 0000000000..1ae108c496 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceUserActivitySummary.java @@ -0,0 +1,533 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit 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.datasourcesummary.datamodel; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationException; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; + +/** + * Provides summary information about user activity in a datasource. At this + * time, the data being provided for domains is fictitious and is done as a + * placeholder. + */ +public class DataSourceUserActivitySummary { + + private static final BlackboardArtifact.Type TYPE_DEVICE_ATTACHED = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED); + + private static final BlackboardAttribute.Type TYPE_DATETIME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME); + private static final BlackboardAttribute.Type TYPE_DATETIME_ACCESSED = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED); + private static final BlackboardAttribute.Type TYPE_DEVICE_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID); + private static final BlackboardAttribute.Type TYPE_DEVICE_MAKE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + private static final BlackboardAttribute.Type TYPE_DEVICE_MODEL = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + private static final BlackboardAttribute.Type TYPE_MESSAGE_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE); + private static final BlackboardAttribute.Type TYPE_TEXT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT); + + private static final BlackboardAttribute.Type TYPE_DATETIME_RCVD = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD); + private static final BlackboardAttribute.Type TYPE_DATETIME_SENT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT); + private static final BlackboardAttribute.Type TYPE_DATETIME_START = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START); + private static final BlackboardAttribute.Type TYPE_DATETIME_END = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END); + + private static final Comparator TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess()); + private static final Comparator TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed()); + private static final String ROOT_HUB_IDENTIFIER = "ROOT_HUB"; + + private static final long SLEEP_TIME = 5000; + + /** + * A function to calculate a result from 2 parameters. + */ + interface Function2 { + + O apply(A1 a1, A2 a2); + } + + /** + * Gets a list of recent domains based on the datasource. + * + * @param dataSource The datasource to query for recent domains. + * @param count The max count of items to return. + * + * @return The list of items retrieved from the database. + * + * @throws InterruptedException + */ + public List getRecentDomains(DataSource dataSource, int count) throws InterruptedException { + Thread.sleep(SLEEP_TIME); + final String dId = Long.toString(dataSource.getId()); + final Function2 getId = (s, idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx); + return IntStream.range(0, count) + .mapToObj(num -> new TopDomainsResult( + getId.apply("domain", num), + getId.apply("url", num), + (long) num, + new Date(((long) num) * 1000 * 60 * 60 * 24) + )) + .collect(Collectors.toList()); + } + + private final SleuthkitCaseProvider caseProvider; + private final TextTranslationService translationService; + private final java.util.logging.Logger logger; + + /** + * Main constructor. + */ + public DataSourceUserActivitySummary() { + this(SleuthkitCaseProvider.DEFAULT, TextTranslationService.getInstance(), + org.sleuthkit.autopsy.coreutils.Logger.getLogger(DataSourceUserActivitySummary.class.getName())); + } + + /** + * Main constructor with external dependencies specified. This constructor + * is designed with unit testing in mind since mocked dependencies can be + * utilized. + * + * @param provider The object providing the current SleuthkitCase. + * @param translationService The translation service. + * @param logger The logger to use. + */ + public DataSourceUserActivitySummary( + SleuthkitCaseProvider provider, + TextTranslationService translationService, + java.util.logging.Logger logger) { + + this.caseProvider = provider; + this.translationService = translationService; + this.logger = logger; + } + + /** + * Throws an IllegalArgumentException if count <= 0. + * + * @param count The count being checked. + */ + private void assertValidCount(int count) { + if (count <= 0) { + throw new IllegalArgumentException("Count must be greater than 0"); + } + } + + /** + * Attempts to obtain a web search result record from a blackboard artifact. + * + * @param artifact The artifact. + * + * @return The TopWebSearchResult or null if the search string or date + * accessed cannot be determined. + */ + private static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact) { + String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT); + Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED); + return (StringUtils.isNotBlank(searchString) && dateAccessed != null) + ? new TopWebSearchResult(searchString, dateAccessed) + : null; + } + + /** + * Retrieves most recent web searches by most recent date grouped by search + * term. + * + * @param dataSource The data source. + * @param count The maximum number of records to be shown (must be > + * 0). + * + * @return The list of most recent web searches where most recent search + * appears first. + * + * @throws + * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException + * @throws TskCoreException + */ + public List getMostRecentWebSearches(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException { + assertValidCount(count); + + // get the artifacts + List webSearchArtifacts = caseProvider.get().getBlackboard() + .getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId()); + + // group by search string (case insensitive) + Collection> resultGroups = webSearchArtifacts + .stream() + // get items where search string and date is not null + .map(DataSourceUserActivitySummary::getWebSearchResult) + // remove null records + .filter(result -> result != null) + // get these messages grouped by search to string + .collect(Collectors.groupingBy((result) -> result.getSearchString().toUpperCase())) + .values(); + + // get the most recent date for each search term + List results = resultGroups + .stream() + // get the most recent access per search type + .map((list) -> list.stream().max(TOP_WEBSEARCH_RESULT_DATE_COMPARE).get()) + // get most recent searches first + .sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed()) + .limit(count) + // get as list + .collect(Collectors.toList()); + + // get translation if possible + if (translationService.hasProvider()) { + for (TopWebSearchResult result : results) { + result.setTranslatedResult(getTranslationOrNull(result.getSearchString())); + } + } + + return results; + } + + /** + * Return the translation of the original text if possible and differs from + * the original. Otherwise, return null. + * + * @param original The original text. + * + * @return The translated text or null if no translation can be determined + * or exists. + */ + private String getTranslationOrNull(String original) { + if (!translationService.hasProvider() || StringUtils.isBlank(original)) { + return null; + } + + String translated = null; + try { + translated = translationService.translate(original); + } catch (NoServiceProviderException | TranslationException ex) { + logger.log(Level.WARNING, String.format("There was an error translating text: '%s'", original), ex); + } + + // if there is no translation or the translation is the same as the original, return null. + if (StringUtils.isBlank(translated) + || translated.toUpperCase().trim().equals(original.toUpperCase().trim())) { + + return null; + } + + return translated; + } + + /** + * Retrieves most recent devices used by most recent date attached. + * + * @param dataSource The data source. + * @param count The maximum number of records to be shown (must be > + * 0). + * + * @return The list of most recent devices attached where most recent device + * attached appears first. + * + * @throws + * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException + * @throws TskCoreException + */ + public List getRecentDevices(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException { + assertValidCount(count); + + return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED, + dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0) + .stream() + .map(artifact -> { + return new TopDeviceAttachedResult( + DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID), + DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME), + DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE), + DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL) + ); + }) + // remove Root Hub identifier + .filter(result -> { + return result.getDeviceModel() == null + || !result.getDeviceModel().trim().toUpperCase().equals(ROOT_HUB_IDENTIFIER); + }) + .limit(count) + .collect(Collectors.toList()); + } + + /** + * Obtains a TopAccountResult from a TSK_MESSAGE blackboard artifact. + * + * @param artifact The artifact. + * + * @return The TopAccountResult or null if the account type or message date + * cannot be determined. + */ + private static TopAccountResult getMessageAccountResult(BlackboardArtifact artifact) { + String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE); + Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME); + return (StringUtils.isNotBlank(type) && date != null) + ? new TopAccountResult(type, date) + : null; + } + + /** + * Obtains a TopAccountResult from a blackboard artifact. The date is + * maximum of any found dates for attribute types provided. + * + * @param artifact The artifact. + * @param messageType The type of message this is. + * @param dateAttrs The date attribute types. + * + * @return The TopAccountResult or null if the account type or max date are + * not provided. + */ + private static TopAccountResult getAccountResult(BlackboardArtifact artifact, String messageType, BlackboardAttribute.Type... dateAttrs) { + String type = messageType; + + Date latestDate = null; + if (dateAttrs != null) { + latestDate = Stream.of(dateAttrs) + .map((attr) -> DataSourceInfoUtilities.getDateOrNull(artifact, attr)) + .filter((date) -> date != null) + .max((a, b) -> a.compareTo(b)) + .orElse(null); + } + + return (StringUtils.isNotBlank(type) && latestDate != null) + ? new TopAccountResult(type, latestDate) + : null; + } + + /** + * Retrieves most recent account used by most recent date for a message + * sent. + * + * @param dataSource The data source. + * @param count The maximum number of records to be shown (must be > + * 0). + * + * @return The list of most recent accounts used where the most recent + * account by last message sent occurs first. + * + * @throws + * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException + * @throws TskCoreException + */ + @Messages({ + "DataSourceUserActivitySummary_getRecentAccounts_emailMessage=Email Message", + "DataSourceUserActivitySummary_getRecentAccounts_calllogMessage=Call Log",}) + public List getRecentAccounts(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException { + assertValidCount(count); + + Stream messageResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), dataSource.getId()) + .stream() + .map((art) -> getMessageAccountResult(art)); + + Stream emailResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), dataSource.getId()) + .stream() + .map((art) -> { + return getAccountResult( + art, + Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), + TYPE_DATETIME_RCVD, + TYPE_DATETIME_SENT); + }); + + Stream calllogResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(), dataSource.getId()) + .stream() + .map((art) -> { + return getAccountResult( + art, + Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), + TYPE_DATETIME_START, + TYPE_DATETIME_END); + }); + + Stream allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults)); + + // get them grouped by account type + Collection> groupedResults = allResults + // remove null records + .filter(result -> result != null) + // get these messages grouped by account type + .collect(Collectors.groupingBy(TopAccountResult::getAccountType)) + .values(); + + // get account type sorted by most recent date + return groupedResults + .stream() + // get the most recent access per account type + .map((accountGroup) -> accountGroup.stream().max(TOP_ACCOUNT_RESULT_DATE_COMPARE).get()) + // get most recent accounts accessed + .sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed()) + // limit to count + .limit(count) + // get as list + .collect(Collectors.toList()); + } + + /** + * Object containing information about a web search artifact. + */ + public static class TopWebSearchResult { + + private final String searchString; + private final Date dateAccessed; + private String translatedResult; + + /** + * Main constructor. + * + * @param searchString The search string. + * @param dateAccessed The latest date searched. + */ + public TopWebSearchResult(String searchString, Date dateAccessed) { + this.searchString = searchString; + this.dateAccessed = dateAccessed; + } + + /** + * @return The translated result if one was determined. + */ + public String getTranslatedResult() { + return translatedResult; + } + + /** + * Sets the translated result for this web search. + * + * @param translatedResult The translated result. + */ + public void setTranslatedResult(String translatedResult) { + this.translatedResult = translatedResult; + } + + /** + * @return The search string. + */ + public String getSearchString() { + return searchString; + } + + /** + * @return The date for the search. + */ + public Date getDateAccessed() { + return dateAccessed; + } + } + + /** + * A record of a device attached. + */ + public static class TopDeviceAttachedResult { + + private final String deviceId; + private final Date dateAccessed; + private final String deviceMake; + private final String deviceModel; + + /** + * Main constructor. + * + * @param deviceId The device id. + * @param dateAccessed The date last attached. + * @param deviceMake The device make. + * @param deviceModel The device model. + */ + public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) { + this.deviceId = deviceId; + this.dateAccessed = dateAccessed; + this.deviceMake = deviceMake; + this.deviceModel = deviceModel; + } + + /** + * @return The device id. + */ + public String getDeviceId() { + return deviceId; + } + + /** + * @return The date last attached. + */ + public Date getDateAccessed() { + return dateAccessed; + } + + /** + * @return The device make. + */ + public String getDeviceMake() { + return deviceMake; + } + + /** + * @return The device model. + */ + public String getDeviceModel() { + return deviceModel; + } + } + + /** + * A record of an account and the last time it was used determined by + * messages. + */ + public static class TopAccountResult { + + private final String accountType; + private final Date lastAccess; + + /** + * Main constructor. + * + * @param accountType The account type. + * @param lastAccess The date the account was last accessed. + */ + public TopAccountResult(String accountType, Date lastAccess) { + this.accountType = accountType; + this.lastAccess = lastAccess; + } + + /** + * @return The account type. + */ + public String getAccountType() { + return accountType; + } + + /** + * @return The date the account was last accessed. + */ + public Date getLastAccess() { + return lastAccess; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java index 9a0cddd05a..d28a734e34 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java @@ -40,12 +40,9 @@ abstract class BaseDataSourceSummaryPanel extends JPanel { * @param dataSource The datasource to use in this panel. */ synchronized void setDataSource(DataSource dataSource) { - DataSource oldDataSource = this.dataSource; this.dataSource = dataSource; - if (this.dataSource != oldDataSource) { - this.executor.cancelRunning(); - onNewDataSource(this.dataSource); - } + this.executor.cancelRunning(); + onNewDataSource(this.dataSource); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties index 350daeb57e..49e673f50f 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties @@ -31,4 +31,7 @@ DataSourceSummaryDetailsPanel.unallocatedSizeValue.text= DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs +DataSourceSummaryUserActivityPanel.recentAccountsLabel.text=Recent Accounts +DataSourceSummaryUserActivityPanel.topWebSearchLabel.text=Recent Web Searches +DataSourceSummaryUserActivityPanel.topDevicesAttachedLabel.text=Recent Devices Attached DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 7cc0fd7235..2037f419e4 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -69,9 +69,17 @@ DataSourceSummaryTabbedPane_detailsTab_title=Container DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History DataSourceSummaryTabbedPane_userActivityTab_title=User Activity DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs +DataSourceSummaryUserActivityPanel.recentAccountsLabel.text=Recent Accounts +DataSourceSummaryUserActivityPanel.topWebSearchLabel.text=Recent Web Searches +DataSourceSummaryUserActivityPanel.topDevicesAttachedLabel.text=Recent Devices Attached DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains DataSourceSummaryUserActivityPanel_noDataExists=No communication data exists DataSourceSummaryUserActivityPanel_tab_title=User Activity +DataSourceSummaryUserActivityPanel_TopAccountTableModel_accountType_header=Account Type +DataSourceSummaryUserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed +DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header=Last Accessed +DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_deviceId_header=Device Id +DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_makeModel_header=Make and Model DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL @@ -79,4 +87,7 @@ DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program +DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed +DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_searchString_header=Search String +DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated ViewSummaryInformationAction.name.text=View Summary Information diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java index 0467432ac0..ed12bbf94e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java @@ -45,7 +45,8 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane { // A pair of the tab name and the corresponding BaseDataSourceSummaryTabs to be displayed. private final List> tabs = new ArrayList<>(Arrays.asList( Pair.of(Bundle.DataSourceSummaryTabbedPane_countsTab_title(), new DataSourceSummaryCountsPanel()), - Pair.of(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new DataSourceSummaryUserActivityPanel()))); + Pair.of(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new DataSourceSummaryUserActivityPanel()) + )); private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel(); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form index 9798add7eb..f24955084b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form @@ -11,7 +11,7 @@ - + @@ -45,10 +45,10 @@ - + - + @@ -97,13 +97,13 @@ - + - + - + @@ -166,13 +166,13 @@ - + - + - + @@ -181,6 +181,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java index eb3f09a81f..309b70008e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java @@ -22,17 +22,21 @@ import java.awt.Component; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopDomainsSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceUserActivitySummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopProgramsSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceUserActivitySummary.TopAccountResult; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceUserActivitySummary.TopDeviceAttachedResult; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceUserActivitySummary.TopWebSearchResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopDomainsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.HorizontalAlign; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; @@ -52,107 +56,232 @@ import org.sleuthkit.datamodel.DataSource; "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain", "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL", "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access", - "DataSourceSummaryUserActivityPanel_noDataExists=No communication data exists",}) + "DataSourceSummaryUserActivityPanel_noDataExists=No communication data exists", + "DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_searchString_header=Search String", + "DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed", + "DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated", + "DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_deviceId_header=Device Id", + "DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_makeModel_header=Make and Model", + "DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header=Last Accessed", + "DataSourceSummaryUserActivityPanel_TopAccountTableModel_accountType_header=Account Type", + "DataSourceSummaryUserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed",}) public class DataSourceSummaryUserActivityPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); private static final int TOP_PROGS_COUNT = 10; private static final int TOP_DOMAINS_COUNT = 10; - - private final JTablePanel topProgramsTable; - private final JTablePanel recentDomainsTable; - private final List> dataFetchComponents; - private final List> tables; + private static final int TOP_SEARCHES_COUNT = 10; + private static final int TOP_ACCOUNTS_COUNT = 5; + private static final int TOP_DEVICES_COUNT = 10; /** - * Creates a new DataSourceUserActivityPanel. + * Gets a string formatted date or returns empty string if the date is null. + * + * @param date The date. + * + * @return The formatted date string or empty string if the date is null. + */ + private static String getFormatted(Date date) { + return date == null ? "" : DATETIME_FORMAT.format(date); + } + + // set up recent programs table + private final JTablePanel topProgramsTable = JTablePanel.getJTablePanel(Arrays.asList( + // program name column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(), + (prog) -> { + return new DefaultCellModel(prog.getProgramName()) + .setTooltip(prog.getProgramPath()); + }, + 250), + // program folder column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(), + (prog) -> { + return new DefaultCellModel( + getShortFolderName( + prog.getProgramPath(), + prog.getProgramName())); + }, + 150), + // run count column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(), + (prog) -> { + String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()); + return new DefaultCellModel(runTimes); + }, + 80), + // last run date column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header(), + (prog) -> new DefaultCellModel(getFormatted(prog.getLastRun())), + 150) + )); + + // set up recent domains table + private final JTablePanel recentDomainsTable = JTablePanel.getJTablePanel(Arrays.asList( + // domain column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header(), + (recentDomain) -> new DefaultCellModel(recentDomain.getDomain()), + 250), + // url column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header(), + (recentDomain) -> new DefaultCellModel(recentDomain.getUrl()), + 250), + // last accessed column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header(), + (recentDomain) -> new DefaultCellModel(getFormatted(recentDomain.getLastVisit())), + 150) + )); + + // top web searches table + private final JTablePanel topWebSearchesTable = JTablePanel.getJTablePanel(Arrays.asList( + // search string column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_searchString_header(), + (webSearch) -> new DefaultCellModel(webSearch.getSearchString()), + 250 + ), + // last accessed + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_dateAccessed_header(), + (webSearch) -> new DefaultCellModel(getFormatted(webSearch.getDateAccessed())), + 150 + ), + // translated value + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_translatedResult_header(), + (webSearch) -> new DefaultCellModel(webSearch.getTranslatedResult()), + 250 + ) + )); + + // top devices attached table + private final JTablePanel topDevicesAttachedTable = JTablePanel.getJTablePanel(Arrays.asList( + // device id column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(), + (device) -> new DefaultCellModel(device.getDeviceId()), + 250 + ), + // last accessed + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(), + (device) -> new DefaultCellModel(getFormatted(device.getDateAccessed())), + 150 + ), + // make and model + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopDeviceAttachedTableModel_makeModel_header(), + (device) -> { + String make = StringUtils.isBlank(device.getDeviceMake()) ? "" : device.getDeviceMake().trim(); + String model = StringUtils.isBlank(device.getDeviceModel()) ? "" : device.getDeviceModel().trim(); + String makeModelString = (make.isEmpty() || model.isEmpty()) + ? make + model + : String.format("%s - %s", make, model); + return new DefaultCellModel(makeModelString); + }, + 250 + ) + )); + + // top accounts table + private final JTablePanel topAccountsTable = JTablePanel.getJTablePanel(Arrays.asList( + // account type column + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopAccountTableModel_accountType_header(), + (account) -> new DefaultCellModel(account.getAccountType()), + 250 + ), + // last accessed + new ColumnModel<>( + Bundle.DataSourceSummaryUserActivityPanel_TopAccountTableModel_lastAccess_header(), + (account) -> new DefaultCellModel(getFormatted(account.getLastAccess())), + 150 + ) + )); + + private final List> tables = Arrays.asList( + topProgramsTable, + recentDomainsTable, + topWebSearchesTable, + topDevicesAttachedTable, + topAccountsTable + ); + + private final List> dataFetchComponents; + private final DataSourceTopProgramsSummary topProgramsData; + + /** + * Creates a new DataSourceSummaryUserActivityPanel. */ public DataSourceSummaryUserActivityPanel() { - this(new DataSourceTopProgramsSummary(), new DataSourceTopDomainsSummary()); + this(new DataSourceTopProgramsSummary(), new DataSourceUserActivitySummary()); } /** * Creates a new DataSourceSummaryUserActivityPanel. * - * @param topProgramsData Class from which to obtain top programs data. - * @param topDomainsData Class from which to obtain recent domains data. + * @param topProgramsData Class from which to obtain top programs data. + * @param userActivityData Class from which to obtain remaining user + * activity data. */ - public DataSourceSummaryUserActivityPanel(DataSourceTopProgramsSummary topProgramsData, DataSourceTopDomainsSummary topDomainsData) { - // set up recent programs table - this.topProgramsTable = JTablePanel.getJTablePanel(Arrays.asList(new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(), - (prog) -> { - return new DefaultCellModel(prog.getProgramName()) - .setTooltip(prog.getProgramPath()); - }, - 250), - new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(), - (prog) -> { - return new DefaultCellModel( - topProgramsData.getShortFolderName( - prog.getProgramPath(), - prog.getProgramName())); - }, - 150), - new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(), - (prog) -> { - String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()); - return new DefaultCellModel(runTimes) - .setHorizontalAlignment(HorizontalAlign.RIGHT); - }, - 80), - new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header(), - (prog) -> { - String date = prog.getLastRun() == null ? "" : DATETIME_FORMAT.format(prog.getLastRun()); - return new DefaultCellModel(date) - .setHorizontalAlignment(HorizontalAlign.RIGHT); - }, - 150) - )); + public DataSourceSummaryUserActivityPanel( + DataSourceTopProgramsSummary topProgramsData, + DataSourceUserActivitySummary userActivityData) { - // set up recent domains table - this.recentDomainsTable = JTablePanel.getJTablePanel(Arrays.asList(new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header(), - (d) -> new DefaultCellModel(d.getDomain()), - 250), - new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header(), - (d) -> new DefaultCellModel(d.getUrl()), - 250), - new ColumnModel<>( - Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header(), - (prog) -> { - String lastVisit = prog.getLastVisit() == null ? "" : DATETIME_FORMAT.format(prog.getLastVisit()); - return new DefaultCellModel(lastVisit) - .setHorizontalAlignment(HorizontalAlign.RIGHT); - }, - 150) - )); - - this.tables = Arrays.asList( - topProgramsTable, - recentDomainsTable - ); + this.topProgramsData = topProgramsData; // set up data acquisition methods - dataFetchComponents = Arrays.asList( - new DataFetchComponents>( + this.dataFetchComponents = Arrays.asList( + // top programs query + new DataFetchComponents<>( (dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT), (result) -> topProgramsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(), Bundle.DataSourceSummaryUserActivityPanel_noDataExists())), - new DataFetchComponents>( - (dataSource) -> topDomainsData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT), + // top domains query + new DataFetchComponents<>( + (dataSource) -> userActivityData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT), (result) -> recentDomainsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(), + Bundle.DataSourceSummaryUserActivityPanel_noDataExists())), + // top web searches query + new DataFetchComponents<>( + (dataSource) -> userActivityData.getMostRecentWebSearches(dataSource, TOP_SEARCHES_COUNT), + (result) -> topWebSearchesTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(), + Bundle.DataSourceSummaryUserActivityPanel_noDataExists())), + // top devices query + new DataFetchComponents<>( + (dataSource) -> userActivityData.getRecentDevices(dataSource, TOP_DEVICES_COUNT), + (result) -> topDevicesAttachedTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(), + Bundle.DataSourceSummaryUserActivityPanel_noDataExists())), + // top accounts query + new DataFetchComponents<>( + (dataSource) -> userActivityData.getRecentAccounts(dataSource, TOP_ACCOUNTS_COUNT), + (result) -> topAccountsTable.showDataFetchResult(result, JTablePanel.getDefaultErrorMessage(), Bundle.DataSourceSummaryUserActivityPanel_noDataExists())) ); initComponents(); } + /** + * Queries DataSourceTopProgramsSummary instance for short folder name. + * + * @param path The path for the application. + * @param appName The application name. + * + * @return The underlying short folder name if one exists. + */ + private String getShortFolderName(String path, String appName) { + return this.topProgramsData.getShortFolderName(path, appName); + } + @Override protected void onNewDataSource(DataSource dataSource) { // if no data source is present or the case is not open, @@ -194,16 +323,27 @@ public class DataSourceSummaryUserActivityPanel extends BaseDataSourceSummaryPan javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel(); javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable; + javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JLabel topWebSearchLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel topWebSearches = topWebSearchesTable; + javax.swing.Box.Filler filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JLabel topDevicesAttachedLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel recentDevicesAttached = topDevicesAttachedTable; + javax.swing.Box.Filler filler8 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JLabel recentAccountsLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler9 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel topAccounts = topAccountsTable; - setMaximumSize(null); setLayout(new java.awt.BorderLayout()); contentScrollPane.setMaximumSize(null); contentScrollPane.setMinimumSize(null); contentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); - contentPanel.setMaximumSize(new java.awt.Dimension(720, 450)); - contentPanel.setMinimumSize(new java.awt.Dimension(720, 450)); + contentPanel.setMaximumSize(new java.awt.Dimension(32767, 450)); + contentPanel.setMinimumSize(new java.awt.Dimension(10, 450)); contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS)); programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); @@ -213,9 +353,9 @@ public class DataSourceSummaryUserActivityPanel extends BaseDataSourceSummaryPan contentPanel.add(filler1); topProgramsTablePanel.setAlignmentX(0.0F); - topProgramsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187)); - topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187)); - topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187)); + topProgramsTablePanel.setMaximumSize(new java.awt.Dimension(32767, 106)); + topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); + topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); contentPanel.add(topProgramsTablePanel); contentPanel.add(filler3); @@ -225,10 +365,46 @@ public class DataSourceSummaryUserActivityPanel extends BaseDataSourceSummaryPan contentPanel.add(filler2); recentDomainsTablePanel.setAlignmentX(0.0F); - recentDomainsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187)); - recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187)); - recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187)); + recentDomainsTablePanel.setMaximumSize(new java.awt.Dimension(32767, 106)); + recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); + recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); contentPanel.add(recentDomainsTablePanel); + contentPanel.add(filler4); + + topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(topWebSearchLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.topWebSearchLabel.text")); // NOI18N + contentPanel.add(topWebSearchLabel); + contentPanel.add(filler5); + + topWebSearches.setAlignmentX(0.0F); + topWebSearches.setMaximumSize(new java.awt.Dimension(32767, 106)); + topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106)); + topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106)); + contentPanel.add(topWebSearches); + contentPanel.add(filler6); + + topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(topDevicesAttachedLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.topDevicesAttachedLabel.text")); // NOI18N + contentPanel.add(topDevicesAttachedLabel); + contentPanel.add(filler7); + + recentDevicesAttached.setAlignmentX(0.0F); + recentDevicesAttached.setMaximumSize(new java.awt.Dimension(32767, 106)); + recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106)); + recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106)); + contentPanel.add(recentDevicesAttached); + contentPanel.add(filler8); + + recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(recentAccountsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.recentAccountsLabel.text")); // NOI18N + contentPanel.add(recentAccountsLabel); + contentPanel.add(filler9); + + topAccounts.setAlignmentX(0.0F); + topAccounts.setMaximumSize(new java.awt.Dimension(32767, 106)); + topAccounts.setMinimumSize(new java.awt.Dimension(10, 106)); + topAccounts.setPreferredSize(new java.awt.Dimension(10, 106)); + contentPanel.add(topAccounts); contentScrollPane.setViewportView(contentPanel); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModelTableCellRenderer.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModelTableCellRenderer.java index cb826fefbd..aeb29b9fe9 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModelTableCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModelTableCellRenderer.java @@ -19,8 +19,11 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils; import java.awt.Component; +import java.awt.Insets; +import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JTable; +import javax.swing.border.Border; import javax.swing.table.DefaultTableCellRenderer; import org.apache.commons.lang3.StringUtils; @@ -80,6 +83,11 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer { * @return The horizontal alignment for the text in the cell. */ HorizontalAlign getHorizontalAlignment(); + + /** + * @return The insets for the cell text. + */ + Insets getInsets(); } /** @@ -90,6 +98,7 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer { private final String text; private String tooltip; private HorizontalAlign horizontalAlignment; + private Insets insets; /** * Main constructor. @@ -139,13 +148,31 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer { return this; } + @Override + public Insets getInsets() { + return insets; + } + + /** + * Sets the insets for the text within the cell + * + * @param insets The insets. + * + * @return As a utility, returns this. + */ + public DefaultCellModel setInsets(Insets insets) { + this.insets = insets; + return this; + } + @Override public String toString() { return getText(); } } - private static int DEFAULT_ALIGNMENT = JLabel.LEFT; + private static final int DEFAULT_ALIGNMENT = JLabel.LEFT; + private static final Border DEFAULT_BORDER = BorderFactory.createEmptyBorder(1, 5, 1, 5); @Override public Component getTableCellRendererComponent(JTable table, Object value, @@ -186,6 +213,14 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer { defaultCell.setToolTipText(null); } + // sets the padding for cell text within the cell. + Insets insets = cellModel.getInsets(); + if (insets != null) { + defaultCell.setBorder(BorderFactory.createEmptyBorder(insets.top, insets.left, insets.bottom, insets.right)); + } else { + defaultCell.setBorder(DEFAULT_BORDER); + } + // sets the JLabel alignment (left, center, right) or default alignment // if no alignment is specified int alignment = (cellModel.getHorizontalAlignment() == null)