Explorar el Código

refactor about/dashboard jsp to bean

Jason Rivard hace 7 años
padre
commit
e1377ba188

+ 2 - 0
server/src/build/checkstyle-import.xml

@@ -80,6 +80,8 @@
     <allow pkg="lombok"/>
     <allow pkg="com.github.benmanes.caffeine"/>
 
+    <allow pkg="sun.management"/>
+
 
     <!--servlet -->
     <subpackage name="http.servlet">

+ 71 - 62
server/src/main/java/password/pwm/PwmAboutProperty.java

@@ -40,71 +40,77 @@ import java.util.TreeMap;
 
 public enum PwmAboutProperty {
 
-    app_version,
-    app_chaiApiVersion,
-    app_currentTime,
-    app_startTime,
-    app_installTime,
-    app_currentPublishedVersion,
-    app_currentPublishedVersionCheckTime,
-    app_siteUrl,
-    app_instanceID,
-    app_trialMode,
-    app_mode_appliance,
-    app_mode_docker,
-    app_mode_manageHttps,
-    app_applicationPath,
-    app_environmentFlags,
-    app_wordlistSize,
-    app_seedlistSize,
-    app_sharedHistorySize,
-    app_sharedHistoryOldestTime,
-    app_emailQueueSize,
-    app_emailQueueOldestTime,
-    app_smsQueueSize,
-    app_smsQueueOldestTime,
-    app_syslogQueueSize,
-    app_localDbLogSize,
-    app_localDbLogOldestTime,
-    app_localDbStorageSize,
-    app_localDbFreeSpace,
-    app_configurationRestartCounter,
-    app_secureBlockAlgorithm,
-    app_secureHashAlgorithm,
-    app_ldapProfileCount,
-
-    build_Time,
-    build_Number,
-    build_Type,
-    build_User,
-    build_Revision,
-    build_JavaVendor,
-    build_JavaVersion,
-    build_Version,
-
-    java_memoryFree,
-    java_memoryAllocated,
-    java_memoryMax,
-    java_threadCount,
-    java_vmVendor,
-    java_vmLocation,
-    java_vmVersion,
-    java_runtimeVersion,
-    java_vmName,
-    java_osName,
-    java_osVersion,
-    java_osArch,
-    java_randomAlgorithm,
-    java_defaultCharset,
-    java_appServerInfo,
-
-    database_driverName,
-    database_driverVersion,
-    database_databaseProductName,
-    database_databaseProductVersion,
+    app_version(null),
+    app_chaiApiVersion(null),
+    app_currentTime(null),
+    app_startTime(null),
+    app_installTime(null),
+    app_currentPublishedVersion(null),
+    app_currentPublishedVersionCheckTime(null),
+    app_siteUrl(null),
+    app_instanceID(null),
+    app_trialMode(null),
+    app_mode_appliance(null),
+    app_mode_docker(null),
+    app_mode_manageHttps(null),
+    app_applicationPath(null),
+    app_environmentFlags(null),
+    app_wordlistSize(null),
+    app_seedlistSize(null),
+    app_sharedHistorySize(null),
+    app_sharedHistoryOldestTime(null),
+    app_emailQueueSize(null),
+    app_emailQueueOldestTime(null),
+    app_smsQueueSize(null),
+    app_smsQueueOldestTime(null),
+    app_syslogQueueSize(null),
+    app_localDbLogSize(null),
+    app_localDbLogOldestTime(null),
+    app_localDbStorageSize(null),
+    app_localDbFreeSpace(null),
+    app_configurationRestartCounter(null),
+    app_secureBlockAlgorithm(null),
+    app_secureHashAlgorithm(null),
+    app_ldapProfileCount(null),
+
+    build_Time(null),
+    build_Number(null),
+    build_Type(null),
+    build_User(null),
+    build_Revision(null),
+    build_JavaVendor(null),
+    build_JavaVersion(null),
+    build_Version(null),
+
+    java_memoryFree("Java Memory Free"),
+    java_memoryAllocated("Java Memory Allocated"),
+    java_memoryMax("Java Memory Max"),
+    java_threadCount("Java Thread Count"),
+    java_vmVendor("Java Vendor"),
+    java_vmLocation("Java VM Location"),
+    java_vmVersion("Java VM Version"),
+    java_runtimeVersion("Java Runtime Version"),
+    java_vmName("Java VM Name"),
+    java_osName("Java OS Name"),
+    java_osVersion("Java OS Version"),
+    java_osArch("Java OS Architecture"),
+    java_randomAlgorithm(null),
+    java_defaultCharset(null),
+    java_appServerInfo("Java AppServer Info"),
+
+    database_driverName(null),
+    database_driverVersion(null),
+    database_databaseProductName(null),
+    database_databaseProductVersion(null),
 
     ;
 
+    private final String label;
+
+    PwmAboutProperty(final String label) {
+        this.label = label;
+    }
+
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmAboutProperty.class);
 
     public static Map<PwmAboutProperty,String> makeInfoBean(
@@ -234,4 +240,7 @@ public enum PwmAboutProperty {
 
     }
 
+    public String getLabel() {
+        return label == null ? this.name() : label;
+    }
 }

+ 1 - 0
server/src/main/java/password/pwm/http/PwmRequestAttribute.java

@@ -92,4 +92,5 @@ public enum PwmRequestAttribute {
     NextUrl,
 
     UserDebugData,
+    AppDashboardData,
 }

+ 19 - 0
server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java

@@ -532,6 +532,25 @@ public class AdminServlet extends ControlledPwmServlet {
             return;
         }
 
