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;
 
   95         timingData = 
new HashMap<>();
 
   96         userData = 
new ArrayList<>();
 
   97         parentWindow = parent;
 
  103     @NbBundle.Messages({
"HealthMonitorDashboard.display.errorCreatingDashboard=Error creating health monitor dashboard",
 
  104                         "HealthMonitorDashboard.display.dashboardTitle=Health Monitor"})
 
  117         } 
catch (HealthMonitorException ex) {
 
  118             logger.log(Level.SEVERE, 
"Error creating panels for health monitor dashboard", ex);
 
  124         JPanel mainPanel = 
new JPanel();
 
  125         mainPanel.setLayout(
new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
 
  128         mainPanel.add(timingPanel);
 
  131         mainPanel.add(userPanel);
 
  134         File adminFile = 
new File(ADMIN_ACCESS_FILE_PATH);
 
  135         if(adminFile.exists()) {
 
  136             mainPanel.add(adminPanel);
 
  140         dialog = 
new JDialog();
 
  141         dialog.setTitle(Bundle.HealthMonitorDashboard_display_dashboardTitle());
 
  142         dialog.add(mainPanel);
 
  144         dialog.setLocationRelativeTo(parentWindow);
 
  145         dialog.setVisible(
true);
 
  153         if (dialog != null) {
 
  154             dialog.setVisible(
false);
 
  183     @NbBundle.Messages({
"HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled",
 
  184                         "HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics"})
 
  190             JPanel emptyTimingMetricPanel = 
new JPanel();
 
  191             emptyTimingMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle()));
 
  192             emptyTimingMetricPanel.add(
new JLabel(
" "));
 
  193             emptyTimingMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_noData()));
 
  195             return emptyTimingMetricPanel;
 
  198         JPanel timingMetricPanel = 
new JPanel();
 
  199         timingMetricPanel.setLayout(
new BoxLayout(timingMetricPanel, BoxLayout.PAGE_AXIS));
 
  200         timingMetricPanel.setBorder(BorderFactory.createEtchedBorder());
 
  203         JLabel timingMetricTitle = 
new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle());
 
  204         timingMetricPanel.add(timingMetricTitle);
 
  205         timingMetricPanel.add(
new JSeparator());   
 
  209         timingMetricPanel.add(
new JSeparator());
 
  212         timingGraphPanel = 
new JPanel();
 
  213         timingGraphPanel.setLayout(
new GridLayout(0,2));
 
  217         JScrollPane scrollPane = 
