]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9802 Add nodes to system info in cluster mode
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 11 Sep 2017 15:01:34 +0000 (17:01 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 26 Sep 2017 21:49:37 +0000 (23:49 +0200)
101 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java
server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java
server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java [deleted file]
server/sonar-web/src/main/js/api/system.ts
server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/App.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/PageActions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/main.js
server/sonar-web/src/main/js/apps/system/routes.ts
server/sonar-web/src/main/js/apps/system/styles.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/RestartForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/facet/FacetHeader.js
server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap
server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/icons.js
server/sonar-web/src/main/js/helpers/urls.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index cb61583ff0676930b614ecf9aa51da4eb27d3406..e239b685e16083f6ded195a01862719b983f280b 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.ce;
 import org.sonar.ce.configuration.CeConfigurationImpl;
 import org.sonar.ce.log.CeLogging;
 import org.sonar.core.platform.Module;
-import org.sonar.process.systeminfo.ProcessStateSystemInfo;
+import org.sonar.process.systeminfo.JvmStateSection;
 import org.sonar.ce.monitoring.CeDatabaseMBeanImpl;
 
 public class CeConfigurationModule extends Module {
@@ -32,6 +32,6 @@ public class CeConfigurationModule extends Module {
       CeConfigurationImpl.class,
       CeLogging.class,
       CeDatabaseMBeanImpl.class,
-      new ProcessStateSystemInfo("Compute Engine State"));
+      new JvmStateSection("Compute Engine JVM State"));
   }
 }
index 086d087ec0d1896f893b0ad2c4273285ea621784..9452c4da1cd4c97a4eaa2933057cbbc9fc0fd933 100644 (file)
@@ -26,7 +26,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.ce.httpd.HttpAction;
-import org.sonar.process.systeminfo.ProcessStateSystemInfo;
+import org.sonar.process.systeminfo.JvmStateSection;
 import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
@@ -44,8 +44,8 @@ public class SystemInfoHttpActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1");
-  private SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2");
+  private SystemInfoSection stateProvider1 = new JvmStateSection("state1");
+  private SystemInfoSection stateProvider2 = new JvmStateSection("state2");
   private SystemInfoHttpAction underTest;
 
   @Before
index 25c60a3c712a36750abbcf308c39ed674dc527e6..be6105cc511887e6cba8c4f68e0c5cd451926cc8 100644 (file)
@@ -59,15 +59,15 @@ public class DistributedAnswer<T> {
     return members;
   }
 