+        if (currentPage == Page.dashboard) {
+            final List<AppDashboardData.Flag> flags = new ArrayList<>();
+            if (pwmRequest.readParameterAsBoolean("showLocalDBCounts")) {
+                flags.add(AppDashboardData.Flag.IncludeLocalDbTableSizes);
+            }
+
+            if (pwmRequest.readParameterAsBoolean("showThreadDetails")) {
+                flags.add(AppDashboardData.Flag.ShowThreadData);
+            }
+
+            final AppDashboardData appDashboardData = AppDashboardData.makeDashboardData(
+                    pwmRequest.getPwmApplication(),
+                    pwmRequest.getContextManager(),
+                    pwmRequest.getLocale(),
+                    flags.toArray(new AppDashboardData.Flag[flags.size()])
+            );
+            pwmRequest.setAttribute(PwmRequestAttribute.AppDashboardData, appDashboardData);
+        }
+
         if (currentPage != null) {
             pwmRequest.forwardToJsp(currentPage.getJspURL());
             return;

+ 529 - 0
server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java

@@ -0,0 +1,529 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.admin;
+
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.PwmAboutProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.option.DataStorageMethod;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.health.HealthRecord;
+import password.pwm.http.ContextManager;
+import password.pwm.i18n.Admin;
+import password.pwm.i18n.Display;
+import password.pwm.svc.PwmService;
+import password.pwm.svc.cluster.NodeInfo;
+import password.pwm.svc.sessiontrack.SessionTrackService;
+import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.FileSystemUtility;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.PwmNumberFormat;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.localdb.LocalDB;
+import password.pwm.util.localdb.LocalDBException;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.Serializable;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+@Value
+@Builder
+public class AppDashboardData implements Serializable {
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(AppDashboardData.class);
+
+    @Value
+    public static class DataElement implements Serializable {
+        private String key;
+        private Type type;
+        private String label;
+        private String value;
+    }
+
+    @Value
+    public static class ServiceData implements Serializable {
+        private String name;
+        private PwmService.STATUS status;
+        private Collection<DataStorageMethod> storageMethod;
+        private List<HealthRecord> health;
+        private Map<String, String> debugData;
+    }
+
+    @Value
+    public static class ThreadData implements Serializable {
+        private String id;
+        private String name;
+        private String state;
+        private String trace;
+    }
+
+    @Value
+    public static class NodeData implements Serializable {
+        private String instanceID;
+        private String uptime;
+        private String lastSeen;
+        private NodeInfo.NodeState state;
+        private boolean configMatch;
+    }
+
+    public enum Type {
+        string,
+        timestamp,
+        number,
+    }
+
+    public enum Flag {
+        IncludeLocalDbTableSizes,
+        ShowThreadData,
+    }
+
+    private List<DataElement> about;
+    private List<ServiceData> services;
+    private List<DataElement> localDbInfo;
+    private List<DataElement> javaAbout;
+    private List<ThreadData> threads;
+    private Map<LocalDB.DB, String> localDbSizes;
+    private List<NodeData> nodeData;
+    private String nodeSummary;
+    private int ldapConnectionCount;
+    private int sessionCount;
+
+
+    static AppDashboardData makeDashboardData(
+            final PwmApplication pwmApplication,
+            final ContextManager contextManager,
+            final Locale locale,
+            final Flag... flags
+    ) {
+        final Instant startTime = Instant.now();
+
+        final AppDashboardDataBuilder builder = AppDashboardData.builder();
+        builder.about(makeAboutData(pwmApplication, contextManager, locale));
+        builder.services(getServiceData(pwmApplication));
+        builder.localDbInfo(makeLocalDbInfo(pwmApplication, locale));
+        builder.javaAbout(makeAboutJavaData(pwmApplication, locale));
+
+        if (JavaHelper.enumArrayContainsValue(flags, Flag.IncludeLocalDbTableSizes)) {
+            builder.localDbSizes(makeLocalDbTableSizes(pwmApplication, locale));
+        } else {
+            builder.localDbSizes(Collections.emptyMap());
+        }
+
+        if (JavaHelper.enumArrayContainsValue(flags, Flag.ShowThreadData)) {
+            builder.threads(makeThreadInfo());
+        } else {
+            builder.threads(Collections.emptyList());
+        }
+
+        builder.nodeData = makeNodeData(pwmApplication, locale);
+        builder.nodeSummary = pwmApplication.getClusterService().isMaster()
+                ? "This node is the current master"
+                : "This node is not the current master";
+
+        builder.ldapConnectionCount(ldapConnectionCount(pwmApplication));
+        builder.sessionCount(pwmApplication.getSessionTrackService().sessionCount());
+
+        LOGGER.trace("AppDashboardData bean created in " + TimeDuration.compactFromCurrent(startTime));
+        return builder.build();
+    }
+
+    private static int ldapConnectionCount(final PwmApplication pwmApplication) {
+        return pwmApplication.getSessionTrackService().ldapConnectionCount()
+                + pwmApplication.getLdapConnectionService().connectionCount();
+    }
+
+    private static List<DataElement> makeAboutData(
+            final PwmApplication pwmApplication,
+            final ContextManager contextManager,
+            final Locale locale
+    ) {
+        final LocaleHelper.DisplayMaker l = new LocaleHelper.DisplayMaker(locale, Admin.class, pwmApplication);
+        final String NA_VALUE = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, pwmApplication.getConfig());
+        final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale(locale);
+
+        final List<DataElement> aboutData = new ArrayList<>();
+        aboutData.add(new DataElement(
+                "appVersion",
+                Type.string,
+                l.forKey("Field_AppVersion", PwmConstants.PWM_APP_NAME),
+                PwmConstants.SERVLET_VERSION
+        ));
+        aboutData.add(new DataElement(
+                "currentTime",
+                Type.timestamp,
+                l.forKey("Field_CurrentTime"),
+                JavaHelper.toIsoDate(Instant.now())
+        ));
+        aboutData.add(new DataElement(
+                "startupTime",
+                Type.timestamp,
+                l.forKey("Field_StartTime"),
+                JavaHelper.toIsoDate(pwmApplication.getStartupTime())
+        ));
+        aboutData.add(new DataElement(
+                "runningDuration",
+                Type.string,
+                l.forKey("Field_UpTime"),
+                TimeDuration.fromCurrent(pwmApplication.getStartupTime()).asLongString(locale)
+        ));
+        aboutData.add(new DataElement(
+                "installTime",
+                Type.timestamp,
+                l.forKey("Field_InstallTime"),
+                JavaHelper.toIsoDate(pwmApplication.getInstallTime())
+        ));
+        aboutData.add(new DataElement(
+                "siteURL",
+                Type.string,
+                l.forKey("Field_SiteURL"),
+                pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL)
+        ));
+        aboutData.add(new DataElement(
+                "instanceID",
+                Type.string,
+                l.forKey("Field_InstanceID"),
+                pwmApplication.getInstanceID()
+        ));
+        aboutData.add(new DataElement(
+                "configRestartCounter",
+                Type.number,
+                "Configuration Restart Counter",
+                contextManager == null ? NA_VALUE : numberFormat.format(contextManager.getRestartCount())
+        ));
+        aboutData.add(new DataElement(
+                "chaiApiVersion",
+                Type.string,
+                l.forKey("Field_ChaiAPIVersion"),
+                com.novell.ldapchai.ChaiConstant.CHAI_API_VERSION
+        ));
+
+        return Collections.unmodifiableList(aboutData);
+    }
+
+    private static List<ServiceData> getServiceData(final PwmApplication pwmApplication) {
+        final Map<String, ServiceData> returnData = new TreeMap<>();
+        for (final PwmService pwmService : pwmApplication.getPwmServices()) {
+            final PwmService.ServiceInfo serviceInfo = pwmService.serviceInfo();
+            final Collection<DataStorageMethod> storageMethods = serviceInfo == null
+                    ? Collections.emptyList()
+                    : serviceInfo.getUsedStorageMethods() == null
+                    ? Collections.emptyList()
+                    : serviceInfo.getUsedStorageMethods();
+
+            final Map<String, String> debugData = serviceInfo == null
+                    ? Collections.emptyMap()
+                    : serviceInfo.getDebugProperties() == null
+                    ? Collections.emptyMap()
+                    : serviceInfo.getDebugProperties();
+
+            returnData.put(pwmService.getClass().getSimpleName(), new ServiceData(
+                    pwmService.getClass().getSimpleName(),
+                    pwmService.status(),
+                    storageMethods,
+                    pwmService.healthCheck(),
+                    debugData
+            ));
+        }
+
+        return Collections.unmodifiableList(new ArrayList<>(returnData.values()));
+    }
+
+    private static List<DataElement> makeLocalDbInfo(final PwmApplication pwmApplication, final Locale locale) {
+        final List<DataElement> localDbInfo = new ArrayList<>();
+        final String NA_VALUE = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, pwmApplication.getConfig());
+        final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale(locale);
+
+        localDbInfo.add(new DataElement(
+                "worlistSize",
+                Type.number,
+                "Word List Dictionary Size",
+                numberFormat.format(pwmApplication.getWordlistManager().size())
+        ));
+        localDbInfo.add(new DataElement(
+                "seedlistSize",
+                Type.number,
+                "Seed List Dictionary Size",
+                numberFormat.format(pwmApplication.getSeedlistManager().size())
+        ));
+        localDbInfo.add(new DataElement(
+                "sharedHistorySize",
+                Type.number,
+                "Shared Password History Size",
+                numberFormat.format(pwmApplication.getSharedHistoryManager().size())
+        ));
+        {
+            final Date oldestEntryAge = pwmApplication.getSharedHistoryManager().getOldestEntryTime();
+            final String display = oldestEntryAge == null
+                    ? NA_VALUE
+                    : TimeDuration.fromCurrent(oldestEntryAge).asCompactString();
+            localDbInfo.add(new DataElement(
+                    "oldestSharedHistory",
+                    Type.string,
+                    "OldestShared Password Entry",
+                    display
+            ));
+        }
+        localDbInfo.add(new DataElement(
+                "emailQueueSize",
+                Type.number,
+                "Email Queue Size",
+                numberFormat.format(pwmApplication.getEmailQueue().queueSize())
+        ));
+        localDbInfo.add(new DataElement(
+                "smsQueueSize",
+                Type.number,
+                "SMS Queue Size",
+                numberFormat.format(pwmApplication.getSmsQueue().queueSize())
+        ));
+        localDbInfo.add(new DataElement(
+                "sharedHistorySize",
+                Type.number,
+                "Syslog Queue Size",
+                String.valueOf(pwmApplication.getAuditManager().syslogQueueSize())
+        ));
+        localDbInfo.add(new DataElement(
+                "localAuditRecords",
+                Type.number,
+                "Audit Records",
+                pwmApplication.getAuditManager().sizeToDebugString()
+        ));
+        {
+            final Instant eldestAuditRecord = pwmApplication.getAuditManager().eldestVaultRecord();
+            final String display = eldestAuditRecord != null
+                    ? TimeDuration.fromCurrent(eldestAuditRecord).asLongString()
+                    : NA_VALUE;
+            localDbInfo.add(new DataElement(
+                    "oldestLocalAuditRecords",
+                    Type.string,
+                    "Oldest Audit Record",
+                    display
+            ));
+        }
+        localDbInfo.add(new DataElement(
+                "logEvents",
+                Type.number,
+                "Log Events",
+                pwmApplication.getLocalDBLogger().sizeToDebugString()
+        ));
+        {
+            final String display = pwmApplication.getLocalDBLogger() != null && pwmApplication.getLocalDBLogger().getTailDate() != null
+                    ? TimeDuration.fromCurrent(pwmApplication.getLocalDBLogger().getTailDate()).asLongString()
+                    : NA_VALUE;
+            localDbInfo.add(new DataElement(
+                    "oldestLogEvents",
+                    Type.string,
+                    "Oldest Log Event",
+                    display
+            ));
+        }
+        {
+            final String display = pwmApplication.getLocalDB() == null
+                    ? NA_VALUE
+                    : pwmApplication.getLocalDB().getFileLocation() == null
+                    ? NA_VALUE
+                    : StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(
+                    pwmApplication.getLocalDB().getFileLocation()));
+            localDbInfo.add(new DataElement(
+                    "localDbSizeOnDisk",
+                    Type.string,
+                    "LocalDB Size On Disk",
+                    display
+            ));
+        }
+        {
+            final String display = pwmApplication.getLocalDB() == null
+                    ? NA_VALUE
+                    : pwmApplication.getLocalDB().getFileLocation() == null
+                    ? NA_VALUE
+                    : StringUtil.formatDiskSize(FileSystemUtility.diskSpaceRemaining(pwmApplication.getLocalDB().getFileLocation()));
+            localDbInfo.add(new DataElement(
+                    "localDbFreeSpace",
+                    Type.string,
+                    "LocalDB Free Space",
+                    display
+            ));
+        }
+
+        return Collections.unmodifiableList(localDbInfo);
+    }
+
+    private static Map<LocalDB.DB, String> makeLocalDbTableSizes(
+            final PwmApplication pwmApplication,
+            final Locale locale
+    ) {
+        final Map<LocalDB.DB, String> returnData = new LinkedHashMap<>();
+        final LocalDB localDB = pwmApplication.getLocalDB();
+        final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale(locale);
+        try {
+            for (final LocalDB.DB db : LocalDB.DB.values()) {
+                returnData.put(db, numberFormat.format(localDB.size(db)));
+            }
+        } catch (LocalDBException e) {
+            LOGGER.error("error making localDB size bean: " + e.getMessage());
+        }
+        return Collections.unmodifiableMap(returnData);
+    }
+
+
+    private static List<DataElement> makeAboutJavaData(
+            final PwmApplication pwmApplication,
+            final Locale locale
+    ) {
+        final Map<PwmAboutProperty, String> aboutMap = PwmAboutProperty.makeInfoBean(pwmApplication);
+        final List<DataElement> javaInfo = new ArrayList<>();
+        final String NA_VALUE = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, pwmApplication.getConfig());
+
+        {
+            final List<PwmAboutProperty> interestedProperties = Arrays.asList(
+                    PwmAboutProperty.java_vmName,
+                    PwmAboutProperty.java_vmVendor,
+                    PwmAboutProperty.java_vmVersion,
+                    PwmAboutProperty.java_runtimeVersion,
+                    PwmAboutProperty.java_vmLocation,
+                    PwmAboutProperty.java_appServerInfo,
+                    PwmAboutProperty.java_osName,
+                    PwmAboutProperty.java_osVersion,
+                    PwmAboutProperty.java_osArch,
+                    PwmAboutProperty.java_memoryFree,
+                    PwmAboutProperty.java_memoryAllocated,
+                    PwmAboutProperty.java_memoryMax,
+                    PwmAboutProperty.java_threadCount
+            );
+
+            for (final PwmAboutProperty property : interestedProperties) {
+                javaInfo.add(new DataElement(
+                        property.name(),
+                        Type.string,
+                        property.getLabel(),
+                        aboutMap.getOrDefault(property, NA_VALUE)
+                ));
+            }
+        }
+
+        {
+            final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale(locale);
+
+            final String display = numberFormat.format(pwmApplication.getResourceServletService().itemsInCache())
+                    + "items (" + numberFormat.format(pwmApplication.getResourceServletService().bytesInCache()) + " bytes)";
+
+            javaInfo.add(new DataElement(
+                    "resourceFileServletCacheSize",
+                    Type.string,
+                    "ResourceFileServlet Cache",
+                    display
+            ));
+        }
+
+        javaInfo.add(new DataElement(
+                "resourceFileServletCacheHitRatio",
+                Type.string,
+                "ResourceFileServlet Cache Hit Ratio",
+                pwmApplication.getResourceServletService().cacheHitRatio().pretty(2)
+        ));
+
+        {
+            final Map<SessionTrackService.DebugKey, String> debugInfoMap = pwmApplication.getSessionTrackService().getDebugData();
+
+            javaInfo.add(new DataElement(
+                    "sessionTotalSize",
+                    Type.string,
+                    "Estimated Session Total Size",
+                    debugInfoMap.get(SessionTrackService.DebugKey.HttpSessionTotalSize)
+            ));
+
+            javaInfo.add(new DataElement(
+                    "sessionAverageSize",
+                    Type.string,
+                    "Estimated Session Total Size",
+                    debugInfoMap.get(SessionTrackService.DebugKey.HttpSessionAvgSize)
+            ));
+        }
+
+        return Collections.unmodifiableList(javaInfo);
+    }
+
+    private static List<ThreadData> makeThreadInfo() {
+        final Map<Long, ThreadData> returnData = new TreeMap<>();
+        final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
+        for (final ThreadInfo threadInfo : threads) {
+            returnData.put(threadInfo.getThreadId(), new ThreadData(
+                    "thread-" + Long.toString(threadInfo.getThreadId()),
+                    threadInfo.getThreadName(),
+                    threadInfo.getThreadState().toString().toLowerCase(),
+                    JavaHelper.threadInfoToString(threadInfo)
+            ));
+        }
+        return Collections.unmodifiableList(new ArrayList<>(returnData.values()));
+    }
+
+    private static List<NodeData> makeNodeData(
+            final PwmApplication pwmApplication,
+            final Locale locale
+    )
+    {
+        if (pwmApplication.getClusterService().status() == PwmService.STATUS.OPEN) {
+            return Collections.emptyList();
+        }
+
+        final String NA_VALUE = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, pwmApplication.getConfig());
+        final List<NodeData> nodeData = new ArrayList<>();
+
+        try {
+            for (final NodeInfo nodeInfo : pwmApplication.getClusterService().nodes()) {
+
+                final String uptime = nodeInfo.getStartupTime() == null
+                        ? NA_VALUE
+                        : TimeDuration.fromCurrent(nodeInfo.getStartupTime()).asLongString(locale);
+
+                nodeData.add(new NodeData(
+                        nodeInfo.getInstanceID(),
+                        uptime,
+                        JavaHelper.toIsoDate(nodeInfo.getLastSeen()),
+                        nodeInfo.getNodeState(),
+                        nodeInfo.isConfigMatch()
+                ));
+            }
+        } catch (PwmUnrecoverableException e) {
+            LOGGER.trace("error building AppDashboardData node-state: " + e.getMessage());
+        }
+
+        return Collections.unmodifiableList(nodeData);
+    }
+}