new JScrollPane(timingGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
  218         timingMetricPanel.add(scrollPane);
 
  219         timingMetricPanel.revalidate();
 
  220         timingMetricPanel.repaint();
 
  222         return timingMetricPanel;
 
  229     @NbBundle.Messages({
"HealthMonitorDashboard.createTimingControlPanel.filterByHost=Filter by host",
 
  230                         "HealthMonitorDashboard.createTimingControlPanel.maxDays=Max days to display",
 
  231                         "HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outliers",
 
  232                         "HealthMonitorDashboard.createTimingControlPanel.showTrendLine=Show trend line"})
 
  234         JPanel timingControlPanel = 
new JPanel();
 
  238             return timingControlPanel;
 
  242         String[] dateOptionStrings = Arrays.stream(
DateRange.values()).map(e -> e.getLabel()).toArray(String[]::
new);
 
  243         timingDateComboBox = 
new JComboBox<>(dateOptionStrings);
 
  247         timingDateComboBox.addActionListener(
new ActionListener() {
 
  249             public void actionPerformed(ActionEvent arg0) {
 
  252                 } 
catch (HealthMonitorException ex) {
 
  253                     logger.log(Level.SEVERE, 
"Error updating timing metric panel", ex);
 
  259         Set<String> hostNameSet = 
new HashSet<>();
 
  260         for(String metricType:timingData.keySet()) {
 
  262                 hostNameSet.add(result.getHostName());
 
  267         timingHostComboBox = 
new JComboBox<>(hostNameSet.toArray(
new String[hostNameSet.size()]));
 
  270         timingHostComboBox.addActionListener(
new ActionListener() {
 
  272             public void actionPerformed(ActionEvent arg0) {
 
  274                     if((timingHostCheckBox != null) && timingHostCheckBox.isSelected()) {
 
  277                 } 
catch (HealthMonitorException ex) {
 
  278                     logger.log(Level.SEVERE, 
"Error populating timing metric panel", ex);
 
  284         timingHostCheckBox = 
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost());
 
  285         timingHostCheckBox.setSelected(
false);
 
  286         timingHostComboBox.setEnabled(
false);
 
  289         timingHostCheckBox.addActionListener(
new ActionListener() {
 
  291             public void actionPerformed(ActionEvent arg0) {
 
  293                     timingHostComboBox.setEnabled(timingHostCheckBox.isSelected());
 
  295                 } 
catch (HealthMonitorException ex) {
 
  296                     logger.log(Level.SEVERE, 
"Error populating timing metric panel", ex);
 
  302         timingShowTrendLineCheckBox = 
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_showTrendLine());
 
  303         timingShowTrendLineCheckBox.setSelected(
true);
 
  306         timingShowTrendLineCheckBox.addActionListener(
new ActionListener() {
 
  308             public void actionPerformed(ActionEvent arg0) {
 
  311                 } 
catch (HealthMonitorException ex) {
 
  312                     logger.log(Level.SEVERE, 
"Error populating timing metric panel", ex);
 
  318         timingSkipOutliersCheckBox = 
new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_skipOutliers());
 
  319         timingSkipOutliersCheckBox.setSelected(
false);
 
  322         timingSkipOutliersCheckBox.addActionListener(
new ActionListener() {
 
  324             public void actionPerformed(ActionEvent arg0) {
 
  327                 } 
catch (HealthMonitorException ex) {
 
  328                     logger.log(Level.SEVERE, 
"Error populating timing metric panel", ex);
 
  334         timingControlPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays()));
 
  335         timingControlPanel.add(timingDateComboBox);
 
  338         timingControlPanel.add(Box.createHorizontalStrut(100));
 
  341         timingControlPanel.add(timingHostCheckBox);
 
  342         timingControlPanel.add(timingHostComboBox);
 
  345         timingControlPanel.add(Box.createHorizontalStrut(100));
 
  348         timingControlPanel.add(this.timingShowTrendLineCheckBox);
 
  351         timingControlPanel.add(Box.createHorizontalStrut(100));
 
  354         timingControlPanel.add(this.timingSkipOutliersCheckBox);
 
  356         return timingControlPanel;
 
  363     @NbBundle.Messages({
"HealthMonitorDashboard.updateTimingMetricGraphs.noData=No data to display"})
 
  367         timingGraphPanel.removeAll();
 
  369         if(timingData.keySet().isEmpty()) {
 
  371             timingGraphPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_updateTimingMetricGraphs_noData()));
 
  375         for(String metricName:timingData.keySet()) {
 
  379             if(timingDateComboBox.getSelectedItem() != null) {
 
  381                 long threshold = System.currentTimeMillis() - selectedDateRange.
getTimestampRange();
 
  382                 intermediateTimingDataForDisplay = timingData.get(metricName).stream()
 
  383                         .filter(t -> t.getTimestamp() > threshold)
 
  384                         .collect(Collectors.toList());
 
  386                 intermediateTimingDataForDisplay = timingData.get(metricName);
 
  392             String hostToDisplay = null;
 
  393             if(timingHostCheckBox.isSelected() && (timingHostComboBox.getSelectedItem() != null)) {
 
  394                 hostToDisplay = timingHostComboBox.getSelectedItem().toString();
 
  398             TimingMetricGraphPanel singleTimingGraphPanel = 
new TimingMetricGraphPanel(intermediateTimingDataForDisplay, 
 
  399                     hostToDisplay, 
true, metricName, timingSkipOutliersCheckBox.isSelected(), timingShowTrendLineCheckBox.isSelected());
 
  400             singleTimingGraphPanel.setPreferredSize(
new Dimension(700,200));
 
  402             timingGraphPanel.add(singleTimingGraphPanel);
 
  404         timingGraphPanel.revalidate();
 
  405         timingGraphPanel.repaint();
 
  413     @NbBundle.Messages({
"HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled",
 
  414                     "HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics"})
 
  418             JPanel emptyUserMetricPanel = 
new JPanel();
 
  419             emptyUserMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle()));
 
  420             emptyUserMetricPanel.add(
new JLabel(
" "));
 
  421             emptyUserMetricPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_noData()));
 
  423             return emptyUserMetricPanel;
 
  426         JPanel userMetricPanel = 
new JPanel();
 
  427         userMetricPanel.setLayout(
new BoxLayout(userMetricPanel, BoxLayout.PAGE_AXIS));
 
  428         userMetricPanel.setBorder(BorderFactory.createEtchedBorder());
 
  431         JLabel userMetricTitle = 
new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle());
 
  432         userMetricPanel.add(userMetricTitle);
 
  433         userMetricPanel.add(
new JSeparator());  
 
  437         userMetricPanel.add(
new JSeparator());
 
  440         userGraphPanel = 
new JPanel();
 
  441         userGraphPanel.setLayout(
new GridLayout(0,2));
 
  445         JScrollPane scrollPane = 
new JScrollPane(userGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 
  446         userMetricPanel.add(scrollPane);
 
  447         userMetricPanel.revalidate();
 
  448         userMetricPanel.repaint();
 
  450         return userMetricPanel;
 
  457     @NbBundle.Messages({
"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display",
 
  458         "HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report",
 
  459         "HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report",
 
  460         "# {0} - Report file name",
 
  461         "HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}"})
 
  463         JPanel userControlPanel = 
new JPanel();
 
  467             return userControlPanel;
 
  471         String[] dateOptionStrings = Arrays.stream(
DateRange.values()).map(e -> e.getLabel()).toArray(String[]::
new);
 
  472         userDateComboBox = 
new JComboBox<>(dateOptionStrings);
 
  476         userDateComboBox.addActionListener(
new ActionListener() {
 
  478             public void actionPerformed(ActionEvent arg0) {
 
  481                 } 
catch (HealthMonitorException ex) {
 
  482                     logger.log(Level.SEVERE, 
"Error updating user metric panel", ex);
 
  488         userControlPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays()));
 
  489         userControlPanel.add(userDateComboBox);
 
  492         JButton reportButton = 
new JButton(Bundle.HealthMonitorDashboard_createUserControlPanel_userReportButton());
 
  495         reportButton.addActionListener(
new ActionListener() {
 
  497             public void actionPerformed(ActionEvent arg0) {
 
  498                 JFileChooser reportFileChooser = 
new JFileChooser();
 
  499                 reportFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
 
  501                 final DateFormat csvTimestampFormat = 
new SimpleDateFormat(
"yyyyMMdd_HHmmss");
 
  502                 String fileName = 
"UserReport_" + csvTimestampFormat.format(
new Date())+ 
".csv";
 
  503                 reportFileChooser.setSelectedFile(
new File(fileName));
 
  505                 int returnVal = reportFileChooser.showSaveDialog(userControlPanel);
 
  506                 if (returnVal == JFileChooser.APPROVE_OPTION) {
 
  508                     File selectedFile = reportFileChooser.getSelectedFile();
 
  511                         dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  514                     } 
catch (HealthMonitorException ex) {
 
  515                         logger.log(Level.SEVERE, 
"Error generating report", ex);
 
  518                         dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  523         userControlPanel.add(reportButton);
 
  525         return userControlPanel;
 
  536         final DateFormat timestampFormat = 
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
  538         try (BufferedWriter writer = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(reportFile), StandardCharsets.UTF_8))) {
 
  540             writer.write(
"Case open,Case close,Duration,Host,User,Case name");
 
  545             Collections.sort(dataForReport);
 
  549             for (
int caseOpenIndex = 0; caseOpenIndex < dataForReport.size() - 1; caseOpenIndex++) {
 
  558                 for (
int nextEventIndex = caseOpenIndex + 1; nextEventIndex < dataForReport.size(); nextEventIndex++) {
 
  561                     if ( nextEvent.getHostname().equals(caseOpenEvent.getHostname())
 
  562                         && nextEvent.getUserName().equals(caseOpenEvent.getUserName())) {
 
  563                         nextEventAfterCaseOpen = nextEvent;
 
  569                 String caseOpenTime = timestampFormat.format(caseOpenEvent.getTimestamp());
 
  577                 String caseCloseTime = 
"";
 
  578                 String duration = 
"";
 
  579                 if (nextEventAfterCaseOpen != null
 
  581                         && nextEventAfterCaseOpen.getCaseName().equals(caseOpenEvent.getCaseName())) {
 
  582                     caseCloseTime = timestampFormat.format(nextEventAfterCaseOpen.getTimestamp());
 
  583                     duration = 
getDuration(caseOpenEvent.getTimestamp(), nextEventAfterCaseOpen.getTimestamp());
 
  586                 String host = caseOpenEvent.getHostname();
 
  587                 String user = caseOpenEvent.getUserName();
 
  588                 String caseName = caseOpenEvent.getCaseName();
 
  590                 String csvEntry = caseOpenTime + 
"," + caseCloseTime + 
"," + duration + 
"," + host + 
"," + user + 
",\"" + caseName + 
"\"";
 
  591                 writer.write(csvEntry);
 
  594         } 
catch (IOException ex) {
 
  595             throw new HealthMonitorException(
"Error writing to output file " + reportFile.getAbsolutePath(), ex);
 
  609         long durationInSeconds = (end - start) / 1000;
 
  610         long second = durationInSeconds % 60;
 
  611         long minute = (durationInSeconds / 60) % 60;
 
  612         long hours = durationInSeconds / (60 * 60);
 
  614         return String.format(
"%d:%02d:%02d", hours, minute, second);
 
  621     @NbBundle.Messages({
"HealthMonitorDashboard.updateUserMetricGraphs.noData=No data to display"})
 
  625         userGraphPanel.removeAll();
 
  627         if(userData.isEmpty()) {
 
  629             userGraphPanel.add(
new JLabel(Bundle.HealthMonitorDashboard_updateUserMetricGraphs_noData()));
 
  638         long timestampThreshold;
 
  639         if(userDateComboBox.getSelectedItem() != null) {
 
  641             timestampThreshold = System.currentTimeMillis() - selectedDateRange.
getTimestampRange();
 
  648         UserMetricGraphPanel caseGraphPanel = 
new UserMetricGraphPanel(userData, timestampThreshold, 
true);
 
  649         caseGraphPanel.setPreferredSize(
new Dimension(700,200));
 
  651         UserMetricGraphPanel logonGraphPanel = 
new UserMetricGraphPanel(userData, timestampThreshold, 
false);
 
  652         logonGraphPanel.setPreferredSize(
new Dimension(700,200));
 
  654         userGraphPanel.add(caseGraphPanel);
 
  655         userGraphPanel.add(logonGraphPanel);
 
  656         userGraphPanel.revalidate();
 
  657         userGraphPanel.repaint();
 
  665     @NbBundle.Messages({
"HealthMonitorDashboard.createAdminPanel.enableButton=Enable monitor",
 
  666                         "HealthMonitorDashboard.createAdminPanel.disableButton=Disable monitor"})
 
  669         JPanel adminPanel = 
new JPanel();
 
  670         adminPanel.setBorder(BorderFactory.createEtchedBorder());
 
  673         JButton enableButton = 
new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_enableButton());
 
  674         JButton disableButton = 
new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_disableButton());
 
  677         enableButton.setEnabled(! isEnabled);
 
  678         disableButton.setEnabled(isEnabled);
 
  681         enableButton.addActionListener(
new ActionListener() {
 
  683             public void actionPerformed(ActionEvent arg0) {
 
  685                     dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  688                 } 
catch (HealthMonitorException ex) {
 
  689                     logger.log(Level.SEVERE, 
"Error enabling monitoring", ex);
 
  691                     dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  697         disableButton.addActionListener(
new ActionListener() {
 
  699             public void actionPerformed(ActionEvent arg0) {
 
  701                     dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
 
  704                 } 
catch (HealthMonitorException ex) {
 
  705                     logger.log(Level.SEVERE, 
"Error disabling monitoring", ex);
 
  707                     dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 
  713         adminPanel.add(enableButton);
 
  714         adminPanel.add(Box.createHorizontalStrut(25));
 
  715         adminPanel.add(disableButton);
 
  723     @NbBundle.Messages({
"HealthMonitorDashboard.DateRange.oneMonth=One month",
 
  724                         "HealthMonitorDashboard.DateRange.twoWeeks=Two weeks",
 
  725                         "HealthMonitorDashboard.DateRange.oneWeek=One week",
 
  726                         "HealthMonitorDashboard.DateRange.oneDay=One day"})
 
  728         ONE_DAY(Bundle.HealthMonitorDashboard_DateRange_oneDay(), 1),
 
  729         ONE_WEEK(Bundle.HealthMonitorDashboard_DateRange_oneWeek(), 7),
 
  730         TWO_WEEKS(Bundle.HealthMonitorDashboard_DateRange_twoWeeks(), 14),
 
  731         ONE_MONTH(Bundle.HealthMonitorDashboard_DateRange_oneMonth(), 31);
 
  735         private static final long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
 
  739             this.numberOfDays = numberOfDays;
 
  757             if (numberOfDays > 0) {
 
  758                 return numberOfDays * MILLISECONDS_PER_DAY;
 
  760                 return Long.MAX_VALUE;
 
  770             long maxRange = Long.MIN_VALUE;
 
  772                 if (dateRange.getTimestampRange() > maxRange) {
 
  773                     maxRange = dateRange.getTimestampRange();
 
  781                 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
 
JComboBox< String > timingDateComboBox
 
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)