]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9802 add information to System Info page
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 19 Sep 2017 14:13:55 +0000 (16:13 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 26 Sep 2017 21:49:38 +0000 (23:49 +0200)
57 files changed:
server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSectionMBean.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStateSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
server/sonar-web/src/main/js/api/system.ts
server/sonar-web/src/main/js/apps/system/__tests__/system-test.js [deleted file]
server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/system/components/App.tsx
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx [deleted file]
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__/ClusterSysInfos-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
server/sonar-web/src/main/js/apps/system/item-boolean.js [deleted file]
server/sonar-web/src/main/js/apps/system/item-log-level.js [deleted file]
server/sonar-web/src/main/js/apps/system/item-object.js [deleted file]
server/sonar-web/src/main/js/apps/system/item-value.js [deleted file]
server/sonar-web/src/main/js/apps/system/main.js [deleted file]
server/sonar-web/src/main/js/apps/system/routes.ts
server/sonar-web/src/main/js/apps/system/section.js [deleted file]
server/sonar-web/src/main/js/apps/system/styles.css
server/sonar-web/src/main/js/apps/system/utils.ts
server/sonar-web/src/main/js/components/common/BranchStatus.css
server/sonar-web/src/main/js/components/common/BranchStatus.tsx
server/sonar-web/src/main/js/components/common/StatusIndicator.css [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/StatusIndicator.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 575083280b10879e44b4a317e62a9b281bf02e82..656fba7e75b0e30d99d12027893cb2f56fc4cb8e 100644 (file)
@@ -57,8 +57,11 @@ public class JvmStateSection implements SystemInfoSection {
     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());
+    setAttribute(protobuf, "Threads", thread.getThreadCount());
+
+    setAttribute(protobuf,"Processors", Runtime.getRuntime().availableProcessors());
 
     return protobuf.build();
   }
index 5276a5243536496daa2f80623b0c7324ba8e0703..3057e9933e9b50b9dfa45c42ee94e07bd7ec386b 100644 (file)
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.when;
 
 public class JvmStateSectionTest {
 
-  public static final String PROCESS_NAME = "the process name";
+  private static final String PROCESS_NAME = "the process name";
 
   @Test
   public void toSystemInfoSection() {
@@ -39,7 +39,7 @@ public class JvmStateSectionTest {
 
     assertThat(section.getName()).isEqualTo(PROCESS_NAME);
     assertThat(section.getAttributesCount()).isGreaterThan(0);
-    assertThat(section.getAttributesList()).extracting("key").contains("Thread Count");
+    assertThat(section.getAttributesList()).extracting("key").contains("Threads", "Processors");
   }
 
   @Test
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
deleted file mode 100644 (file)
index 2e86e6e..0000000
+++ /dev/null
@@ -1,119 +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.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
deleted file mode 100644 (file)
index 6358f68..0000000
+++ /dev/null
@@ -1,28 +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 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/EsStateSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java
new file mode 100644 (file)
index 0000000..d6f5f0d
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.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.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.breaker.CircuitBreaker;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.systeminfo.SystemInfoSection;
+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 EsStateSection implements SystemInfoSection {
+
+  private final EsClient esClient;
+
+  public EsStateSection(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  private ClusterHealthStatus getStateAsEnum() {
+    return clusterStats().getStatus();
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("Search State");
+    try {
+      setAttribute(protobuf, "State", getStateAsEnum().name());
+      completeNodeAttributes(protobuf);
+   } catch (Exception es) {
+      Loggers.get(EsStateSection.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 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/EsStatisticsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.java
new file mode 100644 (file)
index 0000000..12656ae
--- /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.server.platform.monitoring;
+
+import java.util.Map;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.indices.stats.IndexStats;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.systeminfo.SystemInfoSection;
+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 EsStatisticsSection implements SystemInfoSection {
+
+  private final EsClient esClient;
+
+  public EsStatisticsSection(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("Search Statistics");
+    try {
+      completeIndexAttributes(protobuf);
+    } catch (Exception es) {
+      Loggers.get(EsStatisticsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"Error\" attribute.", es);
+      setAttribute(protobuf, "Error", 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()));
+    }
+  }
+}
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
deleted file mode 100644 (file)
index ead5a74..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.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.health.Health;
-import org.sonar.server.health.HealthChecker;
-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;
-  private final HealthChecker healthChecker;
-
-  public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory,
-    IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
-    ServerIdLoader serverIdLoader, HealthChecker healthChecker) {
-    this.config = config;
-    this.securityRealmFactory = securityRealmFactory;
-    this.identityProviderRepository = identityProviderRepository;
-    this.server = server;
-    this.serverLogging = serverLogging;
-    this.serverIdLoader = serverIdLoader;
-    this.healthChecker = healthChecker;
-  }
-
-  @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());
-    });
-    Health health = healthChecker.checkNode();
-    setAttribute(protobuf, "Health", health.getStatus().name());
-    setAttribute(protobuf, "Health Causes", health.getCauses());
-    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
deleted file mode 100644 (file)
index 1a562a6..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 SonarQubeSectionMBean {
-  @CheckForNull
-  String getServerId();
-
-  String getVersion();
-
-  String getLogLevel();
-}
index bbd623f52b927f929c1826eae76c35687ee1bcf2..d19f7162ae44e64a3f207d307bbaa4bee72d45aa 100644 (file)
  */
 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 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.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.user.SecurityRealmFactory;
 
-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 class SystemSection extends BaseSectionMBean implements SystemSectionMBean {
+
+  private static final Joiner COMMA_JOINER = Joiner.on(", ");
+
+  static final String BRANDING_FILE_PATH = "web/WEB-INF/classes/com/sonarsource/branding";
 
-  public SystemSection() {
-    this(System2.INSTANCE);
+  private final Configuration config;
+  private final SecurityRealmFactory securityRealmFactory;
+  private final IdentityProviderRepository identityProviderRepository;
+  private final Server server;
+  private final ServerLogging serverLogging;
+  private final ServerIdLoader serverIdLoader;
+  private final HealthChecker healthChecker;
+
+  public SystemSection(Configuration config, SecurityRealmFactory securityRealmFactory,
+    IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
+    ServerIdLoader serverIdLoader, HealthChecker healthChecker) {
+    this.config = config;
+    this.securityRealmFactory = securityRealmFactory;
+    this.identityProviderRepository = identityProviderRepository;
+    this.server = server;
+    this.serverLogging = serverLogging;
+    this.serverIdLoader = serverIdLoader;
+    this.healthChecker = healthChecker;
   }
 
-  SystemSection(System2 system) {
-    this.system = system;
+  @Override
+  public String getServerId() {
+    return serverIdLoader.getRaw().orElse(null);
   }
 
   @Override
-  public ProtobufSystemInfo.Section toProtobuf() {
-    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
-    protobuf.setName("System");
+  public String getVersion() {
+    return server.getVersion();
+  }
 
-    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();
+  @Override
+  public String getLogLevel() {
+    return serverLogging.getRootLoggerLevel().name();
   }
 
-  private static RuntimeMXBean runtimeMXBean() {
-    return ManagementFactory.getRuntimeMXBean();
+  @CheckForNull
+  private String getExternalUserAuthentication() {
+    SecurityRealm realm = securityRealmFactory.getRealm();
+    return realm == null ? null : realm.getName();
   }
 
-  private static Runtime runtime() {
-    return Runtime.getRuntime();
+  private List<String> getEnabledIdentityProviders() {
+    return identityProviderRepository.getAllEnabledAndSorted()
+      .stream()
+      .filter(IdentityProvider::isEnabled)
+      .map(IdentityProvider::getName)
+      .collect(MoreCollectors.toList());
   }
 
-  private static MemoryMXBean memoryMXBean() {
-    return ManagementFactory.getMemoryMXBean();
+  private List<String> getAllowsToSignUpEnabledIdentityProviders() {
+    return identityProviderRepository.getAllEnabledAndSorted()
+      .stream()
+      .filter(IdentityProvider::isEnabled)
+      .filter(IdentityProvider::allowsUsersToSignUp)
+      .map(IdentityProvider::getName)
+      .collect(MoreCollectors.toList());
   }
 
-  private static ClassLoadingMXBean classLoadingMXBean() {
-    return ManagementFactory.getClassLoadingMXBean();
+  private boolean getForceAuthentication() {
+    return config.getBoolean(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false);
   }
 
-  private static ThreadMXBean threadMXBean() {
-    return ManagementFactory.getThreadMXBean();
+  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() {
+    // JMX name
+    return "SonarQube";
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    protobuf.setName("System");
+
+    serverIdLoader.get().ifPresent(serverId -> {
+      setAttribute(protobuf, "Server ID", serverId.getId());
+      setAttribute(protobuf, "Server ID validated", serverId.isValid());
+    });
+    Health health = healthChecker.checkNode();
+    setAttribute(protobuf, "Health", health.getStatus().name());
+    setAttribute(protobuf, "Health Causes", health.getCauses());
+    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, "High Availability", false);
+    setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
+    setAttribute(protobuf, "Force authentication", getForceAuthentication());
+    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 String formatMemory(long memoryInBytes) {
-    return format("%d MB", memoryInBytes / 1_000_000);
+  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/SystemSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSectionMBean.java
new file mode 100644 (file)
index 0000000..7c3f8f0
--- /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 SystemSectionMBean {
+  @CheckForNull
+  String getServerId();
+
+  String getVersion();
+
+  String getLogLevel();
+}
index c565219240f131c19fe858cdd922ca2e972aa418..09c40749fc84a1ba86443365cac54daf9e3cb707 100644 (file)
@@ -35,10 +35,10 @@ public class WebSystemInfoModule {
       new JvmPropertiesSection("Web JVM Properties"),
       new JvmStateSection("Web JVM State"),
       DatabaseSection.class,
-      EsSection.class,
+      EsStateSection.class,
+      EsStatisticsSection.class,
       PluginsSection.class,
       SettingsSection.class,
-      SonarQubeSection.class,
       SystemSection.class,
 
       StandaloneInfoAction.class
@@ -50,7 +50,7 @@ public class WebSystemInfoModule {
       new JvmPropertiesSection("Web JVM Properties"),
       new JvmStateSection("Web JVM State"),
       DatabaseSection.class,
-      EsSection.class,
+      EsStateSection.class,
       PluginsSection.class,
 
       ClusterInfoAction.class
index 6f9125f40cced90f7af830ac59ba4a7c3137c89c..d298d99c03ab5bc32e7e0107f47e3dcb0a219aa7 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.platform.ws;
 
+import java.util.Arrays;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -50,14 +51,16 @@ public class ClusterInfoAction implements SystemWsAction {
     try (JsonWriter json = response.newJsonWriter()) {
       json.beginObject();
 
-      // global section
-      json.prop("Cluster", true);
-      json.prop("Cluster Name", "foo");
+      json.name("System");
+      json.beginObject();
+      json.prop("High Availability", true);
+      json.prop("Cluster Name", "fooo");
       json.prop("Server Id", "ABC123");
       json.prop("Health", "RED");
       json
         .name("Health Causes")
-        .beginArray().beginObject().prop("message", "Requires at least two search nodes").endObject().endArray();
+        .beginArray().values(Arrays.asList("foo", "bar")).endArray();
+      json.endObject();
 
       json.name("Settings");
       json.beginObject();
@@ -65,8 +68,6 @@ public class ClusterInfoAction implements SystemWsAction {
       json.prop("sonar.externalIdentityProviders", "GitHub, BitBucket");
       json.endObject();
 
-
-
       json.name("Database");
       json
         .beginObject()
@@ -83,7 +84,7 @@ public class ClusterInfoAction implements SystemWsAction {
         .prop("workersPerNode", 4)
         .endObject();
 
-      json.name("Elasticsearch");
+      json.name("Search");
       json
         .beginObject()
         .prop("Health", "GREEN")
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
deleted file mode 100644 (file)
index a55ae81..0000000
+++ /dev/null
@@ -1,101 +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.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/EsStateSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStateSectionTest.java
new file mode 100644 (file)
index 0000000..af248d6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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 EsStateSectionTest {
+
+  @Rule
+  public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
+
+  private EsStateSection underTest = new EsStateSection(esTester.client());
+
+  @Test
+  public void name() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Search State");
+  }
+
+  @Test
+  public void es_state() {
+    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 attributes_displays_exception_message_when_cause_null_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsStateSection underTest = new EsStateSection(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);
+    EsStateSection underTest = new EsStateSection(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);
+    EsStateSection underTest = new EsStateSection(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/EsStatisticsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java
new file mode 100644 (file)
index 0000000..44c583e
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.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 EsStatisticsSectionTest {
+
+  @Rule
+  public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
+
+  private EsStatisticsSection underTest = new EsStatisticsSection(esTester.client());
+
+  @Test
+  public void name() {
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Search Statistics");
+  }
+
+  @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);
+    EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+    when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "Error", "RuntimeException with no cause");
+  }
+
+  @Test
+  public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+    when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "Error", "RuntimeException with cause not ES");
+  }
+
+  @Test
+  public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
+    EsClient esClientMock = mock(EsClient.class);
+    EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+    when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
+
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThatAttributeIs(section, "Error", "some cause message");
+  }
+}
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
deleted file mode 100644 (file)
index fac46e8..0000000
+++ /dev/null
@@ -1,217 +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.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.health.Health;
-import org.sonar.server.health.TestStandaloneHealthChecker;
-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 java.util.Arrays.asList;
-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 TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
-
-  private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
-    serverLogging, serverIdLoader, healthChecker);
-
-  @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");
-  }
-
-  @Test
-  public void return_health() {
-    healthChecker.setHealth(Health.newHealthCheckBuilder()
-      .setStatus(Health.Status.YELLOW)
-      .addCause("foo")
-      .addCause("bar")
-      .build());
-
-    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
-    assertThatAttributeIs(protobuf, "Health", "YELLOW");
-    SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
-  }
-}
index 94378667a5927236eb78371f0d34f21184dfa0d2..c1ae9b3305d7a96e59ea3bb4abb989043be6374a 100644 (file)
  */
 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.health.Health;
+import org.sonar.server.health.TestStandaloneHealthChecker;
+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 java.util.Arrays.asList;
 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 SystemSectionTest {
 
-  private SystemSection underTest = new SystemSection();
+  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 TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
+
+  private SystemSection underTest = new SystemSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
+    serverLogging, serverIdLoader, healthChecker);
+
+  @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 name() {
-    assertThat(underTest.toProtobuf().getName()).isEqualTo("System");
+  public void official_distribution() throws Exception {
+    File rootDir = temp.newFolder();
+    FileUtils.write(new File(rootDir, SystemSection.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");
   }
 
   @Test
-  public void system_properties() {
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+  public void return_health() {
+    healthChecker.setHealth(Health.newHealthCheckBuilder()
+      .setStatus(Health.Status.YELLOW)
+      .addCause("foo")
+      .addCause("bar")
+      .build());
 
-    assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty();
-    assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0);
+    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+    assertThatAttributeIs(protobuf, "Health", "YELLOW");
+    SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
   }
 }
index 1b4fd125a9721b31789064867baf23937d7ebed2..df9da694e4087672d863245fc5ce737bf91801a5 100644 (file)
@@ -44,14 +44,13 @@ export interface NodeInfo extends SysValueObject {
   Name: string;
   Health: HealthType;
   'Health Causes': HealthCause[];
+  'Logs Level': string;
 }
 
 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> {
diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/system-test.js b/server/sonar-web/src/main/js/apps/system/__tests__/system-test.js
deleted file mode 100644 (file)
index 0ad07b2..0000000
+++ /dev/null
@@ -1,101 +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.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import ItemValue from '../item-value';
-import ItemBoolean from '../item-boolean';
-import ItemObject from '../item-object';
-import ItemLogLevel from '../item-log-level';
-
-describe('Item Value', () => {
-  it('should render string', () => {
-    const result = shallow(<ItemValue value="/some/path/as/an/example" />);
-    expect(result.find('code').text()).toBe('/some/path/as/an/example');
-  });
-});
-
-describe('ItemBoolean', () => {
-  it('should render `true`', () => {
-    const result = shallow(<ItemBoolean value={true} />);
-    expect(result.find('.icon-check').length).toBe(1);
-  });
-
-  it('should render `false`', () => {
-    const result = shallow(<ItemBoolean value={false} />);
-    expect(result.find('.icon-delete').length).toBe(1);
-  });
-});
-
-describe('ItemObject', () => {
-  it('should render object', () => {
-    const result = shallow(<ItemObject value={{ name: 'Java', version: '3.2' }} />);
-    expect(result.find('table').length).toBe(1);
-    expect(result.find('tr').length).toBe(2);
-  });
-
-  it('should render `true` inside object', () => {
-    const result = shallow(<ItemObject value={{ isCool: true }} />);
-    const itemValue = result.find(ItemValue);
-    expect(itemValue.length).toBe(1);
-    expect(itemValue.prop('value')).toBe(true);
-  });
-
-  it('should render object inside object', () => {
-    const result = shallow(
-      <ItemObject value={{ users: { docs: 1, shards: 5 }, tests: { docs: 68, shards: 5 } }} />
-    );
-    expect(result.find(ItemValue).length).toBe(2);
-    expect(
-      result
-        .find(ItemValue)
-        .at(0)
-        .prop('value')
-    ).toEqual({ docs: 1, shards: 5 });
-    expect(
-      result
-        .find(ItemValue)
-        .at(1)
-        .prop('value')
-    ).toEqual({ docs: 68, shards: 5 });
-  });
-});
-
-describe('Log Level', () => {
-  it('should render select box', () => {
-    const result = shallow(<ItemLogLevel value="INFO" />);
-    expect(result.find('select').length).toBe(1);
-    expect(result.find('option').length).toBe(3);
-  });
-
-  it('should set initial value', () => {
-    const result = shallow(<ItemLogLevel value="DEBUG" />);
-    expect(result.find('select').prop('value')).toBe('DEBUG');
-  });
-
-  it('should render warning', () => {
-    const result = shallow(<ItemLogLevel value="DEBUG" />);
-    expect(result.find('.alert').length).toBe(1);
-  });
-
-  it('should not render warning', () => {
-    const result = shallow(<ItemLogLevel value="INFO" />);
-    expect(result.find('.alert').length).toBe(0);
-  });
-});
index cd10c605418f6de93bdf8d2fe7ac7a20effa2115..0c550fd943fa4f3e435d458b9aa5c94c2e610fc9 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 * as utils from '../utils';
+import * as u 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'] });
+    expect(u.parseQuery({})).toEqual({ expandedCards: [] });
+    expect(u.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' });
+    expect(u.serializeQuery({ expandedCards: [] })).toEqual({});
+    expect(u.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({
+    expect(u.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
       mainSection: { foo: 'Foo', bar: 3 },
       sections: { baz: { a: 'a' } }
     });
   });
 });
+
+describe('getSystemLogsLevel', () => {
+  it('should correctly return log level for standalone mode', () => {
+    expect(u.getSystemLogsLevel({ 'Logs Level': 'FOO' } as u.StandaloneSysInfo)).toBe('FOO');
+    expect(u.getSystemLogsLevel({} as u.StandaloneSysInfo)).toBe('INFO');
+    expect(u.getSystemLogsLevel()).toBe('INFO');
+  });
+  it('should return the worst log level for cluster mode', () => {
+    expect(
+      u.getSystemLogsLevel({
+        Cluster: true,
+        'Application Nodes': [{ 'Logs Level': 'INFO' }, { 'Logs Level': 'DEBUG' }]
+      } as u.ClusterSysInfo)
+    ).toBe('DEBUG');
+  });
+});
index 440b117929a9c61e1fa71321d5f92cab0be873ee..1cd3752de880a56357b81b36a49a17603b1190ef 100644 (file)
@@ -22,10 +22,18 @@ import * as PropTypes from 'prop-types';
 import Helmet from 'react-helmet';
 import ClusterSysInfos from './ClusterSysInfos';
 import PageHeader from './PageHeader';
-import StandAloneSysInfos from './StandAloneSysInfos';
+import StandaloneSysInfos from './StandaloneSysInfos';
 import { translate } from '../../../helpers/l10n';
 import { getSystemInfo, SysInfo } from '../../../api/system';
-import { isCluster, parseQuery, Query, serializeQuery } from '../utils';
+import {
+  ClusterSysInfo,
+  getSystemLogsLevel,
+  isCluster,
+  parseQuery,
+  Query,
+  serializeQuery,
+  StandaloneSysInfo
+} from '../utils';
 import { RawQuery } from '../../../helpers/query';
 import '../styles.css';
 
@@ -84,7 +92,7 @@ export default class App extends React.PureComponent<Props, State> {
 
   updateQuery = (newQuery: Query) => {
     const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
-    this.context.router.push({ pathname: this.props.location.pathname, query });
+    this.context.router.replace({ pathname: this.props.location.pathname, query });
   };
 
   renderSysInfo() {
@@ -97,26 +105,32 @@ export default class App extends React.PureComponent<Props, State> {
     if (isCluster(sysInfoData)) {
       return (
         <ClusterSysInfos
-          sysInfoData={sysInfoData}
           expandedCards={query.expandedCards}
+          sysInfoData={sysInfoData as ClusterSysInfo}
           toggleCard={this.toggleSysInfoCards}
         />
       );
     }
-    return <StandAloneSysInfos sysInfoData={sysInfoData} />;
+    return (
+      <StandaloneSysInfos
+        expandedCards={query.expandedCards}
+        sysInfoData={sysInfoData as StandaloneSysInfo}
+        toggleCard={this.toggleSysInfoCards}
+      />
+    );
   }
 
   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"
+          logLevel={getSystemLogsLevel(sysInfoData)}
           showActions={sysInfoData != undefined}
+          onLogLevelChange={this.fetchSysInfo}
         />
         {this.renderSysInfo()}
       </div>
index 3065a0be66db4bf1f858da5960eb43ae26201582..6118fe2d9568915b79b95df64db216ef0cbdd5f7 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import Modal from 'react-modal';
 import { setLogLevel } from '../../../api/system';
 import { translate } from '../../../helpers/l10n';
+import { LOGS_LEVELS } from '../utils';
 
 interface Props {
   infoMsg: string;
@@ -34,8 +35,6 @@ interface State {
   updating: boolean;
 }
 
-const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
-
 export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
@@ -78,9 +77,10 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
             <h2>{header}</h2>
           </div>
           <div className="modal-body">
-            {LOG_LEVELS.map(level => (
+            {LOGS_LEVELS.map(level => (
               <p key={level} className="spacer-bottom">
                 <input
+                  id={`loglevel-${level}`}
                   type="radio"
                   className="spacer-right text-middle"
                   name="system.log_levels"
@@ -88,7 +88,7 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
                   checked={level === newLevel}
                   onChange={this.handleLevelChange}
                 />
-                {level}
+                <label htmlFor={`loglevel-${level}`}>{level}</label>
               </p>
             ))}
             <div className="alert alert-info spacer-top">{this.props.infoMsg}</div>
index 7f68dc6771a9888c6702aeb39df91bec73174f87..cb3000dea3254d8a380bf4f2393eea1f0dfba37e 100644 (file)
@@ -22,19 +22,19 @@ import { sortBy } from 'lodash';
 import HealthCard from './info-items/HealthCard';
 import { translate } from '../../../helpers/l10n';
 import {
+  ClusterSysInfo,
   getAppNodes,
   getHealth,
   getHealthCauses,
-  getMainCardSection,
+  getClusterMainCardSection,
   getNodeName,
   getSearchNodes,
   ignoreInfoFields
 } from '../utils';
-import { SysInfo } from '../../../api/system';
 
 interface Props {
   expandedCards: string[];
-  sysInfoData: SysInfo;
+  sysInfoData: ClusterSysInfo;
   toggleCard: (toggledCard: string) => void;
 }
 
@@ -49,7 +49,7 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
         name={mainCardName}
         onClick={toggleCard}
         open={expandedCards.includes(mainCardName)}
-        sysInfoData={ignoreInfoFields(getMainCardSection(sysInfoData))}
+        sysInfoData={ignoreInfoFields(getClusterMainCardSection(sysInfoData))}
       />
       <li className="note system-info-health-title">
         {translate('system.application_nodes_title')}
index 1b2ef92d34ad81de5767b1c86b3124888ef69aac..4e26c343f023de90707460fb8c78763c8e7a4db5 100644 (file)
@@ -28,6 +28,7 @@ interface Props {
   canRestart: boolean;
   cluster: boolean;
   logLevel: string;
+  onLogLevelChange: () => void;
 }
 
 interface State {
@@ -40,9 +41,9 @@ export default class PageActions extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
+      logLevel: props.logLevel,
       openLogsLevelForm: false,
-      openRestartForm: false,
-      logLevel: props.logLevel
+      openRestartForm: false
     };
   }
 
@@ -59,6 +60,7 @@ export default class PageActions extends React.PureComponent<Props, State> {
 
   handleLogsLevelChange = (logLevel: string) => {
     this.setState({ logLevel });
+    this.props.onLogLevelChange();
     this.handleLogsLevelClose();
   };
 
index c9d84569e302c3d02689f11e0e798ac65cfee190..97c4f1077c0f4c58c26d315ad8215dce47af5122 100644 (file)
@@ -26,21 +26,23 @@ interface Props {
   loading: boolean;
   logLevel: string;
   showActions: boolean;
+  onLogLevelChange: () => void;
 }
 
-export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) {
+export default function PageHeader(props: Props) {
   return (
     <header className="page-header">
       <h1 className="page-title">{translate('system_info.page')}</h1>
-      {showActions && (
+      {props.showActions && (
         <PageActions
-          canDownloadLogs={!isCluster}
-          canRestart={!isCluster}
-          cluster={isCluster}
-          logLevel={logLevel}
+          canDownloadLogs={!props.isCluster}
+          canRestart={!props.isCluster}
+          cluster={props.isCluster}
+          logLevel={props.logLevel}
+          onLogLevelChange={props.onLogLevelChange}
         />
       )}
-      {loading && (
+      {props.loading && (
         <div className="page-actions">
           <i className="spinner" />
         </div>
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
deleted file mode 100644 (file)
index 2a142c5..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-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/StandaloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
new file mode 100644 (file)
index 0000000..4ed444c
--- /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.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import HealthCard from './info-items/HealthCard';
+import {
+  getHealth,
+  getHealthCauses,
+  getStandaloneMainSections,
+  getStandaloneSecondarySections,
+  ignoreInfoFields,
+  StandaloneSysInfo
+} from '../utils';
+
+interface Props {
+  expandedCards: string[];
+  sysInfoData: StandaloneSysInfo;
+  toggleCard: (toggledCard: string) => void;
+}
+
+export default function StandAloneSysInfos({ 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(getStandaloneMainSections(sysInfoData))}
+      />
+      {map(getStandaloneSecondarySections(sysInfoData), (section, name) => (
+        <HealthCard
+          key={name}
+          name={name}
+          onClick={toggleCard}
+          open={expandedCards.includes(name)}
+          sysInfoData={ignoreInfoFields(section)}
+        />
+      ))}
+    </ul>
+  );
+}
index 5b703a1a214d3c63dfd04c6dc8ca054030502fee..21c60cbd6fc7e224b3a73d55bdb88d83ce23563b 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import ClusterSysInfos from '../ClusterSysInfos';
-import { HealthType, SysInfo } from '../../../../api/system';
+import { HealthType } from '../../../../api/system';
+import { ClusterSysInfo } from '../../utils';
 
-const sysInfoData: SysInfo = {
+const sysInfoData: ClusterSysInfo = {
   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': [] }]
+  'Application Nodes': [
+    { Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' }
+  ],
+  'Search Nodes': [
+    { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'INFO' }
+  ]
 };
 
 it('should render correctly', () => {
+  expect(
+    getWrapper({
+      sysInfoData: {
+        ...sysInfoData,
+        'Application Nodes': [
+          { Name: 'Foo', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' },
+          { Name: 'Bar', Health: HealthType.RED, 'Health Causes': [], 'Logs Level': 'DEBUG' },
+          { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'TRACE' }
+        ]
+      }
+    }).find('HealthCard')
+  ).toHaveLength(5);
+});
+
+it('should support more than two nodes', () => {
   expect(getWrapper()).toMatchSnapshot();
 });
 
index 098e897ec921d90f5423479cf9ae6ad3c6e7f01d..627347f8886298afd54777b413efa4a992456ce4 100644 (file)
@@ -25,7 +25,13 @@ import { click } from '../../../../helpers/testUtils';
 it('should render correctly', () => {
   expect(
     shallow(
-      <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+      <PageActions
+        canDownloadLogs={true}
+        canRestart={true}
+        cluster={false}
+        logLevel="INFO"
+        onLogLevelChange={() => {}}
+      />
     )
   ).toMatchSnapshot();
 });
@@ -33,14 +39,26 @@ it('should render correctly', () => {
 it('should render without restart and log download', () => {
   expect(
     shallow(
-      <PageActions canDownloadLogs={false} canRestart={false} cluster={true} logLevel="INFO" />
+      <PageActions
+        canDownloadLogs={false}
+        canRestart={false}
+        cluster={true}
+        logLevel="INFO"
+        onLogLevelChange={() => {}}
+      />
     )
   ).toMatchSnapshot();
 });
 
 it('should open restart modal', () => {
   const wrapper = shallow(
-    <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+    <PageActions
+      canDownloadLogs={true}
+      canRestart={true}
+      cluster={false}
+      logLevel="INFO"
+      onLogLevelChange={() => {}}
+    />
   );
   click(wrapper.find('#restart-server-button'));
   expect(wrapper.find('RestartForm')).toHaveLength(1);
@@ -48,7 +66,13 @@ it('should open restart modal', () => {
 
 it('should open change log level modal', () => {
   const wrapper = shallow(
-    <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+    <PageActions
+      canDownloadLogs={true}
+      canRestart={true}
+      cluster={false}
+      logLevel="INFO"
+      onLogLevelChange={() => {}}
+    />
   );
   click(wrapper.find('#edit-logs-level-button'));
   expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1);
index f5255c7228ed214cd464585b535a50ce6402e59a..a101f213406592efe730c6b30373e94b30aa6108 100644 (file)
@@ -23,12 +23,28 @@ import PageHeader from '../PageHeader';
 
 it('should render correctly', () => {
   expect(
-    shallow(<PageHeader isCluster={true} loading={false} logLevel="INFO" showActions={true} />)
+    shallow(
+      <PageHeader
+        isCluster={true}
+        loading={false}
+        logLevel="INFO"
+        showActions={true}
+        onLogLevelChange={() => {}}
+      />
+    )
   ).toMatchSnapshot();
 });
 
 it('should show a loading spinner and no actions', () => {
   expect(
-    shallow(<PageHeader isCluster={true} loading={true} logLevel="INFO" showActions={false} />)
+    shallow(
+      <PageHeader
+        isCluster={true}
+        loading={true}
+        logLevel="INFO"
+        showActions={false}
+        onLogLevelChange={() => {}}
+      />
+    )
   ).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx
new file mode 100644 (file)
index 0000000..c70202e
--- /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 { shallow } from 'enzyme';
+import StandaloneSysInfos from '../StandaloneSysInfos';
+import { HealthType } from '../../../../api/system';
+import { StandaloneSysInfo } from '../../utils';
+
+const sysInfoData: StandaloneSysInfo = {
+  Cluster: true,
+  Health: HealthType.RED,
+  'Logs Level': 'DEBUG',
+  Name: 'Foo',
+  'Health Causes': [{ message: 'Database down' }],
+  'Web JVM': { 'Max Memory': '2Gb' },
+  'Compute Engine': { Pending: 4 },
+  Elasticsearch: { 'Number of Nodes': 1 }
+};
+
+it('should render correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <StandaloneSysInfos
+      expandedCards={['Compute Engine', 'Foo']}
+      sysInfoData={sysInfoData}
+      toggleCard={() => {}}
+      {...props}
+    />
+  );
+}
index 04cfc11d49b75fa288c40322d97cf40df00aa70a..eeaa775462de635b075b9c713bda33ac06fe1844 100644 (file)
@@ -34,12 +34,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
         <input
           checked={false}
           className="spacer-right text-middle"
+          id="loglevel-INFO"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="INFO"
         />
-        INFO
+        <label
+          htmlFor="loglevel-INFO"
+        >
+          INFO
+        </label>
       </p>
       <p
         className="spacer-bottom"
@@ -47,12 +52,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
         <input
           checked={true}
           className="spacer-right text-middle"
+          id="loglevel-DEBUG"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="DEBUG"
         />
-        DEBUG
+        <label
+          htmlFor="loglevel-DEBUG"
+        >
+          DEBUG
+        </label>
       </p>
       <p
         className="spacer-bottom"
@@ -60,12 +70,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
         <input
           checked={false}
           className="spacer-right text-middle"
+          id="loglevel-TRACE"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="TRACE"
         />
-        TRACE
+        <label
+          htmlFor="loglevel-TRACE"
+        >
+          TRACE
+        </label>
       </p>
       <div
         className="alert alert-info spacer-top"
@@ -133,12 +148,17 @@ exports[`should render correctly 1`] = `
         <input
           checked={true}
           className="spacer-right text-middle"
+          id="loglevel-INFO"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="INFO"
         />
-        INFO
+        <label
+          htmlFor="loglevel-INFO"
+        >
+          INFO
+        </label>
       </p>
       <p
         className="spacer-bottom"
@@ -146,12 +166,17 @@ exports[`should render correctly 1`] = `
         <input
           checked={false}
           className="spacer-right text-middle"
+          id="loglevel-DEBUG"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="DEBUG"
         />
-        DEBUG
+        <label
+          htmlFor="loglevel-DEBUG"
+        >
+          DEBUG
+        </label>
       </p>
       <p
         className="spacer-bottom"
@@ -159,12 +184,17 @@ exports[`should render correctly 1`] = `
         <input
           checked={false}
           className="spacer-right text-middle"
+          id="loglevel-TRACE"
           name="system.log_levels"
           onChange={[Function]}
           type="radio"
           value="TRACE"
         />
-        TRACE
+        <label
+          htmlFor="loglevel-TRACE"
+        >
+          TRACE
+        </label>
       </p>
       <div
         className="alert alert-info spacer-top"
index 20ed8d541f6e82e85503dacbe135d834afc9f0b9..abd7d3a63ace6321ffa6ca3588a59f9a6d0ac6ee 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render correctly 1`] = `
+exports[`should support more than two nodes 1`] = `
 <ul>
   <HealthCard
     biggerHealth={true}
@@ -34,6 +34,7 @@ exports[`should render correctly 1`] = `
     open={false}
     sysInfoData={
       Object {
+        "Logs Level": "INFO",
         "Name": "Bar",
       }
     }
@@ -51,6 +52,7 @@ exports[`should render correctly 1`] = `
     open={false}
     sysInfoData={
       Object {
+        "Logs Level": "INFO",
         "Name": "Baz",
       }
     }
index 2945b6df9327ce50a687a4f103bb8cc95978f6ce..39f93da43227c2a872fb0569d0ed89df02e96ad0 100644 (file)
@@ -14,6 +14,7 @@ exports[`should render correctly 1`] = `
     canRestart={false}
     cluster={true}
     logLevel="INFO"
+    onLogLevelChange={[Function]}
   />
 </header>
 `;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap
new file mode 100644 (file)
index 0000000..f4961ae
--- /dev/null
@@ -0,0 +1,66 @@
+// 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={false}
+    sysInfoData={
+      Object {
+        "Logs Level": "DEBUG",
+        "Name": "Foo",
+      }
+    }
+  />
+  <HealthCard
+    name="Web"
+    onClick={[Function]}
+    open={false}
+    sysInfoData={
+      Object {
+        "Web Database Connectivity": undefined,
+        "Web JVM": Object {
+          "Max Memory": "2Gb",
+        },
+        "Web JVM Properties": undefined,
+      }
+    }
+  />
+  <HealthCard
+    name="Compute Engine"
+    onClick={[Function]}
+    open={true}
+    sysInfoData={
+      Object {
+        "Compute Engine JVM": undefined,
+        "Compute Engine JVM Properties": undefined,
+        "Pending": 4,
+      }
+    }
+  />
+  <HealthCard
+    name="Search"
+    onClick={[Function]}
+    open={false}
+    sysInfoData={
+      Object {
+        "Elasticsearch": Object {
+          "Number of Nodes": 1,
+        },
+        "Search JVM": undefined,
+        "Search JVM Properties": undefined,
+      }
+    }
+  />
+</ul>
+`;
index b45205dccbab754cc7353e49913ed5d3222f3f95..459f26ee7216c7c79a3affa940cf44067591a1cf 100644 (file)
@@ -24,12 +24,13 @@ 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';
+import { LOGS_LEVELS, groupSections, getLogsLevel } from '../../utils';
+import { translate } from '../../../../helpers/l10n';
 
 interface Props {
   biggerHealth?: boolean;
-  health: HealthType;
-  healthCauses: HealthCause[];
+  health?: HealthType;
+  healthCauses?: HealthCause[];
   onClick: (toggledCard: string) => void;
   open: boolean;
   name: string;
@@ -48,10 +49,12 @@ export default class HealthCard extends React.PureComponent<Props, State> {
   onDetailLeave = () => this.setState({ hoveringDetail: false });
 
   render() {
-    const { open, sysInfoData } = this.props;
+    const { health, open, sysInfoData } = this.props;
     const { mainSection, sections } = groupSections(sysInfoData);
     const showFields = open && mainSection && Object.keys(mainSection).length > 0;
     const showSections = open && sections;
+    const logLevel = getLogsLevel(sysInfoData);
+    const showLogLevelWarning = logLevel && logLevel !== LOGS_LEVELS[0];
     return (
       <li
         className={classNames('boxed-group system-info-health-card', {
@@ -62,11 +65,19 @@ export default class HealthCard extends React.PureComponent<Props, State> {
             <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}
-          />
+          {health && (
+            <HealthItem
+              biggerHealth={this.props.biggerHealth}
+              className="pull-right spacer-left"
+              health={health}
+              healthCauses={this.props.healthCauses}
+            />
+          )}
+          {showLogLevelWarning && (
+            <span className="pull-right alert alert-danger">
+              {translate('system.log_level.warning.short')}
+            </span>
+          )}
         </div>
         {open && (
           <div
index a0793a818311bae8c9a2971d76c96a00dfb9da43..86618c34c8a19ce9a5b9d7775789af4bd5614d39 100644 (file)
 import * as React from 'react';
 import * as classNames from 'classnames';
 import HealthCauseItem from './HealthCauseItem';
+import StatusIndicator from '../../../../components/common/StatusIndicator';
 import { HealthType, HealthCause } from '../../../../api/system';
 
 interface Props {
+  biggerHealth?: boolean;
   className?: string;
   health: HealthType;
   healthCauses?: HealthCause[];
 }
 
-export default function HealthItem({ className, health, healthCauses }: Props) {
+export default function HealthItem({ biggerHealth, className, health, healthCauses }: Props) {
   const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
   return (
     <div className={classNames('system-info-health-info', className)}>
@@ -36,7 +38,7 @@ export default function HealthItem({ className, health, healthCauses }: Props) {
         healthCauses!.map((cause, idx) => (
           <HealthCauseItem key={idx} className="spacer-right" health={health} healthCause={cause} />
         ))}
-      <span className={classNames('system-info-health-dot', health)} />
+      <StatusIndicator color={health.toLowerCase()} size={biggerHealth ? 'big' : undefined} />
     </div>
   );
 }
index c080f444decfafa7ccd1f988653be9db95fdcfa5..98b33bd31eebaa2f41521384c61904f1c80c0003 100644 (file)
@@ -48,6 +48,12 @@ it('should show a main section and multiple sub sections', () => {
   expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot();
 });
 
+it('should display the log level alert', () => {
+  expect(
+    getShallowWrapper({ sysInfoData: { 'Logs Level': 'DEBUG' } }).find('.alert')
+  ).toMatchSnapshot();
+});
+
 function getShallowWrapper(props = {}) {
   return shallow(
     <HealthCard
index f77622b9e92bb8a36a176c46a352ad948bcdfe57..9f1bd19835268a2867462f875b757e4c953dab01 100644 (file)
@@ -24,7 +24,9 @@ import { HealthType } from '../../../../../api/system';
 
 it('should render correctly', () => {
   expect(
-    shallow(<HealthItem health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />)
+    shallow(
+      <HealthItem biggerHealth={true} health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />
+    )
   ).toMatchSnapshot();
 });
 
index 724001990fa932da1464b9288c2bbc8e4ca72dab..7390ef8020e73ae840bd77734751ba4084252675 100644 (file)
@@ -1,5 +1,13 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should display the log level alert 1`] = `
+<span
+  className="pull-right alert alert-danger"
+>
+  system.log_level.warning.short
+</span>
+`;
+
 exports[`should display the sysinfo detail 1`] = `
 <li
   className="boxed-group system-info-health-card"
@@ -18,7 +26,8 @@ exports[`should display the sysinfo detail 1`] = `
       Foobar
     </span>
     <HealthItem
-      className="pull-right big-dot"
+      biggerHealth={true}
+      className="pull-right spacer-left"
       health="RED"
       healthCauses={
         Array [
@@ -55,7 +64,8 @@ exports[`should render correctly 1`] = `
       Foobar
     </span>
     <HealthItem
-      className="pull-right"
+      biggerHealth={false}
+      className="pull-right spacer-left"
       health="RED"
       healthCauses={
         Array [
@@ -87,7 +97,8 @@ exports[`should show a main section and multiple sub sections 1`] = `
       Foobar
     </span>
     <HealthItem
-      className="pull-right"
+      biggerHealth={false}
+      className="pull-right spacer-left"
       health="RED"
       healthCauses={
         Array [
index d9fa53a295642372493ea6e190955611fc65200f..f962437404c374c88db67e012386c912528f5c29 100644 (file)
@@ -4,8 +4,8 @@ exports[`should not render health causes 1`] = `
 <div
   className="system-info-health-info"
 >
-  <span
-    className="system-info-health-dot GREEN"
+  <StatusIndicator
+    color="green"
   />
 </div>
 `;
@@ -14,8 +14,8 @@ exports[`should not render health causes 2`] = `
 <div
   className="system-info-health-info"
 >
-  <span
-    className="system-info-health-dot YELLOW"
+  <StatusIndicator
+    color="yellow"
   />
 </div>
 `;
@@ -33,8 +33,9 @@ exports[`should render correctly 1`] = `
       }
     }
   />
-  <span
-    className="system-info-health-dot RED"
+  <StatusIndicator
+    color="red"
+    size="big"
   />
 </div>
 `;
@@ -61,8 +62,8 @@ exports[`should render multiple health causes 1`] = `
       }
     }
   />
-  <span
-    className="system-info-health-dot YELLOW"
+  <StatusIndicator
+    color="yellow"
   />
 </div>
 `;
diff --git a/server/sonar-web/src/main/js/apps/system/item-boolean.js b/server/sonar-web/src/main/js/apps/system/item-boolean.js
deleted file mode 100644 (file)
index aeac6e8..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-import React from 'react';
-
-export default class ItemBoolean extends React.PureComponent {
-  render() {
-    if (this.props.value) {
-      return <i className="icon-check" />;
-    } else {
-      return <i className="icon-delete" />;
-    }
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/item-log-level.js b/server/sonar-web/src/main/js/apps/system/item-log-level.js
deleted file mode 100644 (file)
index 89364f1..0000000
+++ /dev/null
@@ -1,60 +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.
- */
-import React from 'react';
-import { setLogLevel } from '../../api/system';
-import { translate } from '../../helpers/l10n';
-
-const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
-
-export default class ItemLogLevel extends React.PureComponent {
-  constructor(props) {
-    super(props);
-    this.state = { level: props.value };
-  }
-
-  onChange = () => {
-    const newValue = this.refs.select.value;
-    setLogLevel(newValue).then(() => {
-      this.setState({ level: newValue });
-    });
-  };
-
-  render() {
-    const options = LOG_LEVELS.map(level => (
-      <option key={level} value={level}>
-        {level}
-      </option>
-    ));
-    const warning =
-      this.state.level !== 'INFO' ? (
-        <div className="alert alert-danger spacer-top" style={{ wordBreak: 'normal' }}>
-          {translate('system.log_level.warning')}
-        </div>
-      ) : null;
-    return (
-      <div>
-        <select ref="select" onChange={this.onChange} value={this.state.level}>
-          {options}
-        </select>
-        {warning}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/item-object.js b/server/sonar-web/src/main/js/apps/system/item-object.js
deleted file mode 100644 (file)
index d9386cb..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.
- */
-import React from 'react';
-import ItemValue from './item-value';
-
-export default class ItemObject extends React.PureComponent {
-  render() {
-    const rows = Object.keys(this.props.value).map(key => {
-      return (
-        <tr key={key}>
-          <td className="thin nowrap">{key}</td>
-          <td>
-            <ItemValue value={this.props.value[key]} />
-          </td>
-        </tr>
-      );
-    });
-    return (
-      <table className="data">
-        <tbody>{rows}</tbody>
-      </table>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/item-value.js b/server/sonar-web/src/main/js/apps/system/item-value.js
deleted file mode 100644 (file)
index 5461aa9..0000000
+++ /dev/null
@@ -1,45 +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.
- */
-import React from 'react';
-import ItemBoolean from './item-boolean';
-import ItemObject from './item-object';
-import ItemLogLevel from './item-log-level';
-
-export default class ItemValue extends React.PureComponent {
-  render() {
-    if (this.props.name === 'Logs Level') {
-      return <ItemLogLevel value={this.props.value} />;
-    }
-
-    const rawValue = this.props.value;
-    let formattedValue;
-    switch (typeof this.props.value) {
-      case 'boolean':
-        formattedValue = <ItemBoolean value={rawValue} />;
-        break;
-      case 'object':
-        formattedValue = <ItemObject value={rawValue} />;
-        break;
-      default:
-        formattedValue = <code>{rawValue}</code>;
-    }
-    return formattedValue;
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/main.js b/server/sonar-web/src/main/js/apps/system/main.js
deleted file mode 100644 (file)
index 150bd04..0000000
+++ /dev/null
@@ -1,127 +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.
- */
-import React from 'react';
-import Helmet from 'react-helmet';
-import { sortBy } from 'lodash';
-import { getSystemInfo } from '../../api/system';
-import Section from './section';
-import { translate } from '../../helpers/l10n';
-import RestartForm from '../../components/common/RestartForm';
-
-const SECTIONS_ORDER = [
-  'SonarQube',
-  'Database',
-  'System',
-  'Elasticsearch State',
-  'Elasticsearch',
-  'Compute Engine Tasks',
-  'Compute Engine State',
-  'Compute Engine Database Connection',
-  'JvmProperties'
-];
-
-export default class Main extends React.PureComponent {
-  state = { openRestartForm: false };
-
-  componentDidMount() {
-    getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) }));
-  }
-
-  parseSections = data => {
-    const sections = Object.keys(data).map(section => {
-      return { name: section, items: this.parseItems(data[section]) };
-    });
-    return this.orderSections(sections);
-  };
-
-  orderSections = sections => sortBy(sections, section => SECTIONS_ORDER.indexOf(section.name));
-
-  parseItems = data => {
-    const items = Object.keys(data).map(item => {
-      return { name: item, value: data[item] };
-    });
-    return this.orderItems(items);
-  };
-
-  orderItems = items => sortBy(items, 'name');
-
-  handleServerRestartOpen = () => this.setState({ openRestartForm: true });
-  handleServerRestartClose = () => this.setState({ openRestartForm: false });
-
-  render() {
-    let sections = null;
-    if (this.state && this.state.sections) {
-      sections = this.state.sections
-        .filter(section => SECTIONS_ORDER.indexOf(section.name) >= 0)
-        .map(section => (
-          <Section key={section.name} section={section.name} items={section.items} />
-        ));
-    }
-
-    return (
-      <div className="page page-limited">
-        <Helmet title={translate('system_info.page')} />
-        <header className="page-header">
-          <h1 className="page-title">{translate('system_info.page')}</h1>
-          <div className="page-actions">
-            <a href={window.baseUrl + '/api/system/info'} id="download-link">
-              Download
-            </a>
-            <div className="display-inline-block dropdown big-spacer-left">
-              <button data-toggle="dropdown">
-                Logs <i className="icon-dropdown" />
-              </button>
-              <ul className="dropdown-menu">
-                <li>
-                  <a href={window.baseUrl + '/api/system/logs?process=app'} id="logs-link">
-                    Main Process
-                  </a>
-                </li>
-                <li>
-                  <a href={window.baseUrl + '/api/system/logs?process=ce'} id="ce-logs-link">
-                    Compute Engine
-                  </a>
-                </li>
-                <li>
-                  <a href={window.baseUrl + '/api/system/logs?process=es'} id="es-logs-link">
-                    Elasticsearch
-                  </a>
-                </li>
-                <li>
-                  <a href={window.baseUrl + '/api/system/logs?process=web'} id="web-logs-link">
-                    Web Server
-                  </a>
-                </li>
-              </ul>
-            </div>
-            <button
-              id="restart-server-button"
-              className="big-spacer-left"
-              onClick={this.handleServerRestartOpen}>
-              Restart Server
-            </button>
-            {this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
-          </div>
-        </header>
-        {sections}
-      </div>
-    );
-  }
-}
index 1a8d6a5ab38caff6597143db8d4dcaf1c3d8a9de..fcffeff191c6f053c61eb39bed4b280553e5bac4 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, RouteComponent, IndexRouteProps } from 'react-router';
+import { RouterState, IndexRouteProps } from 'react-router';
 
 const routes = [
   {
     getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
       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/section.js b/server/sonar-web/src/main/js/apps/system/section.js
deleted file mode 100644 (file)
index 89b90e9..0000000
+++ /dev/null
@@ -1,49 +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.
- */
-import React from 'react';
-import ItemValue from './item-value';
-
-export default class Section extends React.PureComponent {
-  render() {
-    const items = this.props.items.map(item => {
-      return (
-        <tr key={item.name}>
-          <td className="thin">
-            <div style={{ width: '25vw', overflow: 'hidden', textOverflow: 'ellipsis' }}>
-              {item.name}
-            </div>
-          </td>
-          <td style={{ wordBreak: 'break-all' }}>
-            <ItemValue name={item.name} value={item.value} />
-          </td>
-        </tr>
-      );
-    });
-
-    return (
-      <div className="big-spacer-bottom">
-        <h3 className="spacer-bottom">{this.props.section}</h3>
-        <table className="data zebra" id={this.props.section}>
-          <tbody>{items}</tbody>
-        </table>
-      </div>
-    );
-  }
-}
index f1156609add294bca6ee27027c535e2bd154549c..f34dc3fa3940420aa13b308c6974e41e34bb820a 100644 (file)
   padding-bottom: 15px;
 }
 
+.system-info-health-card .boxed-group-header > .alert {
+  margin-top: -6px;
+}
+
 .system-info-health-card .boxed-group-inner {
   padding-top: 0;
 }
 }
 
 .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;
+  margin-top: -12px;
 }
 
-.system-info-health-dot.YELLOW {
-  background-color: #eabe06;
-}
-.system-info-health-dot.RED {
-  background-color: #d4333f;
+.system-info-health-info .status-indicator {
+  position: relative;
+  top: 8px;
 }
 
 .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 {
+.system-info-health-info.no-margin .status-indicator {
   margin: 0;
 }
 
index 41ad5223544e9a08201bfd72caa96a2aca18d1f5..ae8818edf9d99622ec86c76d7a2512816e01bd2a 100644 (file)
@@ -17,7 +17,7 @@
  * 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 { each, omit, memoize, sortBy } from 'lodash';
 import {
   cleanQuery,
   parseAsArray,
@@ -38,6 +38,16 @@ export interface Query {
   expandedCards: string[];
 }
 
+export interface ClusterSysInfo extends SysInfo {
+  'Application Nodes': NodeInfo[];
+  'Search Nodes': NodeInfo[];
+}
+
+export interface StandaloneSysInfo extends SysInfo {
+  'Logs Level': string;
+}
+
+export const LOGS_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
 export const HEALTH_FIELD = 'Health';
 export const HEALTHCAUSES_FIELD = 'Health Causes';
 
@@ -45,10 +55,6 @@ 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;
 }
@@ -57,15 +63,74 @@ 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 getLogsLevel(sysInfoObject: SysValueObject): string {
+  return (sysInfoObject['Logs Level'] || LOGS_LEVELS[0]) as string;
+}
+
+export function getSystemLogsLevel(sysInfoData?: SysInfo): string {
+  const defaultLevel = LOGS_LEVELS[0];
+  if (!sysInfoData) {
+    return defaultLevel;
+  }
+  if (isCluster(sysInfoData)) {
+    const nodes = sortBy(getAppNodes(sysInfoData as ClusterSysInfo), node =>
+      LOGS_LEVELS.indexOf(getLogsLevel(node))
+    );
+    return nodes.length > 0 ? getLogsLevel(nodes[nodes.length - 1]) : defaultLevel;
+  } else {
+    return getLogsLevel(sysInfoData);
+  }
 }
 
 export function getNodeName(nodeInfo: NodeInfo): string {
   return nodeInfo['Name'];
 }
 
-export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] {
+export function getClusterMainCardSection(sysInfoData: ClusterSysInfo): SysValueObject {
+  return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
+}
+
+export function getStandaloneMainSections(sysInfoData: StandaloneSysInfo): SysValueObject {
+  return omit(sysInfoData, [
+    'Settings',
+    'Statistics',
+    'Compute Engine',
+    'Compute Engine JVM',
+    'Compute Engine JVM Properties',
+    'Elasticsearch',
+    'Search JVM',
+    'Search JVM Properties',
+    'Web Database Connectivity',
+    'Web JVM',
+    'Web JVM Properties'
+  ]);
+}
+
+export function getStandaloneSecondarySections(sysInfoData: StandaloneSysInfo): SysInfoSection {
+  return {
+    Web: {
+      'Web Database Connectivity': sysInfoData['Web Database Connectivity'],
+      'Web JVM': sysInfoData['Web JVM'],
+      'Web JVM Properties': sysInfoData['Web JVM Properties']
+    },
+    'Compute Engine': {
+      ...sysInfoData['Compute Engine'] as SysValueObject,
+      'Compute Engine JVM': sysInfoData['Compute Engine JVM'],
+      'Compute Engine JVM Properties': sysInfoData['Compute Engine JVM Properties']
+    },
+    Search: {
+      Elasticsearch: sysInfoData['Elasticsearch'] as SysValueObject,
+      'Search JVM': sysInfoData['Search JVM'],
+      'Search JVM Properties': sysInfoData['Search JVM Properties']
+    }
+  };
+}
+
+export function getAppNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
+  return sysInfoData['Application Nodes'];
+}
+
+export function getSearchNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
   return sysInfoData['Search Nodes'];
 }
 
@@ -83,7 +148,7 @@ export function groupSections(sysInfoData: SysValueObject) {
 }
 
 export function isCluster(sysInfoData?: SysInfo): boolean {
-  return sysInfoData != undefined && sysInfoData['Cluster'];
+  return sysInfoData != undefined && sysInfoData['Cluster'] === true;
 }
 
 export const parseQuery = memoize((urlQuery: RawQuery): Query => {
index 74278d6757300b6527392aa2fff1dc044770cd42..ccfccb351f108a69db75bf0123dd2554a9da6b53 100644 (file)
@@ -3,18 +3,6 @@
   text-align: right;
 }
 
-.branch-status-indicator {
-  display: block;
-  width: 8px;
-  height: 8px;
-  border-radius: 8px;
-  margin: 4px 0;
-}
-
-.branch-status-indicator.is-failed {
-  background-color: #d4333f;
-}
-
-.branch-status-indicator.is-passed {
-  background-color: #00aa00;
+.branch-status .status-indicator {
+  margin: 0;
 }
index 40688087cf02aae2731bc546a4fee4916627bab2..bb9069bc2348334483860ad03179a033e78f5a32 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as classNames from 'classnames';
 import { Branch } from '../../app/types';
 import Level from '../ui/Level';
 import BugIcon from '../icons-components/BugIcon';
 import CodeSmellIcon from '../icons-components/CodeSmellIcon';
+import StatusIndicator from './StatusIndicator';
 import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
 import { isShortLivingBranch } from '../../helpers/branches';
 import './BranchStatus.css';
@@ -41,22 +41,19 @@ export default function BranchStatus({ branch, concise = false }: Props) {
     const totalIssues =
       branch.status.bugs + branch.status.vulnerabilities + branch.status.codeSmells;
 
-    const indicatorClassName = classNames('branch-status-indicator', {
-      'is-failed': totalIssues > 0,
-      'is-passed': totalIssues === 0
-    });
+    const indicatorColor = totalIssues > 0 ? 'red' : 'green';
 
     return concise ? (
       <ul className="list-inline branch-status">
         <li>{totalIssues}</li>
         <li className="spacer-left">
-          <i className={indicatorClassName} />
+          <StatusIndicator color={indicatorColor} size="small" />
         </li>
       </ul>
     ) : (
       <ul className="list-inline branch-status">
         <li className="spacer-right">
-          <i className={indicatorClassName} />
+          <StatusIndicator color={indicatorColor} size="small" />
         </li>
         <li>
           {branch.status.bugs}
diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.css b/server/sonar-web/src/main/js/components/common/StatusIndicator.css
new file mode 100644 (file)
index 0000000..2600883
--- /dev/null
@@ -0,0 +1,34 @@
+.status-indicator {
+  display: inline-block;
+  box-sizing: border-box;
+  width: 16px;
+  height: 16px;
+  border-radius: 16px;
+  margin: 4px;
+}
+
+.status-indicator.small-status-indicator {
+  width: 8px;
+  height: 8px;
+  border-radius: 8px;
+  margin: 8px;
+}
+
+.status-indicator.big-status-indicator {
+  width: 24px;
+  height: 24px;
+  border-radius: 24px;
+  margin: 0;
+}
+
+.status-indicator.red {
+  background-color: #d4333f;
+}
+
+.sstatus-indicator.yellow {
+  background-color: #eabe06;
+}
+
+.status-indicator.green {
+  background-color: #00aa00;
+}
diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx b/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx
new file mode 100644 (file)
index 0000000..6fc9635
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 './StatusIndicator.css';
+
+interface Props {
+  className?: string;
+  color?: string;
+  size?: string;
+}
+
+export default function StatusIndicator({ className, color, size }: Props) {
+  return (
+    <i
+      className={classNames(
+        'status-indicator',
+        color,
+        {
+          'small-status-indicator': size === 'small',
+          'big-status-indicator': size === 'big'
+        },
+        className
+      )}
+    />
+  );
+}
index b106929fbe47f20614f9ef4a4a9b87b3dfe2e67e..32c0d0a2afa8e6f6d880cc353fea800438ba3dad 100644 (file)
@@ -23,8 +23,9 @@ exports[`renders status of short-living branches 1`] = `
   <li
     className="spacer-right"
   >
-    <i
-      className="branch-status-indicator is-passed"
+    <StatusIndicator
+      color="green"
+      size="small"
     />
   </li>
   <li>
@@ -49,8 +50,9 @@ exports[`renders status of short-living branches 2`] = `
   <li
     className="spacer-right"
   >
-    <i
-      className="branch-status-indicator is-failed"
+    <StatusIndicator
+      color="red"
+      size="small"
     />
   </li>
   <li>
@@ -75,8 +77,9 @@ exports[`renders status of short-living branches 3`] = `
   <li
     className="spacer-right"
   >
-    <i
-      className="branch-status-indicator is-failed"
+    <StatusIndicator
+      color="red"
+      size="small"
     />
   </li>
   <li>
index 739bc6027e7c06c33f5ad3afec134d6b4dadfd61..ba583b3de7c8a1bd8b4d5a2a8040dc89e30c60cc 100644 (file)
@@ -35,9 +35,9 @@ export default function OpenCloseIcon({ className, open, size = 14 }: Props) {
       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.506 9.289l-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" />
+        <path d="M13.527 9.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 ef96a4360d580be056bbee6b7d771842063f5f75..377cec174a193c08ad5dcc5a5d42b626156199bc 100644 (file)
@@ -2847,7 +2847,8 @@ system.cluster_log_level.info=Changes apply to all Application nodes but not to
 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.warning=This 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.warning.short=Current logs level has performance impacts, get back to INFO level.
 system.log_level.info=Changes don't apply to Search.
 system.logs_level=Logs level
 system.restart_server=Restart Server