+ 24 - 0
server/src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -39,6 +39,8 @@ import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -230,4 +232,26 @@ public class LdapConnectionService implements PwmService {
 
         return perProfile;
     }
+
+    private Collection<ChaiProvider> getAllProviders() {
+        final List<ChaiProvider> returnList = new ArrayList<>();
+        for (final Map<Integer,ChaiProvider> loopProfileMap : proxyChaiProviders.values()) {
+            for (final ChaiProvider chaiProvider : loopProfileMap.values()) {
+                if (chaiProvider != null) {
+                    returnList.add(chaiProvider);
+                }
+            }
+        }
+        return Collections.unmodifiableList(returnList);
+    }
+
+    public int connectionCount() {
+        int count = 0;
+        for (final ChaiProvider chaiProvider : getAllProviders()) {
+            if (chaiProvider.isConnected()) {
+                count++;
+            }
+        }
+        return count;
+    }
 }

+ 1 - 1
server/src/main/java/password/pwm/svc/cluster/NodeInfo.java

@@ -38,7 +38,7 @@ public class NodeInfo implements Serializable {
     private NodeState nodeState;
     private boolean configMatch;
 
-    enum NodeState {
+    public enum NodeState {
         master,
         online,
         offline

+ 9 - 0
server/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -54,6 +54,7 @@ import java.util.Enumeration;
 import java.util.GregorianCalendar;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.TimeZone;
 import java.util.TreeSet;
@@ -453,4 +454,12 @@ public class JavaHelper {
         copyWhilePredicate(inputStream, byteArrayOutputStream, o -> true);
         return byteArrayOutputStream.toString(PwmConstants.DEFAULT_CHARSET.name());
     }
+
+    public static boolean isEmpty(final Collection collection) {
+        return collection == null ? true : collection.isEmpty();
+    }
+
+    public static boolean isEmpty(final Map map) {
+        return map == null ? true : map.isEmpty();
+    }
 }

+ 21 - 0
server/src/main/java/password/pwm/util/java/PwmNumberFormat.java

@@ -0,0 +1,21 @@
+package password.pwm.util.java;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+public class PwmNumberFormat {
+    private final Locale locale;
+
+    private PwmNumberFormat(final Locale locale) {
+        this.locale = locale;
+    }
+
+    public static PwmNumberFormat forLocale(final Locale locale) {
+        return new PwmNumberFormat(locale);
+    }
+
+    public String format(final long number) {
+        final NumberFormat numberFormat = NumberFormat.getInstance(locale);
+        return numberFormat.format(number);
+    }
+}

+ 0 - 171
server/src/main/java/password/pwm/ws/server/rest/bean/AppDashboardData.java

@@ -1,171 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.ws.server.rest.bean;
-
-import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
-import password.pwm.i18n.Admin;
-import password.pwm.i18n.Display;
-import password.pwm.util.LocaleHelper;
-import password.pwm.util.java.TimeDuration;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-public class AppDashboardData implements Serializable {
-    public static class DataElement {
-        public String key;
-        public Type type;
-        public String label;
-        public Object value;
-
-        public DataElement(
-                final String key,
-                final Type type,
-                final String label,
-                final Object value
-        )
-        {
-            this.key = key;
-            this.type = type;
-            this.label = label;
-            this.value = value;
-        }
-    }
-
-    public enum Type {
-        string,
-        timestamp,
-        number,
-    }
-
-    public Map<String,DataElement> about = new LinkedHashMap<>();
-    public Map<String,DataElement> appStats = new LinkedHashMap<>();
-
-    public AppDashboardData()
-    {
-    }
-
-    public static AppDashboardData makeDashboardData(
-            final PwmApplication pwmApplication,
-            final Locale locale
-    )
-    {
-        if (pwmApplication == null) {
-            return new AppDashboardData();
-        }
-        final Configuration config = pwmApplication.getConfig();
-        final String NA_VALUE = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, config);
-        final LocaleHelper.DisplayMaker l = new LocaleHelper.DisplayMaker(locale, Admin.class, pwmApplication);
-
-        final AppDashboardData appDashboardData = new AppDashboardData();
-        {
-            final List<DataElement> data = new ArrayList<>();
-
-            data.add(new DataElement(
-                    "appVersion",
-                    Type.string,
-                    l.forKey("Field_AppVersion", PwmConstants.PWM_APP_NAME),
-                    PwmConstants.SERVLET_VERSION
-            ));
-            data.add(new DataElement(
-                    "currentTime",
-                    Type.timestamp,
-                    l.forKey("Field_CurrentTime"),
-                    new Date()
-            ));
-            data.add(new DataElement(
-                    "startupTime",
-                    Type.timestamp,
-                    l.forKey("Field_StartTime"),
-                    pwmApplication.getStartupTime()
-            ));
-            data.add(new DataElement(
-                    "runningDuration",
-                    Type.string,
-                    l.forKey("Field_UpTime"),
-                    TimeDuration.fromCurrent(pwmApplication.getStartupTime()).asLongString(locale)
-            ));
-            data.add(new DataElement(
-                    "installTime",
-                    Type.timestamp,
-                    l.forKey("Field_InstallTime"),
-                    pwmApplication.getInstallTime()
-            ));
-            data.add(new DataElement(
-                    "siteURL",
-                    Type.string,
-                    l.forKey("Field_SiteURL"),
-                    pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL)
-            ));
-            data.add(new DataElement(
-                    "instanceID",
-                    Type.string,
-                    l.forKey("Field_InstanceID"),
-                    pwmApplication.getInstanceID()
-            ));
-            data.add(new DataElement(
-                    "chaiApiVersion",
-                    Type.string,
-                    l.forKey("Field_ChaiAPIVersion"),
-                    com.novell.ldapchai.ChaiConstant.CHAI_API_VERSION
-            ));
-
-
-            final Map<String, DataElement> aboutMap = new LinkedHashMap<>();
-            for (final DataElement dataElement : data) {
-                aboutMap.put(dataElement.key, dataElement);
-            }
-            appDashboardData.about = aboutMap;
-        }
-
-        {
-            final List<DataElement> data = new ArrayList<>();
-
-            data.add(new DataElement(
-                    "appVersion",
-                    Type.string,
-                    l.forKey("Field_AppVersion", PwmConstants.PWM_APP_NAME),
-                    PwmConstants.SERVLET_VERSION
-            ));
-
-            final Map<String, DataElement> statsMap = new LinkedHashMap<>();
-            for (final DataElement dataElement : data) {
-                statsMap.put(dataElement.key, dataElement);
-            }
-            appDashboardData.appStats = statsMap;
-        }
-
-
-        return appDashboardData;
-    }
-
-
-}

