From 5ddd5dc26f73a50eaa93b46212ad4d33d93a0e96 Mon Sep 17 00:00:00 2001 From: jmillman Date: Thu, 28 Apr 2016 10:54:40 -0400 Subject: [PATCH] more cleanup in SaveSnapshotAsReport and SnapShotReportWriter and templates --- .../actions/SaveSnapshotAsReport.java | 73 ++++-- .../org/sleuthkit/autopsy/timeline/index.css | 16 -- .../snapshot/SnapShotReportWriter.java | 241 +++++++++++------- .../autopsy/timeline/snapshot/index.css | 18 ++ .../{actions => snapshot}/index_template.html | 0 ...vigation_template.html => navigation.html} | 0 .../timeline/snapshot/snapshot_template.html | 23 +- .../timeline/snapshot/summary_template.html | 3 +- .../timeline/ui/VisualizationPanel.java | 70 ++--- 9 files changed, 260 insertions(+), 184 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/timeline/index.css create mode 100644 Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css rename Core/src/org/sleuthkit/autopsy/timeline/{actions => snapshot}/index_template.html (100%) rename Core/src/org/sleuthkit/autopsy/timeline/snapshot/{navigation_template.html => navigation.html} (100%) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index cffa464d6f..4f2028e421 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2014-16 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,15 @@ package org.sleuthkit.autopsy.timeline.actions; import java.awt.Desktop; +import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.function.Supplier; import java.util.logging.Level; +import javafx.embed.swing.SwingFXUtils; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; @@ -32,8 +35,6 @@ import javafx.scene.control.ButtonType; import javafx.scene.control.TextInputDialog; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.stage.Modality; -import javafx.stage.StageStyle; import javax.swing.JOptionPane; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.HyperlinkLabel; @@ -47,19 +48,27 @@ import org.sleuthkit.autopsy.timeline.snapshot.SnapShotReportWriter; import org.sleuthkit.datamodel.TskCoreException; /** - * Save a snapshot of the given node as an autopsy report. + * Action that saves a snapshot of the given node as an autopsy report. + * Delegates to SnapsHotReportWrite to actually generate and write the report. */ public class SaveSnapshotAsReport extends Action { private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName()); - private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true); // + private static final Image SNAP_SHOT = new Image("org/sleuthkit/autopsy/timeline/images/image.png", 16, 16, true, true); private static final ButtonType OPEN = new ButtonType(Bundle.OpenReportAction_DisplayName(), ButtonBar.ButtonData.NO); private static final ButtonType OK = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE); private final TimeLineController controller; private final Case currentCase; - @NbBundle.Messages({"SaveSnapshot.action.name.text=Snapshot Report", + /** + * Constructor + * + * @param controller The controller for this timeline action + * @param nodeSupplier The Supplier of the node to snapshot. + */ + @NbBundle.Messages({ + "SaveSnapshot.action.name.text=Snapshot Report", "SaveSnapshot.action.longText=Save a screen capture of the visualization as a report.", "SaveSnapshot.fileChoose.title.text=Save snapshot to", "# {0} - report file path", @@ -67,10 +76,10 @@ public class SaveSnapshotAsReport extends Action { "Timeline.ModuleName=Timeline", "SaveSnapShotAsReport.Success=Success", "# {0} - uniqueness identifier, local date time at report creation time", "SaveSnapsHotAsReport.ReportName=timeline-report-{0}", - "SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot as a report. See log for details", + "SaveSnapShotAsReport.FailedToAddReport=Failed to add snaphot to case as a report.", "# {0} - report name", - "SaveSnapShotAsReport.ErrorWritingReport=Error writing report {0} to disk. See log for details",}) - public SaveSnapshotAsReport(TimeLineController controller, Node node) { + "SaveSnapShotAsReport.ErrorWritingReport=Error writing report {0} to disk.",}) + public SaveSnapshotAsReport(TimeLineController controller, Supplier nodeSupplier) { super(Bundle.SaveSnapshot_action_name_text()); setLongText(Bundle.SaveSnapshot_action_longText()); setGraphic(new ImageView(SNAP_SHOT)); @@ -79,9 +88,13 @@ public class SaveSnapshotAsReport extends Action { this.currentCase = controller.getAutopsyCase(); setEventHandler(actionEvent -> { + //capture generation date and use to make default report name Date generationDate = new Date(); final String defaultReportName = FileUtil.escapeFileName(currentCase.getName() + " " + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss").format(generationDate)); + BufferedImage snapshot = SwingFXUtils.fromFXImage(nodeSupplier.get().snapshot(null, null), null); + + //prompt user to pick report name TextInputDialog textInputDialog = new TextInputDialog(); textInputDialog.setTitle("Timeline"); textInputDialog.getEditor().setPromptText("leave empty for default report name: " + defaultReportName); @@ -89,48 +102,54 @@ public class SaveSnapshotAsReport extends Action { textInputDialog.setHeaderText("Enter a report name for the Timeline Snapshot Report."); textInputDialog.showAndWait().ifPresent(enteredReportName -> { + //reportName defaults to case name + timestamp if left blank String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName); Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, "Timeline Snapshot"); - Path reportIndexFilePath; - try { + Path reportMainFilePath; - reportIndexFilePath = new SnapShotReportWriter(currentCase, reportFolderPath, reportName, controller.getEventsModel().getZoomParamaters(), generationDate, node).writeReport(); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, "Error writing report " + reportFolderPath + " to disk", e); // + try { + //generate and write report + reportMainFilePath = new SnapShotReportWriter(currentCase, + reportFolderPath, + reportName, + controller.getEventsModel().getZoomParamaters(), + generationDate, snapshot).writeReport(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Error writing report to disk at " + reportFolderPath, ex); new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_ErrorWritingReport(reportFolderPath)).show(); return; } + try { - //add html file as report to case - Case.getCurrentCase().addReport(reportIndexFilePath.toString(), Bundle.Timeline_ModuleName(), reportName); + //add main file as report to case + Case.getCurrentCase().addReport(reportMainFilePath.toString(), Bundle.Timeline_ModuleName(), reportName); } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "failed to add html wrapper as a report", ex); // + LOGGER.log(Level.WARNING, "Failed to add " + reportMainFilePath.toString() + " to case as a report", ex); // new Alert(Alert.AlertType.ERROR, Bundle.SaveSnapShotAsReport_FailedToAddReport()).show(); return; } - //create alert to notify user of report location + //notify user of report location final Alert alert = new Alert(Alert.AlertType.INFORMATION, null, OPEN, OK); alert.setTitle(Bundle.SaveSnapshot_action_name_text()); alert.setHeaderText(Bundle.SaveSnapShotAsReport_Success()); - alert.initStyle(StageStyle.UTILITY); - alert.initOwner(node.getScene().getWindow()); - alert.initModality(Modality.APPLICATION_MODAL); //make action to open report, and hyperlinklable to invoke action - final OpenReportAction openReportAction = new OpenReportAction(reportIndexFilePath); - HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(Bundle.SaveSnapShotAsReport_ReportSavedAt(reportIndexFilePath.toString())); + final OpenReportAction openReportAction = new OpenReportAction(reportMainFilePath); + HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(Bundle.SaveSnapShotAsReport_ReportSavedAt(reportMainFilePath.toString())); hyperlinkLabel.setOnAction(openReportAction); alert.getDialogPane().setContent(hyperlinkLabel); - alert.showAndWait().filter(OPEN::equals) - .ifPresent(buttonType -> openReportAction.handle(null)); - + alert.showAndWait().filter(OPEN::equals).ifPresent(buttonType -> openReportAction.handle(null)); }); }); } - @NbBundle.Messages({"OpenReportAction.DisplayName=Open Report", + /** + * Action that opens the given Path in the system default application. + */ + @NbBundle.Messages({ + "OpenReportAction.DisplayName=Open Report", "OpenReportAction.NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.", "OpenReportAction.MessageBoxTitle=Open Report Failure", "OpenReportAction.NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.", diff --git a/Core/src/org/sleuthkit/autopsy/timeline/index.css b/Core/src/org/sleuthkit/autopsy/timeline/index.css deleted file mode 100644 index fd6e48afd9..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/index.css +++ /dev/null @@ -1,16 +0,0 @@ -body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;} -#content {padding: 30px;} -#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;} -h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;} -h2 {font-size: 20px; font-weight: bolder; color: #07A;} -h3 {font-size: 16px; color: #07A;} -h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;} -ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;} -ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;} -ul li a:hover {text-decoration: underline;} -p {margin: 0 0 20px 0;} -table {max-width: 100%; min-width: 700px; padding: 0; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;} -.keyword_list table {width: 100%; margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;} -table th {display: table-cell; text-align: left; padding: 8px 16px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;} -table td {display: table-cell; padding: 8px 16px; font: 13px/20px Arial, Helvetica, sans-serif; max-width: 500px; min-width: 125px; word-break: break-all; overflow: auto;} -table tr:nth-child(even) td {background: #f3f3f3;} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java index 3b7fe30aeb..3197e8ac2d 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java @@ -1,13 +1,27 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.timeline.snapshot; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; +import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -19,156 +33,197 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; -import javafx.embed.swing.SwingFXUtils; -import javafx.scene.Node; import javax.imageio.ImageIO; import org.apache.commons.lang3.StringUtils; -import org.joda.time.Interval; import org.joda.time.format.DateTimeFormat; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.report.ReportBranding; -import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; /** - * + * Generate and write the Timeline snapshot report to disk. */ public class SnapShotReportWriter { + /** + * mustache.java template factory. + */ private final static MustacheFactory mf = new DefaultMustacheFactory(); + private final Case currentCase; private final Path reportFolderPath; private final String reportName; + private final ReportBranding reportBranding; + private final ZoomParams zoomParams; private final Date generationDate; + private final BufferedImage image; - public SnapShotReportWriter(Case currentCase, Path reportFolderPath ,String reportName, ZoomParams zoomParams, Date generationDate, Node node) { + /** + * Constructor + * + * @param currentCase The Case to write a report for. + * @param reportFolderPath The Path to the folder that will contain the + * report. + * @param reportName The name of the report. + * @param zoomParams The ZoomParams in effect when the snapshot was + * taken. + * @param generationDate The generation Date of the report. + * @param snapshot A snapshot of the visualization to include in the + * report. + */ + public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { this.currentCase = currentCase; this.reportFolderPath = reportFolderPath; this.reportName = reportName; this.zoomParams = zoomParams; this.generationDate = generationDate; + this.image = snapshot; + + this.reportBranding = new ReportBranding(); } + /** + * Generate and write the report to disk. + * + * @return The Path to the "main file" of the report. This is the file that + * Autopsy shows in the results view when the Reports Node is + * selected in the DirectoryTree. + * + * @throws IOException If there is a problem writing the report. + */ public Path writeReport() throws IOException { - ReportBranding reportBranding = new ReportBranding(); - - - Path reportIndexFilePath = reportFolderPath.resolve("index.html"); - - //ensure directory exists and write html files + //ensure directory exists Files.createDirectories(reportFolderPath); - writeSummary(reportFolderPath, generationDate, reportBranding, reportName); - writeIndexHTML(reportFolderPath); - writeSnapShotHTMLFile(reportFolderPath, reportName, zoomParams); - //take snapshot and save in report directory - ImageIO.write(SwingFXUtils.fromFXImage(node.snapshot(null, null), null), "png", - reportFolderPath.resolve("snapshot.png").toFile()); + //save the snapshot in the report directory + ImageIO.write(image, "png", reportFolderPath.resolve("snapshot.png").toFile()); - copyResources(reportFolderPath, reportBranding); + copyResources(); - return reportIndexFilePath; + writeSummaryHTML(); + writeSnapShotHTMLFile(); + return writeIndexHTML(); } - private static void writeSnapShotHTMLFile(Path reportPath, String reportTitle, ZoomParams zoomParams) throws IOException { + /** + * Generate and write the html page that shows the snapshot and the state of + * the ZoomParams + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private void writeSnapShotHTMLFile() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap snapShotContext = new HashMap<>(); + snapShotContext.put("reportTitle", reportName); + snapShotContext.put("startTime", zoomParams.getTimeRange().getStart().toString(DateTimeFormat.fullDateTime())); + snapShotContext.put("endTime", zoomParams.getTimeRange().getEnd().toString(DateTimeFormat.fullDateTime())); + snapShotContext.put("zoomParams", zoomParams); - HashMap context = new HashMap<>(); - - context.put("reportTitle", reportTitle); - Interval timeRange = zoomParams.getTimeRange(); - context.put("startTime", timeRange.getStart().toString(DateTimeFormat.fullDateTime())); - context.put("endTime", timeRange.getEnd().toString(DateTimeFormat.fullDateTime())); - context.put("zoomParams", zoomParams); - - try (Writer writer = Files.newBufferedWriter(reportPath.resolve("snapshot.html"), Charset.forName("UTF-8"))) { - Mustache summaryMustache = mf.compile(new InputStreamReader(SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/actions/snapshot_template.html")), "Snapshot"); - summaryMustache.execute(writer, context); - writer.flush(); - } + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html")); } - private void writeIndexHTML(Path reportPath) throws IOException { + /** + * Generate and write the main html page with frames for navigation on the + * left and content on the right. + * + * @return The Path of the written html file. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private Path writeIndexHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap indexContext = new HashMap<>(); + indexContext.put("currentCase", currentCase); + Path reportIndexFile = reportFolderPath.resolve("index.html"); - HashMap context = new HashMap<>(); + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile); + return reportIndexFile; + } - context.put("currentCase", currentCase); + /** + * * Generate and write the summary of the current case for this report. + * + * @throws IOException If there is a problem writing the html file to disk. + */ + private void writeSummaryHTML() throws IOException { + //make a map of context objects to resolve template paramaters against + HashMap summaryContext = new HashMap<>(); + summaryContext.put("reportName", reportName); + summaryContext.put("reportBranding", reportBranding); + summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); + summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); + summaryContext.put("currentCase", currentCase); - try (Writer writer = Files.newBufferedWriter(reportPath.resolve("index.html"), Charset.forName("UTF-8"))) { - Mustache summaryMustache = mf.compile(new InputStreamReader(SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/actions/index_template.html")), "Index"); + fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); + } + + /** + * Fill in the mustache template at the given location using the values from + * the given context object and save it to the given outPutFile. + * + * @param templateLocation The location of the template. suitible for use + * with Class.getResourceAsStream + * @param templateName The name of the tempalte. (Used by mustache to + * cache templates?) + * @param context The contect to use to fill in the template + * values. + * @param outPutFile The filled in tempalte will be saced at this + * Path. + * + * @throws IOException If there is a problem saving the filled in template + * to disk. + */ + private void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException { + + Mustache summaryMustache = mf.compile(new InputStreamReader(SnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName); + try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { summaryMustache.execute(writer, context); - writer.flush(); } } /** - * Write the summary of the current case for this report. + * Copy static resources (static html, css, images, etc) to the reports + * folder. * - * @param reportPath the value of reportPath - * @param generationDate the value of generationDate + * @throws IOException If there is a problem copying the resources. */ - @NbBundle.Messages({ - "ReportHTML.writeSum.caseName=Case:", - "ReportHTML.writeSum.caseNum=Case Number:", - "ReportHTML.writeSum.noCaseNum=No case number", - "ReportHTML.writeSum.noExaminer=No examiner", - "ReportHTML.writeSum.examiner=Examiner:", - "ReportHTML.writeSum.timezone=Timezone:", - "ReportHTML.writeSum.path=Path:"}) - private void writeSummary(Path reportPath, final Date generationDate, ReportBranding reportBranding, String reportName) throws IOException { - HashMap context = new HashMap<>(); + private void copyResources() throws IOException { - context.put("reportName", reportName); - context.put("reportBranding", reportBranding); - context.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); - context.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); - context.put("currentCase", currentCase); - - try (Writer writer = Files.newBufferedWriter(reportPath.resolve("summary.html"), Charset.forName("UTF-8"))) { - - Mustache summaryMustache = mf.compile(new InputStreamReader(SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/actions/summary_template.html")), "Summary"); - summaryMustache.execute(writer, context); - writer.flush(); - } - } - - private void copyResources(Path reportPath, ReportBranding reportBranding) throws IOException { - //copy navigation html - try (InputStream navStream = SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/actions/navigation_template.html")) { - Files.copy(navStream, reportPath.resolve("nav.html")); - } - - //pull generator and agency logo from branding + //pull generator and agency logos from branding String generatorLogoPath = reportBranding.getGeneratorLogoPath(); if (StringUtils.isNotBlank(generatorLogoPath)) { - Files.copy(Paths.get(generatorLogoPath), reportPath.resolve("generator_logo.png")); + Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); } - String agencyLogoPath = reportBranding.getAgencyLogoPath(); if (StringUtils.isNotBlank(agencyLogoPath)) { - Files.copy(Paths.get(agencyLogoPath), reportPath.resolve("agency_logo.png")); + Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("agency_logo.png")); } + //copy navigation html + try (InputStream navStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/navigation.html")) { + Files.copy(navStream, reportFolderPath.resolve("nav.html")); + } //copy favicon - try (InputStream faviconStream = SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { - Files.copy(faviconStream, reportPath.resolve("favicon.ico")); + try (InputStream faviconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { + Files.copy(faviconStream, reportFolderPath.resolve("favicon.ico")); } - try (InputStream summaryStream = SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { - Files.copy(summaryStream, reportPath.resolve("summary.png")); + //copy report summary icon + try (InputStream summaryStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { + Files.copy(summaryStream, reportFolderPath.resolve("summary.png")); } - try (InputStream snapshotIconStream = SaveSnapshotAsReport.class.getResourceAsStream("org/sleuthkit/autopsy/timeline/images/image.png")) { - Files.copy(snapshotIconStream, reportPath.resolve("snapshot_icon.png")); + //copy snapshot icon + try (InputStream snapshotIconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/images/image.png")) { + Files.copy(snapshotIconStream, reportFolderPath.resolve("snapshot_icon.png")); } - - //copy report css - try (InputStream resource = SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // - Files.copy(resource, reportPath.resolve("index.css")); // + //copy main report css + try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/index.css")) { // + Files.copy(resource, reportFolderPath.resolve("index.css")); // } //copy summary css - try (InputStream resource = SaveSnapshotAsReport.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/actions/summary.css")) { // - Files.copy(resource, reportPath.resolve("summary.css")); // + try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/summary.css")) { // + Files.copy(resource, reportFolderPath.resolve("summary.css")); // } } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css new file mode 100644 index 0000000000..98919cf3e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index.css @@ -0,0 +1,18 @@ +body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;} +#content {padding: 30px;} +#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;} +h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;} +h2 {font-size: 20px; font-weight: bolder; color: #07A;} +h3 {font-size: 16px; color: #07A;} +h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;} +ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;} +ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;} +ul li a:hover {text-decoration: underline;} +p {margin: 0 0 20px 0;} +table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;} +.keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;} +table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;} +table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; } +table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; } +table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; } +table tr:nth-child(even) td {background: #f3f3f3;} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/index_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/actions/index_template.html rename to Core/src/org/sleuthkit/autopsy/timeline/snapshot/index_template.html diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html similarity index 100% rename from Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation_template.html rename to Core/src/org/sleuthkit/autopsy/timeline/snapshot/navigation.html diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html index d9dd9e34a6..d15c9d1bef 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html @@ -1,14 +1,14 @@ - Autopsy Timeline Snapshot:{{reportTitle}} + Timeline Snapshot: {{reportTitle}} - +
-

