Improved layout and styling of ingest messages to have style sheets and consistent look and feel

This commit is contained in:
Brian Carrier 2013-08-08 18:02:18 -04:00
parent b40291038a
commit 36c37e52ff
6 changed files with 81 additions and 65 deletions

View File

@ -774,7 +774,7 @@ public class IngestManager {
public String toHtmlString() { public String toHtmlString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<html>"); sb.append("<html><body>");
sb.append("Ingest time: ").append(getTotalTimeString()).append("<br />"); sb.append("Ingest time: ").append(getTotalTimeString()).append("<br />");
sb.append("Total errors: ").append(errorsTotal).append("<br />"); sb.append("Total errors: ").append(errorsTotal).append("<br />");
@ -788,7 +788,7 @@ public class IngestManager {
} }
* */ * */
sb.append("</html>"); sb.append("</body></html>");
return sb.toString(); return sb.toString();
} }

View File

@ -171,14 +171,14 @@ public class IngestMessage {
* @param messageType message type * @param messageType message type
* @param source originating module * @param source originating module
* @param subject message subject to be displayed * @param subject message subject to be displayed
* @param details message details to be displayed, or null * @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. Or null.
* @return * @return
*/ */
public static IngestMessage createMessage(long ID, MessageType messageType, IngestModuleAbstract source, String subject, String details) { public static IngestMessage createMessage(long ID, MessageType messageType, IngestModuleAbstract source, String subject, String detailsHtml) {
if (messageType == null || source == null || subject == null) { if (messageType == null || source == null || subject == null) {
throw new IllegalArgumentException("message type, source and subject cannot be null"); throw new IllegalArgumentException("message type, source and subject cannot be null");
} }
return new IngestMessage(ID, messageType, source, subject, details, null); return new IngestMessage(ID, messageType, source, subject, detailsHtml, null);
} }
/** /**
@ -199,14 +199,14 @@ public class IngestMessage {
* @param ID ID of the message, unique in the context of module that generated it * @param ID ID of the message, unique in the context of module that generated it
* @param source originating module * @param source originating module
* @param subject message subject to be displayed * @param subject message subject to be displayed
* @param details message details to be displayed, or null * @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. Or null
* @return * @return
*/ */
public static IngestMessage createErrorMessage(long ID, IngestModuleAbstract source, String subject, String details) { public static IngestMessage createErrorMessage(long ID, IngestModuleAbstract source, String subject, String detailsHtml) {
if (source == null || subject == null) { if (source == null || subject == null) {
throw new IllegalArgumentException("source and subject cannot be null"); throw new IllegalArgumentException("source and subject cannot be null");
} }
return new IngestMessage(ID, MessageType.ERROR, source, subject, details, null); return new IngestMessage(ID, MessageType.ERROR, source, subject, detailsHtml, null);
} }
/** /**
@ -214,14 +214,14 @@ public class IngestMessage {
* @param ID ID of the message, unique in the context of module that generated it * @param ID ID of the message, unique in the context of module that generated it
* @param source originating module * @param source originating module
* @param subject message subject to be displayed * @param subject message subject to be displayed
* @param details message details to be displayed, or null * @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. Or null
* @return * @return
*/ */
public static IngestMessage createWarningMessage(long ID, IngestModuleAbstract source, String subject, String details) { public static IngestMessage createWarningMessage(long ID, IngestModuleAbstract source, String subject, String detailsHtml) {
if (source == null || subject == null) { if (source == null || subject == null) {
throw new IllegalArgumentException("source and subject cannot be null"); throw new IllegalArgumentException("source and subject cannot be null");
} }
return new IngestMessage(ID, MessageType.WARNING, source, subject, details, null); return new IngestMessage(ID, MessageType.WARNING, source, subject, detailsHtml, null);
} }
/** /**
@ -229,7 +229,7 @@ public class IngestMessage {
* @param ID ID of the message, unique in the context of module that generated it * @param ID ID of the message, unique in the context of module that generated it
* @param source originating module * @param source originating module
* @param subject message subject to be displayed * @param subject message subject to be displayed
* @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. * @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. Or null.
* @param uniqueKey Key used to group similar messages together. Shoudl be unique to the analysis. For example, hits for the same keyword in a keyword search would use the keyword as this unique value so that they can be grouped. * @param uniqueKey Key used to group similar messages together. Shoudl be unique to the analysis. For example, hits for the same keyword in a keyword search would use the keyword as this unique value so that they can be grouped.
* @param data blackboard artifact associated with the message, the same as fired in ModuleDataEvent by the module * @param data blackboard artifact associated with the message, the same as fired in ModuleDataEvent by the module
* @return * @return
@ -246,6 +246,9 @@ public class IngestMessage {
/** /**
* Used by IngestMager to post status messages. * Used by IngestMager to post status messages.
* @param subject message subject to be displayed
* @param detailsHtml html formatted detailed message (without leading and closing &lt;html&gt; tags), for instance, a human-readable representation of the data. Or null.
*/ */
static IngestMessage createManagerMessage(String subject, String detailsHtml) { static IngestMessage createManagerMessage(String subject, String detailsHtml) {
return new IngestMessage(++managerMessageId, MessageType.INFO, null, subject, detailsHtml, null); return new IngestMessage(++managerMessageId, MessageType.INFO, null, subject, detailsHtml, null);

View File

@ -24,6 +24,8 @@ import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer; import org.sleuthkit.autopsy.corecomponentinterfaces.BlackboardResultViewer;
@ -51,6 +53,18 @@ class IngestMessageDetailsPanel extends javax.swing.JPanel {
messageDetailsPane.setContentType("text/html"); messageDetailsPane.setContentType("text/html");
viewArtifactButton.setEnabled(false); viewArtifactButton.setEnabled(false);
viewContentButton.setEnabled(false); viewContentButton.setEnabled(false);
HTMLEditorKit kit = new HTMLEditorKit();
messageDetailsPane.setEditorKit(kit);
StyleSheet styleSheet = kit.getStyleSheet();
/* I tried to define the font-size only on body to have it inherit,
* it didn't work in all cases. */
styleSheet.addRule("body {font-family:Arial;font-size:10pt;}");
styleSheet.addRule("p {font-family:Arial;font-size:10pt;}");
styleSheet.addRule("li {font-family:Arial;font-size:10pt;}");
styleSheet.addRule("table {table-layout:fixed;}");
styleSheet.addRule("td {white-space:pre-wrap;overflow:hidden;}");
styleSheet.addRule("th {font-weight:bold;}");
BlackboardResultViewer v = Lookup.getDefault().lookup(BlackboardResultViewer.class); BlackboardResultViewer v = Lookup.getDefault().lookup(BlackboardResultViewer.class);
v.addOnFinishedListener(new PropertyChangeListener() { v.addOnFinishedListener(new PropertyChangeListener() {
@ -236,22 +250,24 @@ class IngestMessageDetailsPanel extends javax.swing.JPanel {
messageDetailsPane.setCursor(null); messageDetailsPane.setCursor(null);
} }
/**
* Display the details of a given message
* @param rowNumber index to the message to display
*/
void showDetails(int rowNumber) { void showDetails(int rowNumber) {
final IngestMessageGroup messageGroup = mainPanel.getMessagePanel().getMessageGroup(rowNumber); final IngestMessageGroup messageGroup = mainPanel.getMessagePanel().getMessageGroup(rowNumber);
if (messageGroup != null) { if (messageGroup != null) {
String details = messageGroup.getDetails(); String details = messageGroup.getDetails();
if (details != null) { if (details != null) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("<html>"); if (details.startsWith("<html><body>") == false) {
b.append("<head>"); b.append("<html><body>");
b.append("<style type='text/css'>"); b.append(details);
b.append("table {table-layout:fixed;}"); b.append("</body></html>");
b.append("td {font-family:Arial;font-size:10pt;white-space:pre-wrap;overflow:hidden;}"); }
b.append("th {font-family:Arial;font-size:10pt;font-weight:bold;}"); else {
b.append("p {font-family:Arial;font-size:10pt;}"); b.append(details);
b.append("</style>"); }
b.append("</head>");
b.append(details).append("</html>");
this.messageDetailsPane.setText(b.toString()); this.messageDetailsPane.setText(b.toString());
} else { } else {
this.messageDetailsPane.setText(""); this.messageDetailsPane.setText("");

View File

@ -130,25 +130,19 @@ public class HashDbIngestModule extends IngestModuleAbstractFile {
//details //details
detailsSb.append("<table border='0' cellpadding='4' width='280'>"); detailsSb.append("<table border='0' cellpadding='4' width='280'>");
detailsSb.append("<tr>"); detailsSb.append("<tr><td>Known bads found:</td>");
detailsSb.append("<th>Number of notable files found:</th>"); detailsSb.append("<td>").append(knownBadCount).append("</td></tr>");
detailsSb.append("<td>").append(knownBadCount).append("</td>");
detailsSb.append("</tr>");
detailsSb.append("<tr>"); detailsSb.append("<tr><td>Total Calculation Time</td><td>").append(calctime).append("</td></tr>\n");
detailsSb.append("<th>Notable databases used:</th>"); detailsSb.append("<tr><td>Total Lookup Time</td><td>").append(lookuptime).append("</td></tr>\n");
detailsSb.append("<td>Calc Time: ").append(calctime).append(" Lookup Time: ").append(lookuptime).append("</td>"); detailsSb.append("</table>");
detailsSb.append("</tr>");
detailsSb.append("<p>Databases Used:</p>\n<ul>");
for (HashDb db : knownBadSets.values()) { for (HashDb db : knownBadSets.values()) {
detailsSb.append("<tr><th>"); detailsSb.append("<li>").append(db.getName()).append("</li>\n");
detailsSb.append(db.getName());
detailsSb.append("</th><td>");
detailsSb.append(db.getDatabasePaths().get(0)); // TODO: support multiple database paths
detailsSb.append("</td></tr>");
} }
detailsSb.append("</table>"); detailsSb.append("</ul>");
services.postMessage(IngestMessage.createMessage(++messageId, IngestMessage.MessageType.INFO, this, "Hash Lookup Results", detailsSb.toString())); services.postMessage(IngestMessage.createMessage(++messageId, IngestMessage.MessageType.INFO, this, "Hash Lookup Results", detailsSb.toString()));
clearHashDatabaseHandles(); clearHashDatabaseHandles();
} }

View File

@ -135,8 +135,10 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
private enum IngestStatus { private enum IngestStatus {
TEXT_INGESTED, /// Text was extracted by knowing file type and text_ingested
INGESTED, EXTRACTED_INGESTED, SKIPPED, INGESTED_META STRINGS_INGESTED, ///< Strings were extracted from file
SKIPPED, ///< File was skipped for whatever reason
METADATA_INGESTED ///< No content, so we just text_ingested metadata
}; };
private Map<Long, IngestStatus> ingestStatus; private Map<Long, IngestStatus> ingestStatus;
@ -238,7 +240,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
} }
//log number of files / chunks in index //log number of files / chunks in index
//signal a potential change in number of indexed files //signal a potential change in number of text_ingested files
try { try {
final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles();
final int numIndexedChunks = KeywordSearch.getServer().queryNumIndexedChunks(); final int numIndexedChunks = KeywordSearch.getServer().queryNumIndexedChunks();
@ -350,7 +352,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
if (!server.isRunning()) { if (!server.isRunning()) {
String msg = "Keyword search server was not properly initialized, cannot run keyword search ingest. "; String msg = "Keyword search server was not properly initialized, cannot run keyword search ingest. ";
logger.log(Level.SEVERE, msg); logger.log(Level.SEVERE, msg);
String details = msg + "Please try stopping old java Solr process (if it exists) and restart the application."; String details = msg + "<br />Please try stopping old java Solr process (if it exists) and restart the application.";
services.postMessage(IngestMessage.createErrorMessage(++messageID, instance, msg, details)); services.postMessage(IngestMessage.createErrorMessage(++messageID, instance, msg, details));
return; return;
@ -359,7 +361,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
logger.log(Level.WARNING, "Error checking if Solr server is running while initializing ingest", ex); logger.log(Level.WARNING, "Error checking if Solr server is running while initializing ingest", ex);
//this means Solr is not properly initialized //this means Solr is not properly initialized
String msg = "Keyword search server was not properly initialized, cannot run keyword search ingest. "; String msg = "Keyword search server was not properly initialized, cannot run keyword search ingest. ";
String details = msg + "Please try stopping old java Solr process (if it exists) and restart the application."; String details = msg + "<br />Please try stopping old java Solr process (if it exists) and restart the application.";
services.postMessage(IngestMessage.createErrorMessage(++messageID, instance, msg, details)); services.postMessage(IngestMessage.createErrorMessage(++messageID, instance, msg, details));
return; return;
} }
@ -485,29 +487,29 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
logger.log(Level.INFO, "Commiting index"); logger.log(Level.INFO, "Commiting index");
ingester.commit(); ingester.commit();
logger.log(Level.INFO, "Index comitted"); logger.log(Level.INFO, "Index comitted");
//signal a potential change in number of indexed files //signal a potential change in number of text_ingested files
indexChangeNotify(); indexChangeNotify();
} }
} }
/** /**
* Posts inbox message with summary of indexed files * Posts inbox message with summary of text_ingested files
*/ */
private void postIndexSummary() { private void postIndexSummary() {
int indexed = 0; int text_ingested = 0;
int indexed_meta = 0; int metadata_ingested = 0;
int indexed_extr = 0; int strings_ingested = 0;
int skipped = 0; int skipped = 0;
for (IngestStatus s : ingestStatus.values()) { for (IngestStatus s : ingestStatus.values()) {
switch (s) { switch (s) {
case INGESTED: case TEXT_INGESTED:
++indexed; ++text_ingested;
break; break;
case INGESTED_META: case METADATA_INGESTED:
++indexed_meta; ++metadata_ingested;
break; break;
case EXTRACTED_INGESTED: case STRINGS_INGESTED:
++indexed_extr; ++strings_ingested;
break; break;
case SKIPPED: case SKIPPED:
++skipped; ++skipped;
@ -518,20 +520,21 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
} }
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
msg.append("Indexed files: ").append(indexed).append("<br />Indexed strings: ").append(indexed_extr); msg.append("<table border=0><tr><td>Files with known types</td><td>").append(text_ingested).append("</td></tr>");
msg.append("<br />Indexed meta-data only: ").append(indexed_meta).append("<br />"); msg.append("<tr><td>Files with general strings extracted</td><td>").append(strings_ingested).append("</td></tr>");
msg.append("<br />Skipped files: ").append(skipped).append("<br />"); msg.append("<tr><td>Metadata only was indexed</td><td>").append(metadata_ingested).append("</td></tr>");
msg.append("<tr><td>Skipped files</td><td>").append(skipped).append("</td></tr>");
msg.append("</table>");
String indexStats = msg.toString(); String indexStats = msg.toString();
logger.log(Level.INFO, "Keyword Indexing Completed: " + indexStats); logger.log(Level.INFO, "Keyword Indexing Completed: " + indexStats);
services.postMessage(IngestMessage.createMessage(++messageID, MessageType.INFO, this, "Keyword Indexing Results", indexStats)); services.postMessage(IngestMessage.createMessage(++messageID, MessageType.INFO, this, "Keyword Indexing Results", indexStats));
} }
/** /**
* Helper method to notify listeners on index update * Helper method to notify listeners on index update
*/ */
private void indexChangeNotify() { private void indexChangeNotify() {
//signal a potential change in number of indexed files //signal a potential change in number of text_ingested files
try { try {
final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles();
KeywordSearch.fireNumIndexedFilesChange(null, new Integer(numIndexedFiles)); KeywordSearch.fireNumIndexedFilesChange(null, new Integer(numIndexedFiles));
@ -662,7 +665,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
* @param aFile file to extract strings from, divide into chunks and * @param aFile file to extract strings from, divide into chunks and
* index * index
* @param detectedFormat mime-type detected, or null if none detected * @param detectedFormat mime-type detected, or null if none detected
* @return true if the file was indexed, false otherwise * @return true if the file was text_ingested, false otherwise
* @throws IngesterException exception thrown if indexing failed * @throws IngesterException exception thrown if indexing failed
*/ */
private boolean extractTextAndIndex(AbstractFile aFile, String detectedFormat) throws IngesterException { private boolean extractTextAndIndex(AbstractFile aFile, String detectedFormat) throws IngesterException {
@ -693,12 +696,12 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
* *
* @param aFile file to extract strings from, divide into chunks and * @param aFile file to extract strings from, divide into chunks and
* index * index
* @return true if the file was indexed, false otherwise * @return true if the file was text_ingested, false otherwise
*/ */
private boolean extractStringsAndIndex(AbstractFile aFile) { private boolean extractStringsAndIndex(AbstractFile aFile) {
try { try {
if (stringExtractor.index(aFile)) { if (stringExtractor.index(aFile)) {
ingestStatus.put(aFile.getId(), IngestStatus.EXTRACTED_INGESTED); ingestStatus.put(aFile.getId(), IngestStatus.STRINGS_INGESTED);
return true; return true;
} else { } else {
logger.log(Level.WARNING, "Failed to extract strings and ingest, file '" + aFile.getName() + "' (id: " + aFile.getId() + ")."); logger.log(Level.WARNING, "Failed to extract strings and ingest, file '" + aFile.getName() + "' (id: " + aFile.getId() + ").");
@ -735,7 +738,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
* Adds the file to the index. Detects file type, calls extractors, etc. * Adds the file to the index. Detects file type, calls extractors, etc.
* *
* @param aFile File to analyze * @param aFile File to analyze
* @param indexContent False if only metadata should be indexed. True if * @param indexContent False if only metadata should be text_ingested. True if
* content and metadata should be index. * content and metadata should be index.
*/ */
private void indexFile(AbstractFile aFile, boolean indexContent) { private void indexFile(AbstractFile aFile, boolean indexContent) {
@ -756,7 +759,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
if ((indexContent == false || aFile.isDir() || size == 0)) { if ((indexContent == false || aFile.isDir() || size == 0)) {
try { try {
ingester.ingest(aFile, false); //meta-data only ingester.ingest(aFile, false); //meta-data only
ingestStatus.put(aFile.getId(), IngestStatus.INGESTED_META); ingestStatus.put(aFile.getId(), IngestStatus.METADATA_INGESTED);
} catch (IngesterException ex) { } catch (IngesterException ex) {
ingestStatus.put(aFile.getId(), IngestStatus.SKIPPED); ingestStatus.put(aFile.getId(), IngestStatus.SKIPPED);
logger.log(Level.WARNING, "Unable to index meta-data for file: " + aFile.getId(), ex); logger.log(Level.WARNING, "Unable to index meta-data for file: " + aFile.getId(), ex);
@ -802,7 +805,7 @@ public final class KeywordSearchIngestModule extends IngestModuleAbstractFile {
logger.log(Level.WARNING, "Failed to extract text and ingest, file '" + aFile.getName() + "' (id: " + aFile.getId() + ")."); logger.log(Level.WARNING, "Failed to extract text and ingest, file '" + aFile.getName() + "' (id: " + aFile.getId() + ").");
ingestStatus.put(aFile.getId(), IngestStatus.SKIPPED); ingestStatus.put(aFile.getId(), IngestStatus.SKIPPED);
} else { } else {
ingestStatus.put(aFile.getId(), IngestStatus.INGESTED); ingestStatus.put(aFile.getId(), IngestStatus.TEXT_INGESTED);
wasTextAdded = true; wasTextAdded = true;
} }

View File

@ -85,7 +85,7 @@ public final class RAImageIngestModule extends IngestModuleDataSource {
StringBuilder errorMessage = new StringBuilder(); StringBuilder errorMessage = new StringBuilder();
String errorMsgSubject; String errorMsgSubject;
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
errorMessage.append("Errors encountered during analysis: <ul>\n"); errorMessage.append("<p>Errors encountered during analysis: <ul>\n");
for (String msg : errors) { for (String msg : errors) {
errorMessage.append("<li>").append(msg).append("</li>\n"); errorMessage.append("<li>").append(msg).append("</li>\n");
} }
@ -97,7 +97,7 @@ public final class RAImageIngestModule extends IngestModuleDataSource {
errorMsgSubject = errors.size() + " errors found"; errorMsgSubject = errors.size() + " errors found";
} }
} else { } else {
errorMessage.append("No errors encountered."); errorMessage.append("<p>No errors encountered.</p>");
errorMsgSubject = "No errors reported"; errorMsgSubject = "No errors reported";
} }
final IngestMessage msg = IngestMessage.createMessage(++messageId, MessageType.INFO, this, "Finished " + dataSource.getName()+ " - " + errorMsgSubject, errorMessage.toString()); final IngestMessage msg = IngestMessage.createMessage(++messageId, MessageType.INFO, this, "Finished " + dataSource.getName()+ " - " + errorMsgSubject, errorMessage.toString());