more cleanup in SaveSnapshotAsReport and SnapShotReportWriter and templates

This commit is contained in:
jmillman 2016-04-28 10:54:40 -04:00
parent 3c68a55ea2
commit 5ddd5dc26f
9 changed files with 260 additions and 184 deletions

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-15 Basis Technology Corp.
* Copyright 2014-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Node> 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.",

View File

@ -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;}

View File

@ -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 <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.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<String, Object> 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<String, Object> context = new HashMap<>();
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html"));
}
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);
/**
* 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<String, Object> indexContext = new HashMap<>();
indexContext.put("currentCase", currentCase);
Path reportIndexFile = reportFolderPath.resolve("index.html");
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");
fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile);
return reportIndexFile;
}
/**
* * 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<String, Object> 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);
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();
}
}
private void writeIndexHTML(Path reportPath) throws IOException {
HashMap<String, Object> context = new HashMap<>();
context.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");
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=<i>No case number</i>",
"ReportHTML.writeSum.noExaminer=<i>No examiner</i>",
"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<String, Object> 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")); //
}
}
}

View File

@ -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;}

View File

@ -1,14 +1,14 @@
<html>
<head>
<title>Autopsy Timeline Snapshot:{{reportTitle}}</title>
<title>Timeline Snapshot: {{reportTitle}}</title>
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/ico" href="favicon.ico" />
</head>
<body>
<body><div id="header">Timeline Snapshot</div>
<div id="content">
<h1>{{reportTitle}}</h1>
<img src = "snapshot.png" alt = "snaphot"/>
<img src = "snapshot.png" alt = "snaphot">
<table>
<tr><td>Time Range: </td><td>{{startTime}}
to
@ -17,15 +17,16 @@
<tr><td>Event Type Zoom Level: </td><td>{{zoomParams.getTypeZoomLevel.getDisplayName}}</td></tr>
{{#zoomParams.getFilter}}
<tr>
<td>Filters: </td>><td>
<td>Filters: </td>
<td>
<ul>
{{#getTextFilter}}<li>text = "{{getText}}"[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li> {{/getTextFilter}}
{{#getTextFilter}}<li>text = "{{getText}}" [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li> {{/getTextFilter}}
{{#getKnownFilter}}<li>Hide Known Files [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li> {{/getKnownFilter}}
{{#getDataSourcesFilter}}
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
<ul>
{{#getSubFilters}}
<li>{{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
{{/getSubFilters}}
</ul>
</li>
@ -34,7 +35,7 @@
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
<ul>
{{#getSubFilters}}
<li>{{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
{{/getSubFilters}}
</ul>
</li>
@ -43,7 +44,7 @@
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
<ul>
{{#getSubFilters}}
<li>{{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
{{/getSubFilters}}
</ul>
</li>
@ -52,10 +53,10 @@
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}] :
<ul>
{{#getSubFilters}}
<li>{{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]
<ul>
{{#getSubFilters}}
<li>{{getDisplayName}}[{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
<li>{{getDisplayName}} [{{#isSelected}}x{{/isSelected}}{{^isSelected}} {{/isSelected}}]</li>
{{/getSubFilters}}
</ul>
</li>

View File

@ -7,7 +7,7 @@
</head>
<body>
<div id="wrapper">
<h1>{{reportBranding.getReportTitle}}:{{reportName}}{{#ingestRunning}}<span>Warning, this report was run before ingest services completed!</span>{{/ingestRunning}}</h1>
<h1>{{reportBranding.getReportTitle}}: {{reportName}}{{#ingestRunning}}<span>Warning, this report was run before ingest services completed!</span>{{/ingestRunning}}</h1>
<p class="subheadding">Timeline Report generated on {{generationDateTime}}</p>
<div class="title">
@ -33,7 +33,6 @@
{{#currentCase.getDataSources}}
<p>{{getName}}</p>
{{#getTimeZone}}
<table>
<tr><td>Timezone:</td><td>{{getTimeZone}}</td></tr>
{{#getPaths}}

View File

@ -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());