+ 66 - 393
server/src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -22,52 +22,27 @@
 
 <%@ page import="password.pwm.config.option.DataStorageMethod" %>
 <%@ page import="password.pwm.config.profile.LdapProfile" %>
-<%@ page import="password.pwm.error.PwmException" %>
 <%@ page import="password.pwm.health.HealthRecord" %>
-<%@ page import="password.pwm.http.PwmSession" %>
 <%@ page import="password.pwm.i18n.Admin" %>
 <%@ page import="password.pwm.i18n.Display" %>
-<%@ page import="password.pwm.svc.PwmService" %>
-<%@ page import="password.pwm.svc.cluster.NodeInfo" %>
-<%@ page import="password.pwm.svc.sessiontrack.SessionTrackService" %>
-<%@ page import="password.pwm.svc.stats.Statistic" %>
-<%@ page import="password.pwm.util.java.FileSystemUtility" %>
 <%@ page import="password.pwm.util.java.JavaHelper" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
-<%@ page import="password.pwm.util.java.TimeDuration" %>
 <%@ page import="password.pwm.util.localdb.LocalDB" %>
-<%@ page import="java.lang.management.ManagementFactory" %>
-<%@ page import="java.lang.management.ThreadInfo" %>
-<%@ page import="java.text.NumberFormat" %>
 <%@ page import="java.time.Instant" %>
 <%@ page import="java.util.Collection" %>
