19 package org.sleuthkit.autopsy.healthmonitor;
21 import java.awt.Container;
22 import java.awt.Cursor;
23 import java.awt.Dimension;
25 import java.util.HashSet;
26 import java.util.HashMap;
28 import java.util.Arrays;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.io.BufferedWriter;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.OutputStreamWriter;
38 import javax.swing.Box;
39 import javax.swing.JButton;
40 import javax.swing.JDialog;
41 import javax.swing.JComboBox;
42 import javax.swing.JSeparator;
43 import javax.swing.JCheckBox;
44 import javax.swing.JLabel;
45 import javax.swing.JPanel;
46 import javax.swing.JScrollPane;
47 import javax.swing.BorderFactory;
48 import javax.swing.BoxLayout;
49 import java.awt.GridLayout;
50 import java.nio.charset.StandardCharsets;
51 import java.nio.file.Paths;
52 import java.text.DateFormat;
53 import java.text.SimpleDateFormat;
54 import java.util.Date;
55 import java.util.Collections;
56 import java.util.logging.Level;
57 import java.util.stream.Collectors;
58 import javax.swing.JFileChooser;
59 import org.openide.util.NbBundle;
98 timingData =
new HashMap<>();
99 userData =
new ArrayList<>();
100 parentWindow = parent;
107 @NbBundle.Messages({
"HealthMonitorDashboard.display.errorCreatingDashboard=Error creating health monitor dashboard",
108 "HealthMonitorDashboard.display.dashboardTitle=Health Monitor"})
121 }
catch (HealthMonitorException ex) {
122 logger.log(Level.SEVERE,
"Error creating panels for health monitor dashboard", ex);
128 JPanel mainPanel =
new JPanel();
129 mainPanel.setLayout(
new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
132 mainPanel.add(timingPanel);
135 mainPanel.add(userPanel);
138 File adminFile =
new File(ADMIN_ACCESS_FILE_PATH);
139 if(adminFile.exists()) {
140 mainPanel.add(adminPanel);
144 dialog =
new JDialog();
145 dialog.setTitle(Bundle.HealthMonitorDashboard_display_dashboardTitle());
146 dialog.add(mainPanel);
148 dialog.setLocationRelativeTo(parentWindow);
149 dialog.setVisible(
true);
157 if (dialog != null) {
158 dialog.setVisible(
false);
187 @NbBundle.Messages({
"HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled",
188 "HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics"})
194 JPanel emptyTimingMetricPanel =
new JPanel();
195 emptyTimingMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle()));
196 emptyTimingMetricPanel.add(
new JLabel(
" "));
197 emptyTimingMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData()));
199 return emptyTimingMetricPanel;
202 JPanel timingMetricPanel =
new JPanel();
203 timingMetricPanel.setLayout(
new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS));
204 timingMetricPanel.setBorder(BorderFactory.createEtchedBorder());
207 JLabel timingMetricTitle =
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle());
208 timingMetricPanel.add(timingMetricTitle);
209 timingMetricPanel.add(
new JSeparator());
213 timingMetricPanel.add(
new JSeparator());
216 timingGraphPanel =
new JPanel();
217 timingGraphPanel.setLayout(
new GridLayout(0,2));
221 JScrollPane scrollPane =
new JScrollPane(timingGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
222 timingMetricPanel.add(scrollPane);
223 timingMetricPanel.revalidate();
224 timingMetricPanel.repaint();
226 return timingMetricPanel;
233 @NbBundle.Messages({
"HealthMonitorDashboard.createTimingControlPanel.filterByHost=Filter by host",
234 "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display",
235 "HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outliers",
236 "HealthMonitorDashboard.createTimingControlPanel.showTrendLine=Show trend line"})
238 JPanel timingControlPanel =
new JPanel();
242 return timingControlPanel;
246 String[] dateOptionStrings = Arrays.stream(
DateRange.values()).map(e -> e.getLabel()).toArray(String[]::
new);
247 timingDateComboBox =
new JComboBox<>(dateOptionStrings);
251 timingDateComboBox.addActionListener(
new ActionListener() {
253 public void actionPerformed(ActionEvent arg0) {
256 }
catch (HealthMonitorException ex) {
257 logger.log(Level.SEVERE,
"Error updating timing metric panel", ex);
263 Set<String> hostNameSet =
new HashSet<>();
264 for(String metricType:timingData.keySet()) {
266 hostNameSet.add(result.getHostName());
271 timingHostComboBox =
new JComboBox<>(hostNameSet.toArray(
new String[hostNameSet.size()]));
274 timingHostComboBox.addActionListener(
new ActionListener() {
276 public void actionPerformed(ActionEvent arg0) {
278 if((timingHostCheckBox != null) && timingHostCheckBox.isSelected()) {
281 }
catch (HealthMonitorException ex) {
282 logger.log(Level.SEVERE,
"Error populating timing metric panel", ex);
288 timingHostCheckBox =
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost());
289 timingHostCheckBox.setSelected(
false);
290 timingHostComboBox.setEnabled(
false);
293 timingHostCheckBox.addActionListener(
new ActionListener() {
295 public void actionPerformed(ActionEvent arg0) {
297 timingHostComboBox.setEnabled(timingHostCheckBox.isSelected());
299 }
catch (HealthMonitorException ex) {
300 logger.log(Level.SEVERE,
"Error populating timing metric panel", ex);
306 timingShowTrendLineCheckBox =
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_showTrendLine());
307 timingShowTrendLineCheckBox.setSelected(
true);
310 timingShowTrendLineCheckBox.addActionListener(
new ActionListener() {
312 public void actionPerformed(ActionEvent arg0) {
315 }
catch (HealthMonitorException ex) {
316 logger.log(Level.SEVERE,
"Error populating timing metric panel", ex);
322 timingSkipOutliersCheckBox =
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_skipOutliers());
323 timingSkipOutliersCheckBox.setSelected(
false);
326 timingSkipOutliersCheckBox.addActionListener(
new ActionListener() {
328 public void actionPerformed(ActionEvent arg0) {
331 }
catch (HealthMonitorException ex) {
332 logger.log(Level.SEVERE,
"Error populating timing metric panel", ex);
338 timingControlPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays()));
339 timingControlPanel.add(timingDateComboBox);
342 timingControlPanel.add(Box.createHorizontalStrut(100));
345 timingControlPanel.add(timingHostCheckBox);
346 timingControlPanel.add(timingHostComboBox);
349 timingControlPanel.add(Box.createHorizontalStrut(100));
352 timingControlPanel.add(this.timingShowTrendLineCheckBox);
355 timingControlPanel.add(Box.createHorizontalStrut(100));
358 timingControlPanel.add(this.timingSkipOutliersCheckBox);
360 return timingControlPanel;
367 @NbBundle.Messages({
"HealthMonitorDashboard.updateTimingMetricGraphs.noData=No data to display"})
371 timingGraphPanel.removeAll();
373 if(timingData.keySet().isEmpty()) {
375 timingGraphPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_updateTimingMetricGraphs_noData()));
379 for(String metricName:timingData.keySet()) {
383 if(timingDateComboBox.getSelectedItem() != null) {
385 long threshold = System.currentTimeMillis() - selectedDateRange.
getTimestampRange();
386 intermediateTimingDataForDisplay = timingData.get(metricName).stream()
387 .filter(t -> t.getTimestamp() > threshold)
388 .collect(Collectors.toList());
390 intermediateTimingDataForDisplay = timingData.get(metricName);
396 String hostToDisplay = null;
397 if(timingHostCheckBox.isSelected() && (timingHostComboBox.getSelectedItem() != null)) {
398 hostToDisplay = timingHostComboBox.getSelectedItem().toString();
402 TimingMetricGraphPanel singleTimingGraphPanel =
new TimingMetricGraphPanel(intermediateTimingDataForDisplay,
403 hostToDisplay,
true, metricName, timingSkipOutliersCheckBox.isSelected(), timingShowTrendLineCheckBox.isSelected());
404 singleTimingGraphPanel.setPreferredSize(
new Dimension(700,200));
406 timingGraphPanel.add(singleTimingGraphPanel);
408 timingGraphPanel.revalidate();
409 timingGraphPanel.repaint();
417 @NbBundle.Messages({
"HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled",
418 "HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics"})
422 JPanel emptyUserMetricPanel =
new JPanel();
423 emptyUserMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle()));
424 emptyUserMetricPanel.add(
new JLabel(
" "));
425 emptyUserMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_noData()));
427 return emptyUserMetricPanel;
430 JPanel userMetricPanel =
new JPanel();
431 userMetricPanel.setLayout(
new BoxLayout(userMetricPanel, BoxLayout.PAGE_AXIS));
432 userMetricPanel.setBorder(BorderFactory.createEtchedBorder());
435 JLabel userMetricTitle =
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle());
436 userMetricPanel.add(userMetricTitle);
437 userMetricPanel.add(
new JSeparator());
441 userMetricPanel.add(
new JSeparator());
444 userGraphPanel =
new JPanel();
445 userGraphPanel.setLayout(
new GridLayout(0,2));
449 JScrollPane scrollPane =
new JScrollPane(userGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
450 userMetricPanel.add(scrollPane);
451 userMetricPanel.revalidate();
452 userMetricPanel.repaint();
454 return userMetricPanel;
461 @NbBundle.Messages({
"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display",
462 "HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report",
463 "HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report",
464 "# {0} - Report file name",
465 "HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}"})
467 JPanel userControlPanel =
new JPanel();
471 return userControlPanel;
475 String[] dateOptionStrings = Arrays.stream(
DateRange.values()).map(e -> e.getLabel()).toArray(String[]::
new);
476 userDateComboBox =
new JComboBox<>(dateOptionStrings);
480 userDateComboBox.addActionListener(
new ActionListener() {
482 public void actionPerformed(ActionEvent arg0) {
485 }
catch (HealthMonitorException ex) {
486 logger.log(Level.SEVERE,
"Error updating user metric panel", ex);
492 userControlPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays()));
493 userControlPanel.add(userDateComboBox);
496 JButton reportButton =
new JButton(Bundle.HealthMonitorDashboard_createUserControlPanel_userReportButton());
499 reportButton.addActionListener(
new ActionListener() {
501 public void actionPerformed(ActionEvent arg0) {
502 JFileChooser reportFileChooser = chooserHelper.
getChooser();
503 reportFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
505 final DateFormat csvTimestampFormat =
new SimpleDateFormat(
"yyyyMMdd_HHmmss");
506 String fileName =
"UserReport_" + csvTimestampFormat.format(
new Date())+
".csv";
507 reportFileChooser.setSelectedFile(
new File(fileName));
509 int returnVal = reportFileChooser.showSaveDialog(userControlPanel);
510 if (returnVal == JFileChooser.APPROVE_OPTION) {
512 File selectedFile = reportFileChooser.getSelectedFile();
515 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
518 }
catch (HealthMonitorException ex) {
519 logger.log(Level.SEVERE,
"Error generating report", ex);
522 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
527 userControlPanel.add(reportButton);
529 return userControlPanel;
540 final DateFormat timestampFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
542 try (BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(reportFile), StandardCharsets.UTF_8))) {
544 writer.write(
"Case open,Case close,Duration,Host,User,Case name");
549 Collections.sort(dataForReport);
553 for (
int caseOpenIndex = 0; caseOpenIndex < dataForReport.size() - 1; caseOpenIndex++) {
562 for (
int nextEventIndex = caseOpenIndex + 1; nextEventIndex < dataForReport.size(); nextEventIndex++) {
565 if ( nextEvent.getHostname().equals(caseOpenEvent.getHostname())
566 && nextEvent.getUserName().equals(caseOpenEvent.getUserName())) {
567 nextEventAfterCaseOpen = nextEvent;
573 String caseOpenTime = timestampFormat.format(caseOpenEvent.getTimestamp());
581 String caseCloseTime =
"";
582 String duration =
"";
583 if (nextEventAfterCaseOpen != null
585 && nextEventAfterCaseOpen.getCaseName().equals(caseOpenEvent.getCaseName())) {
586 caseCloseTime = timestampFormat.format(nextEventAfterCaseOpen.getTimestamp());
587 duration =
getDuration(caseOpenEvent.getTimestamp(), nextEventAfterCaseOpen.getTimestamp());
590 String host = caseOpenEvent.getHostname();
591 String user = caseOpenEvent.getUserName();
592 String caseName = caseOpenEvent.getCaseName();
594 String csvEntry = caseOpenTime +
"," + caseCloseTime +
"," + duration +
"," + host +
"," + user +
",\"" + caseName +
"\"";
595 writer.write(csvEntry);
598 }
catch (IOException ex) {
599 throw new HealthMonitorException(
"Error writing to output file " + reportFile.getAbsolutePath(), ex);
613 long durationInSeconds = (end - start) / 1000;
614 long second = durationInSeconds % 60;
615 long minute = (durationInSeconds / 60) % 60;
616 long hours = durationInSeconds / (60 * 60);
618 return String.format(
"%d:%02d:%02d", hours, minute, second);
625 @NbBundle.Messages({
"HealthMonitorDashboard.updateUserMetricGraphs.noData=No data to display"})
629 userGraphPanel.removeAll();
631 if(userData.isEmpty()) {
633 userGraphPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_updateUserMetricGraphs_noData()));
642 long timestampThreshold;
643 if(userDateComboBox.getSelectedItem() != null) {
645 timestampThreshold = System.currentTimeMillis() - selectedDateRange.
getTimestampRange();
652 UserMetricGraphPanel caseGraphPanel =
new UserMetricGraphPanel(userData, timestampThreshold,
true);
653 caseGraphPanel.setPreferredSize(
new Dimension(700,200));
655 UserMetricGraphPanel logonGraphPanel =
new UserMetricGraphPanel(userData, timestampThreshold,
false);
656 logonGraphPanel.setPreferredSize(
new Dimension(700,200));
658 userGraphPanel.add(caseGraphPanel);
659 userGraphPanel.add(logonGraphPanel);
660 userGraphPanel.revalidate();
661 userGraphPanel.repaint();
669 @NbBundle.Messages({
"HealthMonitorDashboard.createAdminPanel.enableButton=Enable monitor",
670 "HealthMonitorDashboard.createAdminPanel.disableButton=Disable monitor"})
673 JPanel adminPanel =
new JPanel();
674 adminPanel.setBorder(BorderFactory.createEtchedBorder());
677 JButton enableButton =
new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_enableButton());
678 JButton disableButton =
new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_disableButton());
681 enableButton.setEnabled(! isEnabled);
682 disableButton.setEnabled(isEnabled);
685 enableButton.addActionListener(
new ActionListener() {
687 public void actionPerformed(ActionEvent arg0) {
689 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
692 }
catch (HealthMonitorException ex) {
693 logger.log(Level.SEVERE,
"Error enabling monitoring", ex);
695 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
701 disableButton.addActionListener(
new ActionListener() {
703 public void actionPerformed(ActionEvent arg0) {
705 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
708 }
catch (HealthMonitorException ex) {
709 logger.log(Level.SEVERE,
"Error disabling monitoring", ex);
711 dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
717 adminPanel.add(enableButton);
718 adminPanel.add(Box.createHorizontalStrut(25));
719 adminPanel.add(disableButton);
727 @NbBundle.Messages({
"HealthMonitorDashboard.DateRange.oneMonth=One month",
728 "HealthMonitorDashboard.DateRange.twoWeeks=Two weeks",
729 "HealthMonitorDashboard.DateRange.oneWeek=One week",
730 "HealthMonitorDashboard.DateRange.oneDay=One day"})
732 ONE_DAY(Bundle.HealthMonitorDashboard_DateRange_oneDay(), 1),
733 ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7),
734 TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14),
735 ONE_MONTH(Bundle.HealthMonitorDashboard_DateRange_oneMonth(), 31);
739 private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
743 this.numberOfDays = numberOfDays;
761 if (numberOfDays > 0) {
762 return numberOfDays * MILLISECONDS_PER_DAY;
764 return Long.MAX_VALUE;
774 long maxRange = Long.MIN_VALUE;
776 if (dateRange.getTimestampRange() > maxRange) {
777 maxRange = dateRange.getTimestampRange();
785 if (dateRange.label.equalsIgnoreCase(text)) {
JPanel createTimingPanel()
JCheckBox timingSkipOutliersCheckBox
final Container parentWindow
static DateRange fromLabel(String text)
HealthMonitorDashboard(Container parent)
JPanel createTimingControlPanel()
static final String ADMIN_ACCESS_FILE_NAME
static void setHealthMonitorReportPath(String reportPath)
JPanel createAdminPanel()
JCheckBox timingShowTrendLineCheckBox
DateRange(String label, long numberOfDays)
void updateTimingMetricGraphs()
JCheckBox timingHostCheckBox
static final String ADMIN_ACCESS_FILE_PATH
JPanel createUserControlPanel()
static String getHealthMonitorReportPath()
JComboBox< String > timingHostComboBox
JFileChooser getChooser()
JComboBox< String > timingDateComboBox
final JFileChooserFactory chooserHelper
JComboBox< String > userDateComboBox
void updateUserMetricGraphs()
synchronized static Logger getLogger(String name)
static final Logger logger
static long getMaximumTimestampRange()
void generateCSVUserReport(File reportFile)
static void info(String message)
String getDuration(long start, long end)
static void error(String message)