{{reportTitle}}

- snaphot + + snaphot {{#zoomParams.getFilter}} - > +
Time Range: {{startTime}} to @@ -17,15 +17,16 @@
Event Type Zoom Level: {{zoomParams.getTypeZoomLevel.getDisplayName}}
Filters: + Filters:
    - {{#getTextFilter}}
  • text = "{{getText}}"[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
  • {{/getTextFilter}} + {{#getTextFilter}}
  • text = "{{getText}}" [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
  • {{/getTextFilter}} {{#getKnownFilter}}
  • Hide Known Files [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
  • {{/getKnownFilter}} {{#getDataSourcesFilter}}
  • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
      {{#getSubFilters}} -
    • {{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • {{/getSubFilters}}
  • @@ -34,7 +35,7 @@
  • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
      {{#getSubFilters}} -
    • {{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • {{/getSubFilters}}
  • @@ -43,7 +44,7 @@
  • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
      {{#getSubFilters}} -
    • {{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
    • {{/getSubFilters}}
  • @@ -52,10 +53,10 @@
  • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
      {{#getSubFilters}} -
    • {{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] +
    • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
        {{#getSubFilters}} -
      • {{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
      • +
      • {{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
      • {{/getSubFilters}}
    • diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html index f58314e68d..963c6d2096 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html @@ -7,7 +7,7 @@
      -

      {{reportBranding.getReportTitle}}:{{reportName}}{{#ingestRunning}}Warning, this report was run before ingest services completed!{{/ingestRunning}}

      +

      {{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}Warning, this report was run before ingest services completed!{{/ingestRunning}}

      Timeline Report generated on {{generationDateTime}}

      @@ -33,7 +33,6 @@ {{#currentCase.getDataSources}}

      {{getName}}

      {{#getTimeZone}} - {{#getPaths}} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java index 1fe72b5aa8..3e2cf1fbb7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java @@ -288,7 +288,7 @@ final public class VisualizationPanel extends BorderPane { setViewMode(controller.viewModeProperty().get()); //configure snapshor button / action - ActionUtils.configureButton(new SaveSnapshotAsReport(controller, VisualizationPanel.this), snapShotButton); + ActionUtils.configureButton(new SaveSnapshotAsReport(controller, () -> VisualizationPanel.this), snapShotButton); /////configure start and end pickers startLabel.setText(Bundle.VisualizationPanel_startLabel_text()); @@ -427,54 +427,54 @@ final public class VisualizationPanel extends BorderPane { histogramTask = new LoggedTask( NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS - private final Lighting lighting = new Lighting(); + private final Lighting lighting = new Lighting(); @Override protected Void call() throws Exception { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS - long max = 0; - final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); - final long lowerBound = rangeInfo.getLowerBound(); - final long upperBound = rangeInfo.getUpperBound(); - Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone())); + long max = 0; + final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval()); + final long lowerBound = rangeInfo.getLowerBound(); + final long upperBound = rangeInfo.getUpperBound(); + Interval timeRange = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone())); - //extend range to block bounderies (ie day, month, year) - int p = 0; // progress counter + //extend range to block bounderies (ie day, month, year) + int p = 0; // progress counter - //clear old data, and reset ranges and series - Platform.runLater(() -> { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS + //clear old data, and reset ranges and series + Platform.runLater(() -> { + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS - }); + }); - ArrayList bins = new ArrayList<>(); + ArrayList bins = new ArrayList<>(); - DateTime start = timeRange.getStart(); - while (timeRange.contains(start)) { - if (isCancelled()) { - return null; - } - DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod()); - final Interval interval = new Interval(start, end); - //increment for next iteration + DateTime start = timeRange.getStart(); + while (timeRange.contains(start)) { + if (isCancelled()) { + return null; + } + DateTime end = start.plus(rangeInfo.getPeriodSize().getPeriod()); + final Interval interval = new Interval(start, end); + //increment for next iteration - start = end; + start = end; - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS - //query for current range - long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum(); - bins.add(count); + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS + //query for current range + long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum(); + bins.add(count); - max = Math.max(count, max); + max = Math.max(count, max); - final double fMax = Math.log(max); - final ArrayList fbins = new ArrayList<>(bins); - Platform.runLater(() -> { - updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS + final double fMax = Math.log(max); + final ArrayList fbins = new ArrayList<>(bins); + Platform.runLater(() -> { + updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS - histogramBox.getChildren().clear(); + histogramBox.getChildren().clear(); for (Long bin : fbins) { if (isCancelled()) { @@ -499,7 +499,7 @@ final public class VisualizationPanel extends BorderPane { return null; } - }; + }; new Thread(histogramTask).start(); controller.monitorTask(histogramTask); }
      Timezone:{{getTimeZone}}