-<%@ page import="java.util.Date" %>
-<%@ page import="java.util.List" %>
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="password.pwm.svc.stats.EpsStatistic" %>
+<%@ page import="password.pwm.http.servlet.admin.AppDashboardData" %>
+<%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<%
-    final Locale locale = JspUtility.locale(request);
-    final NumberFormat numberFormat = NumberFormat.getInstance(locale);
-    final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true,true);
-    SessionTrackService sessionTrackService = null;
-
-    PwmRequest dashboard_pwmRequest = null;
-    PwmApplication dashboard_pwmApplication = null;
-    PwmSession dashboard_pwmSession = null;
-    try {
-        dashboard_pwmRequest = PwmRequest.forRequest(request, response);
-        dashboard_pwmApplication = dashboard_pwmRequest.getPwmApplication();
-        dashboard_pwmSession = dashboard_pwmRequest.getPwmSession();
-        sessionTrackService = dashboard_pwmApplication.getSessionTrackService();
-    } catch (PwmException e) {
-        JspUtility.logError(pageContext, "error during page setup: " + e.getMessage());
-    }
-%>
+<% final AppDashboardData appDashboardData = (AppDashboardData)JspUtility.getAttribute(pageContext, PwmRequestAttribute.AppDashboardData); %>
+<% final PwmRequest dashboard_pwmRequest = JspUtility.getPwmRequest(pageContext); %>
+<% final PwmApplication dashboard_pwmApplication = dashboard_pwmRequest.getPwmApplication(); %>
+<% final Locale locale = JspUtility.locale(request); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <% final String PageName = JspUtility.localizedString(pageContext,"Title_Dashboard",Admin.class);%>
 <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
