mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
merge
This commit is contained in:
commit
add51c6ecd
@ -42,10 +42,11 @@ to the root 64-bit JRE directory.
|
||||
use a released version or download the latest from github:
|
||||
- git://github.com/sleuthkit/sleuthkit.git
|
||||
|
||||
2b) Build the TSK JAR file by typing 'ant PostgreSQL' in bindings/java in the
|
||||
TSK source code folder from a command line. Note it is case sensitive. You
|
||||
can also add the code to a NetBeans project and build it from there,
|
||||
selecting the PostgreSQL target.
|
||||
2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in
|
||||
bindings/java in the
|
||||
TSK source code folder from a command line. Note it is case
|
||||
sensitive. You can also add the code to a NetBeans project and build
|
||||
it from there, selecting the dist-PostgreSQL target.
|
||||
|
||||
2c) Set TSK_HOME environment variable to the root directory of TSK
|
||||
|
||||
|
@ -245,7 +245,6 @@ NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user
|
||||
NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label
|
||||
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
|
||||
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
|
||||
CaseOpenException.DatabaseSettingsIssue=Database settings:
|
||||
CaseExceptionWarning.CheckMultiUserOptions=Check Multi-user options.
|
||||
SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist!
|
||||
SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user!
|
||||
|
@ -349,7 +349,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
}
|
||||
// start listening for TSK errors for the new case
|
||||
currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
|
||||
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
|
||||
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
|
||||
doCaseChange(currentCase);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
|
||||
@ -378,7 +378,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void receiveError(String context, String errorMessage) {
|
||||
/* NOTE: We are accessing tskErrorReporter from two different threads.
|
||||
@ -464,7 +464,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
});
|
||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage()); //NON-NLS
|
||||
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
|
||||
} catch (UserPreferencesException ex) {
|
||||
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
||||
throw new CaseActionException(
|
||||
@ -581,7 +581,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
|
||||
}
|
||||
try {
|
||||
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
|
||||
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
|
||||
} catch (UserPreferencesException ex) {
|
||||
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
|
||||
throw new CaseActionException(
|
||||
@ -635,7 +635,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
} catch (IllegalStateException unused) {
|
||||
// Already logged.
|
||||
}
|
||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex); //NON-NLS
|
||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ": " + ex.getMessage(), ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
try {
|
||||
Case badCase = Case.getCurrentCase();
|
||||
@ -646,7 +646,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
});
|
||||
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage(), ex); //NON-NLS
|
||||
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -704,7 +704,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
*
|
||||
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
|
||||
* {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
|
||||
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
|
||||
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
|
||||
*/
|
||||
@Deprecated
|
||||
public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
|
||||
@ -719,13 +719,13 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
|
||||
/**
|
||||
* Finishes adding new local data source to the case. Sends out event and
|
||||
* reopens windows if needed.
|
||||
* reopens windows if needed.
|
||||
*
|
||||
* @param newDataSource new data source added
|
||||
*
|
||||
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
|
||||
* {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
|
||||
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
|
||||
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
|
||||
*/
|
||||
@Deprecated
|
||||
void addLocalDataSource(Content newDataSource) {
|
||||
|
@ -101,7 +101,7 @@ public final class CaseOpenAction implements ActionListener {
|
||||
StartupWindowProvider.getInstance().open();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,6 @@ final class NewCaseWizardAction extends CallableSystemAction {
|
||||
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
||||
String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
|
||||
CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
|
||||
|
||||
Case.create(createdDirectory, caseName, caseNumber, examiner, caseType);
|
||||
return null;
|
||||
}
|
||||
@ -121,26 +120,10 @@ final class NewCaseWizardAction extends CallableSystemAction {
|
||||
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
|
||||
try {
|
||||
get();
|
||||
CaseType currentCaseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
|
||||
CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
|
||||
|
||||
if (currentCaseType == CaseType.SINGLE_USER_CASE) {
|
||||
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
||||
addImageAction.actionPerformed(null);
|
||||
} else {
|
||||
if (info.getDbType() != DbType.SQLITE) {
|
||||
SleuthkitCase.tryConnect(info);
|
||||
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
||||
addImageAction.actionPerformed(null);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(null,
|
||||
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem1.text"),
|
||||
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem2.text"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
doFailedCaseCleanup(wizardDescriptor);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException | MissingResourceException | TskCoreException | HeadlessException | UserPreferencesException ex) {
|
||||
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
|
||||
addImageAction.actionPerformed(null);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error creating case", ex); //NON-NLS
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage() + " "
|
||||
+ NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"),
|
||||
|
@ -66,7 +66,7 @@ public class Installer extends ModuleInstall {
|
||||
//We should update this if we officially switch to a new version of CRT/compiler
|
||||
System.loadLibrary("msvcr100"); //NON-NLS
|
||||
System.loadLibrary("msvcp100"); //NON-NLS
|
||||
System.loadLibrary("msvcr120"); //NON-NLS
|
||||
|
||||
logger.log(Level.INFO, "MSVCR100 and MSVCP100 libraries loaded"); //NON-NLS
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading MSVCR100 and MSVCP100 libraries, ", e); //NON-NLS
|
||||
@ -86,6 +86,14 @@ public class Installer extends ModuleInstall {
|
||||
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
/* PostgreSQL */
|
||||
try {
|
||||
System.loadLibrary("msvcr120"); //NON-NLS
|
||||
logger.log(Level.INFO, "MSVCR 120 library loaded"); //NON-NLS
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading MSVCR120 library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
System.loadLibrary("libeay32"); //NON-NLS
|
||||
logger.log(Level.INFO, "LIBEAY32 library loaded"); //NON-NLS
|
||||
|
@ -197,6 +197,7 @@
|
||||
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
|
||||
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
|
||||
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
|
||||
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
|
||||
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
|
||||
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
|
||||
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
|
||||
|
@ -22,6 +22,7 @@ import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
|
||||
borderpane.setCenter(fxImageView);
|
||||
}
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image file into media view (EOF): {0}", file.getName()); //NON-NLS
|
||||
borderpane.setCenter(errorLabel);
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
|
||||
borderpane.setCenter(errorLabel);
|
||||
|
@ -26,6 +26,7 @@ import com.google.common.io.Files;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -135,8 +136,8 @@ public class ImageUtils {
|
||||
/**
|
||||
* thread that saves generated thumbnails to disk in the background
|
||||
*/
|
||||
private static final Executor imageSaver
|
||||
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
private static final Executor imageSaver =
|
||||
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||
.namingPattern("icon saver-%d").build());
|
||||
|
||||
public static List<String> getSupportedImageExtensions() {
|
||||
@ -524,14 +525,13 @@ public class ImageUtils {
|
||||
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
|
||||
|
||||
return null;
|
||||
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
|
||||
} catch (EOFException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
|
||||
return null;
|
||||
|
||||
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.filetypeid;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -117,7 +118,6 @@ class FileType {
|
||||
* The way the signature byte sequence should be interpreted.
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
RAW, ASCII
|
||||
};
|
||||
|
||||
@ -131,14 +131,42 @@ class FileType {
|
||||
*
|
||||
* @param signatureBytes The signature bytes.
|
||||
* @param offset The offset of the signature bytes.
|
||||
* @param type The interpretation of the signature bytes
|
||||
* (e.g., raw bytes, an ASCII string).
|
||||
* @param type The type of data in the byte array. Impacts
|
||||
* how it is displayed to the user in the UI.
|
||||
*/
|
||||
Signature(final byte[] signatureBytes, long offset, Type type) {
|
||||
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
|
||||
this.offset = offset;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file signature consisting of an ASCII string at a
|
||||
* specific offset within a file.
|
||||
*
|
||||
* @param signatureString The ASCII string
|
||||
* @param offset The offset of the signature bytes.
|
||||
*/
|
||||
Signature(String signatureString, long offset) {
|
||||
this.signatureBytes = signatureString.getBytes(StandardCharsets.US_ASCII);
|
||||
this.offset = offset;
|
||||
this.type = Type.ASCII;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file signature consisting of a sequence of bytes at a
|
||||
* specific offset within a file. If bytes correspond to an ASCII
|
||||
* string, use one of the other constructors so that the string is
|
||||
* displayed to the user instead of the raw bytes.
|
||||
*
|
||||
* @param signatureBytes The signature bytes.
|
||||
* @param offset The offset of the signature bytes.
|
||||
*/
|
||||
Signature(final byte[] signatureBytes, long offset) {
|
||||
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
|
||||
this.offset = offset;
|
||||
this.type = Type.RAW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte sequence of the signature.
|
||||
|
@ -180,20 +180,54 @@ final class UserDefinedFileTypesManager {
|
||||
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
|
||||
*/
|
||||
private void loadPredefinedFileTypes() throws UserDefinedFileTypesException {
|
||||
byte[] byteArray;
|
||||
FileType fileType;
|
||||
|
||||
try {
|
||||
FileType fileTypeXml = new FileType("text/xml", new Signature("<?xml".getBytes(ASCII_ENCODING), 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
|
||||
fileTypes.put(fileTypeXml.getMimeType(), fileTypeXml);
|
||||
|
||||
byte[] gzip = DatatypeConverter.parseHexBinary("1F8B08");
|
||||
FileType fileTypeGzip = new FileType("application/x-gzip", new Signature(gzip, 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
|
||||
fileTypes.put(fileTypeGzip.getMimeType(), fileTypeGzip);
|
||||
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
/**
|
||||
* Using an all-or-none policy.
|
||||
*/
|
||||
fileTypes.clear();
|
||||
throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.loadFileTypes.errorMessage");
|
||||
// Add rule for xml
|
||||
fileType = new FileType("text/xml", new Signature("<?xml", 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for gzip
|
||||
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
|
||||
fileType = new FileType("application/x-gzip", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .wk1
|
||||
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
|
||||
fileType = new FileType("application/x-123", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for Radiance image
|
||||
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
|
||||
fileType = new FileType("image/vnd.radiance", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .dcx image
|
||||
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
|
||||
fileType = new FileType("image/x-dcx", new Signature(byteArray, 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .ics image
|
||||
fileType = new FileType("image/x-icns", new Signature("icns", 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .pict image
|
||||
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
|
||||
fileType = new FileType("image/x-pict", new Signature(byteArray, 522L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .pam
|
||||
fileType = new FileType("image/x-portable-arbitrarymap", new Signature("P7", 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
|
||||
// Add rule for .pfm
|
||||
fileType = new FileType("image/x-portable-floatmap", new Signature("PF", 0L), "", false); //NON-NLS
|
||||
fileTypes.put(fileType.getMimeType(), fileType);
|
||||
}
|
||||
// parseHexBinary() throws this if the argument passed in is not Hex
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new UserDefinedFileTypesException("Error creating predefined file types", e); //
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,6 +249,9 @@ public class ReportProgressPanel extends javax.swing.JPanel {
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// make sure we disable an indeterminate
|
||||
reportProgressBar.setIndeterminate(false);
|
||||
|
||||
if (STATUS != ReportStatus.CANCELED) {
|
||||
switch (reportStatus) {
|
||||
case COMPLETE: {
|
||||
@ -261,6 +264,7 @@ public class ReportProgressPanel extends javax.swing.JPanel {
|
||||
// set reportProgressBar color as green.
|
||||
reportProgressBar.setForeground(new Color(50, 205, 50));
|
||||
reportProgressBar.setString("Complete"); //NON-NLS
|
||||
|
||||
break;
|
||||
}
|
||||
case ERROR: {
|
||||
|
@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
|
||||
TimelinePanel.jButton2.text=1m
|
||||
TimelinePanel.jButton3.text=3m
|
||||
TimelinePanel.jButton4.text=2w
|
||||
TimeLineTopComponent.eventsTab.name=Events
|
||||
TimeLineTopComponent.filterTab.name=Filters
|
||||
OpenTimelineAction.title=Timeline
|
||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
||||
ProgressWindow.progressHeader.text=\
|
||||
|
||||
|
@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
* intended only to remove the boilerplate initialization code when defining a
|
||||
* relatively static layout
|
||||
*
|
||||
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
|
||||
* image analyzer
|
||||
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
|
||||
*/
|
||||
public class FXMLConstructor {
|
||||
|
||||
@ -55,12 +54,28 @@ public class FXMLConstructor {
|
||||
*
|
||||
*
|
||||
* @param node a node to initialize from a loaded FXML
|
||||
* @param fxmlFileName the the file name of the FXML to load, relative to
|
||||
* the package that the class of node is defined in.
|
||||
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||
* package that the class of node is defined in.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
static public void construct(Node node, String fxmlFileName) {
|
||||
final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
||||
construct(node, node.getClass(), fxmlFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an fxml file and initialize a node with it. Since this manipulates
|
||||
* the node, it must be called on the JFX thread.
|
||||
*
|
||||
*
|
||||
* @param node a node to initialize from a loaded FXML
|
||||
* @param clazz a class to use for relative location of the fxml
|
||||
* @param fxmlFileName the file name of the FXML to load, relative to the
|
||||
* package of clazz.
|
||||
*
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
static public void construct(Node node, Class<? extends Node> clazz, String fxmlFileName) {
|
||||
final String name = "nbres:/" + StringUtils.replace(clazz.getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
|
||||
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
||||
|
@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
@ -135,6 +135,21 @@ public class TimeLineController {
|
||||
|
||||
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
||||
|
||||
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
|
||||
|
||||
/**
|
||||
* status is a string that will be displayed in the status bar as a kind of
|
||||
* user hint/information when it is not empty
|
||||
*
|
||||
* @return the status property
|
||||
*/
|
||||
public ReadOnlyStringProperty getStatusProperty() {
|
||||
return status.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setStatus(String string) {
|
||||
status.set(string);
|
||||
}
|
||||
private final Case autoCase;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@ -301,8 +316,10 @@ public class TimeLineController {
|
||||
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
|
||||
try {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
@ -347,8 +364,10 @@ public class TimeLineController {
|
||||
void rebuildTagsTable() {
|
||||
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (isWindowOpen()) {
|
||||
mainFrame.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
synchronized (eventsRepository) {
|
||||
@ -373,16 +392,19 @@ public class TimeLineController {
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||
Case.removePropertyChangeListener(caseListener);
|
||||
mainFrame.close();
|
||||
mainFrame.setVisible(false);
|
||||
mainFrame = null;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
synchronized (TimeLineController.this) {
|
||||
mainFrame.close();
|
||||
mainFrame = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show the timeline window and prompt for rebuilding database if necessary.
|
||||
*/
|
||||
synchronized void openTimeLine() {
|
||||
void openTimeLine() {
|
||||
// listen for case changes (specifically images being added, and case changes).
|
||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||
@ -524,20 +546,20 @@ public class TimeLineController {
|
||||
/**
|
||||
* private method to build gui if necessary and make it visible.
|
||||
*/
|
||||
synchronized private void showWindow() {
|
||||
if (mainFrame == null) {
|
||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent();
|
||||
}
|
||||
mainFrame.setController(this);
|
||||
}
|
||||
private void showWindow() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
mainFrame.open();
|
||||
mainFrame.setVisible(true);
|
||||
mainFrame.toFront();
|
||||
synchronized (TimeLineController.this) {
|
||||
if (mainFrame == null) {
|
||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent(this);
|
||||
}
|
||||
}
|
||||
mainFrame.open();
|
||||
mainFrame.toFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.Tab;
|
||||
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.netbeans.api.settings.ConvertAsProperties;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.ExplorerUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
@ -51,135 +50,103 @@ import org.sleuthkit.autopsy.timeline.ui.StatusBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||
|
||||
/**
|
||||
* TopComponent for the timeline feature.
|
||||
*/
|
||||
@ConvertAsProperties(
|
||||
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
|
||||
autostore = false)
|
||||
@TopComponent.Description(
|
||||
preferredID = "TimeLineTopComponent",
|
||||
//iconBase="SET/PATH/TO/ICON/HERE",
|
||||
persistenceType = TopComponent.PERSISTENCE_NEVER)
|
||||
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
|
||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI {
|
||||
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||
|
||||
private DataContentPanel dataContentPanel;
|
||||
private final DataContentPanel dataContentPanel;
|
||||
|
||||
private TimeLineResultView tlrv;
|
||||
private final TimeLineResultView tlrv;
|
||||
|
||||
private final ExplorerManager em = new ExplorerManager();
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
////jfx componenets that make up the interface
|
||||
private final FilterSetPanel filtersPanel = new FilterSetPanel();
|
||||
|
||||
private final Tab eventsTab = new Tab(
|
||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name"));
|
||||
|
||||
private final Tab filterTab = new Tab(
|
||||
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name"));
|
||||
|
||||
private final VBox leftVBox = new VBox(5);
|
||||
|
||||
private final NavPanel navPanel = new NavPanel();
|
||||
|
||||
private final StatusBar statusBar = new StatusBar();
|
||||
|
||||
private final TabPane tabPane = new TabPane();
|
||||
|
||||
private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane();
|
||||
|
||||
private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel);
|
||||
|
||||
private final SplitPane splitPane = new SplitPane();
|
||||
|
||||
private final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
|
||||
public TimeLineTopComponent() {
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
initComponents();
|
||||
|
||||
this.controller = controller;
|
||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||
|
||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
||||
|
||||
timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text"));
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
synchronized private void customizeComponents() {
|
||||
|
||||
dataContentPanel = DataContentPanel.createInstance();
|
||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||
tlrv = new TimeLineResultView(dataContentPanel);
|
||||
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||
dataResultPanel.open();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
//assemble ui componenets together
|
||||
jFXstatusPanel.setScene(new Scene(statusBar));
|
||||
jFXVizPanel.setScene(new Scene(splitPane));
|
||||
|
||||
splitPane.setDividerPositions(0);
|
||||
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setContent(filtersPanel);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
eventsTab.setClosable(false);
|
||||
eventsTab.setContent(navPanel);
|
||||
eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
|
||||
tabPane.getTabs().addAll(filterTab, eventsTab);
|
||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane);
|
||||
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
splitPane.getItems().addAll(leftVBox, visualizationPanel);
|
||||
});
|
||||
customizeFXComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
tlrv.setController(controller);
|
||||
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
void customizeFXComponents() {
|
||||
Platform.runLater(() -> {
|
||||
jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(new ActionEvent());
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(new ActionEvent());
|
||||
}
|
||||
});
|
||||
|
||||
//create and wire up jfx componenets that make up the interface
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.viewModeProperty().addListener((Observable observable) -> {
|
||||
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
||||
tabPane.getSelectionModel().select(filterTab);
|
||||
//if view mode is counts, make sure events tabd is not active
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
visualizationPanel.setController(controller);
|
||||
navPanel.setController(controller);
|
||||
filtersPanel.setController(controller);
|
||||
zoomSettingsPane.setController(controller);
|
||||
statusBar.setController(controller);
|
||||
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXVizPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
||||
jFXstatusPanel = new JFXPanel();
|
||||
splitYPane = new javax.swing.JSplitPane();
|
||||
jFXVizPanel = new javafx.embed.swing.JFXPanel();
|
||||
jFXVizPanel = new JFXPanel();
|
||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||
resultContainerPanel = new javax.swing.JPanel();
|
||||
contentViewerContainerPanel = new javax.swing.JPanel();
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 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.timeline;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface TimeLineUI {
|
||||
|
||||
void setController(TimeLineController controller);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 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.timeline;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by views of the data.
|
||||
*
|
||||
* Most implementations should install the relevant listeners in their
|
||||
* {@link #setController} and {@link #setModel} methods
|
||||
*/
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
|
||||
public interface TimeLineView extends TimeLineUI {
|
||||
|
||||
@Override
|
||||
void setController(TimeLineController controller);
|
||||
|
||||
void setModel(final FilteredEventsModel filteredEvents);
|
||||
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
Back.actions.name.text=Back
|
||||
DefaultFilters.action.name.text=apply default filters
|
||||
Forward.action.name.text=Forward
|
||||
SaveSnapshot.action.name.text=save snapshot
|
||||
SaveSnapshot.fileChoose.title.text=Save snapshot to
|
||||
ZoomOut.action.name.text=apply default filters
|
||||
|
||||
|
@ -46,51 +46,48 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class SaveSnapshot extends Action {
|
||||
public class SaveSnapshotAsReport extends Action {
|
||||
|
||||
private static final String HTML_EXT = ".html";
|
||||
|
||||
private static final String REPORT_IMAGE_EXTENSION = ".png";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshot.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final WritableImage snapshot;
|
||||
|
||||
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
|
||||
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
|
||||
this.controller = controller;
|
||||
this.snapshot = snapshot;
|
||||
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
|
||||
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
|
||||
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
|
||||
super(Bundle.SaveSnapshot_action_name_text());
|
||||
setEventHandler(new Consumer<ActionEvent>() {
|
||||
|
||||
@Override
|
||||
public void accept(ActionEvent t) {
|
||||
//choose location/name
|
||||
DirectoryChooser fileChooser = new DirectoryChooser();
|
||||
fileChooser.setTitle(NbBundle.getMessage(this.getClass(), "SaveSnapshot.fileChoose.title.text"));
|
||||
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
|
||||
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
|
||||
File outFolder = fileChooser.showDialog(null);
|
||||
if (outFolder == null) {
|
||||
File reportDirectory = fileChooser.showDialog(null);
|
||||
if (reportDirectory == null) {
|
||||
return;
|
||||
}
|
||||
outFolder.mkdir();
|
||||
String name = outFolder.getName();
|
||||
reportDirectory.mkdir();
|
||||
String reportName = reportDirectory.getName();
|
||||
String reportPath = reportDirectory.getPath();
|
||||
|
||||
//gather metadata
|
||||
List<Pair<String, String>> reportMetaData = new ArrayList<>();
|
||||
|
||||
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
||||
|
||||
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
|
||||
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
||||
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
|
||||
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
|
||||
|
||||
//save snapshot as png
|
||||
try {
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File(outFolder.getPath() + File.separator + outFolder.getName() + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
|
||||
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
|
||||
return;
|
||||
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
|
||||
//build html string
|
||||
StringBuilder wrapper = new StringBuilder();
|
||||
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
|
||||
wrapper.append("<div id=\"content\">\n<h1>").append(outFolder.getName()).append("</h1>\n"); // NON-NLS
|
||||
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
|
||||
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
|
||||
wrapper.append("<table>\n"); // NON-NLS
|
||||
for (Pair<String, String> pair : reportMetaData) {
|
||||
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
|
||||
}
|
||||
wrapper.append("</table>\n"); // NON-NLS
|
||||
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
|
||||
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
|
||||
|
||||
//write html wrapper
|
||||
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
|
||||
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
|
||||
htmlWriter.write(wrapper.toString());
|
||||
} catch (FileNotFoundException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
|
||||
@ -121,14 +119,14 @@ public class SaveSnapshot extends Action {
|
||||
|
||||
//copy css
|
||||
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
|
||||
Files.copy(resource, Paths.get(outFolder.getPath(), "index.css")); // NON-NLS
|
||||
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
|
||||
}
|
||||
|
||||
//add html file as report to case
|
||||
try {
|
||||
Case.getCurrentCase().addReport(outFolder.getPath() + File.separator + outFolder.getName() + HTML_EXT, "Timeline", outFolder.getName() + HTML_EXT); // NON-NLS
|
||||
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
|
||||
}
|
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
44
Core/src/org/sleuthkit/autopsy/timeline/actions/ZoomIn.java
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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.timeline.actions;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomIn extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.",
|
||||
"ZoomIn.action.text=Zoom in"})
|
||||
public ZoomIn(TimeLineController controller) {
|
||||
super(Bundle.ZoomIn_action_text());
|
||||
setLongText(Bundle.ZoomIn_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_IN));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.pushZoomInTime();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,7 +19,8 @@
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
*/
|
||||
public class ZoomOut extends Action {
|
||||
|
||||
private final TimeLineController controller;
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
|
||||
|
||||
private final FilteredEventsModel eventsModel;
|
||||
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
|
||||
"ZoomOut.action.text=Zoom out"})
|
||||
public ZoomOut(TimeLineController controller) {
|
||||
super(Bundle.ZoomOut_action_text());
|
||||
setLongText(Bundle.ZoomOut_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.pushZoomOutTime();
|
||||
});
|
||||
|
||||
public ZoomOut(final TimeLineController controller) {
|
||||
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
|
||||
this.controller = controller;
|
||||
eventsModel = controller.getEventsModel();
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
}
|
||||
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
|
||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
}
|
||||
});
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
controller.zoomOutToActivity();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 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.timeline.actions;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ZoomToEvents extends Action {
|
||||
|
||||
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N
|
||||
|
||||
@NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events",
|
||||
"ZoomToEvents.longText=Zoom out to show the nearest events."})
|
||||
public ZoomToEvents(final TimeLineController controller) {
|
||||
super(Bundle.ZoomToEvents_action_text());
|
||||
setLongText(Bundle.ZoomToEvents_longText());
|
||||
setGraphic(new ImageView(MAGNIFIER_OUT));
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.zoomOutToActivity();
|
||||
});
|
||||
|
||||
//disable action when the current time range already encompases the entire case.
|
||||
disabledProperty().bind(new BooleanBinding() {
|
||||
private final FilteredEventsModel eventsModel = controller.getEventsModel();
|
||||
|
||||
{
|
||||
bind(eventsModel.zoomParametersProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
//TODO: do a db query to see if using this action will actually result in viewable events
|
||||
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/cross-script.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 623 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 800 B |
@ -40,6 +40,7 @@ import javafx.scene.chart.Chart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.Effect;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
@ -73,8 +73,15 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
|
||||
* common history context menu items out of derived classes? -jm
|
||||
*/
|
||||
public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
|
||||
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane {
|
||||
|
||||
@NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.")
|
||||
private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text());
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
|
||||
|
||||
public static Tooltip getDragTooltip() {
|
||||
return DRAG_TOOLTIP;
|
||||
}
|
||||
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
|
||||
|
||||
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
|
||||
@ -93,11 +100,15 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
protected TimeLineController controller;
|
||||
final protected TimeLineController controller;
|
||||
|
||||
protected FilteredEventsModel filteredEvents;
|
||||
final protected FilteredEventsModel filteredEvents;
|
||||
|
||||
protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
||||
final protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
|
||||
|
||||
private InvalidationListener invalidationListener = (Observable observable) -> {
|
||||
update();
|
||||
};
|
||||
|
||||
public ReadOnlyListProperty<N> getSelectedNodes() {
|
||||
return selectedNodes.getReadOnlyProperty();
|
||||
@ -177,7 +188,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
* Primarily this invokes the background {@link Task} returned by
|
||||
* {@link #getUpdateTask()} which derived classes must implement.
|
||||
*/
|
||||
synchronized public void update() {
|
||||
final synchronized public void update() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
try {
|
||||
this.hasEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NOI18N
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
controller.monitorTask(updateTask);
|
||||
}
|
||||
|
||||
synchronized public void dispose() {
|
||||
final synchronized public void dispose() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
}
|
||||
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
invalidationListener = null;
|
||||
}
|
||||
|
||||
protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
|
||||
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
this.controller = controller;
|
||||
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||
this.leafPane = partPane;
|
||||
this.branchPane = contextPane;
|
||||
this.spacer = spacer;
|
||||
@ -226,32 +242,18 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
chart.setController(controller);
|
||||
|
||||
setModel(controller.getEventsModel());
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
update();
|
||||
TimeLineController.getTimeZone().addListener(invalidationListener);
|
||||
|
||||
//show tooltip text in status bar
|
||||
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
|
||||
if (newActivated) {
|
||||
controller.setStatus(DRAG_TOOLTIP.getText());
|
||||
} else {
|
||||
controller.setStatus("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) {
|
||||
|
||||
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
|
||||
}
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.registerForEvents(this);
|
||||
filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||
}
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
update();
|
||||
}
|
||||
|
||||
protected InvalidationListener invalidationListener = (Observable observable) -> {
|
||||
update();
|
||||
};
|
||||
|
||||
/**
|
||||
* iterate through the list of tick-marks building a two level structure of
|
||||
* replacement tick marl labels. (Visually) upper level has most
|
||||
@ -370,7 +368,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
*/
|
||||
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
|
||||
|
||||
Text label = new Text(" " + labelText + " ");
|
||||
Text label = new Text(" " + labelText + " "); //NOI18N
|
||||
label.setTextAlignment(TextAlignment.CENTER);
|
||||
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
|
||||
//position label accounting for width
|
||||
@ -414,9 +412,9 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
label.relocate(labelX, 0);
|
||||
|
||||
if (labelX == 0) { // first label has no border
|
||||
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
|
||||
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||
} else { // subsequent labels have border on left to create dividers
|
||||
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
|
||||
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS //NOI18N
|
||||
}
|
||||
|
||||
branchPane.getChildren().add(label);
|
||||
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
|
||||
|
||||
TwoPartDateTime(String dateString) {
|
||||
//find index of separator to spit on
|
||||
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
|
||||
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NOI18N
|
||||
if (splitIndex < 0) { // there is only one part
|
||||
leaf = dateString;
|
||||
branch = "";
|
||||
branch = ""; //NOI18N
|
||||
} else { //split at index
|
||||
leaf = StringUtils.substring(dateString, splitIndex + 1);
|
||||
branch = StringUtils.substring(dateString, 0, splitIndex);
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
|
||||
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||
Timeline.ui.ZoomRanges.all.text=All
|
||||
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
|
||||
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||
VisualizationPanel.histogramTask.preparing=preparing
|
||||
@ -56,4 +55,4 @@ VisualizationPanel.zoomMenuButton.text=Zoom in/out to
|
||||
VisualizationPanel.snapShotButton.text=Screenshot
|
||||
VisualizationPanel.detailsToggle.text=Details
|
||||
VisualizationPanel.countsToggle.text=Counts
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
@ -0,0 +1,16 @@
|
||||
.intervalSelector{
|
||||
-fx-background-color: rgba(0,0,255,.25);
|
||||
-fx-border-color: rgba(0,0,255,.25);
|
||||
-fx-border-width: 0 3 0 3;
|
||||
}
|
||||
|
||||
.closeButton{
|
||||
}
|
||||
|
||||
.closeButton:hover{
|
||||
-fx-opacity: 1;
|
||||
}
|
||||
|
||||
.zoomButton:hover{
|
||||
-fx-opacity: 1;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" styleClass="intervalSelector" stylesheets="@IntervalSelector.css" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<Button fx:id="closeButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" opacity="0.5" prefHeight="16.0" prefWidth="16.0" styleClass="closeButton" BorderPane.alignment="CENTER_RIGHT">
|
||||
<graphic>
|
||||
<ImageView opacity="0.5" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/cross-script.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</top>
|
||||
<padding>
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</padding>
|
||||
<bottom>
|
||||
<BorderPane fx:id="bottomBorder" minHeight="32.0" BorderPane.alignment="BOTTOM_LEFT">
|
||||
<left>
|
||||
<HBox alignment="BOTTOM_LEFT" BorderPane.alignment="BOTTOM_LEFT">
|
||||
<children>
|
||||
<Label fx:id="startLabel" alignment="BOTTOM_LEFT" minWidth="0.0" text="Label" />
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
</children>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox alignment="BOTTOM_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Label fx:id="endLabel" minWidth="0.0" text="Label" />
|
||||
</children>
|
||||
</HBox>
|
||||
</right>
|
||||
<center>
|
||||
<Button fx:id="zoomButton" alignment="CENTER" mnemonicParsing="false" opacity="0.66" styleClass="zoomButton" text="Zoom" BorderPane.alignment="CENTER">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/magnifier-zoom-fit.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</fx:root>
|
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
316
Core/src/org/sleuthkit/autopsy/timeline/ui/IntervalSelector.java
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 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.timeline.ui;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
|
||||
/**
|
||||
* Visually represents a 'selected' time range, and allows mouse interactions
|
||||
* with it.
|
||||
*
|
||||
* @param <X> the type of values along the x axis this is a selector for
|
||||
*
|
||||
* This abstract class requires concrete implementations to implement template
|
||||
* methods to handle formating and date 'lookup' of the generic x-axis type
|
||||
*/
|
||||
public abstract class IntervalSelector<X> extends BorderPane {
|
||||
|
||||
private static final Image ClEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true);
|
||||
private static final Image ZOOM_TO_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-fit.png", 16, 16, true, true, true);
|
||||
private static final double STROKE_WIDTH = 3;
|
||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
||||
|
||||
/**
|
||||
* the Axis this is a selector over
|
||||
*/
|
||||
public final TimeLineChart<X> chart;
|
||||
|
||||
private Tooltip tooltip;
|
||||
/////////drag state
|
||||
private DragPosition dragPosition;
|
||||
private double startLeft;
|
||||
private double startDragX;
|
||||
private double startWidth;
|
||||
|
||||
private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
|
||||
/////////end drag state
|
||||
private final TimeLineController controller;
|
||||
|
||||
@FXML
|
||||
private Label startLabel;
|
||||
|
||||
@FXML
|
||||
private Label endLabel;
|
||||
|
||||
@FXML
|
||||
private Button closeButton;
|
||||
|
||||
@FXML
|
||||
private Button zoomButton;
|
||||
|
||||
@FXML
|
||||
private BorderPane bottomBorder;
|
||||
|
||||
public IntervalSelector(TimeLineChart<X> chart) {
|
||||
this.chart = chart;
|
||||
this.controller = chart.getController();
|
||||
FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
|
||||
|
||||
setMaxHeight(USE_PREF_SIZE);
|
||||
setMinHeight(USE_PREF_SIZE);
|
||||
setMaxWidth(USE_PREF_SIZE);
|
||||
setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
BooleanBinding showingControls = hoverProperty().and(isDragging.not());
|
||||
closeButton.visibleProperty().bind(showingControls);
|
||||
closeButton.managedProperty().bind(showingControls);
|
||||
zoomButton.visibleProperty().bind(showingControls);
|
||||
zoomButton.managedProperty().bind(showingControls);
|
||||
|
||||
widthProperty().addListener(o -> {
|
||||
IntervalSelector.this.updateStartAndEnd();
|
||||
if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth()) {
|
||||
this.setCenter(zoomButton);
|
||||
} else {
|
||||
bottomBorder.setCenter(zoomButton);
|
||||
}
|
||||
BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
|
||||
});
|
||||
layoutXProperty().addListener(o -> this.updateStartAndEnd());
|
||||
updateStartAndEnd();
|
||||
|
||||
setOnMouseMoved(mouseMove -> {
|
||||
Point2D parentMouse = getLocalMouseCoords(mouseMove);
|
||||
final double diffX = getLayoutX() - parentMouse.getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
setCursor(Cursor.W_RESIZE);
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.HAND);
|
||||
}
|
||||
mouseMove.consume();
|
||||
});
|
||||
|
||||
setOnMousePressed(mousePress -> {
|
||||
Point2D parentMouse = getLocalMouseCoords(mousePress);
|
||||
final double diffX = getLayoutX() - parentMouse.getX();
|
||||
startDragX = mousePress.getScreenX();
|
||||
startWidth = getWidth();
|
||||
startLeft = getLayoutX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.LEFT;
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.RIGHT;
|
||||
} else {
|
||||
dragPosition = IntervalSelector.DragPosition.CENTER;
|
||||
}
|
||||
mousePress.consume();
|
||||
});
|
||||
|
||||
setOnMouseReleased(mouseRelease -> isDragging.set(false));
|
||||
setOnMouseDragged(mouseDrag -> {
|
||||
isDragging.set(true);
|
||||
double dX = mouseDrag.getScreenX() - startDragX;
|
||||
switch (dragPosition) {
|
||||
case CENTER:
|
||||
setLayoutX(startLeft + dX);
|
||||
break;
|
||||
case LEFT:
|
||||
if (dX > startWidth) {
|
||||
startDragX = mouseDrag.getScreenX();
|
||||
startWidth = 0;
|
||||
dragPosition = DragPosition.RIGHT;
|
||||
} else {
|
||||
setLayoutX(startLeft + dX);
|
||||
setPrefWidth(startWidth - dX);
|
||||
autosize();
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
Point2D parentMouse = getLocalMouseCoords(mouseDrag);
|
||||
if (parentMouse.getX() < startLeft) {
|
||||
dragPosition = DragPosition.LEFT;
|
||||
startDragX = mouseDrag.getScreenX();
|
||||
startWidth = 0;
|
||||
} else {
|
||||
setPrefWidth(startWidth + dX);
|
||||
autosize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
mouseDrag.consume();
|
||||
});
|
||||
|
||||
ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
|
||||
ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
|
||||
|
||||
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
|
||||
setOnMouseClicked(mosueClick -> {
|
||||
if (mosueClick.getButton() == MouseButton.SECONDARY) {
|
||||
chart.clearIntervalSelector();
|
||||
mosueClick.consume();
|
||||
}
|
||||
if (mosueClick.getClickCount() >= 2) {
|
||||
zoomToSelectedInterval();
|
||||
mosueClick.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
|
||||
return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
|
||||
}
|
||||
|
||||
private void zoomToSelectedInterval() {
|
||||
//convert to DateTimes, using max/min if null(off axis)
|
||||
DateTime start = parseDateTime(getSpanStart());
|
||||
DateTime end = parseDateTime(getSpanEnd());
|
||||
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
|
||||
controller.pushTimeRange(i);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i the interval represented by this selector
|
||||
*
|
||||
* @return a modified version of {@code i} adjusted to suite the needs of
|
||||
* the concrete implementation
|
||||
*/
|
||||
protected abstract Interval adjustInterval(Interval i);
|
||||
|
||||
/**
|
||||
* format a string representation of the given x-axis value to use in the
|
||||
* tooltip
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a string representation of the given x-axis value
|
||||
*/
|
||||
protected abstract String formatSpan(final X date);
|
||||
|
||||
/**
|
||||
* parse an x-axis value to a {@link DateTime}
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a {@link DateTime} corresponding to the given x-axis value
|
||||
*/
|
||||
protected abstract DateTime parseDateTime(X date);
|
||||
|
||||
@NbBundle.Messages(value = {"# {0} - start timestamp",
|
||||
"# {1} - end timestamp",
|
||||
"Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
|
||||
private void updateStartAndEnd() {
|
||||
String startString = formatSpan(getSpanStart());
|
||||
String endString = formatSpan(getSpanEnd());
|
||||
startLabel.setText(startString);
|
||||
endLabel.setText(endString);
|
||||
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the left edge of the
|
||||
* selector
|
||||
*/
|
||||
public X getSpanEnd() {
|
||||
return getValueForDisplay(getBoundsInParent().getMaxX());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the right edge of the
|
||||
* selector
|
||||
*/
|
||||
public X getSpanStart() {
|
||||
return getValueForDisplay(getBoundsInParent().getMinX());
|
||||
}
|
||||
|
||||
private X getValueForDisplay(final double display) {
|
||||
return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* enum to represent whether the drag is a left/right-edge modification or a
|
||||
* horizontal slide triggered by dragging the center
|
||||
*/
|
||||
private enum DragPosition {
|
||||
|
||||
LEFT,
|
||||
CENTER,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private class ZoomToSelectedIntervalAction extends Action {
|
||||
|
||||
@NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
|
||||
ZoomToSelectedIntervalAction() {
|
||||
super(Bundle.IntervalSelector_ZoomAction_name());
|
||||
setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
zoomToSelectedInterval();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ClearSelectedIntervalAction extends Action {
|
||||
|
||||
@NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
|
||||
ClearSelectedIntervalAction() {
|
||||
super("");
|
||||
setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
|
||||
setGraphic(new ImageView(ClEAR_INTERVAL_ICON));
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
chart.clearIntervalSelector();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<Label fx:id="refreshLabel">
|
||||
<graphic>
|
||||
@ -17,6 +17,15 @@
|
||||
</graphic>
|
||||
</Label>
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/information-gray.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -28,19 +28,21 @@ import javafx.scene.layout.Region;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineUI;
|
||||
|
||||
/**
|
||||
* simple status bar that only shows one possible message determined by
|
||||
* {@link TimeLineController#newEventsFlag}
|
||||
*/
|
||||
public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
public class StatusBar extends ToolBar {
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
@FXML
|
||||
private Label refreshLabel;
|
||||
|
||||
@FXML
|
||||
private Label statusLabel;
|
||||
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
public StatusBar() {
|
||||
public StatusBar(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@ -70,15 +73,16 @@ public class StatusBar extends ToolBar implements TimeLineUI {
|
||||
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
|
||||
taskLabel.setVisible(false);
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
|
||||
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
|
||||
taskLabel.textProperty().bind(this.controller.getTaskTitle());
|
||||
messageLabel.textProperty().bind(this.controller.getMessage());
|
||||
progressBar.progressProperty().bind(this.controller.getProgress());
|
||||
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
|
||||
|
||||
statusLabel.textProperty().bind(this.controller.getStatusProperty());
|
||||
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,28 +19,27 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.Chart;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
|
||||
/**
|
||||
* Interface for TimeLineViews that are 'charts'.
|
||||
*
|
||||
* @param <X> the type of values along the horizontal axis
|
||||
*/
|
||||
public interface TimeLineChart<X> extends TimeLineView {
|
||||
public interface TimeLineChart<X> {
|
||||
|
||||
// void setController(TimeLineController controller);
|
||||
IntervalSelector<? extends X> getIntervalSelector();
|
||||
|
||||
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
|
||||
@ -49,12 +48,9 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
* derived classes should implement this so as to supply an appropriate
|
||||
* subclass of {@link IntervalSelector}
|
||||
*
|
||||
* @param x the initial x position of the new interval selector
|
||||
* @param axis the axis the new interval selector will be over
|
||||
*
|
||||
* @return a new interval selector
|
||||
*/
|
||||
IntervalSelector<X> newIntervalSelector(double x, Axis<X> axis);
|
||||
IntervalSelector<X> newIntervalSelector();
|
||||
|
||||
/**
|
||||
* clear any references to previous interval selectors , including removing
|
||||
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
*/
|
||||
void clearIntervalSelector();
|
||||
|
||||
public Axis<X> getXAxis();
|
||||
|
||||
public TimeLineController getController();
|
||||
|
||||
ContextMenu getChartContextMenu();
|
||||
|
||||
ContextMenu getChartContextMenu(MouseEvent m);
|
||||
|
||||
/**
|
||||
* drag handler class used by {@link TimeLineChart}s to create
|
||||
* {@link IntervalSelector}s
|
||||
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
|
||||
* @param <X> the type of values along the horizontal axis
|
||||
* @param <Y> the type of chart this is a drag handler for
|
||||
*/
|
||||
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
|
||||
private final Y chart;
|
||||
|
||||
private final Axis<X> dateAxis;
|
||||
|
||||
private double startX; //hanlder mainstains position of drag start
|
||||
|
||||
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
|
||||
public ChartDragHandler(Y chart) {
|
||||
this.chart = chart;
|
||||
this.dateAxis = dateAxis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent t) {
|
||||
if (t.getButton() == MouseButton.SECONDARY) {
|
||||
|
||||
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
|
||||
//caputure x-position, incase we are repositioning existing selector
|
||||
startX = t.getX();
|
||||
chart.setCursor(Cursor.E_RESIZE);
|
||||
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
|
||||
if (chart.getIntervalSelector() == null) {
|
||||
//make new interval selector
|
||||
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
|
||||
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
|
||||
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
|
||||
if (event.getButton() == MouseButton.SECONDARY) {
|
||||
chart.clearIntervalSelector();
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
startX = t.getX();
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
|
||||
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
|
||||
//caputure x-position, incase we are repositioning existing selector
|
||||
startX = mouseEvent.getX();
|
||||
chart.setCursor(Cursor.H_RESIZE);
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
|
||||
if (chart.getIntervalSelector() == null) {
|
||||
//make new interval selector
|
||||
chart.setIntervalSelector(chart.newIntervalSelector());
|
||||
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
|
||||
startX = mouseEvent.getX();
|
||||
chart.getIntervalSelector().relocate(startX, 0);
|
||||
} else {
|
||||
//resize/position existing selector
|
||||
if (mouseEvent.getX() > startX) {
|
||||
chart.getIntervalSelector().relocate(startX, 0);
|
||||
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
|
||||
} else {
|
||||
//resize/position existing selector
|
||||
if (t.getX() > startX) {
|
||||
chart.getIntervalSelector().setX(startX);
|
||||
chart.getIntervalSelector().setWidth(t.getX() - startX);
|
||||
} else {
|
||||
chart.getIntervalSelector().setX(t.getX());
|
||||
chart.getIntervalSelector().setWidth(startX - t.getX());
|
||||
}
|
||||
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
|
||||
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
|
||||
}
|
||||
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
t.consume();
|
||||
chart.getIntervalSelector().autosize();
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
|
||||
chart.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MouseClickedHandler<X, C extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
|
||||
|
||||
private final C chart;
|
||||
|
||||
public MouseClickedHandler(C chart) {
|
||||
this.chart = chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent clickEvent) {
|
||||
if (chart.getChartContextMenu() != null) {
|
||||
chart.getChartContextMenu().hide();
|
||||
}
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
chart.getChartContextMenu(clickEvent);
|
||||
chart.setOnMouseMoved(this);
|
||||
chart.getChartContextMenu().show(chart, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visually represents a 'selected' time range, and allows mouse
|
||||
* interactions with it.
|
||||
*
|
||||
* @param <X> the type of values along the x axis this is a selector for
|
||||
*
|
||||
* This abstract class requires concrete implementations to implement hook
|
||||
* methods to handle formating and date 'lookup' of the generic x-axis type
|
||||
* enum to represent whether the drag is a left/right-edge modification or a
|
||||
* horizontal slide triggered by dragging the center
|
||||
*/
|
||||
static abstract class IntervalSelector<X> extends Rectangle {
|
||||
enum DragPosition {
|
||||
|
||||
private static final double STROKE_WIDTH = 3;
|
||||
LEFT,
|
||||
CENTER,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
private static final double HALF_STROKE = STROKE_WIDTH / 2;
|
||||
|
||||
/**
|
||||
* the Axis this is a selector over
|
||||
*/
|
||||
private final Axis<X> dateAxis;
|
||||
|
||||
protected Tooltip tooltip;
|
||||
|
||||
/////////drag state
|
||||
private DragPosition dragPosition;
|
||||
|
||||
private double startLeft;
|
||||
|
||||
private double startX;
|
||||
|
||||
private double startWidth;
|
||||
/////////end drag state
|
||||
|
||||
/**
|
||||
*
|
||||
* @param x the initial x position of this selector
|
||||
* @param height the initial height of this selector
|
||||
* @param axis the {@link Axis<X>} this is a selector over
|
||||
* @param controller the controller to invoke when this selector is
|
||||
* double clicked
|
||||
*/
|
||||
public IntervalSelector(double x, double height, Axis<X> axis, TimeLineController controller) {
|
||||
super(x, 0, x, height);
|
||||
dateAxis = axis;
|
||||
setStroke(Color.BLUE);
|
||||
setStrokeWidth(STROKE_WIDTH);
|
||||
setFill(Color.BLUE.deriveColor(0, 1, 1, 0.5));
|
||||
setOpacity(0.5);
|
||||
widthProperty().addListener(o -> {
|
||||
setTooltip();
|
||||
});
|
||||
xProperty().addListener(o -> {
|
||||
setTooltip();
|
||||
});
|
||||
setTooltip();
|
||||
|
||||
setOnMouseMoved((MouseEvent event) -> {
|
||||
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
|
||||
final double diffX = getX() - localMouse.getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
setCursor(Cursor.E_RESIZE);
|
||||
} else {
|
||||
setCursor(Cursor.HAND);
|
||||
}
|
||||
});
|
||||
setOnMousePressed((MouseEvent event) -> {
|
||||
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
|
||||
final double diffX = getX() - localMouse.getX();
|
||||
startX = event.getX();
|
||||
startWidth = getWidth();
|
||||
startLeft = getX();
|
||||
if (Math.abs(diffX) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.LEFT;
|
||||
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
|
||||
dragPosition = IntervalSelector.DragPosition.RIGHT;
|
||||
} else {
|
||||
dragPosition = IntervalSelector.DragPosition.CENTER;
|
||||
}
|
||||
});
|
||||
setOnMouseDragged((MouseEvent event) -> {
|
||||
double dX = event.getX() - startX;
|
||||
switch (dragPosition) {
|
||||
case CENTER:
|
||||
setX(startLeft + dX);
|
||||
break;
|
||||
case LEFT:
|
||||
setX(startLeft + dX);
|
||||
setWidth(startWidth - dX);
|
||||
break;
|
||||
case RIGHT:
|
||||
setWidth(startWidth + dX);
|
||||
break;
|
||||
}
|
||||
});
|
||||
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
|
||||
|
||||
public void handle(MouseEvent event) {
|
||||
if (event.getClickCount() >= 2) {
|
||||
//convert to DateTimes, using max/min if null(off axis)
|
||||
DateTime start = parseDateTime(getSpanStart());
|
||||
DateTime end = parseDateTime(getSpanEnd());
|
||||
|
||||
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
|
||||
|
||||
controller.pushTimeRange(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i the interval represented by this selector
|
||||
*
|
||||
* @return a modified version of {@code i} adjusted to suite the needs
|
||||
* of the concrete implementation
|
||||
*/
|
||||
protected abstract Interval adjustInterval(Interval i);
|
||||
|
||||
/**
|
||||
* format a string representation of the given x-axis value to use in
|
||||
* the tooltip
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a string representation of the given x-axis value
|
||||
*/
|
||||
protected abstract String formatSpan(final X date);
|
||||
|
||||
/**
|
||||
* parse an x-axis value to a {@link DateTime}
|
||||
*
|
||||
* @param date a x-axis value of type X
|
||||
*
|
||||
* @return a {@link DateTime} corresponding to the given x-axis value
|
||||
*/
|
||||
protected abstract DateTime parseDateTime(X date);
|
||||
|
||||
private void setTooltip() {
|
||||
final X start = getSpanStart();
|
||||
final X end = getSpanEnd();
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
tooltip = new Tooltip(
|
||||
NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start),
|
||||
formatSpan(end)));
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the left edge of
|
||||
* the selector
|
||||
*/
|
||||
public X getSpanEnd() {
|
||||
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMaxX(), 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value along the x-axis corresponding to the right edge of
|
||||
* the selector
|
||||
*/
|
||||
public X getSpanStart() {
|
||||
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMinX(), 0).getX());
|
||||
}
|
||||
|
||||
/**
|
||||
* enum to represent whether the drag is a left/right-edge modification
|
||||
* or a horizontal slide triggered by dragging the center
|
||||
*/
|
||||
private enum DragPosition {
|
||||
|
||||
LEFT, CENTER, RIGHT
|
||||
}
|
||||
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
|
||||
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
|
||||
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
|
||||
new Back(controller),
|
||||
new Forward(controller));
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
|
||||
/**
|
||||
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
||||
@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
||||
*/
|
||||
public class TimeLineResultView implements TimeLineView {
|
||||
public class TimeLineResultView {
|
||||
|
||||
/**
|
||||
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||
*/
|
||||
private final DataResultPanel dataResultPanel;
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||
|
||||
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
|
||||
return dataResultPanel;
|
||||
}
|
||||
|
||||
public TimeLineResultView(DataContent dataContent) {
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
}
|
||||
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||
|
||||
/**
|
||||
* Set the Controller for this class. Also sets the model provided by the
|
||||
* controller as the model for this view.
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
|
||||
//set up listeners on relevant properties
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
|
||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
|
||||
setModel(controller.getEventsModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Model for this View
|
||||
*
|
||||
* @param filteredEvents
|
||||
*/
|
||||
@Override
|
||||
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,10 @@ import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
|
||||
public void initialize() {
|
||||
|
||||
setText(Bundle.TimeZonePanel_title());
|
||||
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
|
||||
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
|
||||
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));
|
||||
|
@ -9,7 +9,7 @@
|
||||
<?import jfxtras.scene.control.*?>
|
||||
<?import org.controlsfx.control.*?>
|
||||
|
||||
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<items>
|
||||
@ -115,7 +115,7 @@
|
||||
</Separator>
|
||||
<HBox>
|
||||
<children>
|
||||
<Button fx:id="zoomOutButton" mnemonicParsing="false">
|
||||
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -127,7 +127,7 @@
|
||||
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
|
||||
</HBox.margin>
|
||||
</Button>
|
||||
<Button fx:id="zoomInButton" mnemonicParsing="false">
|
||||
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -143,7 +143,7 @@
|
||||
</HBox>
|
||||
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/magnifier-left.png" />
|
||||
</image>
|
||||
|
@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.effect.Lighting;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import org.controlsfx.control.NotificationPane;
|
||||
import org.controlsfx.control.RangeSlider;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.Interval;
|
||||
@ -74,11 +72,12 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||
@ -86,18 +85,18 @@ import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refres
|
||||
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
|
||||
* hold settings widgets supplied by contained {@link AbstractVisualization},
|
||||
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
|
||||
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
|
||||
* and the histogram / timeselection on bottom. Also supplies containers for
|
||||
* replacement axis to contained {@link AbstractVisualization}
|
||||
* replacement axis to contained {@link AbstractAbstractVisualization}
|
||||
*
|
||||
* TODO: refactor common code out of histogram and CountsView? -jm
|
||||
*/
|
||||
public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
||||
@ -107,9 +106,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
@GuardedBy("this")
|
||||
private LoggedTask<Void> histogramTask;
|
||||
|
||||
private final NavPanel navPanel;
|
||||
private final EventsTree eventsTree;
|
||||
|
||||
private AbstractVisualization<?, ?, ?, ?> visualization;
|
||||
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||
|
||||
//// range slider and histogram componenets
|
||||
@FXML
|
||||
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
private final ChangeListener<Object> rangeSliderListener
|
||||
= (observable1, oldValue, newValue) -> {
|
||||
private final ChangeListener<Object> rangeSliderListener =
|
||||
(observable1, oldValue, newValue) -> {
|
||||
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
|
||||
Long minTime = filteredEvents.getMinTime() * 1000;
|
||||
controller.pushTimeRange(new Interval(
|
||||
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
static private final Lighting lighting = new Lighting();
|
||||
|
||||
public VisualizationPanel(NavPanel navPanel) {
|
||||
this.navPanel = navPanel;
|
||||
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
|
||||
this.controller = controller;
|
||||
this.eventsTree = eventsTree;
|
||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
||||
protected void initialize() {
|
||||
void initialize() {
|
||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
@ -289,32 +289,30 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
}
|
||||
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||
|
||||
zoomOutButton.setOnAction(e -> {
|
||||
controller.pushZoomOutTime();
|
||||
});
|
||||
zoomInButton.setOnAction(e -> {
|
||||
controller.pushZoomInTime();
|
||||
});
|
||||
|
||||
snapShotButton.setOnAction((ActionEvent event) -> {
|
||||
//take snapshot
|
||||
final SnapshotParameters snapshotParameters = new SnapshotParameters();
|
||||
snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
|
||||
visualization.getBoundsInParent().getWidth(),
|
||||
contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
|
||||
));
|
||||
WritableImage snapshot = this.snapshot(snapshotParameters, null);
|
||||
//pass snapshot to save action
|
||||
new SaveSnapshot(controller, snapshot).handle(event);
|
||||
});
|
||||
snapShotButton.setOnAction(event ->
|
||||
this.snapshot(snapShotResult -> {
|
||||
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
|
||||
return null;
|
||||
}, null, null)
|
||||
);
|
||||
|
||||
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
||||
}
|
||||
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||
}
|
||||
if (this.filteredEvents != controller.getEventsModel()) {
|
||||
controller.getEventsModel().registerForEvents(this);
|
||||
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
|
||||
}
|
||||
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
setViewMode(controller.viewModeProperty().get());
|
||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||
if (newValue) {
|
||||
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
refreshHistorgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||
this.filteredEvents.unRegisterForEvents(this);
|
||||
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||
}
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.registerForEvents(this);
|
||||
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||
}
|
||||
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||
|
||||
}
|
||||
|
||||
private void setViewMode(VisualizationMode visualizationMode) {
|
||||
switch (visualizationMode) {
|
||||
case COUNTS:
|
||||
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
|
||||
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
|
||||
countsToggle.setSelected(true);
|
||||
break;
|
||||
case DETAIL:
|
||||
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
|
||||
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
|
||||
detailsToggle.setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
|
||||
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
|
||||
Platform.runLater(() -> {
|
||||
synchronized (VisualizationPanel.this) {
|
||||
if (visualization != null) {
|
||||
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
}
|
||||
|
||||
visualization = newViz;
|
||||
visualization.update();
|
||||
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
||||
|
||||
visualization.setController(controller);
|
||||
notificationPane.setContent(visualization);
|
||||
if (visualization instanceof DetailViewPane) {
|
||||
navPanel.setDetailViewPane((DetailViewPane) visualization);
|
||||
eventsTree.setDetailViewPane((DetailViewPane) visualization);
|
||||
}
|
||||
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||
visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == false) {
|
||||
|
||||
notificationPane.setContent(new StackPane(visualization, new Region() {
|
||||
{
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.3);
|
||||
}
|
||||
}, new NoEventsDialog(() -> {
|
||||
notificationPane.setContent(visualization);
|
||||
})));
|
||||
notificationPane.setContent(
|
||||
new StackPane(visualization,
|
||||
new Region() {
|
||||
{
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.3);
|
||||
}
|
||||
},
|
||||
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
|
||||
} else {
|
||||
notificationPane.setContent(visualization);
|
||||
}
|
||||
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
private NoEventsDialog(Runnable closeCallback) {
|
||||
this.closeCallback = closeCallback;
|
||||
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
@ -562,15 +541,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
|
||||
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
|
||||
|
||||
Action zoomOutAction = new ZoomOut(controller);
|
||||
zoomButton.setOnAction(zoomOutAction);
|
||||
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
|
||||
|
||||
dismissButton.setOnAction(e -> {
|
||||
closeCallback.run();
|
||||
});
|
||||
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||
Action defaultFiltersAction = new ResetFilters(controller);
|
||||
resetFiltersButton.setOnAction(defaultFiltersAction);
|
||||
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -26,14 +26,12 @@ Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range
|
||||
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
|
||||
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
|
||||
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
|
||||
Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title=Zoom History
|
||||
CountsViewPane.loggedTask.name=Updating Counts Graph
|
||||
CountsViewPane.loggedTask.prepUpdate=preparing update
|
||||
CountsViewPane.loggedTask.resetUI=resetting ui
|
||||
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
|
||||
CountsViewPane.loggedTask.updatingCounts=updating counts
|
||||
CountsViewPane.loggedTask.wrappingUp=wrapping up
|
||||
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
|
||||
CountsViewPane.scaleLabel.text=Scale\:
|
||||
CountsViewPane.logRadio.text=Logarithmic
|
||||
CountsViewPane.linearRadio.text=Linear
|
||||
|
@ -66,14 +66,13 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
@ -97,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* TODO: refactor common code out of this class and ClusterChartPane into
|
||||
* AbstractChartView
|
||||
*/
|
||||
public class CountsViewPane extends AbstractVisualization<String, Number, Node, EventCountsChart> {
|
||||
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
|
||||
|
||||
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
|
||||
|
||||
@ -213,17 +212,17 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(et.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(et.getColor())); // NON-NLS
|
||||
node.setCursor(Cursor.HAND);
|
||||
|
||||
final Tooltip tooltip = new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
||||
count,
|
||||
et.getDisplayName(),
|
||||
dateString,
|
||||
interval.getEnd().toString(
|
||||
rangeInfo.getTickFormatter())));
|
||||
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
||||
Tooltip.install(node, tooltip);
|
||||
|
||||
node.setOnMouseEntered((MouseEvent event) -> {
|
||||
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
|
||||
final Tooltip tooltip = new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
|
||||
count,
|
||||
et.getDisplayName(),
|
||||
dateString,
|
||||
interval.getEnd().toString(
|
||||
rangeInfo.getTickFormatter())));
|
||||
tooltip.setGraphic(new ImageView(et.getFXImage()));
|
||||
Tooltip.install(node, tooltip);
|
||||
node.setEffect(new DropShadow(10, et.getColor()));
|
||||
});
|
||||
node.setOnMouseExited((MouseEvent event) -> {
|
||||
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
};
|
||||
}
|
||||
|
||||
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(partPane, contextPane, spacer);
|
||||
chart = new EventCountsChart(dateAxis, countAxis);
|
||||
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(controller, partPane, contextPane, spacer);
|
||||
chart = new EventCountsChart(controller, dateAxis, countAxis);
|
||||
setChartClickHandler();
|
||||
chart.setData(dataSets);
|
||||
setCenter(chart);
|
||||
|
||||
Tooltip.install(chart, getDragTooltip());
|
||||
|
||||
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
|
||||
|
||||
dateAxis.getTickMarks().addListener((Observable observable) -> {
|
||||
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected NumberAxis getYAxis() {
|
||||
return countAxis;
|
||||
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
||||
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
|
||||
}
|
||||
|
||||
public CountsViewSettingsPane() {
|
||||
CountsViewSettingsPane() {
|
||||
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,24 +19,19 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui.countsview;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import javafx.scene.chart.Axis;
|
||||
import java.util.MissingResourceException;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.StackedBarChart;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.util.StringConverter;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
@ -44,11 +39,16 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* Customized {@link StackedBarChart<String, Number>} used to display the event
|
||||
* counts in {@link CountsViewPane}
|
||||
*/
|
||||
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
||||
final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
private ContextMenu chartContextMenu;
|
||||
|
||||
private TimeLineController controller;
|
||||
@Override
|
||||
public ContextMenu getChartContextMenu() {
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private IntervalSelector<? extends String> intervalSelector;
|
||||
|
||||
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
*/
|
||||
private RangeDivisionInfo rangeInfo;
|
||||
|
||||
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
|
||||
super(dateAxis, countAxis);
|
||||
this.controller = controller;
|
||||
//configure constant properties on axes and chart
|
||||
dateAxis.setAnimated(true);
|
||||
dateAxis.setLabel(null);
|
||||
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
setAnimated(true);
|
||||
setTitle(null);
|
||||
|
||||
//use one handler with an if chain because it maintains state
|
||||
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
||||
setOnMousePressed(dragHandler);
|
||||
setOnMouseReleased(dragHandler);
|
||||
setOnMouseDragged(dragHandler);
|
||||
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||
setOnMousePressed(chartDragHandler);
|
||||
setOnMouseReleased(chartDragHandler);
|
||||
setOnMouseDragged(chartDragHandler);
|
||||
|
||||
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||
|
||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
||||
contextMenu.hide();
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
//we have defered creating context menu until control is available
|
||||
contextMenu = ActionUtils.createContextMenu(
|
||||
Arrays.asList(new ActionGroup(
|
||||
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
|
||||
new Back(controller),
|
||||
new Forward(controller))));
|
||||
contextMenu.setAutoHide(true);
|
||||
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
|
||||
chartContextMenu = ActionUtils.createContextMenu(
|
||||
Arrays.asList(
|
||||
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||
chartContextMenu.setAutoHide(true);
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearIntervalSelector();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CountsIntervalSelector newIntervalSelector(double x, Axis<String> dateAxis) {
|
||||
return new CountsIntervalSelector(x, getHeight() - dateAxis.getHeight() - dateAxis.getTickLength(), dateAxis, controller);
|
||||
public CountsIntervalSelector newIntervalSelector() {
|
||||
return new CountsIntervalSelector(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
* @return the context menu for this chart
|
||||
*/
|
||||
ContextMenu getContextMenu() {
|
||||
return contextMenu;
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
void setRangeInfo(RangeDivisionInfo rangeInfo) {
|
||||
@ -175,10 +167,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
* Interval Selector for the counts chart, adjusts interval based on
|
||||
* rangeInfo to include final period
|
||||
*/
|
||||
private class CountsIntervalSelector extends IntervalSelector<String> {
|
||||
final static private class CountsIntervalSelector extends IntervalSelector<String> {
|
||||
|
||||
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
|
||||
super(x, height, axis, controller);
|
||||
private final EventCountsChart countsChart;
|
||||
|
||||
CountsIntervalSelector(EventCountsChart chart) {
|
||||
super(chart);
|
||||
this.countsChart = chart;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
||||
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
|
||||
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
|
||||
//add extra block to end that gets cut of by conversion from string/category.
|
||||
return new Interval(lowerDate, upperDate.plus(rangeInfo.getPeriodSize().getPeriod()));
|
||||
return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DateTime parseDateTime(String date) {
|
||||
return date == null ? new DateTime(rangeInfo.getLowerBound()) : rangeInfo.getTickFormatter().parseDateTime(date);
|
||||
return date == null ? new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -73,25 +73,24 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
|
||||
/**
|
||||
* Controller class for a {@link EventDetailChart} based implementation of a
|
||||
* Controller class for a {@link EventDetailsChart} based implementation of a
|
||||
* TimeLineView.
|
||||
*
|
||||
* This class listens to changes in the assigned {@link FilteredEventsModel} and
|
||||
* updates the internal {@link EventDetailChart} to reflect the currently
|
||||
* updates the internal {@link EventDetailsChart} to reflect the currently
|
||||
* requested events.
|
||||
*
|
||||
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
|
||||
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
|
||||
* must only be manipulated on the JavaFx thread.
|
||||
*/
|
||||
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailChart> {
|
||||
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
|
||||
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||
|
||||
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
|
||||
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
return chart.getEventBundles();
|
||||
}
|
||||
|
||||
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(partPane, contextPane, spacer);
|
||||
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
|
||||
|
||||
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
|
||||
super(controller, partPane, contextPane, spacer);
|
||||
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
|
||||
setChartClickHandler();
|
||||
chart.setData(dataSets);
|
||||
setCenter(chart);
|
||||
|
||||
|
||||
chart.setPrefHeight(USE_COMPUTED_SIZE);
|
||||
|
||||
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
|
||||
@ -211,11 +212,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setModel(FilteredEventsModel filteredEvents) {
|
||||
super.setModel(filteredEvents);
|
||||
}
|
||||
|
||||
private void incrementScrollValue(int factor) {
|
||||
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
|
||||
}
|
||||
@ -477,5 +473,4 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
|
||||
return chart.new HideDescriptionAction(description, descriptionLoD);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
b.setManaged(show);
|
||||
}
|
||||
|
||||
protected final EventDetailChart chart;
|
||||
protected final EventDetailsChart chart;
|
||||
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
|
||||
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
|
||||
protected final BundleType eventBundle;
|
||||
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
final ImageView tagIV = new ImageView(TAG);
|
||||
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
|
||||
|
||||
private Tooltip tooltip;
|
||||
private final Tooltip tooltip = new Tooltip("loading...");
|
||||
|
||||
public EventBundleNodeBase(EventDetailChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
||||
public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
|
||||
this.eventBundle = eventBundle;
|
||||
this.parentNode = parentNode;
|
||||
this.chart = chart;
|
||||
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
infoHBox.setMaxWidth(USE_PREF_SIZE);
|
||||
infoHBox.setPadding(new Insets(2, 5, 2, 5));
|
||||
infoHBox.setAlignment(Pos.TOP_LEFT);
|
||||
infoHBox.setPickOnBounds(true);
|
||||
|
||||
//set up subnode pane sizing contraints
|
||||
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
|
||||
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
subNodePane.setMinWidth(USE_PREF_SIZE);
|
||||
subNodePane.setMaxWidth(USE_PREF_SIZE);
|
||||
|
||||
Tooltip.install(this, this.tooltip);
|
||||
|
||||
//set up mouse hover effect and tooltip
|
||||
setOnMouseEntered((MouseEvent e) -> {
|
||||
/*
|
||||
* defer tooltip creation till needed, this had a surprisingly large
|
||||
* impact on speed of loading the chart
|
||||
* defer tooltip content creation till needed, this had a
|
||||
* surprisingly large impact on speed of loading the chart
|
||||
*/
|
||||
installTooltip();
|
||||
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
|
||||
showHoverControls(true);
|
||||
toFront();
|
||||
|
||||
});
|
||||
setOnMouseExited((MouseEvent event) -> {
|
||||
showHoverControls(false);
|
||||
if (parentNode != null) {
|
||||
parentNode.showHoverControls(true);
|
||||
} else {
|
||||
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
|
||||
}
|
||||
});
|
||||
|
||||
setDescriptionVisibility(DescriptionVisibility.SHOWN);
|
||||
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
|
||||
setDescriptionVisibility(newValue);
|
||||
setDescriptionVisibiltiyImpl(newValue);
|
||||
});
|
||||
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
|
||||
|
||||
}
|
||||
|
||||
final DescriptionLoD getDescriptionLoD() {
|
||||
@ -210,8 +217,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void installTooltip() {
|
||||
if (tooltip == null) {
|
||||
if (tooltip.getText().equalsIgnoreCase("loading...")) {
|
||||
final Task<String> tooltTipTask = new Task<String>() {
|
||||
{
|
||||
updateTitle("loading tooltip");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
try {
|
||||
tooltip = new Tooltip(get());
|
||||
tooltip.setAutoHide(true);
|
||||
Tooltip.install(EventBundleNodeBase.this, tooltip);
|
||||
tooltip.setText(get());
|
||||
tooltip.setGraphic(null);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
|
||||
Tooltip.uninstall(EventBundleNodeBase.this, tooltip);
|
||||
tooltip = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -288,15 +295,18 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
return subNodes;
|
||||
}
|
||||
|
||||
abstract void setDescriptionVisibility(DescriptionVisibility get);
|
||||
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
|
||||
|
||||
void showHoverControls(final boolean showControls) {
|
||||
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
|
||||
eventType -> new DropShadow(-10, eventType.getColor()));
|
||||
setEffect(showControls ? dropShadow : null);
|
||||
enableTooltip(showControls);
|
||||
if (parentNode != null) {
|
||||
parentNode.enableTooltip(false);
|
||||
parentNode.showHoverControls(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final EventType getEventType() {
|
||||
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
|
||||
*/
|
||||
abstract void setDescriptionWidth(double w);
|
||||
|
||||
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
|
||||
void setDescriptionVisibility(DescriptionVisibility get) {
|
||||
descVisibility.set(get);
|
||||
}
|
||||
|
||||
void enableTooltip(boolean toolTipEnabled) {
|
||||
if (toolTipEnabled) {
|
||||
Tooltip.install(this, tooltip);
|
||||
} else {
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
||||
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
|
||||
|
||||
public EventClusterNode(EventDetailChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
||||
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
|
||||
super(chart, eventCluster, parentNode);
|
||||
setMinHeight(24);
|
||||
|
||||
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||
final int size = getEventBundle().getEventIDs().size();
|
||||
switch (descrVis) {
|
||||
case HIDDEN:
|
||||
|
@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionGroup;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
@ -77,6 +75,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
|
||||
@ -94,7 +94,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
*
|
||||
* //TODO: refactor the projected lines to a separate class. -jm
|
||||
*/
|
||||
public final class EventDetailChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
||||
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
|
||||
|
||||
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
|
||||
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
|
||||
@ -102,11 +102,18 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
private static final int PROJECTED_LINE_Y_OFFSET = 5;
|
||||
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
|
||||
private static final int MINIMUM_EVENT_NODE_GAP = 4;
|
||||
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private ContextMenu chartContextMenu;
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
|
||||
public ContextMenu getChartContextMenu() {
|
||||
return chartContextMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* a user positionable vertical line to help compare events
|
||||
@ -183,8 +190,21 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
*/
|
||||
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
|
||||
|
||||
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
||||
EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
|
||||
super(dateAxis, verticalAxis);
|
||||
this.controller = controller;
|
||||
this.filteredEvents = this.controller.getEventsModel();
|
||||
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearGuideLine();
|
||||
clearIntervalSelector();
|
||||
selectedNodes.clear();
|
||||
projectionMap.clear();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
|
||||
|
||||
|
||||
dateAxis.setAutoRanging(false);
|
||||
|
||||
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
|
||||
@ -204,6 +224,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
truncateAll.addListener(layoutInvalidationListener);
|
||||
truncateWidth.addListener(layoutInvalidationListener);
|
||||
descrVisibility.addListener(layoutInvalidationListener);
|
||||
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
||||
|
||||
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
|
||||
//TODO: seems like a hack, can we remove? -jm
|
||||
@ -211,22 +232,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
setPrefHeight(boundsInLocalProperty().get().getHeight());
|
||||
});
|
||||
|
||||
///////set up mouse listeners
|
||||
setOnMouseClicked((MouseEvent clickEvent) -> {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
|
||||
getChartContextMenu(clickEvent);
|
||||
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
|
||||
clickEvent.consume();
|
||||
}
|
||||
});
|
||||
//use one handler with an if chain because it maintains state
|
||||
final ChartDragHandler<DateTime, EventDetailChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
|
||||
setOnMousePressed(dragHandler);
|
||||
setOnMouseReleased(dragHandler);
|
||||
setOnMouseDragged(dragHandler);
|
||||
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
|
||||
setOnMousePressed(chartDragHandler);
|
||||
setOnMouseReleased(chartDragHandler);
|
||||
setOnMouseDragged(chartDragHandler);
|
||||
|
||||
setOnMouseClicked(new MouseClickedHandler<>(this));
|
||||
|
||||
this.selectedNodes = selectedNodes;
|
||||
this.selectedNodes.addListener(new SelectionChangeHandler());
|
||||
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
return bundles;
|
||||
}
|
||||
|
||||
TimeLineController getController() {
|
||||
@Override
|
||||
public TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
|
||||
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
|
||||
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
@Override
|
||||
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
|
||||
if (chartContextMenu != null) {
|
||||
chartContextMenu.hide();
|
||||
}
|
||||
|
||||
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
|
||||
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
|
||||
new Back(controller),
|
||||
new Forward(controller))));
|
||||
// new StartIntervalSelectionAction(clickEvent, dragHandler),
|
||||
TimeLineChart.newZoomHistoyActionGroup(controller)));
|
||||
chartContextMenu.setAutoHide(true);
|
||||
return chartContextMenu;
|
||||
}
|
||||
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
|
||||
if (this.filteredEvents != filteredEvents) {
|
||||
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||
clearGuideLine();
|
||||
clearIntervalSelector();
|
||||
|
||||
selectedNodes.clear();
|
||||
projectionMap.clear();
|
||||
controller.selectEventIDs(Collections.emptyList());
|
||||
});
|
||||
}
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntervalSelector<DateTime> newIntervalSelector(double x, Axis<DateTime> axis) {
|
||||
return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller);
|
||||
public IntervalSelector<DateTime> newIntervalSelector() {
|
||||
return new DetailIntervalSelector(this);
|
||||
}
|
||||
|
||||
synchronized void setBandByType(Boolean t1) {
|
||||
@ -300,12 +286,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
/**
|
||||
* get the DateTime along the x-axis that corresponds to the given
|
||||
* x-coordinate in the coordinate system of this {@link EventDetailChart}
|
||||
* x-coordinate in the coordinate system of this {@link EventDetailsChart}
|
||||
*
|
||||
* @param x a x-coordinate in the space of this {@link EventDetailChart}
|
||||
* @param x a x-coordinate in the space of this {@link EventDetailsChart}
|
||||
*
|
||||
* @return the DateTime along the x-axis corresponding to the given x value
|
||||
* (in the space of this {@link EventDetailChart}
|
||||
* (in the space of this {@link EventDetailsChart}
|
||||
*/
|
||||
public DateTime getDateTimeForPosition(double x) {
|
||||
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
|
||||
@ -352,7 +338,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
return EventStripe.merge(u, v);
|
||||
}
|
||||
);
|
||||
EventStripeNode stripeNode = new EventStripeNode(EventDetailChart.this, eventStripe, null);
|
||||
EventStripeNode stripeNode = new EventStripeNode(EventDetailsChart.this, eventStripe, null);
|
||||
stripeNodeMap.put(eventStripe, stripeNode);
|
||||
nodeGroup.getChildren().add(stripeNode);
|
||||
data.setNode(stripeNode);
|
||||
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
bundleNode.setVisible(true);
|
||||
bundleNode.setManaged(true);
|
||||
//apply advanced layout description visibility options
|
||||
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
|
||||
bundleNode.setDescriptionVisibility(descrVisibility.get());
|
||||
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
|
||||
|
||||
//do recursive layout
|
||||
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
|
||||
|
||||
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
|
||||
super(x, height, axis, controller);
|
||||
DetailIntervalSelector(EventDetailsChart chart) {
|
||||
super(chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
|
||||
private class PlaceMarkerAction extends Action {
|
||||
|
||||
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
|
||||
PlaceMarkerAction(MouseEvent clickEvent) {
|
||||
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
|
||||
|
||||
@ -664,7 +651,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
}
|
||||
});
|
||||
}
|
||||
EventDetailChart.this.controller.selectEventIDs(selectedNodes.stream()
|
||||
EventDetailsChart.this.controller.selectEventIDs(selectedNodes.stream()
|
||||
.flatMap(detailNode -> detailNode.getEventIDs().stream())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
@ -708,4 +695,5 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
|
||||
|
||||
/**
|
||||
* Node used in {@link EventDetailChart} to represent an EventStripe.
|
||||
* Node used in {@link EventDetailsChart} to represent an EventStripe.
|
||||
*/
|
||||
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
|
||||
|
||||
@ -53,12 +53,12 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
// private final HBox clustersHBox = new HBox();
|
||||
private final ImageView eventTypeImageView = new ImageView();
|
||||
|
||||
public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
||||
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
|
||||
super(chart, eventStripe, parentNode);
|
||||
|
||||
setMinHeight(48);
|
||||
|
||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
|
||||
configureLoDButton(hideButton);
|
||||
|
||||
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
|
||||
final int size = getEventStripe().getEventIDs().size();
|
||||
|
||||
switch (descrVis) {
|
||||
@ -150,7 +150,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
public void handle(MouseEvent t) {
|
||||
|
||||
if (t.getButton() == MouseButton.PRIMARY) {
|
||||
|
||||
|
||||
if (t.isShiftDown()) {
|
||||
if (chart.selectedNodes.contains(EventStripeNode.this) == false) {
|
||||
chart.selectedNodes.add(EventStripeNode.this);
|
||||
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
|
||||
contextMenu = new ContextMenu();
|
||||
contextMenu.setAutoHide(true);
|
||||
|
||||
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
|
||||
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
|
||||
contextMenu.getItems().addAll(hideDescriptionMenuItem);
|
||||
contextMenu.getItems().addAll(chartContextMenu.getItems());
|
||||
|
@ -1 +0,0 @@
|
||||
NavPanel.eventsTreeLabel.text=Sort By\:
|
@ -1 +1 @@
|
||||
NavPanel.eventsTreeLabel.text=\u4E0B\u8A18\u306B\u5F93\u3044\u4E26\u3079\u66FF\u3048\uFF1A
|
||||
EventsTree.Label.text=\u4e0b\u8a18\u306b\u5f93\u3044\u4e26\u3079\u66ff\u3048\uff1a
|
@ -46,9 +46,7 @@ import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
@ -59,11 +57,9 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
* out. Right clicking on a item in the tree shows a context menu to show/hide
|
||||
* it.
|
||||
*/
|
||||
public class NavPanel extends BorderPane implements TimeLineView {
|
||||
final public class EventsTree extends BorderPane {
|
||||
|
||||
private TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private DetailViewPane detailViewPane;
|
||||
|
||||
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
@FXML
|
||||
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
|
||||
|
||||
public NavPanel() {
|
||||
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
|
||||
public EventsTree(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
|
||||
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
public void setDetailViewPane(DetailViewPane detailViewPane) {
|
||||
@ -112,19 +110,8 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages("EventsTree.Label.text=Sort By:")
|
||||
void initialize() {
|
||||
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
|
||||
|
||||
@ -137,7 +124,7 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
|
||||
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
|
||||
eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text"));
|
||||
eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,6 +219,5 @@ public class NavPanel extends BorderPane implements TimeLineView {
|
||||
setContextMenu(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
@ -64,7 +63,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil
|
||||
* This also implements {@link TimeLineView} since it dynamically updates its
|
||||
* filters based on the contents of a {@link FilteredEventsModel}
|
||||
*/
|
||||
final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
final public class FilterSetPanel extends BorderPane {
|
||||
|
||||
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
|
||||
|
||||
@ -171,20 +170,25 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
|
||||
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
|
||||
|
||||
}
|
||||
|
||||
public FilterSetPanel() {
|
||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setController(TimeLineController timeLineController) {
|
||||
this.controller = timeLineController;
|
||||
Action defaultFiltersAction = new ResetFilters(controller);
|
||||
defaultButton.setOnAction(defaultFiltersAction);
|
||||
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||
this.setModel(timeLineController.getEventsModel());
|
||||
|
||||
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
|
||||
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
||||
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
|
||||
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> {
|
||||
applyFilters();
|
||||
});
|
||||
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
public FilterSetPanel(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||
@ -40,18 +39,19 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
* A TreeTableCell that shows an icon and color corresponding to the represented
|
||||
* filter
|
||||
*/
|
||||
class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implements TimeLineView {
|
||||
final class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
|
||||
|
||||
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
|
||||
|
||||
private TimeLineController controller;
|
||||
private final TimeLineController controller;
|
||||
|
||||
private FilteredEventsModel filteredEvents;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
|
||||
public LegendCell(TimeLineController controller) {
|
||||
LegendCell(TimeLineController controller) {
|
||||
setEditable(false);
|
||||
setController(controller);
|
||||
this.controller = controller;
|
||||
this.filteredEvents = this.controller.getEventsModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -119,15 +119,4 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public final void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(this.controller.getEventsModel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
}
|
||||
}
|
||||
|
@ -18,19 +18,20 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.zooming;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.util.StringConverter;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
@ -44,13 +45,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* has sliders to provide context/control over three axes of zooming (timescale,
|
||||
* event hierarchy, and description detail).
|
||||
*/
|
||||
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
|
||||
@FXML
|
||||
private URL location;
|
||||
public class ZoomSettingsPane extends TitledPane {
|
||||
|
||||
@FXML
|
||||
private Button backButton;
|
||||
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
|
||||
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
|
||||
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
|
||||
}
|
||||
|
||||
public ZoomSettingsPane() {
|
||||
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void setController(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
setModel(controller.getEventsModel());
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
Back back = new Back(controller);
|
||||
backButton.disableProperty().bind(back.disabledProperty());
|
||||
backButton.setOnAction(back);
|
||||
backButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
||||
back.getAccelerator().getName())));
|
||||
Forward forward = new Forward(controller);
|
||||
forwardButton.disableProperty().bind(forward.disabledProperty());
|
||||
forwardButton.setOnAction(forward);
|
||||
forwardButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
||||
forward.getAccelerator().getName())));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModel(FilteredEventsModel filteredEvents) {
|
||||
this.filteredEvents = filteredEvents;
|
||||
|
||||
initializeSlider(timeUnitSlider,
|
||||
() -> {
|
||||
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
|
||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||
});
|
||||
|
||||
initializeSlider(descrLODSlider,
|
||||
() -> {
|
||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
if (controller.pushDescrLOD(newLOD) == false) {
|
||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
||||
}
|
||||
}, this.filteredEvents.descriptionLODProperty(),
|
||||
() -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||
});
|
||||
|
||||
initializeSlider(descrLODSlider, () -> {
|
||||
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
|
||||
if (controller.pushDescrLOD(newLOD) == false) {
|
||||
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
|
||||
}
|
||||
}, this.filteredEvents.descriptionLODProperty(), () -> {
|
||||
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||
});
|
||||
initializeSlider(typeZoomSlider,
|
||||
() -> {
|
||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||
@ -172,6 +134,26 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
||||
() -> {
|
||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
|
||||
});
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
Back back = new Back(controller);
|
||||
backButton.disableProperty().bind(back.disabledProperty());
|
||||
backButton.setOnAction(back);
|
||||
backButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
|
||||
back.getAccelerator().getName())));
|
||||
Forward forward = new Forward(controller);
|
||||
forwardButton.disableProperty().bind(forward.disabledProperty());
|
||||
forwardButton.setOnAction(forward);
|
||||
forwardButton.setTooltip(new Tooltip(
|
||||
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
|
||||
forward.getAccelerator().getName())));
|
||||
|
||||
}
|
||||
|
||||
public ZoomSettingsPane(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
|
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
34
CoreLibs/src/com/sun/javafx/Utils.java
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 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 com.sun.javafx;
|
||||
|
||||
/**
|
||||
* This class takes the place of the one in Java8 versions prior to u60. As of
|
||||
* u60 com.sun.javafx.Utils was moved to the com.sun.javafx.util package and
|
||||
* code, specifically ControlsFX, that depended on it broke. ControlsFX has
|
||||
* removed their dependency on this class, but their fix will not be released
|
||||
* until version 8.60.10 of ControlsFX. Until then, this shim class allows
|
||||
* version 8.40.9 to run on Java 8u60. This class (and package) should and will
|
||||
* be removed once we upgrade to ControlsFX 8.60.x.
|
||||
*/
|
||||
@Deprecated // DO NOT USE
|
||||
public class Utils extends com.sun.javafx.util.Utils {
|
||||
|
||||
//Does nothing but expose com.sun.javafx.utila.Utils in the old package (com.sun.javafx.Utils)
|
||||
}
|
@ -49,10 +49,12 @@ import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
@ -395,6 +397,9 @@ public final class ImageGalleryController {
|
||||
tagsManager.clearFollowUpTagName();
|
||||
tagsManager.unregisterListener(groupManager);
|
||||
tagsManager.unregisterListener(categoryManager);
|
||||
dbWorkerThread.cancelAllTasks();
|
||||
dbWorkerThread = null;
|
||||
restartWorker();
|
||||
|
||||
Toolbar.getDefault(this).reset();
|
||||
groupManager.clear();
|
||||
@ -418,7 +423,12 @@ public final class ImageGalleryController {
|
||||
dbWorkerThread.addTask(innerTask);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
||||
if (Objects.isNull(db)) {
|
||||
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed.");
|
||||
return null;
|
||||
}
|
||||
return db.getFileFromID(fileID);
|
||||
}
|
||||
|
||||
@ -587,7 +597,7 @@ public final class ImageGalleryController {
|
||||
try {
|
||||
InnerTask it = workQueue.take();
|
||||
|
||||
if (it.cancelled == false) {
|
||||
if (it.isCancelled() == false) {
|
||||
it.run();
|
||||
}
|
||||
|
||||
@ -609,7 +619,7 @@ public final class ImageGalleryController {
|
||||
/**
|
||||
* Abstract base class for task to be done on {@link DBWorkerThread}
|
||||
*/
|
||||
static public abstract class InnerTask implements Runnable {
|
||||
static public abstract class InnerTask implements Runnable, Cancellable {
|
||||
|
||||
public double getProgress() {
|
||||
return progress.get();
|
||||
@ -653,13 +663,13 @@ public final class ImageGalleryController {
|
||||
protected InnerTask() {
|
||||
}
|
||||
|
||||
protected volatile boolean cancelled = false;
|
||||
|
||||
public void cancel() {
|
||||
@Override
|
||||
synchronized public boolean cancel() {
|
||||
updateState(Worker.State.CANCELLED);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isCancelled() {
|
||||
synchronized protected boolean isCancelled() {
|
||||
return getState() == Worker.State.CANCELLED;
|
||||
}
|
||||
}
|
||||
@ -693,7 +703,7 @@ public final class ImageGalleryController {
|
||||
*/
|
||||
static private class UpdateFileTask extends FileTask {
|
||||
|
||||
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
@ -720,7 +730,7 @@ public final class ImageGalleryController {
|
||||
*/
|
||||
static private class RemoveFileTask extends FileTask {
|
||||
|
||||
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
}
|
||||
|
||||
@ -756,7 +766,7 @@ public final class ImageGalleryController {
|
||||
private final DrawableDB taskDB;
|
||||
private final SleuthkitCase tskCase;
|
||||
|
||||
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
this.controller = controller;
|
||||
this.taskDB = taskDB;
|
||||
this.tskCase = tskCase;
|
||||
@ -766,8 +776,8 @@ public final class ImageGalleryController {
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
|
||||
"' or name LIKE '%.")
|
||||
+ "')";
|
||||
static private final String MIMETYPE_CLAUSE
|
||||
= "blackboard_attributes.value_text LIKE '"
|
||||
static private final String MIMETYPE_CLAUSE =
|
||||
"blackboard_attributes.value_text LIKE '"
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
|
||||
"' OR blackboard_attributes.value_text LIKE '") + "' ";
|
||||
|
||||
@ -801,7 +811,7 @@ public final class ImageGalleryController {
|
||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||
int units = 0;
|
||||
for (final AbstractFile f : files) {
|
||||
if (cancelled) {
|
||||
if (isCancelled()) {
|
||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||
progressHandle.finish();
|
||||
break;
|
||||
@ -848,12 +858,12 @@ public final class ImageGalleryController {
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||
}
|
||||
|
||||
}
|
||||
progressHandle.finish();
|
||||
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
|
||||
controller.setStale(false);
|
||||
}
|
||||
}
|
||||
@ -877,13 +887,13 @@ public final class ImageGalleryController {
|
||||
// (name like '.jpg' or name like '.png' ...)
|
||||
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
|
||||
|
||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database");
|
||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database", this);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataSourceId Data source object ID
|
||||
*/
|
||||
public PrePopulateDataSourceFiles(Content dataSource) {
|
||||
PrePopulateDataSourceFiles(Content dataSource) {
|
||||
super();
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
@ -933,7 +943,7 @@ public final class ImageGalleryController {
|
||||
DrawableDB.DrawableTransaction tr = db.beginTransaction();
|
||||
int units = 0;
|
||||
for (final AbstractFile f : files) {
|
||||
if (cancelled) {
|
||||
if (isCancelled()) {
|
||||
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
|
||||
progressHandle.finish();
|
||||
break;
|
||||
@ -951,7 +961,7 @@ public final class ImageGalleryController {
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
|
||||
}
|
||||
}
|
||||
|
||||
progressHandle.finish();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import org.openide.util.Lookup;
|
||||
*/
|
||||
@OptionsPanelController.TopLevelRegistration(
|
||||
categoryName = "#OptionsCategory_Name_Options",
|
||||
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png",
|
||||
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
|
||||
keywords = "#OptionsCategory_Keywords_Options",
|
||||
keywordsCategory = "Options",
|
||||
position = 10
|
||||
|
@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
return fileOpt;
|
||||
} else {
|
||||
try {
|
||||
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
|
||||
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
|
||||
fileOpt = Optional.empty();
|
||||
@ -206,7 +206,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
super.failed();
|
||||
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
|
||||
}
|
||||
|
||||
|
||||
abstract void saveToCache(X result);
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -158,7 +158,6 @@ public class Server {
|
||||
public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8"); ///< default Charset to index text as
|
||||
private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
|
||||
private Process curSolrProcess = null;
|
||||
private static Ingester ingester = null;
|
||||
static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
|
||||
static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
|
||||
static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
|
||||
@ -291,13 +290,12 @@ public class Server {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
InputStreamReader isr = new InputStreamReader(stream);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
OutputStreamWriter osw = null;
|
||||
BufferedWriter bw = null;
|
||||
try {
|
||||
osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
|
||||
bw = new BufferedWriter(osw);
|
||||
|
||||
try (InputStreamReader isr = new InputStreamReader(stream);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
|
||||
BufferedWriter bw = new BufferedWriter(osw);) {
|
||||
|
||||
String line = null;
|
||||
while (doRun && (line = br.readLine()) != null) {
|
||||
bw.write(line);
|
||||
@ -309,22 +307,7 @@ public class Server {
|
||||
}
|
||||
bw.flush();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error redirecting Solr output stream"); //NON-NLS
|
||||
} finally {
|
||||
if (bw != null) {
|
||||
try {
|
||||
bw.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error closing Solr output stream writer"); //NON-NLS
|
||||
}
|
||||
}
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error closing Solr output stream reader"); //NON-NLS
|
||||
}
|
||||
}
|
||||
logger.log(Level.SEVERE, "Error redirecting Solr output stream", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,7 +340,7 @@ public class Server {
|
||||
void killSolr() {
|
||||
List<Long> solrPids = getSolrPIDs();
|
||||
for (long pid : solrPids) {
|
||||
logger.log(Level.INFO, "Trying to kill old Solr process, PID: " + pid); //NON-NLS
|
||||
logger.log(Level.INFO, "Trying to kill old Solr process, PID: {0}", pid); //NON-NLS
|
||||
PlatformUtil.killProcess(pid);
|
||||
}
|
||||
}
|
||||
@ -399,22 +382,12 @@ public class Server {
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "Starting Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
|
||||
logger.log(Level.INFO, "Starting Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
|
||||
|
||||
if (isPortAvailable(currentSolrServerPort)) {
|
||||
logger.log(Level.INFO, "Port [" + currentSolrServerPort + "] available, starting Solr"); //NON-NLS
|
||||
logger.log(Level.INFO, "Port [{0}] available, starting Solr", currentSolrServerPort); //NON-NLS
|
||||
try {
|
||||
final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + Integer.toString(MAX_SOLR_MEM_MB) + "m"; //NON-NLS
|
||||
|
||||
// String loggingPropertiesOpt = "-Djava.util.logging.config.file="; //NON-NLS
|
||||
// String loggingPropertiesFilePath = instanceDir + File.separator + "conf" + File.separator; //NON-NLS
|
||||
//
|
||||
// if (DEBUG) {
|
||||
// loggingPropertiesFilePath += "logging-development.properties"; //NON-NLS
|
||||
// } else {
|
||||
// loggingPropertiesFilePath += "logging-release.properties"; //NON-NLS
|
||||
// }
|
||||
// final String loggingProperties = loggingPropertiesOpt + loggingPropertiesFilePath;
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(javaPath);
|
||||
commandLine.add(MAX_SOLR_MEM_MB_PAR);
|
||||
@ -434,7 +407,7 @@ public class Server {
|
||||
Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr");
|
||||
solrProcessBuilder.redirectError(solrStderrPath.toFile());
|
||||
|
||||
logger.log(Level.INFO, "Starting Solr using: " + solrProcessBuilder.command()); //NON-NLS
|
||||
logger.log(Level.INFO, "Starting Solr using: {0}", solrProcessBuilder.command()); //NON-NLS
|
||||
curSolrProcess = solrProcessBuilder.start();
|
||||
logger.log(Level.INFO, "Finished starting Solr"); //NON-NLS
|
||||
|
||||
@ -447,7 +420,7 @@ public class Server {
|
||||
}
|
||||
|
||||
final List<Long> pids = this.getSolrPIDs();
|
||||
logger.log(Level.INFO, "New Solr process PID: " + pids); //NON-NLS
|
||||
logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS
|
||||
} catch (SecurityException ex) {
|
||||
logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS
|
||||
throw new KeywordSearchModuleException(
|
||||
@ -526,7 +499,7 @@ public class Server {
|
||||
}
|
||||
|
||||
try {
|
||||
logger.log(Level.INFO, "Stopping Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
|
||||
logger.log(Level.INFO, "Stopping Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
|
||||
|
||||
//try graceful shutdown
|
||||
final String[] SOLR_STOP_CMD = {
|
||||
@ -1278,6 +1251,7 @@ public class Server {
|
||||
}
|
||||
|
||||
class ServerAction extends AbstractAction {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@ -1289,11 +1263,12 @@ public class Server {
|
||||
* Exception thrown if solr port not available
|
||||
*/
|
||||
class SolrServerNoPortException extends SocketException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* the port number that is not available
|
||||
*/
|
||||
private int port;
|
||||
private final int port;
|
||||
|
||||
SolrServerNoPortException(int port) {
|
||||
super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Updated by build script
|
||||
#Tue, 13 Oct 2015 17:00:33 -0400
|
||||
#Fri, 23 Oct 2015 09:28:23 -0400
|
||||
LBL_splash_window_title=Starting Autopsy
|
||||
SPLASH_HEIGHT=314
|
||||
SPLASH_WIDTH=538
|
||||
|
@ -1,4 +1,4 @@
|
||||
#Updated by build script
|
||||
#Tue, 13 Oct 2015 17:00:33 -0400
|
||||
#Fri, 23 Oct 2015 09:28:23 -0400
|
||||
CTL_MainWindow_Title=Autopsy 3.1.3
|
||||
CTL_MainWindow_Title_No_Project=Autopsy 3.1.3
|
||||
|
@ -30,9 +30,11 @@
|
||||
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
|
||||
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
|
||||
<property name="win32.TskLib.postgres_path" value="${env.TSK_HOME}/win32/Release_PostgreSQL"/>
|
||||
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
|
||||
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
|
||||
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
|
||||
<available property="win32.TskLib_postgres.exists" type="dir" file="{win32.TskLib.postgres_path}" />
|
||||
</target>
|
||||
|
||||
<!-- The following copy the libtsk_jni dependencies to the Autopsy
|
||||
@ -50,6 +52,7 @@
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="libintl-8.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
<include name="msvcr120.dll"/>
|
||||
</fileset>
|
||||
|
||||
<copy todir="${amd64}" overwrite="true">
|
||||
@ -67,23 +70,35 @@
|
||||
<target name="copyWinTskLibs32ToBaseDir" if="win32.TskLib.exists">
|
||||
<fileset dir="${win32.TskLib.path}" id="win32dlls">
|
||||
<include name="zlib.dll" />
|
||||
<include name="libewf.dll"/>
|
||||
<include name="libewf.dll"/>
|
||||
</fileset>
|
||||
|
||||
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
|
||||
<include name="libeay32.dll"/>
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="intl.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
<include name="msvcr120.dll"/>
|
||||
</fileset>
|
||||
|
||||
<copy todir="${i386}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${x86}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${i586}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
|
||||
<copy todir="${i686}" overwrite="true">
|
||||
<fileset refid="win32dlls" />
|
||||
<fileset refid="postgres32dlls" />
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
@ -147,6 +162,4 @@
|
||||
<fileset refid="crt64dlls"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
|
||||
</project>
|
||||
|
@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation"
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 3.1
|
||||
PROJECT_NUMBER = 4.0
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
@ -1025,7 +1025,7 @@ GENERATE_HTML = YES
|
||||
# The default directory is: html.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_OUTPUT = 3.1
|
||||
HTML_OUTPUT = 4.0
|
||||
|
||||
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
||||
# generated HTML page (for example: .htm, .php, .asp).
|
||||
|
@ -16,6 +16,8 @@ The New Case wizard dialog will open and you will need to enter the case name an
|
||||
|
||||
\image html case-newcase.png
|
||||
|
||||
NOTE: You will only have the option of making a multi-user case if you have configured Autopsy with multi-user settings. See \ref install_multiuser_page for installation instructions and \ref creating_multi_user_cases for details on creating multi-user cases.
|
||||
|
||||
You will also be prompted for optional information, such as investigator name and case number.
|
||||
|
||||
After you create the case, you will be prompted to add a data source, as described in \ref ds_add.
|
||||
|
@ -25,6 +25,8 @@ Regardless of the type of data source, there are some common steps in the proces
|
||||
|
||||
\image html select-data-source-type.PNG
|
||||
|
||||
NOTE: If you are adding a data source to a multi-user case, ensure that all Autopsy clients will have access to the data source at the same path. We recommend using UNC paths to ensure this consistent mapping.
|
||||
|
||||
2) Autopsy will perform a basic examination of the data source and populate an embedded database with an entry for each file in the data source. No content is analyzed in the process, only the files are enumerated.
|
||||
|
||||
3) While it is examining the data source, you will be prompted with a list of ingest modules to enable.
|
||||
|
@ -1,61 +1,81 @@
|
||||
/*! \page install_activemq Install and Configure ActiveMQ
|
||||
To install ActiveMQ, perform the following steps:
|
||||
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||
|
||||
\section install_activemq_prereq Prerequisites
|
||||
|
||||
You will need:
|
||||
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
|
||||
- Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
|
||||
|
||||
|
||||
\section install_activemq_install Installation
|
||||
|
||||
\subsection install_activemq_install_java JRE Installation
|
||||
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||
<br><br>
|
||||
\image html wherejava.PNG
|
||||
<br><br>
|
||||
You can test if you have a 32-bit or 64-bit JRE installed via the following:
|
||||
- _java -d32 -version_ for a 32-bit JRE
|
||||
- _java -d64 -version_ for a 64-bit JRE
|
||||
<br><br>
|
||||
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
|
||||
<br><br>
|
||||
\image html JRE_bitness.PNG
|
||||
<br><br>
|
||||
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
|
||||
<br><br>
|
||||
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
|
||||
3. Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
|
||||
4. Extract the files in the archive
|
||||
5. Edit <i>apache-activemq-5.11.1\\conf\\activemq.xml</i> to add <i>"&wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
|
||||
If you need the JRE, install it with the default settings.
|
||||
|
||||
|
||||
\subsection install_activemq_install_mq ActiveMQ Installation
|
||||
|
||||
1. Extract the contents of the ActiveMQ archive folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
|
||||
|
||||
2. Edit the <i>conf\\activemq.xml</i> in the extracted folder to add <i>"&wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
|
||||
<br><br>
|
||||
\image html maxinactivityduration.PNG
|
||||
<br><br>
|
||||
6. Move the <i>apache-activemq-5.11.1</i> folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
|
||||
7. Install ActiveMQ as a service by navigating to the folder <i>apache-activemq-5.11.1-bin\\apache-activemq-5.11.1\\bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
|
||||
8. If you desire authentication for your ActiveMQ server (a good idea), the following directions allow you to set up credentials:
|
||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||
|
||||
admins=system,sslclient,client,broker1,broker2
|
||||
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2
|
||||
users=system,user,sslclient,client,broker1,broker2
|
||||
guests=guest
|
||||
|
||||
<br><br>
|
||||
\image html groups.properties.before.PNG
|
||||
<br><br>
|
||||
When complete, the file should look like this:
|
||||
<br><br>
|
||||
\image html groups.properties.after.PNG
|
||||
<br><br>
|
||||
|
||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||
|
||||
system=manager
|
||||
user=password
|
||||
guest=password
|
||||
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US
|
||||
|
||||
<br><br>
|
||||
\image html users.properties.before.PNG
|
||||
<br><br>
|
||||
When complete, the file should look like this:
|
||||
<br><br>
|
||||
\image html users.properties.after.PNG
|
||||
<br><br>
|
||||
|
||||
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
|
||||
|
||||
|
||||
|
||||
3. Install ActiveMQ as a service by navigating to the folder <i>bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
|
||||
|
||||
4. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||
|
||||
5. ActiveMQ should now be installed and configured using the default credentials. You should go to the next section to change the default passwords. To test your installation, you can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by following the instructions below. If you can see a page that looks like the following, it is ready to function.
|
||||
<br><br>
|
||||
\image html activemq.PNG
|
||||
<br><br>
|
||||
|
||||
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
|
||||
|
||||
|
||||
\subsection install_activemq_install_pw Configuring Authentication
|
||||
|
||||
You can optionally add authentication to your ActiveMQ server. The ActiveMQ communications are not encrypted and contain basic messages between the systems about when new data has been found.
|
||||
|
||||
The following directions allow you to set up credentials:
|
||||
|
||||
1. Copy and paste the following text to the file <i>"conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||
<BLOCKQUOTE>
|
||||
admins=system,sslclient,client,broker1,broker2<br />
|
||||
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2<br />
|
||||
users=system,user,sslclient,client,broker1,broker2<br />
|
||||
guests=guest<br />
|
||||
</BLOCKQUOTE>
|
||||
<br><br>
|
||||
\image html groups.properties.before.PNG
|
||||
<br><br>
|
||||
When complete, the file should look like this:
|
||||
<br><br>
|
||||
\image html groups.properties.after.PNG
|
||||
<br><br>
|
||||
2. Copy and paste the following text to the file <i>"conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
|
||||
<BLOCKQUOTE>
|
||||
system=manager<br />
|
||||
user=password<br />
|
||||
guest=password<br />
|
||||
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US<br />
|
||||
</BLOCKQUOTE>
|
||||
<br><br>
|
||||
\image html users.properties.before.PNG
|
||||
<br><br>
|
||||
When complete, the file should look like this:
|
||||
<br><br>
|
||||
\image html users.properties.after.PNG
|
||||
<br><br>
|
||||
3. Copy and paste the following text to the file <i>"conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
|
||||
|
||||
<plugins>
|
||||
<jaasAuthenticationPlugin configuration="activemq-domain" />
|
||||
<simpleAuthenticationPlugin>
|
||||
@ -67,41 +87,28 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
|
||||
</simpleAuthenticationPlugin>
|
||||
</plugins>
|
||||
|
||||
<br><br>
|
||||
\image html insertTextHere.PNG
|
||||
<br><br>
|
||||
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
|
||||
<br><br>
|
||||
\image html insertedText.PNG
|
||||
<br><br>
|
||||
To add a new user or change the password:
|
||||
+ Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
|
||||
<br>
|
||||
<br>
|
||||
\image html StopActiveMQService.PNG
|
||||
<br>
|
||||
<br>
|
||||
+ Edit <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
|
||||
<br>
|
||||
<br>
|
||||
\image html newUserAndPassword.PNG
|
||||
<br>
|
||||
<br>
|
||||
+ Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||
<br>
|
||||
<br>
|
||||
\image html StartActiveMQService.PNG
|
||||
<br>
|
||||
<br>
|
||||
|
||||
9. If not already started, start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||
10. ActiveMQ should now be fully installed and configured. You can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by editing the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> on the ActiveMQ server as discussed above. If you can see a page that looks like the following, it is ready to function.
|
||||
<br><br>
|
||||
\image html activemq.PNG
|
||||
<br>
|
||||
<br>
|
||||
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
|
||||
\image html insertTextHere.PNG
|
||||
<br><br>
|
||||
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
|
||||
<br><br>
|
||||
\image html insertedText.PNG
|
||||
<br><br>
|
||||
|
||||
|
||||
To add a new user or change the password:
|
||||
|
||||
1. Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
|
||||
<br><br>
|
||||
\image html StopActiveMQService.PNG
|
||||
<br><br>
|
||||
2. Edit <i>"conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
|
||||
<br><br>
|
||||
\image html newUserAndPassword.PNG
|
||||
<br><br>
|
||||
3. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
|
||||
<br><br>
|
||||
\image html StartActiveMQService.PNG
|
||||
<br><br>
|
||||
|
||||
*/
|
||||
|
31
docs/doxygen-user/installMultiUser.dox
Normal file
31
docs/doxygen-user/installMultiUser.dox
Normal file
@ -0,0 +1,31 @@
|
||||
/*! \page install_multiuser_page Setting Up Multi-user Environment
|
||||
|
||||
\section multiuser_install Multi-user Installation
|
||||
|
||||
Autopsy can be setup to work in an environment where multiple users on different computers can have the same case open at the same time. To set up this type of environment, you will need to configure additional (free and open source) network-based services.
|
||||
|
||||
\subsection multiuser_install_services Network-based Services
|
||||
|
||||
You will need the following that all Autopsy clients can access:
|
||||
- Centralized storage that all clients running Autopsy have access to. The central storage should be either mounted at the same Windows drive letter or UNC paths should be used everywhere. All clients need to be able to access data using the same path.
|
||||
- A central PostgreSQL database. A database will be created for each case and will be stored on the local drive of the database server. Installation and configuration is explained in \ref install_postgresql.
|
||||
- A central Solr text index. A Solr core will be created for each case and will be stored in the case folder (not on the local drive of the Solr server). We recommend using Bitnami Solr. This is explained in \ref install_solr.
|
||||
- An ActiveMQ messaging server to allow the various clients to communicate with each other. This service has minimal storage requirements. This is explained in \ref install_activemq.
|
||||
|
||||
When you setup the above services, write down the addresses, user names, and passwords or each so that you can configure each of the client systems afterwards.
|
||||
|
||||
We recommend using at least 2 dedicated computers for this additional infrastructure. Spreading the services out across several machines can improve throughput.
|
||||
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
|
||||
|
||||
Ensure that the central storage and PostgreSQL servers are regularly backed up.
|
||||
|
||||
\subsection multiuser_install_clients Autopsy Clients
|
||||
|
||||
Once the infrastructure is in place, you will need to configure Autopsy to use them.
|
||||
- Install Autopsy on each client system as normal using the steps from \ref installation_page.
|
||||
- Start Autopsy and open the multi-user options panel from “Tools”, “Options”, “Multi-user”. As shown in the screenshot below, you can then enter all of the address and authentication information for the network-based services. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
|
||||
|
||||
\image html multiuser_settings.PNG
|
||||
|
||||
|
||||
*/
|
@ -1,32 +1,42 @@
|
||||
/*! \page install_solr Install and Configure Solr
|
||||
A central Solr server is needed to store keyword indexes. To install Solr, perform the following steps:
|
||||
|
||||
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||
|
||||
|
||||
\section install_solr_prereq Prerequisites
|
||||
|
||||
You will need:
|
||||
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
|
||||
- Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer.
|
||||
- Access to an installed version of Autopsy so that you can copy files from it.
|
||||
|
||||
|
||||
\section install_solr_install Installation
|
||||
|
||||
\subsection install_solr_install_java JRE Installation
|
||||
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
|
||||
<br><br>
|
||||
\image html wherejava.PNG
|
||||
<br><br>
|
||||
You can test if you have a 32-bit or 64-bit JRE installed via the following:
|
||||
- _java -d32 -version_ for a 32-bit JRE
|
||||
- _java -d64 -version_ for a 64-bit JRE
|
||||
<br><br>
|
||||
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
|
||||
<br><br>
|
||||
\image html JRE_bitness.PNG
|
||||
<br><br>
|
||||
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
|
||||
<br><br>
|
||||
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
|
||||
3. Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer. The following steps will configure Solr to run using an account that will have access to the network storage.
|
||||
4. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
|
||||
5. If Windows prompts with User Account Control, click _Yes_
|
||||
6. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
|
||||
7. If you see an error dialog like the following, you may safely ignore it.
|
||||
If you need the JRE, install it with the default settings.
|
||||
|
||||
|
||||
\subsection install_solr_install_solr Solr Installation
|
||||
|
||||
The following steps will configure Solr to run using an account that will have access to the network storage.
|
||||
|
||||
1. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
|
||||
2. If Windows prompts with User Account Control, click _Yes_
|
||||
3. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
|
||||
4. If you see an error dialog like the following, you may safely ignore it.
|
||||
<br><br>
|
||||
\image html apachebadmessage.PNG
|
||||
<br>
|
||||
8. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
|
||||
9. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
|
||||
10. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
|
||||
5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
|
||||
|
||||
\subsection install_solr_config Solr Configuration
|
||||
1. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
|
||||
2. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
|
||||
<br>
|
||||
<br>
|
||||
- Add the following options in the _JvmOptions_ section of the line that begins with <i>"C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe"</i> :
|
||||
@ -49,7 +59,7 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
|
||||
<br><br>
|
||||
\image html updatedServiceInstall.PNG
|
||||
<br><br>
|
||||
11. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
|
||||
3. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
|
||||
<i>\<int name="transientCacheSize">10\</int></i>
|
||||
<br><br>
|
||||
The added part is highlighted in yellow below. Ensure that it is inside the <i>\<solr></i> tag as follows:
|
||||
@ -58,7 +68,7 @@ The added part is highlighted in yellow below. Ensure that it is inside the <i>\
|
||||
<br>
|
||||
Again you may have trouble saving to the file in the current location. If so, just save it out to the desktop and copy the edited file back over the top of the original.
|
||||
<br><br>
|
||||
12. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
|
||||
4. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
|
||||
- Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB.
|
||||
- Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line.
|
||||
<br><br>
|
||||
@ -66,17 +76,17 @@ The log file should end up looking like this (modified lines are highlighted in
|
||||
<br><br>
|
||||
\image html log4j.PNG
|
||||
<br><br>
|
||||
13. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
|
||||
5. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html tickTime.PNG
|
||||
<br><br>
|
||||
14. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
|
||||
6. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html dataDir.PNG
|
||||
<br><br>
|
||||
15. Copy the folder _configsets_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
|
||||
16. Copy the folder _lib_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
|
||||
17. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
|
||||
7. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\configsets"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
|
||||
8. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\lib"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
|
||||
9. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
|
||||
|
||||
If there is no "Run as administrator" option when you right-click the _serviceinstall.bat_ file, start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service:
|
||||
<br><br>
|
||||
@ -86,13 +96,13 @@ The log file should end up looking like this (modified lines are highlighted in
|
||||
<br><br>
|
||||
\image html solrinstall1.PNG
|
||||
<br><br>
|
||||
18. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
|
||||
10. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
|
||||
<br><br>
|
||||
\image html solrinstall2.PNG
|
||||
<br>
|
||||
If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right."
|
||||
|
||||
19. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
|
||||
11. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html solrinstall3.PNG
|
||||
<br><br>
|
||||
|
@ -5,68 +5,28 @@
|
||||
\section prereqs Prerequisites
|
||||
It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them.
|
||||
<br><br>
|
||||
|
||||
|
||||
\section install Deployment Types
|
||||
Starting with Autopsy 4.0, there are two ways to deploy Autopsy:
|
||||
- **Single-User**: Cases can be open by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process.
|
||||
- **Multi-User**: Cases can be open by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page.
|
||||
|
||||
|
||||
\section download Download
|
||||
Download Autopsy from the website:
|
||||
|
||||
http://sleuthkit.org/autopsy/download.php
|
||||
|
||||
The current version of Autopsy 3 runs only on Microsoft Windows.
|
||||
We have gotten older versions to run on other platforms, such as Linux and OS X, but we do not have it in a state that makes it easy to distribute and find the needed libraries.
|
||||
<br><br>
|
||||
The current version of Autopsy 4 is distributed on sleuthkit.org only as a Windows installer. It can run on Linux and OS X, but requires some manual setup.
|
||||
|
||||
\section installation_section Installation
|
||||
|
||||
\section install_standalone Installation
|
||||
To install Autopsy, perform the following steps:
|
||||
1. Run the Autopsy _msi_ file
|
||||
2. If Windows prompts with User Account Control, click _Yes_
|
||||
3. Click through the dialog boxes until you click a button that says _Finish_
|
||||
4. Core Autopsy should now be fully installed
|
||||
|
||||
<br>
|
||||
\section deployment_types Deployment Types
|
||||
There are two types of cases that Autopsy can create and use:
|
||||
- **Standalone**: A single user with a single computer. Not intended to have multiple examiners working on the same case.
|
||||
- **Collaborative**: A multi-user environment with multiple computers. Multiple examiners can work on the same case at the same time.
|
||||
|
||||
Both deployment types use the same analysis modules and the same base installer.
|
||||
<br><br>
|
||||
\subsection standalone_install Standalone (Single User) Installation
|
||||
1. Install Autopsy as explained in \ref installation_section above. The Windows installer is self-contained and will place everything in the needed places. Simply follow the standard prompts for installation.
|
||||
|
||||
<br>
|
||||
\subsection collab_install Collaborative (Multi-user) Installation
|
||||
To use the Multi-user collaboration feature, three additional software packages are required. These packages install servers that need to be accessible to machines running Autopsy collaborative cases via the network. These servers do not have to be installed on the same machine as Autopsy, nor on the same machine as each other.
|
||||
|
||||
1. Install Autopsy just as in \ref standalone_install above
|
||||
2. Install and configure Apache ActiveMQ on a machine accessible to Autopsy nodes. This is explained in \ref install_activemq.
|
||||
3. Install and configure Bitnami Solr on a machine accessible to Autopsy nodes. This is explained in \ref install_solr.
|
||||
4. Install and configure PostgreSQL on a machine accessible to Autopsy nodes. This is explained in \ref install_postgresql.
|
||||
5. Configure Multi-user settings. This is explained in \ref multi_user_options.
|
||||
|
||||
<br>
|
||||
While you may run all of the external services (ActiveMQ, Solr, and PostgreSQL) on the same machine that is running Autopsy, this is not ideal. Spreading the services out across several machines can improve throughput. Keep in mind that all the machines need to be able to communicate with each other and see the shared drive over the network.
|
||||
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
|
||||
<br><br>
|
||||
To use Multi-user cases, there needs to be a shared network drive accessible to all participating computers.
|
||||
<br><br>
|
||||
|
||||
\section optimizing_performance Optimizing Performance
|
||||
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
|
||||
1. Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
|
||||
- Run Autopsy from the Start Menu or desktop
|
||||
- When presented with the case creation splash screen, cancel/close the window
|
||||
- Select "Tools", "Options"
|
||||
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
|
||||
- After each change, restart Autopsy to let this setting take effect.
|
||||
<br><br>
|
||||
\image html threadcount.PNG
|
||||
<br><br>
|
||||
2. In the screenshot above, there is an option to <i>Enable timeout to allow modules to automatically terminate after a set amount of time</i>. Enabling this feature by applying a checkmark and setting a number of hours puts a maximum amount of time an individual module may attempt to process before being stopped. If enabled and a module attempts to run for longer than this value, Autopsy stops the module and moves on to process the next module. This allows processing to continue even if a rogue module does not end appropriately in a reasonable amount of time.
|
||||
<br><br>
|
||||
3. When making a case, use different drives to store the case and the images. The case directory is where the SQLite database and keyword search index are stored in Single-user cases. This allows the maximum amount of data to be read and written at the same time. If using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Fully-specified UNC paths should be in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
||||
<br><br>
|
||||
4. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
|
||||
|
||||
|
||||
4. Autopsy should now be fully installed
|
||||
|
||||
|
||||
*/
|
||||
|
@ -6,7 +6,7 @@ Overview
|
||||
|
||||
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/3.1/"> developer's guide</a> will help you develop your own Autopsy modules.
|
||||
|
||||
Autopsy 3 is a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
|
||||
Autopsy 4 (and 3) are a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
|
||||
|
||||
Help Topics
|
||||
-------
|
||||
@ -15,11 +15,6 @@ The following topics are available here:
|
||||
- \subpage installation_page
|
||||
- \subpage quick_start_guide "Quick Start Guide"
|
||||
- \subpage workflow_page
|
||||
- Multi-user Collaboration Setup
|
||||
- \subpage install_activemq
|
||||
- \subpage install_postgresql
|
||||
- \subpage install_solr
|
||||
- \subpage multiuser_page
|
||||
- Cases and Adding Data Sources
|
||||
- \subpage cases_page
|
||||
- \subpage ds_page
|
||||
@ -42,14 +37,21 @@ The following topics are available here:
|
||||
- \subpage tree_viewer_page
|
||||
- \subpage result_viewer_page
|
||||
- \subpage content_viewer_page
|
||||
<!-- - \subpage image_gallery_page Not released yet, coming soon-->
|
||||
- \subpage image_gallery_page
|
||||
- \subpage file_search_page
|
||||
- \subpage timeline_page
|
||||
- \subpage stix_page
|
||||
- Reporting
|
||||
- \subpage tagging_page
|
||||
- \subpage reporting_page
|
||||
- \subpage tagging_page
|
||||
- \subpage reporting_page
|
||||
- \subpage module_install_page
|
||||
- \subpage performance_page
|
||||
- Multi-user Collaborative Deployments
|
||||
- \subpage install_multiuser_page
|
||||
- \subpage install_activemq
|
||||
- \subpage install_postgresql
|
||||
- \subpage install_solr
|
||||
- \subpage multiuser_page
|
||||
|
||||
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.
|
||||
|
||||
|
@ -1,38 +1,42 @@
|
||||
/*! \page multiuser_page Using Multi-user Collaboration
|
||||
/*! \page multiuser_page Using Multi-user Cases
|
||||
|
||||
\section creating_multi_user_cases Creating Multi-user cases
|
||||
The collaborative Multi-user capabilities enable Autopsy cases to be opened by multiple reviewers at the same time allowing simultaneous case review with multiple reviewers.
|
||||
|
||||
Multi-user cases allow multiple instances of Autopsy to have the same case open at the same time. When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
|
||||
|
||||
When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html case-newcase.PNG
|
||||
<br><br>
|
||||
|
||||
Single-user functions the same as always, with a back end SQLite database and a machine-local version of Solr.
|
||||
|
||||
Multi-user allows multiple computers to open the same case at the same time. In order to accomplish this, some setup needs to take place. To use a Multi-user setup, one must install PostgreSQL, Solr, and ActiveMQ (see \subpage installation_page) on machines connected to the network, and properly configure Autopsy to interact with these services. This configuration is done in the Multi-user options panel, discussed below.
|
||||
<br><br>
|
||||
To create a multi-user case, the following must occur:
|
||||
- The network services must be installed, configured, and running. See \ref multiuser_install_services.
|
||||
- The Case folder needs to be in a shared folder that all other clients can also access at the same path (UNC or drive letter).
|
||||
- The data sources that are added with the Add Data Source wizard must be in a shared folder that all clients can access at the same path.
|
||||
|
||||
|
||||
|
||||
\section multi_user_other Other Multi-user Information
|
||||
|
||||
- When using a multi-user case, other nodes could be running data ingest on the same case. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
|
||||
|
||||
\section multi_user_options Multi-user options panel
|
||||
As shown in the screenshot below, all three services need configuration of IP addresses and ports, and two of them need user names and passwords. Enter the correct information into the dialog and press okay. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
|
||||
<br><br>
|
||||
\image html multiuser_settings.PNG
|
||||
<br><br>
|
||||
Other Multi-user Information
|
||||
=======
|
||||
- When using a case in collaborative Multi-user mode, other nodes could be running data ingest. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html othernodeingesting.PNG
|
||||
<br><br>
|
||||
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
|
||||
|
||||
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
|
||||
|
||||
<br><br>
|
||||
\image html messagebubbles.PNG
|
||||
<br><br>
|
||||
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
|
||||
|
||||
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
|
||||
|
||||
<br><br>
|
||||
\image html messagebubblesbigger.PNG
|
||||
|
||||
<br><br>
|
||||
- While using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
||||
|
||||
- When creating multi-user cases, we recommend using UNC paths to specify drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
|
||||
|
||||
*/
|
||||
|
19
docs/doxygen-user/performance.dox
Normal file
19
docs/doxygen-user/performance.dox
Normal file
@ -0,0 +1,19 @@
|
||||
/*! \page performance_page Optimizing Performance
|
||||
|
||||
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
|
||||
|
||||
1. Number of Threads: Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
|
||||
- Run Autopsy from the Start Menu or desktop
|
||||
- When presented with the case creation splash screen, cancel/close the window
|
||||
- Select "Tools", "Options"
|
||||
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
|
||||
- After each change, restart Autopsy to let this setting take effect.
|
||||
|
||||
<br><br>
|
||||
\image html threadcount.PNG
|
||||
<br><br>
|
||||
|
||||
2. When making a case, use different drives to store the case and the images. This allows the maximum amount of data to be read and written at the same time.
|
||||
|
||||
3. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
|
||||
*/
|
@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy"
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 3.1
|
||||
PROJECT_NUMBER = 4.0
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
@ -1063,7 +1063,7 @@ GENERATE_HTML = YES
|
||||
# The default directory is: html.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_OUTPUT = api-docs/3.1/
|
||||
HTML_OUTPUT = api-docs/4.0/
|
||||
|
||||
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
|
||||
# generated HTML page (for example: .htm, .php, .asp).
|
||||
@ -2058,7 +2058,7 @@ SKIP_FUNCTION_MACROS = YES
|
||||
# the path). If a tag file is not located in the directory in which doxygen is
|
||||
# run, you must also specify the path to the tagfile here.
|
||||
|
||||
TAGFILES = $(TSK_HOME)/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/
|
||||
TAGFILES = $(TSK_HOME)/bindings/java/doxygen/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.3/
|
||||
|
||||
# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
|
||||
# tag file that is based on the input files it reads. See section "Linking to
|
||||
|
@ -26,17 +26,25 @@ There are also a set of tutorials that Basis Technology published on their blog:
|
||||
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
|
||||
|
||||
To install NetBeans' plug-in:
|
||||
-# Download and install the Jython 2.7 installer (http://www.jython.org/downloads.html).
|
||||
-# Download and install the Jython 2.7 installer to desired location (http://www.jython.org/downloads.html).
|
||||
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
|
||||
-# Unpack the content (.nbm files) of the zip file to the desired location.
|
||||
-# In NetBeans go to Tools->Plugins. In Download tab, click on Add Plugins, then choose extracted .nbm files.
|
||||
-# Setup Jython path from Tools->Python Platform, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
||||
-# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
|
||||
-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
|
||||
|
||||
To install IntelliJ IDEA + Python plug-in:
|
||||
-# Download and install IDEA https://www.jetbrains.com/idea/download/
|
||||
-# In File->Settings->Plugins-> install Python Community Edition
|
||||
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
|
||||
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
|
||||
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
|
||||
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
|
||||
-# Open IDEA and choose desired UI theme. Continue with default settings.
|
||||
-# Choose to either create a new empty project or open an existing one.
|
||||
-# It will ask you to modify Project Structure. Leave that for now and click OK.
|
||||
-# In File->Settings. Go to Plugins tab and click on Install JetBrains Plugin.
|
||||
-# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
|
||||
-# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
|
||||
-# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
|
||||
-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
|
||||
-# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
|
||||
-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
|
||||
|
||||
\section mod_dev_py_create Creating a Basic Python Module
|
||||
|
||||
|
@ -8,14 +8,15 @@ The easiest guidance (from http://bits.netbeans.org/dev/javadoc/org-openide-modu
|
||||
|
||||
\section native_autopsy Autopsy Native Libraries
|
||||
|
||||
Autopsy has two types of native libraries:
|
||||
- The libtsk_jni native library
|
||||
- The libraries that libtsk_jni depends on
|
||||
|
||||
<tt>libtsk_jni</tt> is embedded inside of the <tt>Tsk_DataModel.jar</tt> file from The Sleuth Kit. The java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
|
||||
|
||||
The libraries that <tt>libtsk_jni</tt> depends on are launched by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search pathes.
|
||||
|
||||
There is code in build-windows.xml and build-unix.xml to copy the external libraries into their respective locations when a ZIP package is made of the program. These libraries must be accessible via normal launching methods when developing Autopsy (i.e. we only copy them into the Autopsy structure when building the ZIP).
|
||||
Autopsy has three types of native libraries that it depends on (and ships with):
|
||||
- The Sleuth Kit JNI Library(libtsk_jni)
|
||||
- This is embedded inside of the <tt>Tsk_DataModel.jar</tt> file. It gets copied into there by the datamodel ant target.
|
||||
- At runtime, java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
|
||||
- The libraries that libtsk_jni depends on that are not the Visual Studio runtime dlls that were used to build libtsk_jni
|
||||
- These are copied by Autopsy from TSK_HOME to a folder inside of Autopsy at compile time.
|
||||
- They are loaded by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search paths.
|
||||
- The Visual Studio runtime dlls that were used to build libtsk_jni
|
||||
- These are only copied by Autopsy when a ZIP folder is created of the project (on the theory that the local system doesn't need copies of them because it is already building TSK). There is code in build-windows.xml and build-unix.xml to copy the external libraries (from thidrparty/crt) into their respective locations in the ZIP file.
|
||||
- NOTE: If other versions of the runtimes need to be included (because of the other 3rd party libraries), then they should be treated like other 3rd party libraries and copied at compile time.
|
||||
|
||||
*/
|
||||
|
@ -1,4 +1,5 @@
|
||||
branding.token=autopsy
|
||||
nbjdk.active=JDK_1.8_66
|
||||
# Version of platform that is automatically downloaded
|
||||
# Note build.xml has similar definitions that should be kept in sync (manually)
|
||||
netbeans-plat-version=8.0.2
|
||||
@ -13,15 +14,12 @@ cluster.path=\
|
||||
${nbplatform.active.dir}/java:\
|
||||
${nbplatform.active.dir}/platform
|
||||
disabled.modules=\
|
||||
org.apache.tools.ant.module,\
|
||||
org.netbeans.api.debugger.jpda,\
|
||||
org.netbeans.api.java,\
|
||||
org.netbeans.api.maven,\
|
||||
org.netbeans.lib.nbjavac,\
|
||||
org.netbeans.libs.cglib,\
|
||||
org.netbeans.libs.javacapi,\
|
||||
org.netbeans.libs.javacimpl,\
|
||||
org.netbeans.libs.javafx,\
|
||||
org.netbeans.libs.springframework,\
|
||||
org.netbeans.modules.ant.browsetask,\
|
||||
org.netbeans.modules.ant.debugger,\
|
||||
@ -33,7 +31,6 @@ disabled.modules=\
|
||||
org.netbeans.modules.dbschema,\
|
||||
org.netbeans.modules.debugger.jpda,\
|
||||
org.netbeans.modules.debugger.jpda.ant,\
|
||||
org.netbeans.modules.debugger.jpda.js,\
|
||||
org.netbeans.modules.debugger.jpda.kit,\
|
||||
org.netbeans.modules.debugger.jpda.projects,\
|
||||
org.netbeans.modules.debugger.jpda.ui,\
|
||||
@ -46,8 +43,6 @@ disabled.modules=\
|
||||
org.netbeans.modules.form.nb,\
|
||||
org.netbeans.modules.form.refactoring,\
|
||||
org.netbeans.modules.hibernate,\
|
||||
org.netbeans.modules.hibernate4lib,\
|
||||
org.netbeans.modules.hibernatelib,\
|
||||
org.netbeans.modules.hudson.ant,\
|
||||
org.netbeans.modules.hudson.maven,\
|
||||
org.netbeans.modules.i18n,\
|
||||
@ -69,21 +64,16 @@ disabled.modules=\
|
||||
org.netbeans.modules.java.examples,\
|
||||
org.netbeans.modules.java.freeform,\
|
||||
org.netbeans.modules.java.guards,\
|
||||
org.netbeans.modules.java.helpset,\
|
||||
org.netbeans.modules.java.hints,\
|
||||
org.netbeans.modules.java.hints.declarative,\
|
||||
org.netbeans.modules.java.hints.declarative.test,\
|
||||
org.netbeans.modules.java.hints.legacy.spi,\
|
||||
org.netbeans.modules.java.hints.test,\
|
||||
org.netbeans.modules.java.hints.ui,\
|
||||
org.netbeans.modules.java.j2sedeploy,\
|
||||
org.netbeans.modules.java.j2seembedded,\
|
||||
org.netbeans.modules.java.j2seplatform,\
|
||||
org.netbeans.modules.java.j2seprofiles,\
|
||||
org.netbeans.modules.java.j2seproject,\
|
||||
org.netbeans.modules.java.kit,\
|
||||
org.netbeans.modules.java.lexer,\
|
||||
org.netbeans.modules.java.metrics,\
|
||||
org.netbeans.modules.java.navigation,\
|
||||
org.netbeans.modules.java.platform,\
|
||||
org.netbeans.modules.java.preprocessorbridge,\
|
||||
@ -95,7 +85,6 @@ disabled.modules=\
|
||||
org.netbeans.modules.java.sourceui,\
|
||||
org.netbeans.modules.java.testrunner,\
|
||||
org.netbeans.modules.javadoc,\
|
||||
org.netbeans.modules.javaee.injection,\
|
||||
org.netbeans.modules.javawebstart,\
|
||||
org.netbeans.modules.jellytools.java,\
|
||||
org.netbeans.modules.junit,\
|
||||
@ -116,8 +105,6 @@ disabled.modules=\
|
||||
org.netbeans.modules.maven.repository,\
|
||||
org.netbeans.modules.maven.search,\
|
||||
org.netbeans.modules.maven.spring,\
|
||||
org.netbeans.modules.nashorn.execution,\
|
||||
org.netbeans.modules.performance,\
|
||||
org.netbeans.modules.performance.java,\
|
||||
org.netbeans.modules.projectimport.eclipse.core,\
|
||||
org.netbeans.modules.projectimport.eclipse.j2se,\
|
||||
@ -130,8 +117,6 @@ disabled.modules=\
|
||||
org.netbeans.modules.websvc.jaxws21,\
|
||||
org.netbeans.modules.websvc.jaxws21api,\
|
||||
org.netbeans.modules.websvc.saas.codegen.java,\
|
||||
org.netbeans.modules.whitelist,\
|
||||
org.netbeans.modules.xml.jaxb,\
|
||||
org.netbeans.modules.xml.tools.java,\
|
||||
org.netbeans.spi.java.hints
|
||||
|
||||
|
BIN
thirdparty/crt/win64/msvcr120.dll
vendored
BIN
thirdparty/crt/win64/msvcr120.dll
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user