-  void setAnswer(Member member, T answer) {
+  public void setAnswer(Member member, T answer) {
     this.answers.put(member, answer);
   }
 
-  void setTimedOut(Member member) {
+  public void setTimedOut(Member member) {
     this.timedOutMembers.add(member);
   }
 
-  void setFailed(Member member, Exception e) {
+  public void setFailed(Member member, Exception e) {
     failedMembers.put(member, e);
   }
 }
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java
new file mode 100644 (file)
index 0000000..8b0a31b
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import java.util.Map;
+import java.util.Objects;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * Dumps {@link System#getProperties()}
+ */
+public class JvmPropertiesSection implements SystemInfoSection {
+
+  private final String name;
+
+  public JvmPropertiesSection(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName(name);
+
+    for (Map.Entry<Object, Object> systemProp : System.getProperties().entrySet()) {
+      if (systemProp.getValue() != null) {
+        setAttribute(protobuf, Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
+      }
+    }
+    return protobuf.build();
+  }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
new file mode 100644 (file)
index 0000000..5750832
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.ThreadMXBean;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * Dumps state of JVM (memory, threads)
+ */
+public class JvmStateSection implements SystemInfoSection {
+  private static final long MEGABYTE = 1024L * 1024L;
+  private final String name;
+
+  public JvmStateSection(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    return toProtobuf(ManagementFactory.getMemoryMXBean());
+  }
+
+  // Visible for testing
+  ProtobufSystemInfo.Section toProtobuf(MemoryMXBean memoryBean) {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName(name);
+    MemoryUsage heap = memoryBean.getHeapMemoryUsage();
+    addAttributeInMb(protobuf, "Heap Committed (MB)", heap.getCommitted());
+    addAttributeInMb(protobuf, "Heap Init (MB)", heap.getInit());
+    addAttributeInMb(protobuf, "Heap Max (MB)", heap.getMax());
+    addAttributeInMb(protobuf, "Heap Used (MB)", heap.getUsed());
+    MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
+    addAttributeInMb(protobuf, "Non Heap Committed (MB)", nonHeap.getCommitted());
+    addAttributeInMb(protobuf, "Non Heap Init (MB)", nonHeap.getInit());
+    addAttributeInMb(protobuf, "Non Heap Max (MB)", nonHeap.getMax());
+    addAttributeInMb(protobuf, "Non Heap Used (MB)", nonHeap.getUsed());
+    ThreadMXBean thread = ManagementFactory.getThreadMXBean();
+    setAttribute(protobuf, "Thread Count", thread.getThreadCount());
+
+    return protobuf.build();
+  }
+
+  private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder protobuf, String key, long valueInBytes) {
+    if (valueInBytes >= 0L) {
+      setAttribute(protobuf, key, valueInBytes / MEGABYTE);
+    }
+  }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java
deleted file mode 100644 (file)
index 7ad80c5..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.process.systeminfo;
-
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.MemoryUsage;
-import java.lang.management.ThreadMXBean;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-
-public class ProcessStateSystemInfo implements SystemInfoSection {
-  private static final long MEGABYTE = 1024L * 1024L;
-  private final String name;
-
-  public ProcessStateSystemInfo(String name) {
-    this.name = name;
-  }
-
-  @Override
-  public ProtobufSystemInfo.Section toProtobuf() {
-    return toProtobuf(ManagementFactory.getMemoryMXBean());
-  }
-
-  // Visible for testing
-  ProtobufSystemInfo.Section toProtobuf(MemoryMXBean memoryBean) {
-    ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder();
-    builder.setName(name);
-    MemoryUsage heap = memoryBean.getHeapMemoryUsage();
-    addAttributeInMb(builder, "Heap Committed (MB)", heap.getCommitted());
-    addAttributeInMb(builder, "Heap Init (MB)", heap.getInit());
-    addAttributeInMb(builder, "Heap Max (MB)", heap.getMax());
-    addAttributeInMb(builder, "Heap Used (MB)", heap.getUsed());
-    MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
-    addAttributeInMb(builder, "Non Heap Committed (MB)", nonHeap.getCommitted());
-    addAttributeInMb(builder, "Non Heap Init (MB)", nonHeap.getInit());
-    addAttributeInMb(builder, "Non Heap Max (MB)", nonHeap.getMax());
-    addAttributeInMb(builder, "Non Heap Used (MB)", nonHeap.getUsed());
-    ThreadMXBean thread = ManagementFactory.getThreadMXBean();
-    builder.addAttributesBuilder().setKey("Thread Count").setLongValue(thread.getThreadCount()).build();
-    return builder.build();
-  }
-
-  private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder builder, String key, long valueInBytes) {
-    if (valueInBytes >= 0L) {
-      builder.addAttributesBuilder().setKey(key).setLongValue(valueInBytes / MEGABYTE).build();
-    }
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java
new file mode 100644 (file)
index 0000000..7383294
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+public class SystemInfoUtils {
+
+  private SystemInfoUtils() {
+    // prevent instantiation
+  }
+
+  public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, @Nullable String value) {
+    if (value != null) {
+      section.addAttributesBuilder()
+        .setKey(key)
+        .setStringValue(value)
+        .build();
+    }
+  }
+
+  public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, boolean value) {
+    section.addAttributesBuilder()
+      .setKey(key)
+      .setBooleanValue(value)
+      .build();
+  }
+
+  public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, long value) {
+    section.addAttributesBuilder()
+      .setKey(key)
+      .setLongValue(value)
+      .build();
+  }
+
+  @CheckForNull
+  public static ProtobufSystemInfo.Attribute attribute(ProtobufSystemInfo.Section section, String key) {
+    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+      if (attribute.getKey().equals(key)) {
+        return attribute;
+      }
+    }
+    return null;
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java
new file mode 100644 (file)
index 0000000..dcf4cb9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class JvmPropertiesSectionTest {
+
+  private JvmPropertiesSection underTest = new JvmPropertiesSection("Web JVM Properties");
+
+  @Test
+  public void name_is_not_empty() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Web JVM Properties");
+  }
+
+  @Test
+  public void test_toProtobuf() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+    Assertions.assertThat(attribute(section, "java.vm.vendor").getStringValue()).isNotEmpty();
+    Assertions.assertThat(attribute(section, "os.name").getStringValue()).isNotEmpty();
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
new file mode 100644 (file)
index 0000000..5276a52
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import java.lang.management.MemoryMXBean;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class JvmStateSectionTest {
+
+  public static final String PROCESS_NAME = "the process name";
+
+  @Test
+  public void toSystemInfoSection() {
+    JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+    assertThat(section.getName()).isEqualTo(PROCESS_NAME);
+    assertThat(section.getAttributesCount()).isGreaterThan(0);
+    assertThat(section.getAttributesList()).extracting("key").contains("Thread Count");
+  }
+
+  @Test
+  public void should_hide_attributes_without_values() {
+    MemoryMXBean memoryBean = mock(MemoryMXBean.class, Mockito.RETURNS_DEEP_STUBS);
+    when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(-1L);
+
+    JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
+    ProtobufSystemInfo.Section section = underTest.toProtobuf(memoryBean);
+
+    assertThat(section.getAttributesList()).extracting("key").doesNotContain("Heap Committed (MB)");
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java
deleted file mode 100644 (file)
index 94e9ad1..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.process.systeminfo;
-
-import java.lang.management.MemoryMXBean;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ProcessStateSystemInfoTest {
-
-  public static final String PROCESS_NAME = "the process name";
-
-  @Test
-  public void toSystemInfoSection() {
-    ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
-
-    assertThat(section.getName()).isEqualTo(PROCESS_NAME);
-    assertThat(section.getAttributesCount()).isGreaterThan(0);
-    assertThat(section.getAttributesList()).extracting("key").contains("Thread Count");
-  }
-
-  @Test
-  public void should_hide_attributes_without_values() {
-    MemoryMXBean memoryBean = mock(MemoryMXBean.class, Mockito.RETURNS_DEEP_STUBS);
-    when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(-1L);
-
-    ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
-    ProtobufSystemInfo.Section section = underTest.toProtobuf(memoryBean);
-
-    assertThat(section.getAttributesList()).extracting("key").doesNotContain("Heap Committed (MB)");
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java
deleted file mode 100644 (file)
index 4531b2a..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import org.picocontainer.Startable;
-import org.sonar.process.Jmx;
-
-/**
- * Base implementation of a {@link org.sonar.server.platform.monitoring.Monitor}
- * that is exported as a JMX bean
- */
-public abstract class BaseMonitorMBean implements Monitor, Startable {
-
-  /**
-   * Auto-registers to MBean server
-   */
-  @Override
-  public void start() {
-    Jmx.register(objectName(), this);
-  }
-
-  /**
-   * Unregister, if needed
-   */
-  @Override
-  public void stop() {
-    Jmx.unregister(objectName());
-  }
-
-  String objectName() {
-    return "SonarQube:name=" + name();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java
new file mode 100644 (file)
index 0000000..eba6823
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.picocontainer.Startable;
+import org.sonar.process.Jmx;
+import org.sonar.process.systeminfo.SystemInfoSection;
+
+/**
+ * Base implementation of a {@link SystemInfoSection}
+ * that is exported as a JMX bean
+ */
+public abstract class BaseSectionMBean implements SystemInfoSection, Startable {
+
+  /**
+   * Auto-registers to MBean server
+   */
+  @Override
+  public void start() {
+    Jmx.register(objectName(), this);
+  }
+
+  /**
+   * Unregister, if needed
+   */
+  @Override
+  public void stop() {
+    Jmx.unregister(objectName());
+  }
+
+  String objectName() {
+    return "SonarQube:name=" + name();
+  }
+
+  /**
+   * Name of section in System Info page
+   */
+  abstract String name();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java
deleted file mode 100644 (file)
index 2ec74c6..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.apache.commons.dbcp.BasicDataSource;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.server.platform.db.migration.version.DatabaseVersion;
-
-/**
- * Information about database and connection pool
- */
-public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitorMBean {
-
-  private final DatabaseVersion dbVersion;
-  private final DbClient dbClient;
-
-  public DatabaseMonitor(DatabaseVersion dbVersion, DbClient dbClient) {
-    this.dbVersion = dbVersion;
-    this.dbClient = dbClient;
-  }
-
-  @Override
-  public String name() {
-    return "Database";
-  }
-
-  @Override
-  public String getMigrationStatus() {
-    return dbVersion.getStatus().name();
-  }
-
-  @Override
-  public int getPoolActiveConnections() {
-    return commonsDbcp().getNumActive();
-  }
-
-  @Override
-  public int getPoolMaxActiveConnections() {
-    return commonsDbcp().getMaxActive();
-  }
-
-  @Override
-  public int getPoolIdleConnections() {
-    return commonsDbcp().getNumIdle();
-  }
-
-  @Override
-  public int getPoolMaxIdleConnections() {
-    return commonsDbcp().getMaxIdle();
-  }
-
-  @Override
-  public int getPoolMinIdleConnections() {
-    return commonsDbcp().getMinIdle();
-  }
-
-  @Override
-  public int getPoolInitialSize() {
-    return commonsDbcp().getInitialSize();
-  }
-
-  @Override
-  public long getPoolMaxWaitMillis() {
-    return commonsDbcp().getMaxWait();
-  }
-
-  @Override
-  public boolean getPoolRemoveAbandoned() {
-    return commonsDbcp().getRemoveAbandoned();
-  }
-
-  @Override
-  public int getPoolRemoveAbandonedTimeoutSeconds() {
-    return commonsDbcp().getRemoveAbandonedTimeout();
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    Map<String, Object> attributes = new LinkedHashMap<>();
-    completeDbAttributes(attributes);
-    completePoolAttributes(attributes);
-    return attributes;
-  }
-
-  private void completePoolAttributes(Map<String, Object> attributes) {
-    attributes.put("Pool Active Connections", getPoolActiveConnections());
-    attributes.put("Pool Max Connections", getPoolMaxActiveConnections());
-    attributes.put("Pool Initial Size", getPoolInitialSize());
-    attributes.put("Pool Idle Connections", getPoolIdleConnections());
-    attributes.put("Pool Min Idle Connections", getPoolMinIdleConnections());
-    attributes.put("Pool Max Idle Connections", getPoolMaxIdleConnections());
-    attributes.put("Pool Max Wait (ms)", getPoolMaxWaitMillis());
-    attributes.put("Pool Remove Abandoned", getPoolRemoveAbandoned());
-    attributes.put("Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
-  }
-
-  private BasicDataSource commonsDbcp() {
-    return (BasicDataSource) dbClient.getDatabase().getDataSource();
-  }
-
-  private void completeDbAttributes(Map<String, Object> attributes) {
-    try (DbSession dbSession = dbClient.openSession(false);
-      Connection connection = dbSession.getConnection()) {
-      DatabaseMetaData metadata = connection.getMetaData();
-      attributes.put("Database", metadata.getDatabaseProductName());
-      attributes.put("Database Version", metadata.getDatabaseProductVersion());
-      attributes.put("Username", metadata.getUserName());
-      attributes.put("URL", metadata.getURL());
-      attributes.put("Driver", metadata.getDriverName());
-      attributes.put("Driver Version", metadata.getDriverVersion());
-      attributes.put("Version Status", getMigrationStatus());
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to get DB metadata", e);
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java
deleted file mode 100644 (file)
index b47f6f2..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-public interface DatabaseMonitorMBean {
-
-  /**
-   * Is database schema up-to-date or should it be upgraded ?
-   */
-  String getMigrationStatus();
-
-  /**
-   *
-   */
-  int getPoolActiveConnections();
-
-  /**
-   * The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit.
-   */
-  int getPoolMaxActiveConnections();
-
-  int getPoolIdleConnections();
-
-  /**
-   * The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
-   */
-  int getPoolMaxIdleConnections();
-
-  /**
-   * The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
-   */
-  int getPoolMinIdleConnections();
-
-  /**
-   * The initial number of connections that are created when the pool is started.
-   */
-  int getPoolInitialSize();
-
-  /**
-   * The maximum number of milliseconds that the pool will wait
-   * (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely.
-   */
-  long getPoolMaxWaitMillis();
-
-  /**
-   * Flag to remove abandoned connections if they exceed the {@link #getPoolRemoveAbandonedTimeoutSeconds()}.
-   */
-  boolean getPoolRemoveAbandoned();
-
-  /**
-   * Timeout in seconds before an abandoned connection can be removed.
-   */
-  int getPoolRemoveAbandonedTimeoutSeconds();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java
new file mode 100644 (file)
index 0000000..a64e8d2
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * Information about database and connection pool
+ */
+public class DatabaseSection extends BaseSectionMBean implements DatabaseSectionMBean {
+
+  private final DatabaseVersion dbVersion;
+  private final DbClient dbClient;
+
+  public DatabaseSection(DatabaseVersion dbVersion, DbClient dbClient) {
+    this.dbVersion = dbVersion;
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public String name() {
+    return "Database";
+  }
+
+  @Override
+  public String getMigrationStatus() {
+    return dbVersion.getStatus().name();
+  }
+
+  @Override
+  public int getPoolActiveConnections() {
+    return commonsDbcp().getNumActive();
+  }
+
+  @Override
+  public int getPoolMaxActiveConnections() {
+    return commonsDbcp().getMaxActive();
+  }
+
+  @Override
+  public int getPoolIdleConnections() {
+    return commonsDbcp().getNumIdle();
+  }
+
+  @Override
+  public int getPoolMaxIdleConnections() {
+    return commonsDbcp().getMaxIdle();
+  }
+
+  @Override
+  public int getPoolMinIdleConnections() {
+    return commonsDbcp().getMinIdle();
+  }
+
+  @Override
+  public int getPoolInitialSize() {
+    return commonsDbcp().getInitialSize();
+  }
+
+  @Override
+  public long getPoolMaxWaitMillis() {
+    return commonsDbcp().getMaxWait();
+  }
+
+  @Override
+  public boolean getPoolRemoveAbandoned() {
+    return commonsDbcp().getRemoveAbandoned();
+  }
+
+  @Override
+  public int getPoolRemoveAbandonedTimeoutSeconds() {
+    return commonsDbcp().getRemoveAbandonedTimeout();
+  }
+
+  @Override
+  public Section toProtobuf() {
+    Section.Builder protobuf = Section.newBuilder();
+    protobuf.setName(name());
+    completeDbAttributes(protobuf);
+    completePoolAttributes(protobuf);
+    return protobuf.build();
+  }
+
+  private void completePoolAttributes(Section.Builder protobuf) {
+    setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections());
+    setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections());
+    setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize());
+    setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections());
+    setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections());
+    setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections());
+    setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis());
+    setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned());
+    setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
+  }
+
+  private BasicDataSource commonsDbcp() {
+    return (BasicDataSource) dbClient.getDatabase().getDataSource();
+  }
+
+  private void completeDbAttributes(Section.Builder protobuf) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
+      setAttribute(protobuf, "Database", metadata.getDatabaseProductName());
+      setAttribute(protobuf, "Database Version", metadata.getDatabaseProductVersion());
+      setAttribute(protobuf, "Username", metadata.getUserName());
+      setAttribute(protobuf, "URL", metadata.getURL());
+      setAttribute(protobuf, "Driver", metadata.getDriverName());
+      setAttribute(protobuf, "Driver Version", metadata.getDriverVersion());
+      setAttribute(protobuf, "Version Status", getMigrationStatus());
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to get DB metadata", e);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java
new file mode 100644 (file)
index 0000000..0ddd97f
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+public interface DatabaseSectionMBean {
+
+  /**
+   * Is database schema up-to-date or should it be upgraded ?
+   */
+  String getMigrationStatus();
+
+  /**
+   *
+   */
+  int getPoolActiveConnections();
+
+  /**
+   * The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit.
+   */
+  int getPoolMaxActiveConnections();
+
+  int getPoolIdleConnections();
+
+  /**
+   * The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
+   */
+  int getPoolMaxIdleConnections();
+
+  /**
+   * The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
+   */
+  int getPoolMinIdleConnections();
+
+  /**
+   * The initial number of connections that are created when the pool is started.
+   */
+  int getPoolInitialSize();
+
+  /**
+   * The maximum number of milliseconds that the pool will wait
+   * (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely.
+   */
+  long getPoolMaxWaitMillis();
+
+  /**
+   * Flag to remove abandoned connections if they exceed the {@link #getPoolRemoveAbandonedTimeoutSeconds()}.
+   */
+  boolean getPoolRemoveAbandoned();
+
+  /**
+   * Timeout in seconds before an abandoned connection can be removed.
+   */
+  int getPoolRemoveAbandonedTimeoutSeconds();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java
deleted file mode 100644 (file)
index fcc3ccb..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
-import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
-import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
-import org.elasticsearch.action.admin.indices.stats.IndexStats;
-import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.elasticsearch.cluster.node.DiscoveryNode;
-import org.elasticsearch.common.breaker.CircuitBreaker;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.server.es.EsClient;
-
-import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
-
-public class EsMonitor extends BaseMonitorMBean implements EsMonitorMBean {
-
-  private final EsClient esClient;
-
-  public EsMonitor(EsClient esClient) {
-    this.esClient = esClient;
-  }
-
-  @Override
-  public String name() {
-    return "Elasticsearch";
-  }
-
-  /**
-   * MXBean does not allow to return enum {@link ClusterHealthStatus}, so
-   * returning String.
-   */
-  @Override
-  public String getState() {
-    return getStateAsEnum().name();
-  }
-
-  private ClusterHealthStatus getStateAsEnum() {
-    return clusterStats().getStatus();
-  }
-
-  @Override
-  public int getNumberOfNodes() {
-    return clusterStats().getNodesStats().getCounts().getTotal();
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    try {
-      Map<String, Object> attributes = new LinkedHashMap<>();
-      attributes.put("State", getStateAsEnum());
-      attributes.put("Indices", indexAttributes());
-      attributes.put("Number of Nodes", getNumberOfNodes());
-      attributes.put("Nodes", nodeAttributes());
-      return attributes;
-    } catch (Exception es) {
-      Loggers.get(EsMonitor.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
-      Map<String, Object> attributes = new LinkedHashMap<>();
-      attributes.put("State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
-      return attributes;
-    }
-  }
-
-  private LinkedHashMap<String, LinkedHashMap<String, Object>> indexAttributes() {
-    LinkedHashMap<String, LinkedHashMap<String, Object>> indices = new LinkedHashMap<>();
-    IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
-
-    for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
-      LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
-      indices.put(indexStats.getKey(), attributes);
-      attributes.put("Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
-      attributes.put("Shards", indexStats.getValue().getShards().length);
-      attributes.put("Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
-    }
-    return indices;
-  }
-
-  /**
-   * map of {node name -> node attributes}
-   */
-  private LinkedHashMap<String, LinkedHashMap<String, Object>> nodeAttributes() {
-    LinkedHashMap<String, LinkedHashMap<String, Object>> nodes = new LinkedHashMap<>();
-    NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
-    for (Map.Entry<String, NodeStats> entry : nodesStats.getNodesMap().entrySet()) {
-
-      LinkedHashMap<String, Object> nodeAttributes = new LinkedHashMap<>();
-      NodeStats stats = entry.getValue();
-      DiscoveryNode node = stats.getNode();
-      nodes.put(node.getName(), nodeAttributes);
-      nodeAttributes.put("Address", node.getAddress().toString());
-      nodeAttributes.put("Type", node.isMasterNode() ? "Master" : "Slave");
-      nodeAttributes.put("Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
-      nodeAttributes.put("Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
-      nodeAttributes.put("Open Files", stats.getProcess().getOpenFileDescriptors());
-      nodeAttributes.put("JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
-      nodeAttributes.put("JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
-      nodeAttributes.put("JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
-      nodeAttributes.put("JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
-      nodeAttributes.put("JVM Threads", stats.getJvm().getThreads().getCount());
-      nodeAttributes.put("Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
-      nodeAttributes.put("Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
-      nodeAttributes.put("Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
-      nodeAttributes.put("Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
-      nodeAttributes.put("Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
-      nodeAttributes.put("Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
-      nodeAttributes.put("Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
-    }
-    return nodes;
-  }
-
-  private ClusterStatsResponse clusterStats() {
-    return esClient.prepareClusterStats().get();
-  }
-
-  private static String formatPercent(long amount) {
-    return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java
deleted file mode 100644 (file)
index 647f668..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-/**
- * The public attributes of {@link org.sonar.server.platform.monitoring.EsMonitor}
- * to be exported in JMX bean.
- */
-public interface EsMonitorMBean {
-  String getState();
-  int getNumberOfNodes();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java
new file mode 100644 (file)
index 0000000..2e86e6e
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.util.Map;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
+import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
+import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
+import org.elasticsearch.action.admin.indices.stats.IndexStats;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.breaker.CircuitBreaker;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+
+import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class EsSection extends BaseSectionMBean implements EsSectionMBean {
+
+  private final EsClient esClient;
+
+  public EsSection(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  @Override
+  public String name() {
+    return "Elasticsearch";
+  }
+
+  /**
+   * MXBean does not allow to return enum {@link ClusterHealthStatus}, so
+   * returning String.
+   */
+  @Override
+  public String getState() {
+    return getStateAsEnum().name();
+  }
+
+  private ClusterHealthStatus getStateAsEnum() {
+    return clusterStats().getStatus();
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName(name());
+    try {
+      setAttribute(protobuf, "State", getStateAsEnum().name());
+      completeNodeAttributes(protobuf);
+      completeIndexAttributes(protobuf);
+
+    } catch (Exception es) {
+      Loggers.get(EsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
+      setAttribute(protobuf, "State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
+    }
+    return protobuf.build();
+  }
+
+  private void completeIndexAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
+    IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
+    for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
+      String prefix = "Index " + indexStats.getKey() + " - ";
+      setAttribute(protobuf, prefix + "Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
+      setAttribute(protobuf, prefix + "Shards", indexStats.getValue().getShards().length);
+      setAttribute(protobuf, prefix + "Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
+    }
+  }
+
+  private void completeNodeAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
+    NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
+    if (!nodesStats.getNodes().isEmpty()) {
+      NodeStats stats = nodesStats.getNodes().get(0);
+      setAttribute(protobuf, "Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
+      setAttribute(protobuf, "Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
+      setAttribute(protobuf, "Open Files", stats.getProcess().getOpenFileDescriptors());
+      setAttribute(protobuf, "JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
+      setAttribute(protobuf, "JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
+      setAttribute(protobuf, "JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
+      setAttribute(protobuf, "JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
+      setAttribute(protobuf, "JVM Threads", stats.getJvm().getThreads().getCount());
+      setAttribute(protobuf, "Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
+      setAttribute(protobuf, "Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
+      setAttribute(protobuf, "Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
+      setAttribute(protobuf, "Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
+      setAttribute(protobuf, "Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
+      setAttribute(protobuf, "Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
+      setAttribute(protobuf, "Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
+    }
+  }
+
+  private ClusterStatsResponse clusterStats() {
+    return esClient.prepareClusterStats().get();
+  }
+
+  private static String formatPercent(long amount) {
+    return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
new file mode 100644 (file)
index 0000000..6358f68
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+/**
+ * The public attributes of {@link EsSection}
+ * to be exported in JMX bean.
+ */
+public interface EsSectionMBean {
+  String getState();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java
deleted file mode 100644 (file)
index c179053..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-
-public class JvmPropsMonitor implements Monitor {
-  @Override
-  public String name() {
-    return "JvmProperties";
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    Map<String, Object> sortedProps = new TreeMap<>();
-    for (Map.Entry<Object, Object> systemProp : System.getProperties().entrySet()) {
-      sortedProps.put(Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
-    }
-    return sortedProps;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java
deleted file mode 100644 (file)
index 48ae159..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import org.sonar.api.server.ServerSide;
-import org.sonar.server.platform.ws.InfoAction;
-
-/**
- * Any component that is involved in the information returned by the web service api/system/info
- */
-@ServerSide
-public interface Monitor {
-  /**
-   * Name of section in System Info page
-   */
-  String name();
-
-  /**
-   * Type of attribute values must be supported by {@link org.sonar.api.utils.text.JsonWriter#valueObject(Object)}
-   * because of JSON export by {@link InfoAction}.
-   */
-  Map<String, Object> attributes();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
deleted file mode 100644 (file)
index 9e3c951..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.updatecenter.common.Version;
-
-/**
- * Installed plugins (excluding core plugins)
- */
-public class PluginsMonitor implements Monitor {
-  private final PluginRepository repository;
-
-  public PluginsMonitor(PluginRepository repository) {
-    this.repository = repository;
-  }
-
-  @Override
-  public String name() {
-    return "Plugins";
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    Map<String, Object> attributes = new LinkedHashMap<>();
-    for (PluginInfo plugin : repository.getPluginInfos()) {
-      LinkedHashMap<String, Object> pluginAttributes = new LinkedHashMap<>();
-      pluginAttributes.put("Name", plugin.getName());
-      Version version = plugin.getVersion();
-      if (version != null) {
-        pluginAttributes.put("Version", version.getName());
-      }
-      attributes.put(plugin.getKey(), pluginAttributes);
-    }
-    return attributes;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java
new file mode 100644 (file)
index 0000000..491a8bf
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.updatecenter.common.Version;
+
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class PluginsSection implements SystemInfoSection {
+  private final PluginRepository repository;
+
+  public PluginsSection(PluginRepository repository) {
+    this.repository = repository;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("Plugins");
+    for (PluginInfo plugin : repository.getPluginInfos()) {
+      String label = "[" + plugin.getName() + "]";
+      Version version = plugin.getVersion();
+      if (version != null) {
+        label = version.getName() + " " + label;
+      }
+      setAttribute(protobuf, plugin.getKey(), label);
+    }
+    return protobuf.build();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java
deleted file mode 100644 (file)
index 1b45ef2..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import com.google.common.collect.ImmutableSortedMap;
-import java.util.Map;
-import java.util.SortedMap;
-import org.sonar.api.PropertyType;
-import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.config.Settings;
-
-import static org.apache.commons.lang.StringUtils.abbreviate;
-
-public class SettingsMonitor implements Monitor {
-
-  static final int MAX_VALUE_LENGTH = 500;
-  private final Settings settings;
-
-  public SettingsMonitor(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public String name() {
-    return "Settings";
-  }
-
-  @Override
-  public SortedMap<String, Object> attributes() {
-    PropertyDefinitions definitions = settings.getDefinitions();
-    ImmutableSortedMap.Builder<String, Object> builder = ImmutableSortedMap.naturalOrder();
-    for (Map.Entry<String, String> prop : settings.getProperties().entrySet()) {
-      String key = prop.getKey();
-      PropertyDefinition def = definitions.get(key);
-      if (def == null || def.type() != PropertyType.PASSWORD) {
-        builder.put(key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
-      }
-    }
-    return builder.build();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java
new file mode 100644 (file)
index 0000000..12f1d68
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.util.Map;
+import org.sonar.api.PropertyType;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.Settings;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.apache.commons.lang.StringUtils.abbreviate;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class SettingsSection implements SystemInfoSection {
+
+  static final int MAX_VALUE_LENGTH = 500;
+  private final Settings settings;
+
+  public SettingsSection(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("Settings");
+
+    PropertyDefinitions definitions = settings.getDefinitions();
+    for (Map.Entry<String, String> prop : settings.getProperties().entrySet()) {
+      String key = prop.getKey();
+      PropertyDefinition def = definitions.get(key);
+      if (def == null || def.type() != PropertyType.PASSWORD) {
+        setAttribute(protobuf, key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
+      }
+    }
+    return protobuf.build();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java
deleted file mode 100644 (file)
index 823876d..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import com.google.common.base.Joiner;
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.platform.Server;
-import org.sonar.api.security.SecurityRealm;
-import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.process.ProcessProperties;
-import org.sonar.server.authentication.IdentityProviderRepository;
-import org.sonar.server.platform.ServerIdLoader;
-import org.sonar.server.platform.ServerLogging;
-import org.sonar.server.user.SecurityRealmFactory;
-
-public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonitorMBean {
-
-  private static final Joiner COMMA_JOINER = Joiner.on(", ");
-
-  static final String BRANDING_FILE_PATH = "web/WEB-INF/classes/com/sonarsource/branding";
-
-  private final Configuration config;
-  private final SecurityRealmFactory securityRealmFactory;
-  private final IdentityProviderRepository identityProviderRepository;
-  private final Server server;
-  private final ServerLogging serverLogging;
-  private final ServerIdLoader serverIdLoader;
-
-  public SonarQubeMonitor(Configuration config, SecurityRealmFactory securityRealmFactory,
-    IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
-    ServerIdLoader serverIdLoader) {
-    this.config = config;
-    this.securityRealmFactory = securityRealmFactory;
-    this.identityProviderRepository = identityProviderRepository;
-    this.server = server;
-    this.serverLogging = serverLogging;
-    this.serverIdLoader = serverIdLoader;
-  }
-
-  @Override
-  public String getServerId() {
-    return serverIdLoader.getRaw().orElse(null);
-  }
-
-  @Override
-  public String getVersion() {
-    return server.getVersion();
-  }
-
-  @Override
-  public String getLogLevel() {
-    return serverLogging.getRootLoggerLevel().name();
-  }
-
-  @CheckForNull
-  private String getExternalUserAuthentication() {
-    SecurityRealm realm = securityRealmFactory.getRealm();
-    return realm == null ? null : realm.getName();
-  }
-
-  private List<String> getEnabledIdentityProviders() {
-    return identityProviderRepository.getAllEnabledAndSorted()
-      .stream()
-      .filter(IdentityProvider::isEnabled)
-      .map(IdentityProvider::getName)
-      .collect(MoreCollectors.toList());
-  }
-
-  private List<String> getAllowsToSignUpEnabledIdentityProviders() {
-    return identityProviderRepository.getAllEnabledAndSorted()
-      .stream()
-      .filter(IdentityProvider::isEnabled)
-      .filter(IdentityProvider::allowsUsersToSignUp)
-      .map(IdentityProvider::getName)
-      .collect(MoreCollectors.toList());
-  }
-
-  private boolean getForceAuthentication() {
-    return config.getBoolean(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false);
-  }
-
-  private boolean isOfficialDistribution() {
-    // the dependency com.sonarsource:sonarsource-branding is shaded to webapp
-    // during release (see sonar-web pom)
-    File brandingFile = new File(server.getRootDir(), BRANDING_FILE_PATH);
-    // no need to check that the file exists. java.io.File#length() returns zero in this case.
-    return brandingFile.length() > 0L;
-  }
-
-  @Override
-  public String name() {
-    return "SonarQube";
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    Map<String, Object> attributes = new LinkedHashMap<>();
-    completeWithServerIdAttributes(attributes);
-    attributes.put("Version", getVersion());
-    addIfNotNull("External User Authentication", getExternalUserAuthentication(), attributes);
-    addIfNotEmpty("Accepted external identity providers", getEnabledIdentityProviders(), attributes);
-    addIfNotEmpty("External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders(), attributes);
-    attributes.put("Force authentication", getForceAuthentication());
-    attributes.put("Official Distribution", isOfficialDistribution());
-    attributes.put("Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
-    attributes.put("Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
-    attributes.put("Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
-    attributes.put("Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
-    attributes.put("Logs Level", getLogLevel());
-    return attributes;
-  }
-
-  private void completeWithServerIdAttributes(Map<String, Object> attributes) {
-    serverIdLoader.get().ifPresent(serverId -> {
-      attributes.put("Server ID", serverId.getId());
-      attributes.put("Server ID validated", serverId.isValid());
-    });
-  }
-
-  private static void addIfNotNull(String key, @Nullable String value, Map<String, Object> attributes) {
-    if (value != null) {
-      attributes.put(key, value);
-    }
-  }
-
-  private static void addIfNotEmpty(String key, List<String> values, Map<String, Object> attributes) {
-    if (!values.isEmpty()) {
-      attributes.put(key, COMMA_JOINER.join(values));
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java
deleted file mode 100644 (file)
index 9a74127..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import javax.annotation.CheckForNull;
-
-public interface SonarQubeMonitorMBean {
-  @CheckForNull
-  String getServerId();
-
-  String getVersion();
-
-  String getLogLevel();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
new file mode 100644 (file)
index 0000000..66eefa1
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import com.google.common.base.Joiner;
+import java.io.File;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.Server;
+import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.authentication.IdentityProviderRepository;
+import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.user.SecurityRealmFactory;
+
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class SonarQubeSection extends BaseSectionMBean implements SonarQubeSectionMBean {
+
+  private static final Joiner COMMA_JOINER = Joiner.on(", ");
+
+  static final String BRANDING_FILE_PATH = "web/WEB-INF/classes/com/sonarsource/branding";
+
+  private final Configuration config;
+  private final SecurityRealmFactory securityRealmFactory;
+  private final IdentityProviderRepository identityProviderRepository;
+  private final Server server;
+  private final ServerLogging serverLogging;
+  private final ServerIdLoader serverIdLoader;
+
+  public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory,
+    IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
+    ServerIdLoader serverIdLoader) {
+    this.config = config;
+    this.securityRealmFactory = securityRealmFactory;
+    this.identityProviderRepository = identityProviderRepository;
+    this.server = server;
+    this.serverLogging = serverLogging;
+    this.serverIdLoader = serverIdLoader;
+  }
+
+  @Override
+  public String getServerId() {
+    return serverIdLoader.getRaw().orElse(null);
+  }
+
+  @Override
+  public String getVersion() {
+    return server.getVersion();
+  }
+
+  @Override
+  public String getLogLevel() {
+    return serverLogging.getRootLoggerLevel().name();
+  }
+
+  @CheckForNull
+  private String getExternalUserAuthentication() {
+    SecurityRealm realm = securityRealmFactory.getRealm();
+    return realm == null ? null : realm.getName();
+  }
+
+  private List<String> getEnabledIdentityProviders() {
+    return identityProviderRepository.getAllEnabledAndSorted()
+      .stream()
+      .filter(IdentityProvider::isEnabled)
+      .map(IdentityProvider::getName)
+      .collect(MoreCollectors.toList());
+  }
+
+  private List<String> getAllowsToSignUpEnabledIdentityProviders() {
+    return identityProviderRepository.getAllEnabledAndSorted()
+      .stream()
+      .filter(IdentityProvider::isEnabled)
+      .filter(IdentityProvider::allowsUsersToSignUp)
+      .map(IdentityProvider::getName)
+      .collect(MoreCollectors.toList());
+  }
+
+  private boolean getForceAuthentication() {
+    return config.getBoolean(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false);
+  }
+
+  private boolean isOfficialDistribution() {
+    // the dependency com.sonarsource:sonarsource-branding is shaded to webapp
+    // during release (see sonar-web pom)
+    File brandingFile = new File(server.getRootDir(), BRANDING_FILE_PATH);
+    // no need to check that the file exists. java.io.File#length() returns zero in this case.
+    return brandingFile.length() > 0L;
+  }
+
+  @Override
+  public String name() {
+    return "SonarQube";
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName(name());
+
+    serverIdLoader.get().ifPresent(serverId -> {
+      setAttribute(protobuf, "Server ID", serverId.getId());
+      setAttribute(protobuf, "Server ID validated", serverId.isValid());
+    });
+    setAttribute(protobuf, "Version", getVersion());
+    setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
+    addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
+    addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders());
+    setAttribute(protobuf, "Force authentication", getForceAuthentication());
+    setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
+    setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
+    setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
+    setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
+    setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
+    setAttribute(protobuf, "Logs Level", getLogLevel());
+    return protobuf.build();
+  }
+
+  private static void addIfNotEmpty(ProtobufSystemInfo.Section.Builder protobuf, String key, @Nullable List<String> values) {
+    if (values != null && !values.isEmpty()) {
+      setAttribute(protobuf, key, COMMA_JOINER.join(values));
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java
new file mode 100644 (file)
index 0000000..1a562a6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import javax.annotation.CheckForNull;
+
+public interface SonarQubeSectionMBean {
+  @CheckForNull
+  String getServerId();
+
+  String getVersion();
+
+  String getLogLevel();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java
deleted file mode 100644 (file)
index 94e0298..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.lang.management.ClassLoadingMXBean;
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.RuntimeMXBean;
-import java.lang.management.ThreadMXBean;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.sonar.api.utils.System2;
-
-import static org.sonar.api.utils.DateUtils.formatDateTime;
-
-/**
- * JVM runtime information. Not exported as a MXBean because these informations
- * are natively provided.
- */
-public class SystemMonitor implements Monitor {
-  private final System2 system;
-
-  public SystemMonitor() {
-    this(System2.INSTANCE);
-  }
-
-  SystemMonitor(System2 system) {
-    this.system = system;
-  }
-
-  @Override
-  public String name() {
-    return "System";
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    Map<String, Object> attributes = new LinkedHashMap<>();
-    attributes.put("System Date", formatDateTime(new Date(system.now())));
-    attributes.put("Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
-    attributes.put("JVM Vendor", runtimeMXBean().getVmVendor());
-    attributes.put("JVM Name", runtimeMXBean().getVmName());
-    attributes.put("JVM Version", runtimeMXBean().getVmVersion());
-    attributes.put("Processors", runtime().availableProcessors());
-    attributes.put("System Classpath", runtimeMXBean().getClassPath());
-    attributes.put("BootClassPath", runtimeMXBean().getBootClassPath());
-    attributes.put("Library Path", runtimeMXBean().getLibraryPath());
-    attributes.put("Total Memory", formatMemory(runtime().totalMemory()));
-    attributes.put("Free Memory", formatMemory(runtime().freeMemory()));
-    attributes.put("Max Memory", formatMemory(runtime().maxMemory()));
-    attributes.put("Heap", memoryMXBean().getHeapMemoryUsage().toString());
-    attributes.put("Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
-    attributes.put("System Load Average", String.format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
-    attributes.put("Loaded Classes", classLoadingMXBean().getLoadedClassCount());
-    attributes.put("Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
-    attributes.put("Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
-    attributes.put("Threads", threadMXBean().getThreadCount());
-    attributes.put("Threads Peak", threadMXBean().getPeakThreadCount());
-    attributes.put("Daemon Thread", threadMXBean().getDaemonThreadCount());
-    return attributes;
-  }
-
-  private static RuntimeMXBean runtimeMXBean() {
-    return ManagementFactory.getRuntimeMXBean();
-  }
-
-  private static Runtime runtime() {
-    return Runtime.getRuntime();
-  }
-
-  private static MemoryMXBean memoryMXBean() {
-    return ManagementFactory.getMemoryMXBean();
-  }
-
-  private static ClassLoadingMXBean classLoadingMXBean() {
-    return ManagementFactory.getClassLoadingMXBean();
-  }
-
-  private static ThreadMXBean threadMXBean() {
-    return ManagementFactory.getThreadMXBean();
-  }
-
-  private static String formatMemory(long memoryInBytes) {
-    return String.format("%d MB", memoryInBytes / 1_000_000);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
new file mode 100644 (file)
index 0000000..bbd623f
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.lang.management.ClassLoadingMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.lang.management.ThreadMXBean;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static java.lang.String.format;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * JVM runtime information. Not exported as a MXBean because these information
+ * are natively provided.
+ */
+public class SystemSection implements SystemInfoSection {
+  private final System2 system;
+
+  public SystemSection() {
+    this(System2.INSTANCE);
+  }
+
+  SystemSection(System2 system) {
+    this.system = system;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("System");
+
+    setAttribute(protobuf, "System Date", formatDateTime(new Date(system.now())));
+    setAttribute(protobuf, "Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
+    setAttribute(protobuf, "JVM Vendor", runtimeMXBean().getVmVendor());
+    setAttribute(protobuf, "JVM Name", runtimeMXBean().getVmName());
+    setAttribute(protobuf, "JVM Version", runtimeMXBean().getVmVersion());
+    setAttribute(protobuf, "Processors", runtime().availableProcessors());
+    setAttribute(protobuf, "System Classpath", runtimeMXBean().getClassPath());
+    setAttribute(protobuf, "BootClassPath", runtimeMXBean().getBootClassPath());
+    setAttribute(protobuf, "Library Path", runtimeMXBean().getLibraryPath());
+    setAttribute(protobuf, "Total Memory", formatMemory(runtime().totalMemory()));
+    setAttribute(protobuf, "Free Memory", formatMemory(runtime().freeMemory()));
+    setAttribute(protobuf, "Max Memory", formatMemory(runtime().maxMemory()));
+    setAttribute(protobuf, "Heap", memoryMXBean().getHeapMemoryUsage().toString());
+    setAttribute(protobuf, "Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
+    setAttribute(protobuf, "System Load Average", format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
+    setAttribute(protobuf, "Loaded Classes", classLoadingMXBean().getLoadedClassCount());
+    setAttribute(protobuf, "Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
+    setAttribute(protobuf, "Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
+    setAttribute(protobuf, "Threads", threadMXBean().getThreadCount());
+    setAttribute(protobuf, "Threads Peak", threadMXBean().getPeakThreadCount());
+    setAttribute(protobuf, "Daemon Thread", threadMXBean().getDaemonThreadCount());
+    return protobuf.build();
+  }
+
+  private static RuntimeMXBean runtimeMXBean() {
+    return ManagementFactory.getRuntimeMXBean();
+  }
+
+  private static Runtime runtime() {
+    return Runtime.getRuntime();
+  }
+
+  private static MemoryMXBean memoryMXBean() {
+    return ManagementFactory.getMemoryMXBean();
+  }
+
+  private static ClassLoadingMXBean classLoadingMXBean() {
+    return ManagementFactory.getClassLoadingMXBean();
+  }
+
+  private static ThreadMXBean threadMXBean() {
+    return ManagementFactory.getThreadMXBean();
+  }
+
+  private static String formatMemory(long memoryInBytes) {
+    return format("%d MB", memoryInBytes / 1_000_000);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
new file mode 100644 (file)
index 0000000..c565219
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.process.systeminfo.JvmPropertiesSection;
+import org.sonar.process.systeminfo.JvmStateSection;
+import org.sonar.server.platform.ws.ClusterInfoAction;
+import org.sonar.server.platform.ws.StandaloneInfoAction;
+
+public class WebSystemInfoModule {
+
+  private WebSystemInfoModule() {
+    // do not instantiate
+  }
+
+  public static Object[] forStandaloneMode() {
+    return new Object[] {
+      new JvmPropertiesSection("Web JVM Properties"),
+      new JvmStateSection("Web JVM State"),
+      DatabaseSection.class,
+      EsSection.class,
+      PluginsSection.class,
+      SettingsSection.class,
+      SonarQubeSection.class,
+      SystemSection.class,
+
+      StandaloneInfoAction.class
+    };
+  }
+
+  public static Object[] forClusterMode() {
+    return new Object[] {
+      new JvmPropertiesSection("Web JVM Properties"),
+      new JvmStateSection("Web JVM State"),
+      DatabaseSection.class,
+      EsSection.class,
+      PluginsSection.class,
+
+      ClusterInfoAction.class
+    };
+  }
+}
index 46d62bdf8127c26be946d041a6228ee1b5e1dc84..b69534bc6058bb9d7ee940018ccce5e4a32f0729 100644 (file)
@@ -108,19 +108,13 @@ import org.sonar.server.platform.BackendCleanup;
 import org.sonar.server.platform.PersistentSettings;
 import org.sonar.server.platform.ServerLogging;
 import org.sonar.server.platform.SettingsChangeNotifier;
-import org.sonar.server.platform.monitoring.DatabaseMonitor;
-import org.sonar.server.platform.monitoring.EsMonitor;
-import org.sonar.server.platform.monitoring.JvmPropsMonitor;
-import org.sonar.server.platform.monitoring.PluginsMonitor;
-import org.sonar.server.platform.monitoring.SettingsMonitor;
-import org.sonar.server.platform.monitoring.SonarQubeMonitor;
-import org.sonar.server.platform.monitoring.SystemMonitor;
+import org.sonar.server.platform.monitoring.WebSystemInfoModule;
 import org.sonar.server.platform.web.WebPagesFilter;
 import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
 import org.sonar.server.platform.ws.ChangeLogLevelAction;
 import org.sonar.server.platform.ws.DbMigrationStatusAction;
 import org.sonar.server.platform.ws.HealthActionModule;
-import org.sonar.server.platform.ws.InfoActionModule;
+import org.sonar.server.platform.ws.InfoAction;
 import org.sonar.server.platform.ws.L10nWs;
 import org.sonar.server.platform.ws.LogsAction;
 import org.sonar.server.platform.ws.MigrateDbAction;
@@ -192,7 +186,9 @@ import org.sonar.server.source.ws.LinesAction;
 import org.sonar.server.source.ws.RawAction;
 import org.sonar.server.source.ws.ScmAction;
 import org.sonar.server.source.ws.SourcesWs;
-import org.sonar.server.telemetry.TelemetryModule;
+import org.sonar.server.telemetry.TelemetryClient;
+import org.sonar.server.telemetry.TelemetryDaemon;
+import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.test.index.TestIndex;
 import org.sonar.server.test.index.TestIndexDefinition;
 import org.sonar.server.test.index.TestIndexer;
@@ -486,17 +482,9 @@ public class PlatformLevel4 extends PlatformLevel {
       // System
       ServerLogging.class,
       RestartAction.class,
-      InfoActionModule.class,
       PingAction.class,
       UpgradesAction.class,
       StatusAction.class,
-      SystemMonitor.class,
-      SettingsMonitor.class,
-      SonarQubeMonitor.class,
-      EsMonitor.class,
-      PluginsMonitor.class,
-      JvmPropsMonitor.class,
-      DatabaseMonitor.class,
       MigrateDbAction.class,
       LogsAction.class,
       ChangeLogLevelAction.class,
@@ -548,9 +536,14 @@ public class PlatformLevel4 extends PlatformLevel {
 
       RecoveryIndexer.class,
       ProjectIndexersImpl.class);
-    addIfStartupLeader(
-      // Telemetry
-      TelemetryModule.class);
+
+    // telemetry
+    add(TelemetryDataLoader.class);
+    addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);
+
+    // system info
+    add(InfoAction.class);
+    addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());
 
     addAll(level4AddedComponents);
   }
index 3f543c4e8f34a3db5f6152b94d769bbfa5183c1c..447ec555b2823b6ecaf6a93ce2f998eefdf0919b 100644 (file)
  */
 package org.sonar.server.platform.ws;
 
-import java.util.Map;
+import java.util.Arrays;
 import java.util.Optional;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.ce.http.CeHttpClient;
+import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.platform.monitoring.Monitor;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.user.UserSession;
 
@@ -40,14 +40,14 @@ public class InfoAction implements SystemWsAction {
 
   private final UserSession userSession;
   private final CeHttpClient ceHttpClient;
-  private final Monitor[] monitors;
+  private final SystemInfoSection[] systemInfoSections;
   private final TelemetryDataLoader statistics;
 
-  public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, Monitor... monitors) {
+  public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, SystemInfoSection... systemInfoSections) {
     this.userSession = userSession;
     this.ceHttpClient = ceHttpClient;
     this.statistics = statistics;
-    this.monitors = monitors;
+    this.systemInfoSections = systemInfoSections;
   }
 
   @Override
@@ -66,32 +66,19 @@ public class InfoAction implements SystemWsAction {
   public void handle(Request request, Response response) {
     userSession.checkIsSystemAdministrator();
 
-    JsonWriter json = response.newJsonWriter();
-    writeJson(json);
-    json.close();
+    try (JsonWriter json = response.newJsonWriter()) {
+      writeJson(json);
+    }
   }
 
   private void writeJson(JsonWriter json) {
     json.beginObject();
-    for (Monitor monitor : monitors) {
-      Map<String, Object> attributes = monitor.attributes();
-      json.name(monitor.name());
-      json.beginObject();
-      for (Map.Entry<String, Object> attribute : attributes.entrySet()) {
-        json.name(attribute.getKey()).valueObject(attribute.getValue());
-      }
-      json.endObject();
-    }
+    Arrays.stream(systemInfoSections)
+      .map(SystemInfoSection::toProtobuf)
+      .forEach(section -> sectionToJson(section, json));
     Optional<ProtobufSystemInfo.SystemInfo> ceSysInfo = ceHttpClient.retrieveSystemInfo();
     if (ceSysInfo.isPresent()) {
-      for (ProtobufSystemInfo.Section section : ceSysInfo.get().getSectionsList()) {
-        json.name(section.getName());
-        json.beginObject();
-        for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
-          writeAttribute(json, attribute);
-        }
-        json.endObject();
-      }
+      ceSysInfo.get().getSectionsList().forEach(section -> sectionToJson(section, json));
     }
     writeStatistics(json);
     json.endObject();
@@ -102,19 +89,28 @@ public class InfoAction implements SystemWsAction {
     writeTelemetryData(json, statistics.load());
   }
 
-  private static void writeAttribute(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
+  private static void sectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
+    json.name(section.getName());
+    json.beginObject();
+    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+      attributeToJson(json, attribute);
+    }
+    json.endObject();
+  }
+
+  private static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
     switch (attribute.getValueCase()) {
       case BOOLEAN_VALUE:
-        json.name(attribute.getKey()).valueObject(attribute.getBooleanValue());
+        json.prop(attribute.getKey(), attribute.getBooleanValue());
         break;
       case LONG_VALUE:
-        json.name(attribute.getKey()).valueObject(attribute.getLongValue());
+        json.prop(attribute.getKey(), attribute.getLongValue());
         break;
       case DOUBLE_VALUE:
-        json.name(attribute.getKey()).valueObject(attribute.getDoubleValue());
+        json.prop(attribute.getKey(), attribute.getDoubleValue());
         break;
       case STRING_VALUE:
-        json.name(attribute.getKey()).valueObject(attribute.getStringValue());
+        json.prop(attribute.getKey(), attribute.getStringValue());
         break;
       case VALUE_NOT_SET:
         break;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java
deleted file mode 100644 (file)
index 0218ac4..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.lang.management.ManagementFactory;
-import javax.annotation.CheckForNull;
-import javax.management.InstanceNotFoundException;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class BaseMonitorMBeanTest {
-
-  FakeMonitor underTest = new FakeMonitor();
-
-  @Test
-  public void test_registration() throws Exception {
-    assertThat(getMBean()).isNull();
-
-    underTest.start();
-    assertThat(getMBean()).isNotNull();
-
-    underTest.stop();
-    assertThat(getMBean()).isNull();
-  }
-
-  @Test
-  public void do_not_fail_when_stopping_unstarted() throws Exception {
-    underTest.stop();
-    assertThat(getMBean()).isNull();
-  }
-
-  @CheckForNull
-  private ObjectInstance getMBean() throws Exception {
-    try {
-      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(underTest.objectName()));
-    } catch (InstanceNotFoundException e) {
-      return null;
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java
new file mode 100644 (file)
index 0000000..31dc349
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.lang.management.ManagementFactory;
+import javax.annotation.CheckForNull;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BaseSectionMBeanTest {
+
+  private FakeSection underTest = new FakeSection();
+
+  @Test
+  public void test_registration() throws Exception {
+    assertThat(getMBean()).isNull();
+
+    underTest.start();
+    assertThat(getMBean()).isNotNull();
+
+    underTest.stop();
+    assertThat(getMBean()).isNull();
+  }
+
+  @Test
+  public void do_not_fail_when_stopping_unstarted() throws Exception {
+    underTest.stop();
+    assertThat(getMBean()).isNull();
+  }
+
+  @CheckForNull
+  private ObjectInstance getMBean() throws Exception {
+    try {
+      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(underTest.objectName()));
+    } catch (InstanceNotFoundException e) {
+      return null;
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java
deleted file mode 100644 (file)
index 5489e46..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.server.platform.db.migration.version.DatabaseVersion;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DatabaseMonitorTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
-  private DatabaseMonitor underTest = new DatabaseMonitor(databaseVersion, dbTester.getDbClient());
-
-  @Before
-  public void setUp() throws Exception {
-    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE);
-  }
-
-  @Test
-  public void name_is_not_empty() {
-    assertThat(underTest.name()).isNotEmpty();
-  }
-
-  @Test
-  public void db_info() {
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes.get("Database")).isEqualTo("H2");
-    assertThat(attributes.get("Database Version").toString()).startsWith("1.");
-    assertThat(attributes.get("Username")).isEqualTo("SONAR");
-    assertThat(attributes.get("Driver Version").toString()).startsWith("1.");
-  }
-
-  @Test
-  public void pool_info() {
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat((int) attributes.get("Pool Max Connections")).isGreaterThan(0);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java
new file mode 100644 (file)
index 0000000..f4139c8
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class DatabaseSectionTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
+  private DatabaseSection underTest = new DatabaseSection(databaseVersion, dbTester.getDbClient());
+
+  @Before
+  public void setUp() throws Exception {
+    when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE);
+  }
+
+  @Test
+  public void name_is_not_empty() {
+    assertThat(underTest.name()).isNotEmpty();
+  }
+
+  @Test
+  public void db_info() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "Database", "H2");
+    assertThat(attribute(section, "Database Version").getStringValue()).startsWith("1.");
+    assertThatAttributeIs(section, "Username", "SONAR");
+    assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("1.");
+  }
+
+  @Test
+  public void pool_info() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java
deleted file mode 100644 (file)
index 345d237..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.issue.index.IssueIndexDefinition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class EsMonitorTest {
-
-  @Rule
-  public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
-
-  private EsMonitor underTest = new EsMonitor(esTester.client());
-
-  @Test
-  public void name() {
-    assertThat(underTest.name()).isEqualTo("Elasticsearch");
-  }
-
-  @Test
-  public void cluster_attributes() {
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name());
-    assertThat(attributes.get("State")).isEqualTo(ClusterHealthStatus.GREEN);
-    assertThat(attributes.get("Number of Nodes")).isEqualTo(1);
-  }
-
-  @Test
-  public void node_attributes() {
-    Map<String, Object> attributes = underTest.attributes();
-    Map nodesAttributes = (Map) attributes.get("Nodes");
-
-    // one node
-    assertThat(nodesAttributes).hasSize(1);
-    Map nodeAttributes = (Map) nodesAttributes.values().iterator().next();
-    assertThat(nodeAttributes.get("Type")).isEqualTo("Master");
-    assertThat(nodeAttributes.get("Store Size")).isNotNull();
-  }
-
-  @Test
-  public void index_attributes() {
-    Map<String, Object> attributes = underTest.attributes();
-    Map indicesAttributes = (Map) attributes.get("Indices");
-
-    // one index "issues"
-    Map indexAttributes = (Map) indicesAttributes.get(IssueIndexDefinition.INDEX_TYPE_ISSUE.getIndex());
-    assertThat(indexAttributes.get("Docs")).isEqualTo(0L);
-    assertThat((int) indexAttributes.get("Shards")).isGreaterThan(0);
-    assertThat(indexAttributes.get("Store Size")).isNotNull();
-  }
-
-  @Test
-  public void attributes_displays_exception_message_when_cause_null_when_client_fails() {
-    EsClient esClientMock = mock(EsClient.class);
-    EsMonitor underTest = new EsMonitor(esClientMock);
-    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).hasSize(1);
-    assertThat(attributes.get("State")).isEqualTo("RuntimeException with no cause");
-  }
-
-  @Test
-  public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
-    EsClient esClientMock = mock(EsClient.class);
-    EsMonitor underTest = new EsMonitor(esClientMock);
-    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).hasSize(1);
-    assertThat(attributes.get("State")).isEqualTo("RuntimeException with cause not ES");
-  }
-
-  @Test
-  public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
-    EsClient esClientMock = mock(EsClient.class);
-    EsMonitor underTest = new EsMonitor(esClientMock);
-    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).hasSize(1);
-    assertThat(attributes.get("State")).isEqualTo("some cause message");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java
new file mode 100644 (file)
index 0000000..a55ae81
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class EsSectionTest {
+
+  @Rule
+  public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
+
+  private EsSection underTest = new EsSection(esTester.client());
+
+  @Test
+  public void name() {
+    assertThat(underTest.name()).isEqualTo("Elasticsearch");
+  }
+
+  @Test
+  public void es_state() {
+    assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name());
+    assertThatAttributeIs(underTest.toProtobuf(), "State", ClusterHealthStatus.GREEN.name());
+  }
+
+  @Test
+  public void node_attributes() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThat(attribute(section, "Store Size")).isNotNull();
+  }
+
+  @Test
+  public void index_attributes() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+    // one index "issues"
+    assertThat(attribute(section, "Index issues - Docs").getLongValue()).isEqualTo(0L);
+    assertThat(attribute(section, "Index issues - Shards").getLongValue()).isGreaterThan(0);
+    assertThat(attribute(section, "Index issues - Store Size").getStringValue()).isNotNull();
+  }
+
+  @Test
+  public void attributes_displays_exception_message_when_cause_null_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsSection underTest = new EsSection(esClientMock);
+    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "State", "RuntimeException with no cause");
+  }
+
+  @Test
+  public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsSection underTest = new EsSection(esClientMock);
+    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "State", "RuntimeException with cause not ES");
+  }
+
+  @Test
+  public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsSection underTest = new EsSection(esClientMock);
+    when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "State", "some cause message");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java
deleted file mode 100644 (file)
index 959438b..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Collections;
-import java.util.Map;
-
-public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean {
-
-  @Override
-  public int getFake() {
-    return 42;
-  }
-
-  @Override
-  public String name() {
-    return "fake";
-  }
-
-  @Override
-  public Map<String, Object> attributes() {
-    return Collections.emptyMap();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java
deleted file mode 100644 (file)
index d355b62..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-public interface FakeMonitorMBean {
-  int getFake();
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java
new file mode 100644 (file)
index 0000000..7018c1e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+public class FakeSection extends BaseSectionMBean implements FakeSectionMBean {
+
+  @Override
+  public int getFake() {
+    return 42;
+  }
+
+  @Override
+  public String name() {
+    return "fake";
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    return ProtobufSystemInfo.Section.newBuilder().build();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java
new file mode 100644 (file)
index 0000000..6421859
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+public interface FakeSectionMBean {
+  int getFake();
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java
deleted file mode 100644 (file)
index b486496..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class JvmPropsMonitorTest {
-
-  JvmPropsMonitor underTest = new JvmPropsMonitor();
-
-  @Test
-  public void name_is_not_empty() {
-    assertThat(underTest.name()).isNotEmpty();
-  }
-
-  @Test
-  public void attributes() {
-    Map<String, Object> attributes = underTest.attributes();
-
-    assertThat(attributes).containsKeys("java.vm.vendor", "os.name");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
deleted file mode 100644 (file)
index 278f869..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Arrays;
-import java.util.Map;
-import org.junit.Test;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.updatecenter.common.Version;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PluginsMonitorTest {
-
-  PluginRepository repo = mock(PluginRepository.class);
-  PluginsMonitor underTest = new PluginsMonitor(repo);
-
-  @Test
-  public void name() {
-    assertThat(underTest.name()).isEqualTo("Plugins");
-  }
-
-  @Test
-  public void plugin_name_and_version() {
-    when(repo.getPluginInfos()).thenReturn(Arrays.asList(
-      new PluginInfo("key-1")
-        .setName("plugin-1")
-        .setVersion(Version.create("1.1")),
-      new PluginInfo("key-2")
-        .setName("plugin-2")
-        .setVersion(Version.create("2.2")),
-      new PluginInfo("no-version")
-        .setName("No Version")));
-
-    Map<String, Object> attributes = underTest.attributes();
-
-    assertThat(attributes).containsKeys("key-1", "key-2");
-    assertThat((Map) attributes.get("key-1"))
-      .containsEntry("Name", "plugin-1")
-      .containsEntry("Version", "1.1");
-    assertThat((Map) attributes.get("key-2"))
-      .containsEntry("Name", "plugin-2")
-      .containsEntry("Version", "2.2");
-    assertThat((Map) attributes.get("no-version"))
-      .containsEntry("Name", "No Version")
-      .doesNotContainKey("Version");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java
new file mode 100644 (file)
index 0000000..5878325
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.updatecenter.common.Version;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class PluginsSectionTest {
+
+  private PluginRepository repo = mock(PluginRepository.class);
+  private PluginsSection underTest = new PluginsSection(repo);
+
+  @Test
+  public void name() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Plugins");
+  }
+
+  @Test
+  public void plugin_name_and_version() {
+    when(repo.getPluginInfos()).thenReturn(Arrays.asList(
+      new PluginInfo("key-1")
+        .setName("Plugin 1")
+        .setVersion(Version.create("1.1")),
+      new PluginInfo("key-2")
+        .setName("Plugin 2")
+        .setVersion(Version.create("2.2")),
+      new PluginInfo("no-version")
+        .setName("No Version")));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+    assertThatAttributeIs(section, "key-1", "1.1 [Plugin 1]");
+    assertThatAttributeIs(section, "key-2", "2.2 [Plugin 2]");
+    assertThatAttributeIs(section, "no-version", "[No Version]");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java
deleted file mode 100644 (file)
index 9c1edb7..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.SortedMap;
-import org.junit.Test;
-import org.sonar.api.PropertyType;
-import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.config.Settings;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.apache.commons.lang.StringUtils.repeat;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-
-public class SettingsMonitorTest {
-
-  private static final String PASSWORD_PROPERTY = "sonar.password";
-
-  PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
-  Settings settings = new MapSettings(defs);
-  SettingsMonitor underTest = new SettingsMonitor(settings);
-
-  @Test
-  public void return_properties_and_sort_by_key() {
-    settings.setProperty("foo", "foo value");
-    settings.setProperty("bar", "bar value");
-
-    SortedMap<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsExactly(entry("bar", "bar value"), entry("foo", "foo value"));
-  }
-
-  @Test
-  public void truncate_long_property_values() {
-    settings.setProperty("foo", repeat("abcde", 1_000));
-
-    String value = (String) underTest.attributes().get("foo");
-    assertThat(value).hasSize(SettingsMonitor.MAX_VALUE_LENGTH).startsWith("abcde");
-  }
-
-  @Test
-  public void exclude_password_properties() {
-    settings.setProperty(PASSWORD_PROPERTY, "abcde");
-
-    assertThat(underTest.attributes()).isEmpty();
-  }
-
-  @Test
-  public void test_monitor_name() {
-    assertThat(underTest.name()).isEqualTo("Settings");
-
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java
new file mode 100644 (file)
index 0000000..b5f5c14
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.junit.Test;
+import org.sonar.api.PropertyType;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.Settings;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.apache.commons.lang.StringUtils.repeat;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class SettingsSectionTest {
+
+  private static final String PASSWORD_PROPERTY = "sonar.password";
+
+  private PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
+  private Settings settings = new MapSettings(defs);
+  private SettingsSection underTest = new SettingsSection(settings);
+
+  @Test
+  public void return_properties_and_sort_by_key() {
+    settings.setProperty("foo", "foo value");
+    settings.setProperty("bar", "bar value");
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "bar", "bar value");
+    assertThatAttributeIs(protobuf, "foo", "foo value");
+  }
+
+  @Test
+  public void truncate_long_property_values() {
+    settings.setProperty("foo", repeat("abcde", 1_000));
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    String value = attribute(protobuf, "foo").getStringValue();
+    assertThat(value).hasSize(SettingsSection.MAX_VALUE_LENGTH).startsWith("abcde");
+  }
+
+  @Test
+  public void exclude_password_properties() {
+    settings.setProperty(PASSWORD_PROPERTY, "abcde");
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThat(attribute(protobuf, PASSWORD_PROPERTY)).isNull();
+  }
+
+  @Test
+  public void test_monitor_name() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Settings");
+
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java
deleted file mode 100644 (file)
index e967f10..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Optional;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.Server;
-import org.sonar.api.security.SecurityRealm;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.server.authentication.IdentityProviderRepositoryRule;
-import org.sonar.server.authentication.TestIdentityProvider;
-import org.sonar.server.platform.ServerId;
-import org.sonar.server.platform.ServerIdLoader;
-import org.sonar.server.platform.ServerLogging;
-import org.sonar.server.user.SecurityRealmFactory;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SonarQubeMonitorTest {
-
-  private static final String SERVER_ID_PROPERTY = "Server ID";
-  private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
-
-  MapSettings settings = new MapSettings();
-  Server server = mock(Server.class);
-  ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
-  ServerLogging serverLogging = mock(ServerLogging.class);
-  SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
-
-  SonarQubeMonitor underTest = new SonarQubeMonitor(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
-    serverLogging, serverIdLoader);
-
-  @Before
-  public void setUp() throws Exception {
-    when(serverLogging.getRootLoggerLevel()).thenReturn(LoggerLevel.DEBUG);
-    when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
-    when(serverIdLoader.get()).thenReturn(Optional.empty());
-  }
-
-  @Test
-  public void name_is_not_empty() {
-    assertThat(underTest.name()).isNotEmpty();
-  }
-
-  @Test
-  public void test_getServerId() {
-    when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
-    assertThat(underTest.getServerId()).isEqualTo("ABC");
-
-    when(serverIdLoader.getRaw()).thenReturn(Optional.<String>empty());
-    assertThat(underTest.getServerId()).isNull();
-  }
-
-  @Test
-  public void attributes_contain_information_about_valid_server_id() {
-    when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, true));
-  }
-
-  @Test
-  public void attributes_contain_information_about_non_valid_server_id() {
-    when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false)));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, false));
-  }
-
-  @Test
-  public void attributes_do_not_contain_information_about_server_id_if_absent() {
-    when(serverIdLoader.get()).thenReturn(Optional.<ServerId>empty());
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).doesNotContainKeys(SERVER_ID_PROPERTY, SERVER_ID_VALIDATED_PROPERTY);
-  }
-
-  @Test
-  public void official_distribution() throws Exception {
-    File rootDir = temp.newFolder();
-    FileUtils.write(new File(rootDir, SonarQubeMonitor.BRANDING_FILE_PATH), "1.2");
-
-    when(server.getRootDir()).thenReturn(rootDir);
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("Official Distribution", Boolean.TRUE);
-  }
-
-  @Test
-  public void not_an_official_distribution() throws Exception {
-    File rootDir = temp.newFolder();
-    // branding file is missing
-    when(server.getRootDir()).thenReturn(rootDir);
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("Official Distribution", Boolean.FALSE);
-  }
-
-  @Test
-  public void get_log_level() throws Exception {
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("Logs Level", "DEBUG");
-  }
-
-  @Test
-  public void get_realm() throws Exception {
-    SecurityRealm realm = mock(SecurityRealm.class);
-    when(realm.getName()).thenReturn("LDAP");
-    when(securityRealmFactory.getRealm()).thenReturn(realm);
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("External User Authentication", "LDAP");
-  }
-
-  @Test
-  public void no_realm() throws Exception {
-    when(securityRealmFactory.getRealm()).thenReturn(null);
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).doesNotContainKey("External User Authentication");
-  }
-
-  @Test
-  public void get_enabled_identity_providers() throws Exception {
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("github")
-      .setName("GitHub")
-      .setEnabled(true));
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("bitbucket")
-      .setName("Bitbucket")
-      .setEnabled(true));
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("disabled")
-      .setName("Disabled")
-      .setEnabled(false));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("Accepted external identity providers", "Bitbucket, GitHub");
-  }
-
-  @Test
-  public void get_enabled_identity_providers_allowing_users_to_signup() throws Exception {
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("github")
-      .setName("GitHub")
-      .setEnabled(true)
-      .setAllowsUsersToSignUp(true));
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("bitbucket")
-      .setName("Bitbucket")
-      .setEnabled(true)
-      .setAllowsUsersToSignUp(false));
-    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
-      .setKey("disabled")
-      .setName("Disabled")
-      .setEnabled(false)
-      .setAllowsUsersToSignUp(true));
-
-    Map<String, Object> attributes = underTest.attributes();
-    assertThat(attributes).containsEntry("External identity providers whose users are allowed to sign themselves up", "GitHub");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
new file mode 100644 (file)
index 0000000..9817a2e
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.io.File;
+import java.util.Optional;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.authentication.IdentityProviderRepositoryRule;
+import org.sonar.server.authentication.TestIdentityProvider;
+import org.sonar.server.platform.ServerId;
+import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.user.SecurityRealmFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class SonarQubeSectionTest {
+
+  private static final String SERVER_ID_PROPERTY = "Server ID";
+  private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
+
+  private MapSettings settings = new MapSettings();
+  private Server server = mock(Server.class);
+  private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
+  private ServerLogging serverLogging = mock(ServerLogging.class);
+  private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
+
+  private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
+    serverLogging, serverIdLoader);
+
+  @Before
+  public void setUp() throws Exception {
+    when(serverLogging.getRootLoggerLevel()).thenReturn(LoggerLevel.DEBUG);
+    when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
+    when(serverIdLoader.get()).thenReturn(Optional.empty());
+  }
+
+  @Test
+  public void name_is_not_empty() {
+    assertThat(underTest.name()).isNotEmpty();
+  }
+
+  @Test
+  public void test_getServerId() {
+    when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
+    assertThat(underTest.getServerId()).isEqualTo("ABC");
+
+    when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
+    assertThat(underTest.getServerId()).isNull();
+  }
+
+  @Test
+  public void attributes_contain_information_about_valid_server_id() {
+    when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
+    assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, true);
+  }
+
+  @Test
+  public void attributes_contain_information_about_non_valid_server_id() {
+    when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false)));
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
+    assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, false);
+  }
+
+  @Test
+  public void attributes_do_not_contain_information_about_server_id_if_absent() {
+    when(serverIdLoader.get()).thenReturn(Optional.empty());
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThat(attribute(protobuf, SERVER_ID_PROPERTY)).isNull();
+    assertThat(attribute(protobuf, SERVER_ID_VALIDATED_PROPERTY)).isNull();
+  }
+
+  @Test
+  public void official_distribution() throws Exception {
+    File rootDir = temp.newFolder();
+    FileUtils.write(new File(rootDir, SonarQubeSection.BRANDING_FILE_PATH), "1.2");
+
+    when(server.getRootDir()).thenReturn(rootDir);
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "Official Distribution", true);
+  }
+
+  @Test
+  public void not_an_official_distribution() throws Exception {
+    File rootDir = temp.newFolder();
+    // branding file is missing
+    when(server.getRootDir()).thenReturn(rootDir);
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "Official Distribution", false);
+  }
+
+  @Test
+  public void get_log_level() throws Exception {
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "Logs Level", "DEBUG");
+  }
+
+  @Test
+  public void get_realm() throws Exception {
+    SecurityRealm realm = mock(SecurityRealm.class);
+    when(realm.getName()).thenReturn("LDAP");
+    when(securityRealmFactory.getRealm()).thenReturn(realm);
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "External User Authentication", "LDAP");
+  }
+
+  @Test
+  public void no_realm() throws Exception {
+    when(securityRealmFactory.getRealm()).thenReturn(null);
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThat(attribute(protobuf, "External User Authentication")).isNull();
+  }
+
+  @Test
+  public void get_enabled_identity_providers() throws Exception {
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("github")
+      .setName("GitHub")
+      .setEnabled(true));
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("bitbucket")
+      .setName("Bitbucket")
+      .setEnabled(true));
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("disabled")
+      .setName("Disabled")
+      .setEnabled(false));
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub");
+  }
+
+  @Test
+  public void get_enabled_identity_providers_allowing_users_to_signup() throws Exception {
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("github")
+      .setName("GitHub")
+      .setEnabled(true)
+      .setAllowsUsersToSignUp(true));
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("bitbucket")
+      .setName("Bitbucket")
+      .setEnabled(true)
+      .setAllowsUsersToSignUp(false));
+    identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+      .setKey("disabled")
+      .setName("Disabled")
+      .setEnabled(false)
+      .setAllowsUsersToSignUp(true));
+
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java
new file mode 100644 (file)
index 0000000..bd02658
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class SystemInfoTesting {
+
+  private SystemInfoTesting() {
+    // do not instantiate
+  }
+
+  public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, String expectedValue) {
+    ProtobufSystemInfo.Attribute value = attribute(section, key);
+    assertThat(value).as(key).isNotNull();
+    assertThat(value.getStringValue()).isEqualTo(expectedValue);
+  }
+
+  public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, boolean expectedValue) {
+    ProtobufSystemInfo.Attribute value = attribute(section, key);
+    assertThat(value).as(key).isNotNull();
+    assertThat(value.getBooleanValue()).isEqualTo(expectedValue);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java
deleted file mode 100644 (file)
index 6c7b568..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.Map;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class SystemMonitorTest {
-
-  SystemMonitor underTest = new SystemMonitor();
-
-  @Test
-  public void name() {
-    assertThat(underTest.name()).isEqualTo("System");
-  }
-
-  @Test
-  public void system_properties() {
-    Map<String, Object> attributes = underTest.attributes();
-
-    assertThat(attributes).containsKeys("System Date", "Processors");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
new file mode 100644 (file)
index 0000000..9437866
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.junit.Test;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class SystemSectionTest {
+
+  private SystemSection underTest = new SystemSection();
+
+  @Test
+  public void name() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("System");
+  }
+
+  @Test
+  public void system_properties() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+    assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty();
+    assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java
new file mode 100644 (file)
index 0000000..a1bb583
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebSystemInfoModuleTest {
+
+  @Test
+  public void test_forStandaloneMode() {
+    assertThat(WebSystemInfoModule.forStandaloneMode())
+      .isNotEmpty()
+      .doesNotContainNull();
+  }
+
+  @Test
+  public void test_forClusterMode() {
+    assertThat(WebSystemInfoModule.forClusterMode())
+      .isNotEmpty()
+      .doesNotContainNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java
deleted file mode 100644 (file)
index d06d5db..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.ws;
-
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
-
-public class InfoActionModuleTest {
-  @Test
-  public void verify_count_of_added_components() {
-    ComponentContainer container = new ComponentContainer();
-
-    new InfoActionModule().configure(container);
-
-    assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
-  }
-
-}
index b90037f812f959e79d62300e9782b750aee3e18d..9c02608fddd2ec1b121772fbae1aa2e09b2b013b 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.server.platform.ws;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
 import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
@@ -28,8 +26,9 @@ import org.junit.rules.ExpectedException;
 import org.mockito.Mockito;
 import org.sonar.ce.http.CeHttpClient;
 import org.sonar.ce.http.CeHttpClientImpl;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.platform.monitoring.Monitor;
 import org.sonar.server.telemetry.TelemetryData;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.tester.UserSessionRule;
@@ -40,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
 
 public class InfoActionTest {
   @Rule
@@ -48,12 +48,12 @@ public class InfoActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private Monitor monitor1 = mock(Monitor.class);
-  private Monitor monitor2 = mock(Monitor.class);
+  private SystemInfoSection section1 = mock(SystemInfoSection.class);
+  private SystemInfoSection section2 = mock(SystemInfoSection.class);
   private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
   private TelemetryDataLoader statistics = mock(TelemetryDataLoader.class);
 
-  private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, monitor1, monitor2);
+  private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, section1, section2);
   private WsActionTester ws = new WsActionTester(underTest);
 
   @Test
@@ -84,22 +84,23 @@ public class InfoActionTest {
   public void write_json() {
     logInAsSystemAdministrator();
 
-    Map<String, Object> attributes1 = new LinkedHashMap<>();
-    attributes1.put("foo", "bar");
-    Map<String, Object> attributes2 = new LinkedHashMap<>();
-    attributes2.put("one", 1);
-    attributes2.put("two", 2);
-    when(monitor1.name()).thenReturn("Monitor One");
-    when(monitor1.attributes()).thenReturn(attributes1);
-    when(monitor2.name()).thenReturn("Monitor Two");
-    when(monitor2.attributes()).thenReturn(attributes2);
+    ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder()
+      .setName("Section One");
+    setAttribute(attributes1, "foo", "bar");
+    when(section1.toProtobuf()).thenReturn(attributes1.build());
+
+    ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder()
+      .setName("Section Two");
+    setAttribute(attributes2, "one", 1);
+    setAttribute(attributes2, "two", 2);
+    when(section2.toProtobuf()).thenReturn(attributes2.build());
     when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty());
     when(statistics.load()).thenReturn(mock(TelemetryData.class));
 
     TestResponse response = ws.newRequest().execute();
-    // response does not contain empty "Monitor Three"
+    // response does not contain empty "Section Three"
     verify(statistics).load();
-    assertThat(response.getInput()).isEqualTo("{\"Monitor One\":{\"foo\":\"bar\"},\"Monitor Two\":{\"one\":1,\"two\":2}," +
+    assertThat(response.getInput()).isEqualTo("{\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
       "\"Statistics\":{\"plugins\":{},\"userCount\":0,\"projectCount\":0,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":{},\"nclocByLanguage\":{}}}");
   }
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java
deleted file mode 100644 (file)
index 3edc652..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
-
-public class TelemetryModuleTest {
-  @Test
-  public void verify_count_of_added_components() {
-    ComponentContainer container = new ComponentContainer();
-
-    new TelemetryModule().configure(container);
-
-    assertThat(container.size()).isEqualTo(2 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
-  }
-}
index 93eeaaf5b777e2e0dbecd41d6e93c6f51006deec..1b4fd125a9721b31789064867baf23937d7ebed2 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { getJSON, post } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
 
-export function setLogLevel(level: string): Promise<void> {
-  return post('/api/system/change_log_level', { level });
+export type SysValue = boolean | string | number | HealthType | SysValueObject | SysValueArray;
+export interface SysValueObject {
+  [key: string]: SysValue;
 }
+export interface SysValueArray extends Array<SysValue> {}
 
-export function getSystemInfo(): Promise<any> {
-  return getJSON('/api/system/info');
+export interface SysInfoSection {
+  [sectionName: string]: SysValueObject;
+}
+
+export enum HealthType {
+  RED = 'RED',
+  YELLOW = 'YELLOW',
+  GREEN = 'GREEN'
+}
+
+export interface HealthCause extends SysValueObject {
+  message: string;
+}
+
+export interface NodeInfo extends SysValueObject {
+  Name: string;
+  Health: HealthType;
+  'Health Causes': HealthCause[];
+}
+
+export interface SysInfo extends SysValueObject {
+  Cluster: boolean;
+  Health: HealthType;
+  'Health Causes': HealthCause[];
+  'Application Nodes': NodeInfo[];
+  'Search Nodes': NodeInfo[];
+}
+
+export function setLogLevel(level: string): Promise<void | Response> {
+  return post('/api/system/change_log_level', { level }).catch(throwGlobalError);
+}
+
+export function getSystemInfo(): Promise<SysInfo> {
+  return getJSON('/api/system/info').catch(throwGlobalError);
 }
 
 export function getSystemStatus(): Promise<any> {
   return getJSON('/api/system/status');
 }
 
-export function restart(): Promise<void> {
-  return post('/api/system/restart');
+export function restart(): Promise<void | Response> {
+  return post('/api/system/restart').catch(throwGlobalError);
 }
 
 const POLLING_INTERVAL = 2000;
diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
new file mode 100644 (file)
index 0000000..cd10c60
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as utils from '../utils';
+
+describe('parseQuery', () => {
+  it('should correctly parse the expand array', () => {
+    expect(utils.parseQuery({})).toEqual({ expandedCards: [] });
+    expect(utils.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] });
+  });
+});
+
+describe('serializeQuery', () => {
+  it('should correctly serialize the expand array', () => {
+    expect(utils.serializeQuery({ expandedCards: [] })).toEqual({});
+    expect(utils.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' });
+  });
+});
+
+describe('groupSections', () => {
+  it('should correctly group the root field into a main section', () => {
+    expect(utils.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
+      mainSection: { foo: 'Foo', bar: 3 },
+      sections: { baz: { a: 'a' } }
+    });
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx
new file mode 100644 (file)
index 0000000..440b117
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import ClusterSysInfos from './ClusterSysInfos';
+import PageHeader from './PageHeader';
+import StandAloneSysInfos from './StandAloneSysInfos';
+import { translate } from '../../../helpers/l10n';
+import { getSystemInfo, SysInfo } from '../../../api/system';
+import { isCluster, parseQuery, Query, serializeQuery } from '../utils';
+import { RawQuery } from '../../../helpers/query';
+import '../styles.css';
+
+interface Props {
+  location: { pathname: string; query: RawQuery };
+}
+
+interface State {
+  loading: boolean;
+  sysInfoData?: SysInfo;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: true };
+
+  static contextTypes = {
+    router: PropTypes.object
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchSysInfo();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchSysInfo = () => {
+    this.setState({ loading: true });
+    getSystemInfo().then(
+      (sysInfoData: SysInfo) => {
+        if (this.mounted) {
+          this.setState({ loading: false, sysInfoData });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  toggleSysInfoCards = (toggledCard: string) => {
+    const query = parseQuery(this.props.location.query);
+    let expandedCards;
+    if (query.expandedCards.includes(toggledCard)) {
+      expandedCards = query.expandedCards.filter(card => card !== toggledCard);
+    } else {
+      expandedCards = query.expandedCards.concat(toggledCard);
+    }
+    this.updateQuery({ expandedCards });
+  };
+
+  updateQuery = (newQuery: Query) => {
+    const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
+    this.context.router.push({ pathname: this.props.location.pathname, query });
+  };
+
+  renderSysInfo() {
+    const { sysInfoData } = this.state;
+    if (!sysInfoData) {
+      return null;
+    }
+
+    const query = parseQuery(this.props.location.query);
+    if (isCluster(sysInfoData)) {
+      return (
+        <ClusterSysInfos
+          sysInfoData={sysInfoData}
+          expandedCards={query.expandedCards}
+          toggleCard={this.toggleSysInfoCards}
+        />
+      );
+    }
+    return <StandAloneSysInfos sysInfoData={sysInfoData} />;
+  }
+
+  render() {
+    const { loading, sysInfoData } = this.state;
+    // TODO Correctly get logLevel, we are not sure yet how we want to do it for cluster mode
+    return (
+      <div className="page page-limited">
+        <Helmet title={translate('system_info.page')} />
+        <PageHeader
+          loading={loading}
+          isCluster={isCluster(sysInfoData)}
+          logLevel="INFO"
+          showActions={sysInfoData != undefined}
+        />
+        {this.renderSysInfo()}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
new file mode 100644 (file)
index 0000000..3065a0b
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import { setLogLevel } from '../../../api/system';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  infoMsg: string;
+  logLevel: string;
+  onChange: (level: string) => void;
+  onClose: () => void;
+}
+
+interface State {
+  newLevel: string;
+  updating: boolean;
+}
+
+const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
+
+export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+    this.state = { newLevel: props.logLevel, updating: false };
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    const { newLevel } = this.state;
+    if (!this.state.updating && newLevel !== this.props.logLevel) {
+      this.setState({ updating: true });
+      setLogLevel(newLevel).then(
+        () => this.props.onChange(newLevel),
+        () => this.setState({ updating: false })
+      );
+    }
+  };
+
+  handleLevelChange = (event: React.ChangeEvent<HTMLInputElement>) =>
+    this.setState({ newLevel: event.currentTarget.value });
+
+  render() {
+    const { updating, newLevel } = this.state;
+    const header = translate('system.set_log_level');
+    const disableSubmit = updating || newLevel === this.props.logLevel;
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="set-log-level-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+          <div className="modal-body">
+            {LOG_LEVELS.map(level => (
+              <p key={level} className="spacer-bottom">
+                <input
+                  type="radio"
+                  className="spacer-right text-middle"
+                  name="system.log_levels"
+                  value={level}
+                  checked={level === newLevel}
+                  onChange={this.handleLevelChange}
+                />
+                {level}
+              </p>
+            ))}
+            <div className="alert alert-info spacer-top">{this.props.infoMsg}</div>
+            {newLevel !== 'INFO' && (
+              <div className="alert alert-danger spacer-top">
+                {translate('system.log_level.warning')}
+              </div>
+            )}
+          </div>
+          <div className="modal-foot">
+            {updating && <i className="spinner spacer-right" />}
+            <button disabled={disableSubmit} id="set-log-level-submit">
+              {translate('save')}
+            </button>
+            <a href="#" id="set-log-level-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
new file mode 100644 (file)
index 0000000..7f68dc6
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import HealthCard from './info-items/HealthCard';
+import { translate } from '../../../helpers/l10n';
+import {
+  getAppNodes,
+  getHealth,
+  getHealthCauses,
+  getMainCardSection,
+  getNodeName,
+  getSearchNodes,
+  ignoreInfoFields
+} from '../utils';
+import { SysInfo } from '../../../api/system';
+
+interface Props {
+  expandedCards: string[];
+  sysInfoData: SysInfo;
+  toggleCard: (toggledCard: string) => void;
+}
+
+export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
+  const mainCardName = 'System';
+  return (
+    <ul>
+      <HealthCard
+        biggerHealth={true}
+        health={getHealth(sysInfoData)}
+        healthCauses={getHealthCauses(sysInfoData)}
+        name={mainCardName}
+        onClick={toggleCard}
+        open={expandedCards.includes(mainCardName)}
+        sysInfoData={ignoreInfoFields(getMainCardSection(sysInfoData))}
+      />
+      <li className="note system-info-health-title">
+        {translate('system.application_nodes_title')}
+      </li>
+      {sortBy(getAppNodes(sysInfoData), 'name').map(node => (
+        <HealthCard
+          key={getNodeName(node)}
+          health={getHealth(node)}
+          healthCauses={getHealthCauses(node)}
+          name={getNodeName(node)}
+          onClick={toggleCard}
+          open={expandedCards.includes(getNodeName(node))}
+          sysInfoData={ignoreInfoFields(node)}
+        />
+      ))}
+      <li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
+      {sortBy(getSearchNodes(sysInfoData), 'name').map(node => (
+        <HealthCard
+          key={getNodeName(node)}
+          health={getHealth(node)}
+          healthCauses={getHealthCauses(node)}
+          name={getNodeName(node)}
+          onClick={toggleCard}
+          open={expandedCards.includes(getNodeName(node))}
+          sysInfoData={ignoreInfoFields(node)}
+        />
+      ))}
+    </ul>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
new file mode 100644 (file)
index 0000000..1b2ef92
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import ChangeLogLevelForm from './ChangeLogLevelForm';
+import RestartForm from '../../../components/common/RestartForm';
+import { getBaseUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  canDownloadLogs: boolean;
+  canRestart: boolean;
+  cluster: boolean;
+  logLevel: string;
+}
+
+interface State {
+  logLevel: string;
+  openLogsLevelForm: boolean;
+  openRestartForm: boolean;
+}
+
+export default class PageActions extends React.PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      openLogsLevelForm: false,
+      openRestartForm: false,
+      logLevel: props.logLevel
+    };
+  }
+
+  componentWillReceiveProps(nextProps: Props) {
+    if (nextProps.logLevel !== this.state.logLevel) {
+      this.setState({ logLevel: nextProps.logLevel });
+    }
+  }
+
+  handleLogsLevelOpen = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ openLogsLevelForm: true });
+  };
+
+  handleLogsLevelChange = (logLevel: string) => {
+    this.setState({ logLevel });
+    this.handleLogsLevelClose();
+  };
+
+  handleLogsLevelClose = () => this.setState({ openLogsLevelForm: false });
+
+  handleServerRestartOpen = () => this.setState({ openRestartForm: true });
+  handleServerRestartClose = () => this.setState({ openRestartForm: false });
+
+  render() {
+    const infoUrl = getBaseUrl() + '/api/system/info';
+    const logsUrl = getBaseUrl() + '/api/system/logs';
+    return (
+      <div className="page-actions">
+        <span>
+          {translate('system.logs_level')}
+          {':'}
+          <strong className="little-spacer-left">{this.state.logLevel}</strong>
+          <a
+            id="edit-logs-level-button"
+            className="spacer-left icon-edit"
+            href="#"
+            onClick={this.handleLogsLevelOpen}
+          />
+        </span>
+        {this.props.canDownloadLogs && (
+          <div className="display-inline-block dropdown spacer-left">
+            <button data-toggle="dropdown">
+              {translate('system.download_logs')}
+              <i className="icon-dropdown little-spacer-left" />
+            </button>
+            <ul className="dropdown-menu">
+              <li>
+                <a
+                  href={logsUrl + '?process=app'}
+                  id="logs-link"
+                  download="sonarqube_app.log"
+                  target="_blank">
+                  Compute Engine
+                </a>
+              </li>
+              <li>
+                <a
+                  href={logsUrl + '?process=ce'}
+                  id="ce-logs-link"
+                  download="sonarqube_ce.log"
+                  target="_blank">
+                  Main Process
+                </a>
+              </li>
+              <li>
+                <a
+                  href={logsUrl + '?process=es'}
+                  id="es-logs-link"
+                  download="sonarqube_es.log"
+                  target="_blank">
+                  Elasticsearch
+                </a>
+              </li>
+              <li>
+                <a
+                  href={logsUrl + '?process=web'}
+                  id="web-logs-link"
+                  download="sonarqube_web.log"
+                  target="_blank">
+                  Web Server
+                </a>
+              </li>
+            </ul>
+          </div>
+        )}
+        <a
+          href={infoUrl}
+          id="download-link"
+          className="button spacer-left"
+          download="sonarqube_system_info.json"
+          target="_blank">
+          {translate('system.download_system_info')}
+        </a>
+        {this.props.canRestart && (
+          <button
+            id="restart-server-button"
+            className="spacer-left"
+            onClick={this.handleServerRestartOpen}>
+            {translate('system.restart_server')}
+          </button>
+        )}
+        {this.props.canRestart &&
+        this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
+        {this.state.openLogsLevelForm && (
+          <ChangeLogLevelForm
+            infoMsg={translate(
+              this.props.cluster ? 'system.cluster_log_level.info' : 'system.log_level.info'
+            )}
+            logLevel={this.state.logLevel}
+            onChange={this.handleLogsLevelChange}
+            onClose={this.handleLogsLevelClose}
+          />
+        )}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
new file mode 100644 (file)
index 0000000..c9d8456
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import PageActions from './PageActions';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  isCluster: boolean;
+  loading: boolean;
+  logLevel: string;
+  showActions: boolean;
+}
+
+export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) {
+  return (
+    <header className="page-header">
+      <h1 className="page-title">{translate('system_info.page')}</h1>
+      {showActions && (
+        <PageActions
+          canDownloadLogs={!isCluster}
+          canRestart={!isCluster}
+          cluster={isCluster}
+          logLevel={logLevel}
+        />
+      )}
+      {loading && (
+        <div className="page-actions">
+          <i className="spinner" />
+        </div>
+      )}
+    </header>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
new file mode 100644 (file)
index 0000000..2a142c5
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+  sysInfoData: object;
+}
+
+export default class StandAloneSysInfos extends React.PureComponent<Props> {
+  render() {
+    return <div>StandAloneSysInfos</div>;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx
new file mode 100644 (file)
index 0000000..08c177e
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ChangeLogLevelForm from '../ChangeLogLevelForm';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <ChangeLogLevelForm infoMsg="Foo" logLevel="INFO" onChange={() => {}} onClose={() => {}} />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should display some warning messages for non INFO levels', () => {
+  expect(
+    shallow(
+      <ChangeLogLevelForm infoMsg="Foo" logLevel="DEBUG" onChange={() => {}} onClose={() => {}} />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
new file mode 100644 (file)
index 0000000..5b703a1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ClusterSysInfos from '../ClusterSysInfos';
+import { HealthType, SysInfo } from '../../../../api/system';
+
+const sysInfoData: SysInfo = {
+  Cluster: true,
+  Health: HealthType.RED,
+  Name: 'Foo',
+  'Health Causes': [{ message: 'Database down' }],
+  'Application Nodes': [{ Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [] }],
+  'Search Nodes': [{ Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [] }]
+};
+
+it('should render correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <ClusterSysInfos
+      expandedCards={['System', 'Foo']}
+      sysInfoData={sysInfoData}
+      toggleCard={() => {}}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
new file mode 100644 (file)
index 0000000..098e897
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PageActions from '../PageActions';
+import { click } from '../../../../helpers/testUtils';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render without restart and log download', () => {
+  expect(
+    shallow(
+      <PageActions canDownloadLogs={false} canRestart={false} cluster={true} logLevel="INFO" />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should open restart modal', () => {
+  const wrapper = shallow(
+    <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+  );
+  click(wrapper.find('#restart-server-button'));
+  expect(wrapper.find('RestartForm')).toHaveLength(1);
+});
+
+it('should open change log level modal', () => {
+  const wrapper = shallow(
+    <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+  );
+  click(wrapper.find('#edit-logs-level-button'));
+  expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
new file mode 100644 (file)
index 0000000..f5255c7
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PageHeader from '../PageHeader';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<PageHeader isCluster={true} loading={false} logLevel="INFO" showActions={true} />)
+  ).toMatchSnapshot();
+});
+
+it('should show a loading spinner and no actions', () => {
+  expect(
+    shallow(<PageHeader isCluster={true} loading={true} logLevel="INFO" showActions={false} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
new file mode 100644 (file)
index 0000000..04cfc11
--- /dev/null
@@ -0,0 +1,194 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display some warning messages for non INFO levels 1`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="system.set_log_level"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <form
+    id="set-log-level-form"
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-head"
+    >
+      <h2>
+        system.set_log_level
+      </h2>
+    </div>
+    <div
+      className="modal-body"
+    >
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={false}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="INFO"
+        />
+        INFO
+      </p>
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={true}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="DEBUG"
+        />
+        DEBUG
+      </p>
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={false}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="TRACE"
+        />
+        TRACE
+      </p>
+      <div
+        className="alert alert-info spacer-top"
+      >
+        Foo
+      </div>
+      <div
+        className="alert alert-danger spacer-top"
+      >
+        system.log_level.warning
+      </div>
+    </div>
+    <div
+      className="modal-foot"
+    >
+      <button
+        disabled={true}
+        id="set-log-level-submit"
+      >
+        save
+      </button>
+      <a
+        href="#"
+        id="set-log-level-cancel"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </div>
+  </form>
+</Modal>
+`;
+
+exports[`should render correctly 1`] = `
+<Modal
+  ariaHideApp={true}
+  bodyOpenClassName="ReactModal__Body--open"
+  className="modal"
+  closeTimeoutMS={0}
+  contentLabel="system.set_log_level"
+  isOpen={true}
+  onRequestClose={[Function]}
+  overlayClassName="modal-overlay"
+  parentSelector={[Function]}
+  portalClassName="ReactModalPortal"
+  shouldCloseOnOverlayClick={true}
+>
+  <form
+    id="set-log-level-form"
+    onSubmit={[Function]}
+  >
+    <div
+      className="modal-head"
+    >
+      <h2>
+        system.set_log_level
+      </h2>
+    </div>
+    <div
+      className="modal-body"
+    >
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={true}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="INFO"
+        />
+        INFO
+      </p>
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={false}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="DEBUG"
+        />
+        DEBUG
+      </p>
+      <p
+        className="spacer-bottom"
+      >
+        <input
+          checked={false}
+          className="spacer-right text-middle"
+          name="system.log_levels"
+          onChange={[Function]}
+          type="radio"
+          value="TRACE"
+        />
+        TRACE
+      </p>
+      <div
+        className="alert alert-info spacer-top"
+      >
+        Foo
+      </div>
+    </div>
+    <div
+      className="modal-foot"
+    >
+      <button
+        disabled={true}
+        id="set-log-level-submit"
+      >
+        save
+      </button>
+      <a
+        href="#"
+        id="set-log-level-cancel"
+        onClick={[Function]}
+      >
+        cancel
+      </a>
+    </div>
+  </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
new file mode 100644 (file)
index 0000000..20ed8d5
--- /dev/null
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ul>
+  <HealthCard
+    biggerHealth={true}
+    health="RED"
+    healthCauses={
+      Array [
+        Object {
+          "message": "Database down",
+        },
+      ]
+    }
+    name="System"
+    onClick={[Function]}
+    open={true}
+    sysInfoData={
+      Object {
+        "Name": "Foo",
+      }
+    }
+  />
+  <li
+    className="note system-info-health-title"
+  >
+    system.application_nodes_title
+  </li>
+  <HealthCard
+    health="GREEN"
+    healthCauses={Array []}
+    name="Bar"
+    onClick={[Function]}
+    open={false}
+    sysInfoData={
+      Object {
+        "Name": "Bar",
+      }
+    }
+  />
+  <li
+    className="note system-info-health-title"
+  >
+    system.search_nodes_title
+  </li>
+  <HealthCard
+    health="YELLOW"
+    healthCauses={Array []}
+    name="Baz"
+    onClick={[Function]}
+    open={false}
+    sysInfoData={
+      Object {
+        "Name": "Baz",
+      }
+    }
+  />
+</ul>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
new file mode 100644 (file)
index 0000000..9d84700
--- /dev/null
@@ -0,0 +1,126 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="page-actions"
+>
+  <span>
+    system.logs_level
+    :
+    <strong
+      className="little-spacer-left"
+    >
+      INFO
+    </strong>
+    <a
+      className="spacer-left icon-edit"
+      href="#"
+      id="edit-logs-level-button"
+      onClick={[Function]}
+    />
+  </span>
+  <div
+    className="display-inline-block dropdown spacer-left"
+  >
+    <button
+      data-toggle="dropdown"
+    >
+      system.download_logs
+      <i
+        className="icon-dropdown little-spacer-left"
+      />
+    </button>
+    <ul
+      className="dropdown-menu"
+    >
+      <li>
+        <a
+          download="sonarqube_app.log"
+          href="/api/system/logs?process=app"
+          id="logs-link"
+          target="_blank"
+        >
+          Compute Engine
+        </a>
+      </li>
+      <li>
+        <a
+          download="sonarqube_ce.log"
+          href="/api/system/logs?process=ce"
+          id="ce-logs-link"
+          target="_blank"
+        >
+          Main Process
+        </a>
+      </li>
+      <li>
+        <a
+          download="sonarqube_es.log"
+          href="/api/system/logs?process=es"
+          id="es-logs-link"
+          target="_blank"
+        >
+          Elasticsearch
+        </a>
+      </li>
+      <li>
+        <a
+          download="sonarqube_web.log"
+          href="/api/system/logs?process=web"
+          id="web-logs-link"
+          target="_blank"
+        >
+          Web Server
+        </a>
+      </li>
+    </ul>
+  </div>
+  <a
+    className="button spacer-left"
+    download="sonarqube_system_info.json"
+    href="/api/system/info"
+    id="download-link"
+    target="_blank"
+  >
+    system.download_system_info
+  </a>
+  <button
+    className="spacer-left"
+    id="restart-server-button"
+    onClick={[Function]}
+  >
+    system.restart_server
+  </button>
+</div>
+`;
+
+exports[`should render without restart and log download 1`] = `
+<div
+  className="page-actions"
+>
+  <span>
+    system.logs_level
+    :
+    <strong
+      className="little-spacer-left"
+    >
+      INFO
+    </strong>
+    <a
+      className="spacer-left icon-edit"
+      href="#"
+      id="edit-logs-level-button"
+      onClick={[Function]}
+    />
+  </span>
+  <a
+    className="button spacer-left"
+    download="sonarqube_system_info.json"
+    href="/api/system/info"
+    id="download-link"
+    target="_blank"
+  >
+    system.download_system_info
+  </a>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
new file mode 100644 (file)
index 0000000..2945b6d
--- /dev/null
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<header
+  className="page-header"
+>
+  <h1
+    className="page-title"
+  >
+    system_info.page
+  </h1>
+  <PageActions
+    canDownloadLogs={false}
+    canRestart={false}
+    cluster={true}
+    logLevel="INFO"
+  />
+</header>
+`;
+
+exports[`should show a loading spinner and no actions 1`] = `
+<header
+  className="page-header"
+>
+  <h1
+    className="page-title"
+  >
+    system_info.page
+  </h1>
+  <div
+    className="page-actions"
+  >
+    <i
+      className="spinner"
+    />
+  </div>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
new file mode 100644 (file)
index 0000000..b45205d
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { map } from 'lodash';
+import HealthItem from './HealthItem';
+import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon';
+import Section from './Section';
+import { HealthType, HealthCause, SysValueObject } from '../../../../api/system';
+import { groupSections } from '../../utils';
+
+interface Props {
+  biggerHealth?: boolean;
+  health: HealthType;
+  healthCauses: HealthCause[];
+  onClick: (toggledCard: string) => void;
+  open: boolean;
+  name: string;
+  sysInfoData: SysValueObject;
+}
+
+interface State {
+  hoveringDetail: boolean;
+}
+
+export default class HealthCard extends React.PureComponent<Props, State> {
+  state: State = { hoveringDetail: false };
+
+  handleClick = () => this.props.onClick(this.props.name);
+  onDetailEnter = () => this.setState({ hoveringDetail: true });
+  onDetailLeave = () => this.setState({ hoveringDetail: false });
+
+  render() {
+    const { open, sysInfoData } = this.props;
+    const { mainSection, sections } = groupSections(sysInfoData);
+    const showFields = open && mainSection && Object.keys(mainSection).length > 0;
+    const showSections = open && sections;
+    return (
+      <li
+        className={classNames('boxed-group system-info-health-card', {
+          'no-hover': this.state.hoveringDetail
+        })}>
+        <div className="boxed-group-header" onClick={this.handleClick}>
+          <span className="system-info-health-card-title">
+            <OpenCloseIcon className="little-spacer-right" open={open} />
+            {this.props.name}
+          </span>
+          <HealthItem
+            className={classNames('pull-right', { 'big-dot': this.props.biggerHealth })}
+            health={this.props.health}
+            healthCauses={this.props.healthCauses}
+          />
+        </div>
+        {open && (
+          <div
+            className="boxed-group-inner"
+            onMouseEnter={this.onDetailEnter}
+            onMouseLeave={this.onDetailLeave}>
+            {showFields && <Section items={mainSection} />}
+            {showSections &&
+              map(sections, (section, name) => <Section key={name} items={section} name={name} />)}
+          </div>
+        )}
+      </li>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
new file mode 100644 (file)
index 0000000..49b22b7
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { HealthCause, HealthType } from '../../../../api/system';
+
+interface Props {
+  className?: string;
+  health: HealthType;
+  healthCause: HealthCause;
+}
+
+export default function HealthCauseItem({ className, health, healthCause }: Props) {
+  return (
+    <span
+      className={classNames(
+        'alert',
+        health === HealthType.RED ? 'alert-danger' : 'alert-warning',
+        className
+      )}>
+      {healthCause.message}
+    </span>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
new file mode 100644 (file)
index 0000000..a0793a8
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import HealthCauseItem from './HealthCauseItem';
+import { HealthType, HealthCause } from '../../../../api/system';
+
+interface Props {
+  className?: string;
+  health: HealthType;
+  healthCauses?: HealthCause[];
+}
+
+export default function HealthItem({ className, health, healthCauses }: Props) {
+  const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
+  return (
+    <div className={classNames('system-info-health-info', className)}>
+      {hasHealthCauses &&
+        healthCauses!.map((cause, idx) => (
+          <HealthCauseItem key={idx} className="spacer-right" health={health} healthCause={cause} />
+        ))}
+      <span className={classNames('system-info-health-dot', health)} />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
new file mode 100644 (file)
index 0000000..3da339c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import SysInfoItem from './SysInfoItem';
+import { SysValueObject } from '../../../../api/system';
+
+interface Props {
+  name?: string;
+  items: SysValueObject;
+}
+
+export default function Section({ name, items }: Props) {
+  return (
+    <div className="system-info-section">
+      {name && <h4 className="spacer-bottom">{name}</h4>}
+      <table className="data zebra" id={name}>
+        <tbody>
+          {map(items, (value, name) => {
+            return (
+              <tr key={name}>
+                <td className="thin">
+                  <div className="system-info-section-item-name">{name}</div>
+                </td>
+                <td style={{ wordBreak: 'break-all' }}>
+                  <SysInfoItem name={name} value={value} />
+                </td>
+              </tr>
+            );
+          })}
+        </tbody>
+      </table>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
new file mode 100644 (file)
index 0000000..12876d7
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import HealthItem from './HealthItem';
+import { HealthType, SysValue, SysValueObject } from '../../../../api/system';
+import { HEALTH_FIELD } from '../../utils';
+
+interface Props {
+  name: string;
+  value: SysValue;
+}
+
+export default function SysInfoItem({ name, value }: Props): JSX.Element {
+  if (name === HEALTH_FIELD) {
+    return <HealthItem className="no-margin" health={value as HealthType} />;
+  }
+  if (value instanceof Array) {
+    return <code>{JSON.stringify(value)}</code>;
+  }
+  switch (typeof value) {
+    case 'boolean':
+      return <BooleanItem value={value as boolean} />;
+    case 'object':
+      return <ObjectItem value={value as SysValueObject} />;
+    default:
+      return <code>{value}</code>;
+  }
+}
+
+function BooleanItem({ value }: { value: boolean }) {
+  if (value) {
+    return <i className="icon-check" />;
+  } else {
+    return <i className="icon-delete" />;
+  }
+}
+
+function ObjectItem({ value }: { value: SysValueObject }) {
+  return (
+    <table className="data">
+      <tbody>
+        {map(value, (value, name) => (
+          <tr key={name}>
+            <td className="thin nowrap">{name}</td>
+            <td>
+              <SysInfoItem name={name} value={value} />
+            </td>
+          </tr>
+        ))}
+      </tbody>
+    </table>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
new file mode 100644 (file)
index 0000000..c080f44
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthCard from '../HealthCard';
+import { click } from '../../../../../helpers/testUtils';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+  expect(getShallowWrapper()).toMatchSnapshot();
+});
+
+it('should display the sysinfo detail', () => {
+  expect(getShallowWrapper({ biggerHealth: true, open: true })).toMatchSnapshot();
+});
+
+it('should show the sysinfo detail when the card is clicked', () => {
+  const onClick = jest.fn();
+  click(getShallowWrapper({ onClick }).find('.boxed-group-header'));
+  expect(onClick).toBeCalled();
+  expect(onClick).toBeCalledWith('Foobar');
+});
+
+it('should show a main section and multiple sub sections', () => {
+  const sysInfoData = {
+    Name: 'foo',
+    bar: 'Bar',
+    Database: { db: 'test' },
+    Elasticseach: { Elastic: 'search' }
+  };
+  expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot();
+});
+
+function getShallowWrapper(props = {}) {
+  return shallow(
+    <HealthCard
+      biggerHealth={false}
+      health={HealthType.RED}
+      healthCauses={[{ message: 'foo' }]}
+      name="Foobar"
+      onClick={() => {}}
+      open={false}
+      sysInfoData={{}}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
new file mode 100644 (file)
index 0000000..24504f0
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthCauseItem from '../HealthCauseItem';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<HealthCauseItem health={HealthType.RED} healthCause={{ message: 'foo' }} />)
+  ).toMatchSnapshot();
+  expect(
+    shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause={{ message: 'foo' }} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
new file mode 100644 (file)
index 0000000..f77622b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthItem from '../HealthItem';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<HealthItem health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />)
+  ).toMatchSnapshot();
+});
+
+it('should not render health causes', () => {
+  expect(
+    shallow(<HealthItem health={HealthType.GREEN} healthCauses={[{ message: 'foo' }]} />)
+  ).toMatchSnapshot();
+  expect(shallow(<HealthItem health={HealthType.YELLOW} healthCauses={[]} />)).toMatchSnapshot();
+});
+
+it('should render multiple health causes', () => {
+  expect(
+    shallow(
+      <HealthItem
+        health={HealthType.YELLOW}
+        healthCauses={[{ message: 'foo' }, { message: 'bar' }]}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx
new file mode 100644 (file)
index 0000000..2778dc8
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Section from '../Section';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<Section name="foo" items={{ foo: 1, bar: 'Bar', baz: false }} />)
+  ).toMatchSnapshot();
+});
+
+it('should not render a title', () => {
+  expect(shallow(<Section items={{ foo: 'bar' }} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx
new file mode 100644 (file)
index 0000000..2761a2d
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow, mount } from 'enzyme';
+import SysInfoItem from '../SysInfoItem';
+
+it('should render string', () => {
+  const wrapper = shallow(<SysInfoItem name="foo" value="/some/path/as/an/example" />);
+  expect(wrapper.find('code').text()).toBe('/some/path/as/an/example');
+});
+
+it('should render object', () => {
+  const wrapper = shallow(<SysInfoItem name="foo" value={{ bar: 'baz' }} />);
+  expect(wrapper.find('ObjectItem').prop('value')).toEqual({ bar: 'baz' });
+});
+
+it('should render boolean', () => {
+  const wrapper = shallow(<SysInfoItem name="foo" value={true} />);
+  expect(wrapper.find('BooleanItem').prop('value')).toBe(true);
+});
+
+it('should render health item', () => {
+  const wrapper = shallow(<SysInfoItem name="Health" value="GREEN" />);
+  expect(wrapper.find('HealthItem').prop('health')).toBe('GREEN');
+});
+
+it('should render object correctly', () => {
+  expect(
+    mount(
+      <SysInfoItem name="test" value={{ foo: 'Far', bar: { a: 1, b: 'b' }, baz: true }} />
+    ).find('ObjectItem')
+  ).toMatchSnapshot();
+});
+
+it('should render `true`', () => {
+  const wrapper = mount(<SysInfoItem name="test" value={true} />);
+  expect(wrapper.find('.icon-check')).toHaveLength(1);
+});
+
+it('should render `false`', () => {
+  const wrapper = mount(<SysInfoItem name="test" value={false} />);
+  expect(wrapper.find('.icon-delete')).toHaveLength(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
new file mode 100644 (file)
index 0000000..7240019
--- /dev/null
@@ -0,0 +1,132 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the sysinfo detail 1`] = `
+<li
+  className="boxed-group system-info-health-card"
+>
+  <div
+    className="boxed-group-header"
+    onClick={[Function]}
+  >
+    <span
+      className="system-info-health-card-title"
+    >
+      <OpenCloseIcon
+        className="little-spacer-right"
+        open={true}
+      />
+      Foobar
+    </span>
+    <HealthItem
+      className="pull-right big-dot"
+      health="RED"
+      healthCauses={
+        Array [
+          Object {
+            "message": "foo",
+          },
+        ]
+      }
+    />
+  </div>
+  <div
+    className="boxed-group-inner"
+    onMouseEnter={[Function]}
+    onMouseLeave={[Function]}
+  />
+</li>
+`;
+
+exports[`should render correctly 1`] = `
+<li
+  className="boxed-group system-info-health-card"
+>
+  <div
+    className="boxed-group-header"
+    onClick={[Function]}
+  >
+    <span
+      className="system-info-health-card-title"
+    >
+      <OpenCloseIcon
+        className="little-spacer-right"
+        open={false}
+      />
+      Foobar
+    </span>
+    <HealthItem
+      className="pull-right"
+      health="RED"
+      healthCauses={
+        Array [
+          Object {
+            "message": "foo",
+          },
+        ]
+      }
+    />
+  </div>
+</li>
+`;
+
+exports[`should show a main section and multiple sub sections 1`] = `
+<li
+  className="boxed-group system-info-health-card"
+>
+  <div
+    className="boxed-group-header"
+    onClick={[Function]}
+  >
+    <span
+      className="system-info-health-card-title"
+    >
+      <OpenCloseIcon
+        className="little-spacer-right"
+        open={true}
+      />
+      Foobar
+    </span>
+    <HealthItem
+      className="pull-right"
+      health="RED"
+      healthCauses={
+        Array [
+          Object {
+            "message": "foo",
+          },
+        ]
+      }
+    />
+  </div>
+  <div
+    className="boxed-group-inner"
+    onMouseEnter={[Function]}
+    onMouseLeave={[Function]}
+  >
+    <Section
+      items={
+        Object {
+          "Name": "foo",
+          "bar": "Bar",
+        }
+      }
+    />
+    <Section
+      items={
+        Object {
+          "db": "test",
+        }
+      }
+      name="Database"
+    />
+    <Section
+      items={
+        Object {
+          "Elastic": "search",
+        }
+      }
+      name="Elasticseach"
+    />
+  </div>
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap
new file mode 100644 (file)
index 0000000..3202387
--- /dev/null
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<span
+  className="alert alert-danger"
+>
+  foo
+</span>
+`;
+
+exports[`should render correctly 2`] = `
+<span
+  className="alert alert-warning"
+>
+  foo
+</span>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
new file mode 100644 (file)
index 0000000..d9fa53a
--- /dev/null
@@ -0,0 +1,68 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render health causes 1`] = `
+<div
+  className="system-info-health-info"
+>
+  <span
+    className="system-info-health-dot GREEN"
+  />
+</div>
+`;
+
+exports[`should not render health causes 2`] = `
+<div
+  className="system-info-health-info"
+>
+  <span
+    className="system-info-health-dot YELLOW"
+  />
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+  className="system-info-health-info"
+>
+  <HealthCauseItem
+    className="spacer-right"
+    health="RED"
+    healthCause={
+      Object {
+        "message": "foo",
+      }
+    }
+  />
+  <span
+    className="system-info-health-dot RED"
+  />
+</div>
+`;
+
+exports[`should render multiple health causes 1`] = `
+<div
+  className="system-info-health-info"
+>
+  <HealthCauseItem
+    className="spacer-right"
+    health="YELLOW"
+    healthCause={
+      Object {
+        "message": "foo",
+      }
+    }
+  />
+  <HealthCauseItem
+    className="spacer-right"
+    health="YELLOW"
+    healthCause={
+      Object {
+        "message": "bar",
+      }
+    }
+  />
+  <span
+    className="system-info-health-dot YELLOW"
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap
new file mode 100644 (file)
index 0000000..20fce92
--- /dev/null
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render a title 1`] = `
+<div
+  className="system-info-section"
+>
+  <table
+    className="data zebra"
+  >
+    <tbody>
+      <tr>
+        <td
+          className="thin"
+        >
+          <div
+            className="system-info-section-item-name"
+          >
+            foo
+          </div>
+        </td>
+        <td
+          style={
+            Object {
+              "wordBreak": "break-all",
+            }
+          }
+        >
+          <SysInfoItem
+            name="foo"
+            value="bar"
+          />
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+  className="system-info-section"
+>
+  <h4
+    className="spacer-bottom"
+  >
+    foo
+  </h4>
+  <table
+    className="data zebra"
+    id="foo"
+  >
+    <tbody>
+      <tr>
+        <td
+          className="thin"
+        >
+          <div
+            className="system-info-section-item-name"
+          >
+            foo
+          </div>
+        </td>
+        <td
+          style={
+            Object {
+              "wordBreak": "break-all",
+            }
+          }
+        >
+          <SysInfoItem
+            name="foo"
+            value={1}
+          />
+        </td>
+      </tr>
+      <tr>
+        <td
+          className="thin"
+        >
+          <div
+            className="system-info-section-item-name"
+          >
+            bar
+          </div>
+        </td>
+        <td
+          style={
+            Object {
+              "wordBreak": "break-all",
+            }
+          }
+        >
+          <SysInfoItem
+            name="bar"
+            value="Bar"
+          />
+        </td>
+      </tr>
+      <tr>
+        <td
+          className="thin"
+        >
+          <div
+            className="system-info-section-item-name"
+          >
+            baz
+          </div>
+        </td>
+        <td
+          style={
+            Object {
+              "wordBreak": "break-all",
+            }
+          }
+        >
+          <SysInfoItem
+            name="baz"
+            value={false}
+          />
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap
new file mode 100644 (file)
index 0000000..a1ff67c
--- /dev/null
@@ -0,0 +1,180 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render object correctly 1`] = `
+Array [
+  <ObjectItem
+    value={
+        Object {
+            "bar": Object {
+              "a": 1,
+              "b": "b",
+            },
+            "baz": true,
+            "foo": "Far",
+          }
+    }
+>
+    <table
+        className="data"
+    >
+        <tbody>
+            <tr>
+                <td
+                    className="thin nowrap"
+                >
+                    foo
+                </td>
+                <td>
+                    <SysInfoItem
+                        name="foo"
+                        value="Far"
+                    >
+                        <code>
+                            Far
+                        </code>
+                    </SysInfoItem>
+                </td>
+            </tr>
+            <tr>
+                <td
+                    className="thin nowrap"
+                >
+                    bar
+                </td>
+                <td>
+                    <SysInfoItem
+                        name="bar"
+                        value={
+                            Object {
+                                "a": 1,
+                                "b": "b",
+                              }
+                        }
+                    >
+                        <ObjectItem
+                            value={
+                                Object {
+                                    "a": 1,
+                                    "b": "b",
+                                  }
+                            }
+                        >
+                            <table
+                                className="data"
+                            >
+                                <tbody>
+                                    <tr>
+                                        <td
+                                            className="thin nowrap"
+                                        >
+                                            a
+                                        </td>
+                                        <td>
+                                            <SysInfoItem
+                                                name="a"
+                                                value={1}
+                                            >
+                                                <code>
+                                                    1
+                                                </code>
+                                            </SysInfoItem>
+                                        </td>
+                                    </tr>
+                                    <tr>
+                                        <td
+                                            className="thin nowrap"
+                                        >
+                                            b
+                                        </td>
+                                        <td>
+                                            <SysInfoItem
+                                                name="b"
+                                                value="b"
+                                            >
+                                                <code>
+                                                    b
+                                                </code>
+                                            </SysInfoItem>
+                                        </td>
+                                    </tr>
+                                </tbody>
+                            </table>
+                        </ObjectItem>
+                    </SysInfoItem>
+                </td>
+            </tr>
+            <tr>
+                <td
+                    className="thin nowrap"
+                >
+                    baz
+                </td>
+                <td>
+                    <SysInfoItem
+                        name="baz"
+                        value={true}
+                    >
+                        <BooleanItem
+                            value={true}
+                        >
+                            <i
+                                className="icon-check"
+                            />
+                        </BooleanItem>
+                    </SysInfoItem>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</ObjectItem>,
+  <ObjectItem
+    value={
+        Object {
+            "a": 1,
+            "b": "b",
+          }
+    }
+>
+    <table
+        className="data"
+    >
+        <tbody>
+            <tr>
+                <td
+                    className="thin nowrap"
+                >
+                    a
+                </td>
+                <td>
+                    <SysInfoItem
+                        name="a"
+                        value={1}
+                    >
+                        <code>
+                            1
+                        </code>
+                    </SysInfoItem>
+                </td>
+            </tr>
+            <tr>
+                <td
+                    className="thin nowrap"
+                >
+                    b
+                </td>
+                <td>
+                    <SysInfoItem
+                        name="b"
+                        value="b"
+                    >
+                        <code>
+                            b
+                        </code>
+                    </SysInfoItem>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</ObjectItem>,
+]
+`;
index 411170e6f7576023e41b994a0e486cc6b61d9726..150bd042adf745af7d998e1a5063b284a3a3d102 100644 (file)
@@ -23,7 +23,7 @@ import { sortBy } from 'lodash';
 import { getSystemInfo } from '../../api/system';
 import Section from './section';
 import { translate } from '../../helpers/l10n';
-import RestartModal from '../../components/RestartModal';
+import RestartForm from '../../components/common/RestartForm';
 
 const SECTIONS_ORDER = [
   'SonarQube',
@@ -38,6 +38,8 @@ const SECTIONS_ORDER = [
 ];
 
 export default class Main extends React.PureComponent {
+  state = { openRestartForm: false };
+
   componentDidMount() {
     getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) }));
   }
@@ -60,9 +62,8 @@ export default class Main extends React.PureComponent {
 
   orderItems = items => sortBy(items, 'name');
 
-  handleServerRestart = () => {
-    new RestartModal().render();
-  };
+  handleServerRestartOpen = () => this.setState({ openRestartForm: true });
+  handleServerRestartClose = () => this.setState({ openRestartForm: false });
 
   render() {
     let sections = null;
@@ -113,9 +114,10 @@ export default class Main extends React.PureComponent {
             <button
               id="restart-server-button"
               className="big-spacer-left"
-              onClick={this.handleServerRestart}>
+              onClick={this.handleServerRestartOpen}>
               Restart Server
             </button>
+            {this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
           </div>
         </header>
         {sections}
index 9f7f40c4cd2d6afdcacf6f2fad2f6d6a75e965da..1a8d6a5ab38caff6597143db8d4dcaf1c3d8a9de 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { RouterState, IndexRouteProps } from 'react-router';
+import { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
 
 const routes = [
   {
     getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
-      import('./main').then(i => callback(null, { component: (i as any).default }));
+      import('./components/App').then(i => callback(null, { component: i.default }));
+    }
+  },
+  {
+    path: 'old',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./main').then(i => callback(null, (i as any).default));
     }
   }
 ];
diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css
new file mode 100644 (file)
index 0000000..f115660
--- /dev/null
@@ -0,0 +1,81 @@
+.system-info-health-title {
+  margin-top: 24px;
+  margin-bottom: 16px;
+}
+
+.system-info-health-card {
+  margin-bottom: 8px;
+  transition: border-color 0.3s ease;
+}
+
+.system-info-health-card:not(.no-hover):hover {
+  border-color: #4b9fd5;
+}
+
+.system-info-health-card:not(.no-hover):hover .system-info-health-card-title {
+  color: #4b9fd5;
+}
+
+.system-info-health-card .boxed-group-header {
+  cursor: pointer;
+  padding-bottom: 15px;
+}
+
+.system-info-health-card .boxed-group-inner {
+  padding-top: 0;
+}
+
+.system-info-health-card-title {
+  font-weight: bold;
+}
+
+.system-info-health-info {
+  margin-top: -4px;
+}
+
+.system-info-health-dot {
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+  margin: 4px;
+  border-radius: 16px;
+  box-sizing: border-box;
+}
+
+.system-info-health-dot.GREEN {
+  background-color: #00aa00;
+}
+
+.system-info-health-dot.YELLOW {
+  background-color: #eabe06;
+}
+.system-info-health-dot.RED {
+  background-color: #d4333f;
+}
+
+.system-info-health-info .alert {
+  display: inline-block;
+  position: relative;
+  top: -8px;
+}
+
+.system-info-health-info.big-dot .system-info-health-dot {
+  width: 24px;
+  height: 24px;
+  margin: 0;
+  border-radius: 24px;
+}
+
+.system-info-health-info.no-margin .system-info-health-dot {
+  margin: 0;
+}
+
+.system-info-section ~ .system-info-section {
+  margin-top: 16px;
+}
+
+.system-info-section-item-name {
+  width: 25vw;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts
new file mode 100644 (file)
index 0000000..41ad522
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { each, omit, memoize } from 'lodash';
+import {
+  cleanQuery,
+  parseAsArray,
+  parseAsString,
+  RawQuery,
+  serializeStringArray
+} from '../../helpers/query';
+import {
+  HealthCause,
+  HealthType,
+  NodeInfo,
+  SysInfo,
+  SysInfoSection,
+  SysValueObject
+} from '../../api/system';
+
+export interface Query {
+  expandedCards: string[];
+}
+
+export const HEALTH_FIELD = 'Health';
+export const HEALTHCAUSES_FIELD = 'Health Causes';
+
+export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject {
+  return omit(sysInfoObject, ['Cluster', HEALTH_FIELD, HEALTHCAUSES_FIELD]);
+}
+
+export function getAppNodes(sysInfoData: SysInfo): NodeInfo[] {
+  return sysInfoData['Application Nodes'];
+}
+
+export function getHealth(sysInfoObject: SysValueObject): HealthType {
+  return sysInfoObject[HEALTH_FIELD] as HealthType;
+}
+
+export function getHealthCauses(sysInfoObject: SysValueObject): HealthCause[] {
+  return sysInfoObject[HEALTHCAUSES_FIELD] as HealthCause[];
+}
+
+export function getMainCardSection(sysInfoData: SysInfo): SysValueObject {
+  return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
+}
+
+export function getNodeName(nodeInfo: NodeInfo): string {
+  return nodeInfo['Name'];
+}
+
+export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] {
+  return sysInfoData['Search Nodes'];
+}
+
+export function groupSections(sysInfoData: SysValueObject) {
+  let mainSection: SysValueObject = {};
+  let sections: SysInfoSection = {};
+  each(sysInfoData, (item, key) => {
+    if (typeof item !== 'object' || item instanceof Array) {
+      mainSection[key] = item;
+    } else {
+      sections[key] = item;
+    }
+  });
+  return { mainSection, sections };
+}
+
+export function isCluster(sysInfoData?: SysInfo): boolean {
+  return sysInfoData != undefined && sysInfoData['Cluster'];
+}
+
+export const parseQuery = memoize((urlQuery: RawQuery): Query => {
+  return {
+    expandedCards: parseAsArray(urlQuery.expand, parseAsString)
+  };
+});
+
+export const serializeQuery = memoize((query: Query): RawQuery => {
+  return cleanQuery({
+    expand: serializeStringArray(query.expandedCards)
+  });
+});
diff --git a/server/sonar-web/src/main/js/components/common/RestartForm.tsx b/server/sonar-web/src/main/js/components/common/RestartForm.tsx
new file mode 100644 (file)
index 0000000..43e12ab
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Modal from 'react-modal';
+import { restartAndWait } from '../../api/system';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+  onClose: () => void;
+}
+
+interface State {
+  restarting: boolean;
+}
+
+export default class RestartForm extends React.PureComponent<Props, State> {
+  state: State = { restarting: false };
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    if (!this.state.restarting) {
+      this.setState({ restarting: true });
+      restartAndWait().then(
+        () => document.location.reload(),
+        () => this.setState({ restarting: false })
+      );
+    }
+  };
+
+  render() {
+    const { restarting } = this.state;
+    const header = translate('system.restart_server');
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="restart-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>{header}</h2>
+          </div>
+          <div className="modal-body">
+            <p className={classNames('spacer-top spacer-bottom', { 'text-center': restarting })}>
+              {translate(restarting ? 'system.is_restarting' : 'system.are_you_sure_to_restart')}
+            </p>
+            {restarting && (
+              <p className="big-spacer-top spacer-bottom text-center">
+                <i className="spinner" />
+              </p>
+            )}
+          </div>
+          {!restarting && (
+            <div className="modal-foot">
+              <button id="restart-server-submit">{translate('restart')}</button>
+              <a
+                href="#"
+                className="js-modal-close"
+                id="restart-server-cancel"
+                onClick={this.handleCancelClick}>
+                {translate('cancel')}
+              </a>
+            </div>
+          )}
+        </form>
+      </Modal>
+    );
+  }
+}
index 7950444a2e0fd92178d9bbe346b2df21913b97b7..befa67b9223b6370a3c81eef27c06ad334fc8404 100644 (file)
@@ -20,8 +20,9 @@
 // @flow
 /* eslint-disable max-len */
 import React from 'react';
-import Tooltip from '../controls/Tooltip';
+import OpenCloseIcon from '../icons-components/OpenCloseIcon';
 import HelpIcon from '../icons-components/HelpIcon';
+import Tooltip from '../controls/Tooltip';
 import { translate } from '../../helpers/l10n';
 
 /*::
@@ -58,29 +59,6 @@ export default class FacetHeader extends React.PureComponent {
     }
   };
 
-  renderCheckbox() {
-    return (
-      <svg
-        className="little-spacer-right"
-        viewBox="0 0 1792 1792"
-        width="10"
-        height="10"
-        style={{ paddingTop: 3 }}>
-        {this.props.open ? (
-          <path
-            style={{ fill: 'currentColor ' }}
-            d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
-          />
-        ) : (
-          <path
-            style={{ fill: 'currentColor ' }}
-            d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
-          />
-        )}
-      </svg>
-    );
-  }
-
   renderHelper() {
     if (!this.props.helper) {
       return null;
@@ -119,7 +97,7 @@ export default class FacetHeader extends React.PureComponent {
         {this.props.onClick ? (
           <span className="search-navigator-facet-header">
             <a href="#" onClick={this.handleClick}>
-              {this.renderCheckbox()}
+              <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
               {this.props.name}
             </a>
             {this.renderHelper()}
index 7def87dae3ceace294145d361aa8dec9c46154de..dc22f069760536ac563ecc2495912b18527ae3d4 100644 (file)
@@ -15,26 +15,10 @@ exports[`should clear 1`] = `
       href="#"
       onClick={[Function]}
     >
-      <svg
+      <OpenCloseIcon
         className="little-spacer-right"
-        height="10"
-        style={
-          Object {
-            "paddingTop": 3,
-          }
-        }
-        viewBox="0 0 1792 1792"
-        width="10"
-      >
-        <path
-          d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
-          style={
-            Object {
-              "fill": "currentColor ",
-            }
-          }
-        />
-      </svg>
+        open={false}
+      />
       foo
     </a>
     <span
@@ -55,26 +39,10 @@ exports[`should render closed facet with value 1`] = `
       href="#"
       onClick={[Function]}
     >
-      <svg
+      <OpenCloseIcon
         className="little-spacer-right"
-        height="10"
-        style={
-          Object {
-            "paddingTop": 3,
-          }
-        }
-        viewBox="0 0 1792 1792"
-        width="10"
-      >
-        <path
-          d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
-          style={
-            Object {
-              "fill": "currentColor ",
-            }
-          }
-        />
-      </svg>
+        open={false}
+      />
       foo
     </a>
     <span
@@ -95,26 +63,10 @@ exports[`should render closed facet without value 1`] = `
       href="#"
       onClick={[Function]}
     >
-      <svg
+      <OpenCloseIcon
         className="little-spacer-right"
-        height="10"
-        style={
-          Object {
-            "paddingTop": 3,
-          }
-        }
-        viewBox="0 0 1792 1792"
-        width="10"
-      >
-        <path
-          d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
-          style={
-            Object {
-              "fill": "currentColor ",
-            }
-          }
-        />
-      </svg>
+        open={false}
+      />
       foo
     </a>
   </span>
@@ -130,26 +82,10 @@ exports[`should render open facet with value 1`] = `
       href="#"
       onClick={[Function]}
     >
-      <svg
+      <OpenCloseIcon
         className="little-spacer-right"
-        height="10"
-        style={
-          Object {
-            "paddingTop": 3,
-          }
-        }
-        viewBox="0 0 1792 1792"
-        width="10"
-      >
-        <path
-          d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
-          style={
-            Object {
-              "fill": "currentColor ",
-            }
-          }
-        />
-      </svg>
+        open={true}
+      />
       foo
     </a>
   </span>
@@ -165,26 +101,10 @@ exports[`should render open facet without value 1`] = `
       href="#"
       onClick={[Function]}
     >
-      <svg
+      <OpenCloseIcon
         className="little-spacer-right"
-        height="10"
-        style={
-          Object {
-            "paddingTop": 3,
-          }
-        }
-        viewBox="0 0 1792 1792"
-        width="10"
-      >
-        <path
-          d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
-          style={
-            Object {
-              "fill": "currentColor ",
-            }
-          }
-        />
-      </svg>
+        open={true}
+      />
       foo
     </a>
   </span>
diff --git a/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
new file mode 100644 (file)
index 0000000..739bc60
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+  className?: string;
+  open: boolean;
+  size?: number;
+}
+
+export default function OpenCloseIcon({ className, open, size = 14 }: Props) {
+  return (
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width={size}
+      height={size}
+      style={{ fill: 'currentColor' }}>
+      {open ? (
+        <path d="M13.506 8.539l-5.191 5.184q-0.133 0.133-0.315 0.133t-0.315-0.133l-5.191-5.184q-0.133-0.133-0.133-0.318t0.133-0.318l1.161-1.154q0.133-0.133 0.315-0.133t0.315 0.133l3.715 3.715 3.715-3.715q0.133-0.133 0.315-0.133t0.315 0.133l1.161 1.154q0.133 0.133 0.133 0.318t-0.133 0.318z" />
+      ) : (
+        <path d="M13.527 8.318l-5.244 5.244q-0.134 0.134-0.318 0.134t-0.318-0.134l-1.173-1.173q-0.134-0.134-0.134-0.318t0.134-0.318l3.753-3.753-3.753-3.753q-0.134-0.134-0.134-0.318t0.134-0.318l1.173-1.173q0.134-0.134 0.318-0.134t0.318 0.134l5.244 5.244q0.134 0.134 0.134 0.318t-0.134 0.318z" />
+      )}
+    </svg>
+  );
+}
index 890a7c87ebce09eb182acb5e750d70e319a2d23e..c50065921f44100da4ffa1a92a6cb9d53e6a86f5 100644 (file)
@@ -30,6 +30,7 @@ import _HelpIcon from './HelpIcon';
 import _HistoryIcon from './HistoryIcon';
 import _LinkIcon from './LinkIcon';
 import _ListIcon from './ListIcon';
+import _OpenCloseIcon from './OpenCloseIcon';
 import _OrganizationIcon from './OrganizationIcon';
 import _ProjectEventIcon from './ProjectEventIcon';
 import _QualifierIcon from './QualifierIcon';
@@ -51,6 +52,7 @@ export const HelpIcon = _HelpIcon;
 export const HistoryIcon = _HistoryIcon;
 export const LinkIcon = _LinkIcon;
 export const ListIcon = _ListIcon;
+export const OpenCloseIcon = _OpenCloseIcon;
 export const OrganizationIcon = _OrganizationIcon;
 export const ProjectEventIcon = _ProjectEventIcon;
 export const QualifierIcon = _QualifierIcon;
index 8deba26fcad372227da1c0376c9446cfdeb32bb3..773448fad23bc2f097f9695cdf2e8ca23d4691db 100644 (file)
@@ -31,21 +31,23 @@ interface Location {
   query?: Query;
 }
 
+export function getBaseUrl(): string {
+  return (window as any).baseUrl;
+}
+
 /**
  * Generate URL for a component's home page
  */
 export function getComponentUrl(componentKey: string, branch?: string): string {
   const branchQuery = branch ? `&branch=${encodeURIComponent(branch)}` : '';
-  return (
-    (window as any).baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery
-  );
+  return getBaseUrl() + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery;
 }
 
 export function getProjectUrl(key: string, branch?: string): Location {
   return { pathname: '/dashboard', query: { id: key, branch } };
 }
 
-export function getProjectBranchUrl(key: string, branch: Branch) {
+export function getProjectBranchUrl(key: string, branch: Branch): Location {
   if (isShortLivingBranch(branch)) {
     return {
       pathname: '/project/issues',
@@ -74,13 +76,17 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca
 
 export function getComponentIssuesUrlAsString(componentKey: string, query?: Query): string {
   const path = getComponentIssuesUrl(componentKey, query);
-  return `${(window as any).baseUrl}${path.pathname}?${stringify(path.query)}`;
+  return `${getBaseUrl()}${path.pathname}?${stringify(path.query)}`;
 }
 
 /**
  * Generate URL for a component's drilldown page
  */
-export function getComponentDrilldownUrl(componentKey: string, metric: string, branch?: string) {
+export function getComponentDrilldownUrl(
+  componentKey: string,
+  metric: string,
+  branch?: string
+): Location {
   return { pathname: '/component_measures', query: { id: componentKey, metric, branch } };
 }
 
@@ -159,9 +165,9 @@ export function getDeprecatedActiveRulesUrl(query = {}, organization?: string |
 }
 
 export function getProjectsUrl(): string {
-  return (window as any).baseUrl + '/projects';
+  return getBaseUrl() + '/projects';
 }
 
 export function getMarkdownHelpUrl(): string {
-  return (window as any).baseUrl + '/markdown/help';
+  return getBaseUrl() + '/markdown/help';
 }
index aca17c1f279ed45d567f0704040ee2e02c1da040..ef96a4360d580be056bbee6b7d771842063f5f75 100644 (file)
@@ -146,6 +146,7 @@ remove=Remove
 rename=Rename
 reset_verb=Reset
 resolution=Resolution
+restart=Restart
 restore=Restore
 result=Result
 results=Results
@@ -2840,8 +2841,18 @@ background_tasks.add_more_with_governance=Add more with Governance
 # SYSTEM
 #
 #------------------------------------------------------------------------------
+system.application_nodes_title=Application Nodes
+system.are_you_sure_to_restart=Are you sure you want to restart the server?
+system.cluster_log_level.info=Changes apply to all Application nodes but not to Search nodes.
+system.download_logs=Download Logs
+system.download_system_info=Download System Info
+system.is_restarting=Server is restarting. This page will be automatically refreshed.
 system.log_level.warning=Current level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties.
-
+system.log_level.info=Changes don't apply to Search.
+system.logs_level=Logs level
+system.restart_server=Restart Server
+system.search_nodes_title=Search Nodes
+system.set_log_level=Set logs level
 
 
 #------------------------------------------------------------------------------