@@ -87,14 +62,14 @@
                             <pwm:display key="Title_Sessions" bundle="Admin"/>
                         </td>
                         <td id="SessionCount">
-                            <%= sessionTrackService.sessionCount() %>
+                            <%= appDashboardData.getSessionCount() %>
                         </td>
                         <td class="key">
                             <pwm:display key="Title_LDAPConnections" bundle="Admin"/>
 
                         </td>
                         <td id="LDAPConnectionCount">
-                            <%= sessionTrackService.ldapConnectionCount() %>
+                            <%= appDashboardData.getLdapConnectionCount() %>
                         </td>
                     </tr>
                 </table>
@@ -116,7 +91,7 @@
                     <% if ((loopEpsType != EpsStatistic.DB_READS && loopEpsType != EpsStatistic.DB_WRITES) || dashboard_pwmApplication.getConfig().hasDbConfigured()) { %>
                     <tr>
                         <td class="key">
-                            <%= loopEpsType.getLabel(dashboard_pwmSession.getSessionStateBean().getLocale()) %> / Minute
+                            <%= loopEpsType.getLabel(locale) %> / Minute
                         </td>
                         <td style="text-align: center" id="FIELD_<%=loopEpsType.toString()%>_MINUTE">
                             <span style="font-size: smaller; font-style: italic"><pwm:display key="Display_PleaseWait"/></span>
@@ -187,62 +162,22 @@
             <div id="AboutTab" data-dojo-type="dijit.layout.ContentPane" title="<pwm:display key="Title_About" bundle="Admin"/>" class="tabContent">
                 <div style="max-height: 400px; overflow: auto;">
                     <table class="nomargin">
+                        <% for (final AppDashboardData.DataElement dataElement : appDashboardData.getAbout()) { %>
                         <tr>
                             <td class="key">
-                                <%=PwmConstants.PWM_APP_NAME%> Version
-                            </td>
-                            <td>
-                                <%= PwmConstants.SERVLET_VERSION %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                <pwm:display key="Field_CurrentTime" bundle="Admin"/>
+                                <%= dataElement.getLabel() %>
                             </td>
+                            <% if (dataElement.getType() == AppDashboardData.Type.timestamp) { %>
                             <td class="timestamp">
-                                <%= JavaHelper.toIsoDate(Instant.now()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                <pwm:display key="Field_StartTime" bundle="Admin"/>
-                            </td>
-                            <td class="timestamp">
-                                <%= JavaHelper.toIsoDate(dashboard_pwmApplication.getStartupTime()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Up Time
-                            </td>
-                            <td>
-                                <%= TimeDuration.fromCurrent(dashboard_pwmApplication.getStartupTime()).asLongString() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                <pwm:display key="Field_InstallTime" bundle="Admin"/>
-                            </td>
-                            <td class="timestamp">
-                                <%= JavaHelper.toIsoDate(dashboard_pwmApplication.getInstallTime()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Site URL
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Instance ID
+                                <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                             </td>
+                            <% } else { %>
                             <td>
-                                <%= dashboard_pwmApplication.getInstanceID() %>
+                                <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                             </td>
+                            <% } %>
                         </tr>
+                        <% } %>
                         <tr>
                             <td class="key">
                                 Last LDAP Unavailable Time
@@ -258,7 +193,7 @@
                                 <table class="nomargin">
                                     <% for (final LdapProfile ldapProfile : ldapProfiles) { %>
                                     <tr>
-                                        <td><%=ldapProfile.getDisplayName(dashboard_pwmSession.getSessionStateBean().getLocale())%></td>
+                                        <td><%=ldapProfile.getDisplayName(locale)%></td>
                                         <td class="timestamp">
                                             <% final Instant lastError = dashboard_pwmApplication.getLdapConnectionService().getLastLdapFailureTime(ldapProfile); %>
                                             <%= lastError == null ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable) :JavaHelper.toIsoDate(lastError) %>
@@ -269,14 +204,6 @@
                                 <% } %>
                             </td>
                         </tr>
-                        <tr>
-                            <td class="key">
-                                Chai API Version
-                            </td>
-                            <td>
-                                <%= com.novell.ldapchai.ChaiConstant.CHAI_API_VERSION %>
-                            </td>
-                        </tr>
                         <tr>
                             <td class="key">
                                 Dojo API Version
@@ -321,26 +248,23 @@
                             Health
                         </td>
                     </tr>
-                    <% for (final PwmService loopService : dashboard_pwmApplication.getPwmServices()) { %>
+                    <% for (final AppDashboardData.ServiceData loopService : appDashboardData.getServices()) { %>
                     <tr>
                         <td>
-                            <%= loopService.getClass().getSimpleName() %>
+                            <%= loopService.getName() %>
                         </td>
                         <td>
-                            <%= loopService.status() %>
-                            <% final List<HealthRecord> healthRecords = loopService.healthCheck(); %>
+                            <%= loopService.getStatus() %>
                         </td>
                         <td>
-                            <% if (loopService.serviceInfo() != null && loopService.serviceInfo().getUsedStorageMethods() != null) { %>
-                            <% for (final DataStorageMethod loopMethod : loopService.serviceInfo().getUsedStorageMethods()) { %>
+                            <% for (final DataStorageMethod loopMethod : loopService.getStorageMethod()) { %>
                             <%=loopMethod.toString()%>
                             <br/>
                             <% } %>
-                            <% } %>
                         </td>
                         <td>
-                            <% if (healthRecords != null && !healthRecords.isEmpty()) { %>
-                            <% for (final HealthRecord loopRecord : healthRecords) { %>
+                            <% if (!JavaHelper.isEmpty(loopService.getHealth())) { %>
+                            <% for (final HealthRecord loopRecord : loopService.getHealth()) { %>
                             <%= loopRecord.getTopic(locale, dashboard_pwmApplication.getConfig()) %> - <%= loopRecord.getStatus().toString() %> - <%= loopRecord.getDetail(locale,
                                 dashboard_pwmApplication.getConfig()) %>
                             <br/>
@@ -356,155 +280,27 @@
             <div id="LocalDBTab" data-dojo-type="dijit.layout.ContentPane" title="LocalDB" class="tabContent">
                 <div style="max-height: 400px; overflow: auto;">
                     <table class="nomargin">
+                        <% for (final AppDashboardData.DataElement dataElement : appDashboardData.getLocalDbInfo()) { %>
                         <tr>
                             <td class="key">
-                                Word List Dictionary Size
-                            </td>
-                            <td>
-                                <%= numberFormat.format(dashboard_pwmApplication.getWordlistManager().size()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Seed List Size
-                            </td>
-                            <td>
-                                <%= numberFormat.format(dashboard_pwmApplication.getSeedlistManager().size()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Shared Password History Size
+                                <%= dataElement.getLabel() %>
                             </td>
-                            <td>
-                                <%= numberFormat.format(dashboard_pwmApplication.getSharedHistoryManager().size()) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Email Queue Size
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getEmailQueue().queueSize() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                SMS Queue Size
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getSmsQueue().queueSize() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Syslog Queue Size
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getAuditManager().syslogQueueSize() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Local Audit Records
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getAuditManager().sizeToDebugString() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Oldest Local Audit Records
-                            </td>
-                            <td>
-                                <% final Instant eldestAuditRecord = dashboard_pwmApplication.getAuditManager().eldestVaultRecord(); %>
-                                <%= eldestAuditRecord != null
-                                        ? TimeDuration.fromCurrent(eldestAuditRecord).asLongString()
-                                        : JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Log Events in LocalDB
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getLocalDBLogger().sizeToDebugString() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Oldest Log Event in LocalDB
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getLocalDBLogger() != null && dashboard_pwmApplication.getLocalDBLogger().getTailDate() != null
-                                        ? TimeDuration.fromCurrent(dashboard_pwmApplication.getLocalDBLogger().getTailDate()).asLongString()
-                                        : JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Oldest Shared Password Entry
-                            </td>
-                            <td>
-                                <% final Date oldestEntryAge = dashboard_pwmApplication.getSharedHistoryManager().getOldestEntryTime(); %>
-                                <%= oldestEntryAge == null ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable) : TimeDuration.fromCurrent(oldestEntryAge).asCompactString() %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                LocalDB Size On Disk
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getLocalDB() == null
-                                        ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : dashboard_pwmApplication.getLocalDB().getFileLocation() == null
-                                        ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(
-                                        dashboard_pwmApplication.getLocalDB().getFileLocation()))
-                                %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                User Responses in LocalDB
-                            </td>
-                            <td>
-                                <%
-                                    String responseCount = JspUtility.getMessage(pageContext, Display.Value_NotApplicable);
-                                    try {
-                                        responseCount = String.valueOf(dashboard_pwmApplication.getLocalDB().size(LocalDB.DB.RESPONSE_STORAGE));
-                                    } catch (Exception e) { /* na */ }
-                                %>
-                                <%= responseCount %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                LocalDB Free Space
-                            </td>
-                            <td>
-                                <%= dashboard_pwmApplication.getLocalDB() == null
-                                        ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : dashboard_pwmApplication.getLocalDB().getFileLocation() == null
-                                        ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : StringUtil.formatDiskSize(FileSystemUtility.diskSpaceRemaining(dashboard_pwmApplication.getLocalDB().getFileLocation())) %>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td class="key">
-                                Configuration Restart Counter
+                            <% if (dataElement.getType() == AppDashboardData.Type.timestamp) { %>
+                            <td class="timestamp">
+                                <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                             </td>
+                            <% } else { %>
                             <td>
-                                <%= ContextManager.getContextManager(request.getSession()).getRestartCount() %>
+                                <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                             </td>
+                            <% } %>
                         </tr>
+                        <% } %>
                     </table>
                 </div>
-            </div>
-            <div id="LocalDBSizesTab" data-dojo-type="dijit.layout.ContentPane" title="LocalDB Sizes" class="tabContent">
-                <% if (dashboard_pwmApplication.getLocalDB() != null && dashboard_pwmRequest.readParameterAsBoolean("showLocalDBCounts")) { %>
+                <br/>
+                <div style="max-height: 400px; overflow: auto;">
+                <% if (!JavaHelper.isEmpty(appDashboardData.getLocalDbSizes())) { %>
                 <table class="nomargin">
                     <tr>
                         <td class="key">
@@ -514,13 +310,13 @@
                             Record Count
                         </td>
                     </tr>
-                    <% for (final LocalDB.DB loopDB : LocalDB.DB.values()) { %>
+                    <% for (final Map.Entry<LocalDB.DB,String> entry : appDashboardData.getLocalDbSizes().entrySet()) { %>
                     <tr>
                         <td style="text-align: right">
-                            <%= loopDB %>
+                            <%= entry.getKey() %>
                         </td>
                         <td>
-                            <%= numberFormat.format(dashboard_pwmApplication.getLocalDB().size(loopDB)) %>
+                            <%= entry.getValue() %>
                         </td>
                     </tr>
                     <% } %>
@@ -530,137 +326,29 @@
                     <a style="cursor: pointer" id="button-showLocalDBCounts">Show LocalDB record counts</a> (may be slow to load)
                 </div>
                 <% } %>
+                </div>
             </div>
             <div id="JavaTab" data-dojo-type="dijit.layout.ContentPane" title="Java" class="tabContent">
                 <table class="nomargin">
+                    <% for (final AppDashboardData.DataElement dataElement : appDashboardData.getJavaAbout()) { %>
                     <tr>
                         <td class="key">
-                            Java Vendor
-                        </td>
-                        <td>
-                            <%= System.getProperty("java.vm.vendor") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Java Runtime Version
-                        </td>
-                        <td>
-                            <%= System.getProperty("java.runtime.version") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Java VM Version
-                        </td>
-                        <td>
-                            <%= System.getProperty("java.vm.version") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Java Name
-                        </td>
-                        <td>
-                            <%= System.getProperty("java.vm.name") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Java Home
-                        </td>
-                        <td>
-                            <%= System.getProperty("java.home") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            OS Name
-                        </td>
-                        <td>
-                            <%= System.getProperty("os.name") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            OS Version
-                        </td>
-                        <td>
-                            <%= System.getProperty("os.version") %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Free Memory
-                        </td>
-                        <td>
-                            <%= numberFormat.format(Runtime.getRuntime().freeMemory()) %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Memory Allocated
-                        </td>
-                        <td>
-                            <%= numberFormat.format(Runtime.getRuntime().totalMemory()) %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Memory Limit
-                        </td>
-                        <td>
-                            <%= numberFormat.format(Runtime.getRuntime().maxMemory()) %>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Threads
-                        </td>
-                        <td>
-                            <%= threads.length %>
-                        </td>
-                    </tr>
-                </table>
-                <table class="nomargin">
-                    <tr>
-                        <td class="key">
-                            ResourceFileServlet Cache
-                        </td>
-                        <td>
-                            <%= numberFormat.format(dashboard_pwmApplication.getResourceServletService().itemsInCache()) %> items
-                            (<%= numberFormat.format(dashboard_pwmApplication.getResourceServletService().bytesInCache()) %> bytes)
-                        </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            ResourceFileServlet Cache Hit Ratio
-                        </td>
-                        <td>
-                            <%= dashboard_pwmApplication.getResourceServletService().cacheHitRatio().pretty(2) %>
-                        </td>
-                    </tr>
-                    <% final Map<SessionTrackService.DebugKey,String> debugInfoMap = sessionTrackService.getDebugData(); %>
-                    <tr>
-                        <td class="key">
-                            Session Total Size
-                        </td>
-                        <td>
-                            <%= numberFormat.format(Integer.valueOf(debugInfoMap.get(SessionTrackService.DebugKey.HttpSessionTotalSize))) %> bytes
+                            <%= dataElement.getLabel() %>
                         </td>
-                    </tr>
-                    <tr>
-                        <td class="key">
-                            Session Average Size
+                        <% if (dataElement.getType() == AppDashboardData.Type.timestamp) { %>
+                        <td class="timestamp">
+                            <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                         </td>
+                        <% } else { %>
                         <td>
-                            <%= numberFormat.format(Integer.valueOf(debugInfoMap.get(SessionTrackService.DebugKey.HttpSessionAvgSize))) %> bytes
+                            <%= StringUtil.escapeHtml(dataElement.getValue()) %>
                         </td>
+                        <% } %>
                     </tr>
+                    <% } %>
                 </table>
-            </div>
-            <div id="ThreadsTab" data-dojo-type="dijit.layout.ContentPane" title="Threads" class="tabContent">
-                <% if (dashboard_pwmApplication.getLocalDB() != null && dashboard_pwmRequest.readParameterAsBoolean("showThreadDetails")) { %>
+                <br/>
+                <% if (!JavaHelper.isEmpty(appDashboardData.getThreads())) { %>
                 <div style="max-height: 400px; overflow: auto;">
                     <table class="nomargin">
                         <tr>
@@ -674,35 +362,28 @@
                                 State
                             </td>
                         </tr>
-                        <%
-                            try {
-                                for (final ThreadInfo t : threads) {
-                        %>
-                        <tr id="thread_<%=t.getThreadId()%>">
+                        <% for (final AppDashboardData.ThreadData threadData : appDashboardData.getThreads()) { %>
+                        <tr id="<%=threadData.getId()%>">
                             <td>
-                                <%= t.getThreadId() %>
+                                <%= threadData.getId() %>
                             </td>
                             <td>
-                                <%= t.getThreadName() != null ? t.getThreadName() : JspUtility.getMessage(pageContext, Display.Value_NotApplicable) %>
+                                <%= threadData.getName() %>
                             </td>
                             <td>
-                                <%= t.getThreadState().toString().toLowerCase() %>
+                                <%= threadData.getState() %>
                             </td>
                         </tr>
-                        <%
-                            final String threadTrace = JavaHelper.threadInfoToString(t);
-                        %>
                         <pwm:script>
                             <script type="application/javascript">
                                 PWM_GLOBAL['startupFunctions'].push(function(){
-                                    PWM_MAIN.addEventHandler('thread_<%=t.getThreadId()%>','click',function(){
-                                        PWM_MAIN.showDialog({class:'wide',title:'Thread <%=t.getThreadId()%>',text:'<pre>' +'<%=StringUtil.escapeJS(threadTrace)%>' + '</pre>'})
+                                    PWM_MAIN.addEventHandler('<%=threadData.getId()%>','click',function(){
+                                        PWM_MAIN.showDialog({class:'wide',title:'Thread <%=threadData.getId()%>',text:'<pre>' +'<%=StringUtil.escapeJS(threadData.getTrace())%>' + '</pre>'})
                                     });
                                 });
                             </script>
                         </pwm:script>
                         <% } %>
-                        <% } catch (Exception e) { /* */ } %>
                     </table>
                 </div>
                 <% } else { %>
@@ -711,7 +392,7 @@
                 </div>
                 <% } %>
             </div>
-            <% if (dashboard_pwmApplication.getClusterService().status() == PwmService.STATUS.OPEN) { %>
+            <% if (!JavaHelper.isEmpty(appDashboardData.getNodeData())) { %>
             <div id="Status" data-dojo-type="dijit.layout.ContentPane" title="Nodes" class="tabContent">
                 <div style="max-height: 400px; overflow: auto;">
                     <table class="nomargin">
@@ -732,39 +413,31 @@
                                 Config Match
                             </td>
                         </tr>
-                        <% for (final NodeInfo nodeInfo : dashboard_pwmApplication.getClusterService().nodes()) { %>
+                        <% for (final AppDashboardData.NodeData nodeData : appDashboardData.getNodeData()) { %>
                         <tr>
                             <td>
-                                <%= nodeInfo.getInstanceID()  %>
+                                <%= nodeData.getInstanceID()  %>
                             </td>
                             <td>
-                                <% if (nodeInfo.getStartupTime() == null) { %>
-                                <pwm:display key="Value_NotApplicable"/>
-                                <% } else { %>
-                                <%= TimeDuration.fromCurrent(nodeInfo.getStartupTime()).asLongString(dashboard_pwmRequest.getLocale()) %>
-                                <% } %>
+                                <%= nodeData.getUptime() %>
                             </td>
                             <td>
                                 <span class="timestamp">
-                                    <%= JspUtility.freindlyWrite(pageContext, nodeInfo.getLastSeen()) %>
+                                    <%= nodeData.getLastSeen() %>
                                 </span>
                             </td>
                             <td>
-                                <%= nodeInfo.getNodeState() %>
+                                <%= nodeData.getState() %>
                             </td>
                             <td>
-                                <%= JspUtility.freindlyWrite(pageContext, nodeInfo.isConfigMatch())%>
+                                <%= JspUtility.freindlyWrite(pageContext, nodeData.isConfigMatch())%>
                             </td>
                         </tr>
                         <% } %>
                     </table>
                     <br/>
                     <div class="footnote">
-                    <% if (dashboard_pwmApplication.getClusterService().isMaster()) { %>
-                    This node is the current master.
-                    <% } else { %>
-                    This node is not the current master.
-                    <% } %>
+                        <%=appDashboardData.getNodeSummary()%>
                     </div>
                 </div>
             </div>