aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java4
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java6
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java6
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java)31
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java (renamed from server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java)38
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java64
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java)25
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java (renamed from server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java)6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java)10
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java)66
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java)2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java140
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java119
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java)5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java)32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java)22
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java)57
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java)2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java104
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java103
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java59
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java33
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java56
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java)4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java)22
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java)55
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java)9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java)2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java)30
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java)28
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java)68
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java44
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java)18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java)19
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java35
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java37
-rw-r--r--server/sonar-web/src/main/js/api/system.ts47
-rw-r--r--server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts43
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/App.tsx125
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx114
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx82
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageActions.tsx163
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java)47
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap194
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap59
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap126
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap38
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx84
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx42
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap132
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap68
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap125
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap180
-rw-r--r--server/sonar-web/src/main/js/apps/system/main.js12
-rw-r--r--server/sonar-web/src/main/js/apps/system/routes.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/system/styles.css81
-rw-r--r--server/sonar-web/src/main/js/apps/system/utils.ts99
-rw-r--r--server/sonar-web/src/main/js/components/common/RestartForm.tsx93
-rw-r--r--server/sonar-web/src/main/js/components/facet/FacetHeader.js28
-rw-r--r--server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap110
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx44
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/icons.js2
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts22
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties13
78 files changed, 3375 insertions, 789 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java
index cb61583ff06..e239b685e16 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java
@@ -22,7 +22,7 @@ package org.sonar.ce;
import org.sonar.ce.configuration.CeConfigurationImpl;
import org.sonar.ce.log.CeLogging;
import org.sonar.core.platform.Module;
-import org.sonar.process.systeminfo.ProcessStateSystemInfo;
+import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.ce.monitoring.CeDatabaseMBeanImpl;
public class CeConfigurationModule extends Module {
@@ -32,6 +32,6 @@ public class CeConfigurationModule extends Module {
CeConfigurationImpl.class,
CeLogging.class,
CeDatabaseMBeanImpl.class,
- new ProcessStateSystemInfo("Compute Engine State"));
+ new JvmStateSection("Compute Engine JVM State"));
}
}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
index 086d087ec0d..9452c4da1cd 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
@@ -26,7 +26,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.ce.httpd.HttpAction;
-import org.sonar.process.systeminfo.ProcessStateSystemInfo;
+import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
@@ -44,8 +44,8 @@ public class SystemInfoHttpActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1");
- private SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2");
+ private SystemInfoSection stateProvider1 = new JvmStateSection("state1");
+ private SystemInfoSection stateProvider2 = new JvmStateSection("state2");
private SystemInfoHttpAction underTest;
@Before
diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java
index 25c60a3c712..be6105cc511 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java
@@ -59,15 +59,15 @@ public class DistributedAnswer<T> {
return members;
}
- void setAnswer(Member member, T answer) {
+ public void setAnswer(Member member, T answer) {
this.answers.put(member, answer);
}
- void setTimedOut(Member member) {
+ public void setTimedOut(Member member) {
this.timedOutMembers.add(member);
}
- void setFailed(Member member, Exception e) {
+ public void setFailed(Member member, Exception e) {
failedMembers.put(member, e);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java
index c1790530c9a..8b0a31b0065 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java
@@ -17,24 +17,35 @@
* 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;
+package org.sonar.process.systeminfo;
import java.util.Map;
import java.util.Objects;
-import java.util.TreeMap;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-public class JvmPropsMonitor implements Monitor {
- @Override
- public String name() {
- return "JvmProperties";
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * Dumps {@link System#getProperties()}
+ */
+public class JvmPropertiesSection implements SystemInfoSection {
+
+ private final String name;
+
+ public JvmPropertiesSection(String name) {
+ this.name = name;
}
@Override
- public Map<String, Object> attributes() {
- Map<String, Object> sortedProps = new TreeMap<>();
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName(name);
+
for (Map.Entry<Object, Object> systemProp : System.getProperties().entrySet()) {
- sortedProps.put(Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
+ if (systemProp.getValue() != null) {
+ setAttribute(protobuf, Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
+ }
}
- return sortedProps;
+ return protobuf.build();
}
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
index 7ad80c576e1..575083280b1 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
@@ -25,11 +25,16 @@ import java.lang.management.MemoryUsage;
import java.lang.management.ThreadMXBean;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-public class ProcessStateSystemInfo implements SystemInfoSection {
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * Dumps state of JVM (memory, threads)
+ */
+public class JvmStateSection implements SystemInfoSection {
private static final long MEGABYTE = 1024L * 1024L;
private final String name;
- public ProcessStateSystemInfo(String name) {
+ public JvmStateSection(String name) {
this.name = name;
}
@@ -40,26 +45,27 @@ public class ProcessStateSystemInfo implements SystemInfoSection {
// Visible for testing
ProtobufSystemInfo.Section toProtobuf(MemoryMXBean memoryBean) {
- ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder();
- builder.setName(name);
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName(name);
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
- addAttributeInMb(builder, "Heap Committed (MB)", heap.getCommitted());
- addAttributeInMb(builder, "Heap Init (MB)", heap.getInit());
- addAttributeInMb(builder, "Heap Max (MB)", heap.getMax());
- addAttributeInMb(builder, "Heap Used (MB)", heap.getUsed());
+ addAttributeInMb(protobuf, "Heap Committed (MB)", heap.getCommitted());
+ addAttributeInMb(protobuf, "Heap Init (MB)", heap.getInit());
+ addAttributeInMb(protobuf, "Heap Max (MB)", heap.getMax());
+ addAttributeInMb(protobuf, "Heap Used (MB)", heap.getUsed());
MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
- addAttributeInMb(builder, "Non Heap Committed (MB)", nonHeap.getCommitted());
- addAttributeInMb(builder, "Non Heap Init (MB)", nonHeap.getInit());
- addAttributeInMb(builder, "Non Heap Max (MB)", nonHeap.getMax());
- addAttributeInMb(builder, "Non Heap Used (MB)", nonHeap.getUsed());
+ addAttributeInMb(protobuf, "Non Heap Committed (MB)", nonHeap.getCommitted());
+ addAttributeInMb(protobuf, "Non Heap Init (MB)", nonHeap.getInit());
+ addAttributeInMb(protobuf, "Non Heap Max (MB)", nonHeap.getMax());
+ addAttributeInMb(protobuf, "Non Heap Used (MB)", nonHeap.getUsed());
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
- builder.addAttributesBuilder().setKey("Thread Count").setLongValue(thread.getThreadCount()).build();
- return builder.build();
+ setAttribute(protobuf, "Thread Count", thread.getThreadCount());
+
+ return protobuf.build();
}
- private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder builder, String key, long valueInBytes) {
+ private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder protobuf, String key, long valueInBytes) {
if (valueInBytes >= 0L) {
- builder.addAttributesBuilder().setKey(key).setLongValue(valueInBytes / MEGABYTE).build();
+ setAttribute(protobuf, key, valueInBytes / MEGABYTE);
}
}
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java
new file mode 100644
index 00000000000..738329480ff
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+public class SystemInfoUtils {
+
+ private SystemInfoUtils() {
+ // prevent instantiation
+ }
+
+ public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, @Nullable String value) {
+ if (value != null) {
+ section.addAttributesBuilder()
+ .setKey(key)
+ .setStringValue(value)
+ .build();
+ }
+ }
+
+ public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, boolean value) {
+ section.addAttributesBuilder()
+ .setKey(key)
+ .setBooleanValue(value)
+ .build();
+ }
+
+ public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, long value) {
+ section.addAttributesBuilder()
+ .setKey(key)
+ .setLongValue(value)
+ .build();
+ }
+
+ @CheckForNull
+ public static ProtobufSystemInfo.Attribute attribute(ProtobufSystemInfo.Section section, String key) {
+ for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+ if (attribute.getKey().equals(key)) {
+ return attribute;
+ }
+ }
+ return null;
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java
index d06d5dbdf20..dcf4cb9893c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java
@@ -17,22 +17,29 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.ws;
+package org.sonar.process.systeminfo;
+import org.assertj.core.api.Assertions;
import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
-public class InfoActionModuleTest {
- @Test
- public void verify_count_of_added_components() {
- ComponentContainer container = new ComponentContainer();
+public class JvmPropertiesSectionTest {
- new InfoActionModule().configure(container);
+ private JvmPropertiesSection underTest = new JvmPropertiesSection("Web JVM Properties");
- assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
+ @Test
+ public void name_is_not_empty() {
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("Web JVM Properties");
}
+ @Test
+ public void test_toProtobuf() {
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+ Assertions.assertThat(attribute(section, "java.vm.vendor").getStringValue()).isNotEmpty();
+ Assertions.assertThat(attribute(section, "os.name").getStringValue()).isNotEmpty();
+ }
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
index 94e9ad1fd51..5276a524353 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
@@ -28,13 +28,13 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-public class ProcessStateSystemInfoTest {
+public class JvmStateSectionTest {
public static final String PROCESS_NAME = "the process name";
@Test
public void toSystemInfoSection() {
- ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
+ JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThat(section.getName()).isEqualTo(PROCESS_NAME);
@@ -47,7 +47,7 @@ public class ProcessStateSystemInfoTest {
MemoryMXBean memoryBean = mock(MemoryMXBean.class, Mockito.RETURNS_DEEP_STUBS);
when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(-1L);
- ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
+ JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
ProtobufSystemInfo.Section section = underTest.toProtobuf(memoryBean);
assertThat(section.getAttributesList()).extracting("key").doesNotContain("Heap Committed (MB)");
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java
index 4531b2ab7a1..eba68231c9e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java
@@ -21,12 +21,13 @@ package org.sonar.server.platform.monitoring;
import org.picocontainer.Startable;
import org.sonar.process.Jmx;
+import org.sonar.process.systeminfo.SystemInfoSection;
/**
- * Base implementation of a {@link org.sonar.server.platform.monitoring.Monitor}
+ * Base implementation of a {@link SystemInfoSection}
* that is exported as a JMX bean
*/
-public abstract class BaseMonitorMBean implements Monitor, Startable {
+public abstract class BaseSectionMBean implements SystemInfoSection, Startable {
/**
* Auto-registers to MBean server
@@ -47,4 +48,9 @@ public abstract class BaseMonitorMBean implements Monitor, Startable {
String objectName() {
return "SonarQube:name=" + name();
}
+
+ /**
+ * Name of section in System Info page
+ */
+ abstract String name();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java
index 2ec74c63f2d..a64e8d25bb1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java
@@ -19,25 +19,25 @@
*/
package org.sonar.server.platform.monitoring;
-import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.Map;
import org.apache.commons.dbcp.BasicDataSource;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
/**
* Information about database and connection pool
*/
-public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitorMBean {
+public class DatabaseSection extends BaseSectionMBean implements DatabaseSectionMBean {
private final DatabaseVersion dbVersion;
private final DbClient dbClient;
- public DatabaseMonitor(DatabaseVersion dbVersion, DbClient dbClient) {
+ public DatabaseSection(DatabaseVersion dbVersion, DbClient dbClient) {
this.dbVersion = dbVersion;
this.dbClient = dbClient;
}
@@ -98,40 +98,40 @@ public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitor
}
@Override
- public Map<String, Object> attributes() {
- Map<String, Object> attributes = new LinkedHashMap<>();
- completeDbAttributes(attributes);
- completePoolAttributes(attributes);
- return attributes;
- }
-
- private void completePoolAttributes(Map<String, Object> attributes) {
- attributes.put("Pool Active Connections", getPoolActiveConnections());
- attributes.put("Pool Max Connections", getPoolMaxActiveConnections());
- attributes.put("Pool Initial Size", getPoolInitialSize());
- attributes.put("Pool Idle Connections", getPoolIdleConnections());
- attributes.put("Pool Min Idle Connections", getPoolMinIdleConnections());
- attributes.put("Pool Max Idle Connections", getPoolMaxIdleConnections());
- attributes.put("Pool Max Wait (ms)", getPoolMaxWaitMillis());
- attributes.put("Pool Remove Abandoned", getPoolRemoveAbandoned());
- attributes.put("Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
+ public Section toProtobuf() {
+ Section.Builder protobuf = Section.newBuilder();
+ protobuf.setName(name());
+ completeDbAttributes(protobuf);
+ completePoolAttributes(protobuf);
+ return protobuf.build();
+ }
+
+ private void completePoolAttributes(Section.Builder protobuf) {
+ setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections());
+ setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections());
+ setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize());
+ setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections());
+ setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections());
+ setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections());
+ setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis());
+ setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned());
+ setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
}
private BasicDataSource commonsDbcp() {
return (BasicDataSource) dbClient.getDatabase().getDataSource();
}
- private void completeDbAttributes(Map<String, Object> attributes) {
- try (DbSession dbSession = dbClient.openSession(false);
- Connection connection = dbSession.getConnection()) {
- DatabaseMetaData metadata = connection.getMetaData();
- attributes.put("Database", metadata.getDatabaseProductName());
- attributes.put("Database Version", metadata.getDatabaseProductVersion());
- attributes.put("Username", metadata.getUserName());
- attributes.put("URL", metadata.getURL());
- attributes.put("Driver", metadata.getDriverName());
- attributes.put("Driver Version", metadata.getDriverVersion());
- attributes.put("Version Status", getMigrationStatus());
+ private void completeDbAttributes(Section.Builder protobuf) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
+ setAttribute(protobuf, "Database", metadata.getDatabaseProductName());
+ setAttribute(protobuf, "Database Version", metadata.getDatabaseProductVersion());
+ setAttribute(protobuf, "Username", metadata.getUserName());
+ setAttribute(protobuf, "URL", metadata.getURL());
+ setAttribute(protobuf, "Driver", metadata.getDriverName());
+ setAttribute(protobuf, "Driver Version", metadata.getDriverVersion());
+ setAttribute(protobuf, "Version Status", getMigrationStatus());
} catch (SQLException e) {
throw new IllegalStateException("Fail to get DB metadata", e);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java
index b47f6f2848d..0ddd97fff68 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java
@@ -19,7 +19,7 @@
*/
package org.sonar.server.platform.monitoring;
-public interface DatabaseMonitorMBean {
+public interface DatabaseSectionMBean {
/**
* Is database schema up-to-date or should it be upgraded ?
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java
deleted file mode 100644
index fcc3ccb32e2..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
-import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
-import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
-import org.elasticsearch.action.admin.indices.stats.IndexStats;
-import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.elasticsearch.cluster.node.DiscoveryNode;
-import org.elasticsearch.common.breaker.CircuitBreaker;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.server.es.EsClient;
-
-import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
-
-public class EsMonitor extends BaseMonitorMBean implements EsMonitorMBean {
-
- private final EsClient esClient;
-
- public EsMonitor(EsClient esClient) {
- this.esClient = esClient;
- }
-
- @Override
- public String name() {
- return "Elasticsearch";
- }
-
- /**
- * MXBean does not allow to return enum {@link ClusterHealthStatus}, so
- * returning String.
- */
- @Override
- public String getState() {
- return getStateAsEnum().name();
- }
-
- private ClusterHealthStatus getStateAsEnum() {
- return clusterStats().getStatus();
- }
-
- @Override
- public int getNumberOfNodes() {
- return clusterStats().getNodesStats().getCounts().getTotal();
- }
-
- @Override
- public Map<String, Object> attributes() {
- try {
- Map<String, Object> attributes = new LinkedHashMap<>();
- attributes.put("State", getStateAsEnum());
- attributes.put("Indices", indexAttributes());
- attributes.put("Number of Nodes", getNumberOfNodes());
- attributes.put("Nodes", nodeAttributes());
- return attributes;
- } catch (Exception es) {
- Loggers.get(EsMonitor.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
- Map<String, Object> attributes = new LinkedHashMap<>();
- attributes.put("State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
- return attributes;
- }
- }
-
- private LinkedHashMap<String, LinkedHashMap<String, Object>> indexAttributes() {
- LinkedHashMap<String, LinkedHashMap<String, Object>> indices = new LinkedHashMap<>();
- IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
-
- for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
- LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
- indices.put(indexStats.getKey(), attributes);
- attributes.put("Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
- attributes.put("Shards", indexStats.getValue().getShards().length);
- attributes.put("Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
- }
- return indices;
- }
-
- /**
- * map of {node name -> node attributes}
- */
- private LinkedHashMap<String, LinkedHashMap<String, Object>> nodeAttributes() {
- LinkedHashMap<String, LinkedHashMap<String, Object>> nodes = new LinkedHashMap<>();
- NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
- for (Map.Entry<String, NodeStats> entry : nodesStats.getNodesMap().entrySet()) {
-
- LinkedHashMap<String, Object> nodeAttributes = new LinkedHashMap<>();
- NodeStats stats = entry.getValue();
- DiscoveryNode node = stats.getNode();
- nodes.put(node.getName(), nodeAttributes);
- nodeAttributes.put("Address", node.getAddress().toString());
- nodeAttributes.put("Type", node.isMasterNode() ? "Master" : "Slave");
- nodeAttributes.put("Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
- nodeAttributes.put("Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
- nodeAttributes.put("Open Files", stats.getProcess().getOpenFileDescriptors());
- nodeAttributes.put("JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
- nodeAttributes.put("JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
- nodeAttributes.put("JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
- nodeAttributes.put("JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
- nodeAttributes.put("JVM Threads", stats.getJvm().getThreads().getCount());
- nodeAttributes.put("Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
- nodeAttributes.put("Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
- nodeAttributes.put("Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
- nodeAttributes.put("Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
- nodeAttributes.put("Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
- nodeAttributes.put("Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
- nodeAttributes.put("Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
- }
- return nodes;
- }
-
- private ClusterStatsResponse clusterStats() {
- return esClient.prepareClusterStats().get();
- }
-
- private static String formatPercent(long amount) {
- return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java
new file mode 100644
index 00000000000..2e86e6ef854
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.util.Map;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
+import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
+import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
+import org.elasticsearch.action.admin.indices.stats.IndexStats;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.common.breaker.CircuitBreaker;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+
+import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class EsSection extends BaseSectionMBean implements EsSectionMBean {
+
+ private final EsClient esClient;
+
+ public EsSection(EsClient esClient) {
+ this.esClient = esClient;
+ }
+
+ @Override
+ public String name() {
+ return "Elasticsearch";
+ }
+
+ /**
+ * MXBean does not allow to return enum {@link ClusterHealthStatus}, so
+ * returning String.
+ */
+ @Override
+ public String getState() {
+ return getStateAsEnum().name();
+ }
+
+ private ClusterHealthStatus getStateAsEnum() {
+ return clusterStats().getStatus();
+ }
+
+ @Override
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName(name());
+ try {
+ setAttribute(protobuf, "State", getStateAsEnum().name());
+ completeNodeAttributes(protobuf);
+ completeIndexAttributes(protobuf);
+
+ } catch (Exception es) {
+ Loggers.get(EsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
+ setAttribute(protobuf, "State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
+ }
+ return protobuf.build();
+ }
+
+ private void completeIndexAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
+ IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
+ for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
+ String prefix = "Index " + indexStats.getKey() + " - ";
+ setAttribute(protobuf, prefix + "Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
+ setAttribute(protobuf, prefix + "Shards", indexStats.getValue().getShards().length);
+ setAttribute(protobuf, prefix + "Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
+ }
+ }
+
+ private void completeNodeAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
+ NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
+ if (!nodesStats.getNodes().isEmpty()) {
+ NodeStats stats = nodesStats.getNodes().get(0);
+ setAttribute(protobuf, "Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
+ setAttribute(protobuf, "Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
+ setAttribute(protobuf, "Open Files", stats.getProcess().getOpenFileDescriptors());
+ setAttribute(protobuf, "JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
+ setAttribute(protobuf, "JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
+ setAttribute(protobuf, "JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
+ setAttribute(protobuf, "JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
+ setAttribute(protobuf, "JVM Threads", stats.getJvm().getThreads().getCount());
+ setAttribute(protobuf, "Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
+ setAttribute(protobuf, "Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
+ setAttribute(protobuf, "Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
+ setAttribute(protobuf, "Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
+ setAttribute(protobuf, "Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
+ setAttribute(protobuf, "Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
+ setAttribute(protobuf, "Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
+ }
+ }
+
+ private ClusterStatsResponse clusterStats() {
+ return esClient.prepareClusterStats().get();
+ }
+
+ private static String formatPercent(long amount) {
+ return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
index 647f6687dce..6358f68593f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
@@ -20,10 +20,9 @@
package org.sonar.server.platform.monitoring;
/**
- * The public attributes of {@link org.sonar.server.platform.monitoring.EsMonitor}
+ * The public attributes of {@link EsSection}
* to be exported in JMX bean.
*/
-public interface EsMonitorMBean {
+public interface EsSectionMBean {
String getState();
- int getNumberOfNodes();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java
index 9e3c9517e39..491a8bf1265 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java
@@ -19,39 +19,33 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.LinkedHashMap;
-import java.util.Map;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.updatecenter.common.Version;
-/**
- * Installed plugins (excluding core plugins)
- */
-public class PluginsMonitor implements Monitor {
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class PluginsSection implements SystemInfoSection {
private final PluginRepository repository;
- public PluginsMonitor(PluginRepository repository) {
+ public PluginsSection(PluginRepository repository) {
this.repository = repository;
}
@Override
- public String name() {
- return "Plugins";
- }
-
- @Override
- public Map<String, Object> attributes() {
- Map<String, Object> attributes = new LinkedHashMap<>();
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName("Plugins");
for (PluginInfo plugin : repository.getPluginInfos()) {
- LinkedHashMap<String, Object> pluginAttributes = new LinkedHashMap<>();
- pluginAttributes.put("Name", plugin.getName());
+ String label = "[" + plugin.getName() + "]";
Version version = plugin.getVersion();
if (version != null) {
- pluginAttributes.put("Version", version.getName());
+ label = version.getName() + " " + label;
}
- attributes.put(plugin.getKey(), pluginAttributes);
+ setAttribute(protobuf, plugin.getKey(), label);
}
- return attributes;
+ return protobuf.build();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java
index 1b45ef205fc..12f1d688623 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java
@@ -19,41 +19,39 @@
*/
package org.sonar.server.platform.monitoring;
-import com.google.common.collect.ImmutableSortedMap;
import java.util.Map;
-import java.util.SortedMap;
import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import static org.apache.commons.lang.StringUtils.abbreviate;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
-public class SettingsMonitor implements Monitor {
+public class SettingsSection implements SystemInfoSection {
static final int MAX_VALUE_LENGTH = 500;
private final Settings settings;
- public SettingsMonitor(Settings settings) {
+ public SettingsSection(Settings settings) {
this.settings = settings;
}
@Override
- public String name() {
- return "Settings";
- }
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName("Settings");
- @Override
- public SortedMap<String, Object> attributes() {
PropertyDefinitions definitions = settings.getDefinitions();
- ImmutableSortedMap.Builder<String, Object> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, String> prop : settings.getProperties().entrySet()) {
String key = prop.getKey();
PropertyDefinition def = definitions.get(key);
if (def == null || def.type() != PropertyType.PASSWORD) {
- builder.put(key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
+ setAttribute(protobuf, key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
}
}
- return builder.build();
+ return protobuf.build();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
index 823876d9911..66eefa13421 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
@@ -21,9 +21,7 @@ package org.sonar.server.platform.monitoring;
import com.google.common.base.Joiner;
import java.io.File;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.CoreProperties;
@@ -33,12 +31,15 @@ import org.sonar.api.security.SecurityRealm;
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.process.ProcessProperties;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.authentication.IdentityProviderRepository;
import org.sonar.server.platform.ServerIdLoader;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.SecurityRealmFactory;
-public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonitorMBean {
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class SonarQubeSection extends BaseSectionMBean implements SonarQubeSectionMBean {
private static final Joiner COMMA_JOINER = Joiner.on(", ");
@@ -51,7 +52,7 @@ public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonit
private final ServerLogging serverLogging;
private final ServerIdLoader serverIdLoader;
- public SonarQubeMonitor(Configuration config, SecurityRealmFactory securityRealmFactory,
+ public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory,
IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
ServerIdLoader serverIdLoader) {
this.config = config;
@@ -118,39 +119,31 @@ public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonit
}
@Override
- public Map<String, Object> attributes() {
- Map<String, Object> attributes = new LinkedHashMap<>();
- completeWithServerIdAttributes(attributes);
- attributes.put("Version", getVersion());
- addIfNotNull("External User Authentication", getExternalUserAuthentication(), attributes);
- addIfNotEmpty("Accepted external identity providers", getEnabledIdentityProviders(), attributes);
- addIfNotEmpty("External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders(), attributes);
- attributes.put("Force authentication", getForceAuthentication());
- attributes.put("Official Distribution", isOfficialDistribution());
- attributes.put("Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
- attributes.put("Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
- attributes.put("Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
- attributes.put("Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
- attributes.put("Logs Level", getLogLevel());
- return attributes;
- }
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName(name());
- private void completeWithServerIdAttributes(Map<String, Object> attributes) {
serverIdLoader.get().ifPresent(serverId -> {
- attributes.put("Server ID", serverId.getId());
- attributes.put("Server ID validated", serverId.isValid());
+ setAttribute(protobuf, "Server ID", serverId.getId());
+ setAttribute(protobuf, "Server ID validated", serverId.isValid());
});
+ setAttribute(protobuf, "Version", getVersion());
+ setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
+ addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
+ addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders());
+ setAttribute(protobuf, "Force authentication", getForceAuthentication());
+ setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
+ setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
+ setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
+ setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
+ setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
+ setAttribute(protobuf, "Logs Level", getLogLevel());
+ return protobuf.build();
}
- private static void addIfNotNull(String key, @Nullable String value, Map<String, Object> attributes) {
- if (value != null) {
- attributes.put(key, value);
- }
- }
-
- private static void addIfNotEmpty(String key, List<String> values, Map<String, Object> attributes) {
- if (!values.isEmpty()) {
- attributes.put(key, COMMA_JOINER.join(values));
+ 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/SonarQubeMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java
index 9a74127dd9a..1a562a6bc30 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java
@@ -21,7 +21,7 @@ package org.sonar.server.platform.monitoring;
import javax.annotation.CheckForNull;
-public interface SonarQubeMonitorMBean {
+public interface SonarQubeSectionMBean {
@CheckForNull
String getServerId();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java
deleted file mode 100644
index 94e02989a6b..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.lang.management.ClassLoadingMXBean;
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.RuntimeMXBean;
-import java.lang.management.ThreadMXBean;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import org.sonar.api.utils.System2;
-
-import static org.sonar.api.utils.DateUtils.formatDateTime;
-
-/**
- * JVM runtime information. Not exported as a MXBean because these informations
- * are natively provided.
- */
-public class SystemMonitor implements Monitor {
- private final System2 system;
-
- public SystemMonitor() {
- this(System2.INSTANCE);
- }
-
- SystemMonitor(System2 system) {
- this.system = system;
- }
-
- @Override
- public String name() {
- return "System";
- }
-
- @Override
- public Map<String, Object> attributes() {
- Map<String, Object> attributes = new LinkedHashMap<>();
- attributes.put("System Date", formatDateTime(new Date(system.now())));
- attributes.put("Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
- attributes.put("JVM Vendor", runtimeMXBean().getVmVendor());
- attributes.put("JVM Name", runtimeMXBean().getVmName());
- attributes.put("JVM Version", runtimeMXBean().getVmVersion());
- attributes.put("Processors", runtime().availableProcessors());
- attributes.put("System Classpath", runtimeMXBean().getClassPath());
- attributes.put("BootClassPath", runtimeMXBean().getBootClassPath());
- attributes.put("Library Path", runtimeMXBean().getLibraryPath());
- attributes.put("Total Memory", formatMemory(runtime().totalMemory()));
- attributes.put("Free Memory", formatMemory(runtime().freeMemory()));
- attributes.put("Max Memory", formatMemory(runtime().maxMemory()));
- attributes.put("Heap", memoryMXBean().getHeapMemoryUsage().toString());
- attributes.put("Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
- attributes.put("System Load Average", String.format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
- attributes.put("Loaded Classes", classLoadingMXBean().getLoadedClassCount());
- attributes.put("Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
- attributes.put("Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
- attributes.put("Threads", threadMXBean().getThreadCount());
- attributes.put("Threads Peak", threadMXBean().getPeakThreadCount());
- attributes.put("Daemon Thread", threadMXBean().getDaemonThreadCount());
- return attributes;
- }
-
- private static RuntimeMXBean runtimeMXBean() {
- return ManagementFactory.getRuntimeMXBean();
- }
-
- private static Runtime runtime() {
- return Runtime.getRuntime();
- }
-
- private static MemoryMXBean memoryMXBean() {
- return ManagementFactory.getMemoryMXBean();
- }
-
- private static ClassLoadingMXBean classLoadingMXBean() {
- return ManagementFactory.getClassLoadingMXBean();
- }
-
- private static ThreadMXBean threadMXBean() {
- return ManagementFactory.getThreadMXBean();
- }
-
- private static String formatMemory(long memoryInBytes) {
- return String.format("%d MB", memoryInBytes / 1_000_000);
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
new file mode 100644
index 00000000000..bbd623f52b9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.lang.management.ClassLoadingMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.lang.management.ThreadMXBean;
+import java.util.Date;
+import org.sonar.api.utils.System2;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static java.lang.String.format;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+/**
+ * JVM runtime information. Not exported as a MXBean because these information
+ * are natively provided.
+ */
+public class SystemSection implements SystemInfoSection {
+ private final System2 system;
+
+ public SystemSection() {
+ this(System2.INSTANCE);
+ }
+
+ SystemSection(System2 system) {
+ this.system = system;
+ }
+
+ @Override
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName("System");
+
+ setAttribute(protobuf, "System Date", formatDateTime(new Date(system.now())));
+ setAttribute(protobuf, "Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
+ setAttribute(protobuf, "JVM Vendor", runtimeMXBean().getVmVendor());
+ setAttribute(protobuf, "JVM Name", runtimeMXBean().getVmName());
+ setAttribute(protobuf, "JVM Version", runtimeMXBean().getVmVersion());
+ setAttribute(protobuf, "Processors", runtime().availableProcessors());
+ setAttribute(protobuf, "System Classpath", runtimeMXBean().getClassPath());
+ setAttribute(protobuf, "BootClassPath", runtimeMXBean().getBootClassPath());
+ setAttribute(protobuf, "Library Path", runtimeMXBean().getLibraryPath());
+ setAttribute(protobuf, "Total Memory", formatMemory(runtime().totalMemory()));
+ setAttribute(protobuf, "Free Memory", formatMemory(runtime().freeMemory()));
+ setAttribute(protobuf, "Max Memory", formatMemory(runtime().maxMemory()));
+ setAttribute(protobuf, "Heap", memoryMXBean().getHeapMemoryUsage().toString());
+ setAttribute(protobuf, "Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
+ setAttribute(protobuf, "System Load Average", format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
+ setAttribute(protobuf, "Loaded Classes", classLoadingMXBean().getLoadedClassCount());
+ setAttribute(protobuf, "Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
+ setAttribute(protobuf, "Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
+ setAttribute(protobuf, "Threads", threadMXBean().getThreadCount());
+ setAttribute(protobuf, "Threads Peak", threadMXBean().getPeakThreadCount());
+ setAttribute(protobuf, "Daemon Thread", threadMXBean().getDaemonThreadCount());
+ return protobuf.build();
+ }
+
+ private static RuntimeMXBean runtimeMXBean() {
+ return ManagementFactory.getRuntimeMXBean();
+ }
+
+ private static Runtime runtime() {
+ return Runtime.getRuntime();
+ }
+
+ private static MemoryMXBean memoryMXBean() {
+ return ManagementFactory.getMemoryMXBean();
+ }
+
+ private static ClassLoadingMXBean classLoadingMXBean() {
+ return ManagementFactory.getClassLoadingMXBean();
+ }
+
+ private static ThreadMXBean threadMXBean() {
+ return ManagementFactory.getThreadMXBean();
+ }
+
+ private static String formatMemory(long memoryInBytes) {
+ return format("%d MB", memoryInBytes / 1_000_000);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
new file mode 100644
index 00000000000..c565219240f
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.process.systeminfo.JvmPropertiesSection;
+import org.sonar.process.systeminfo.JvmStateSection;
+import org.sonar.server.platform.ws.ClusterInfoAction;
+import org.sonar.server.platform.ws.StandaloneInfoAction;
+
+public class WebSystemInfoModule {
+
+ private WebSystemInfoModule() {
+ // do not instantiate
+ }
+
+ public static Object[] forStandaloneMode() {
+ return new Object[] {
+ new JvmPropertiesSection("Web JVM Properties"),
+ new JvmStateSection("Web JVM State"),
+ DatabaseSection.class,
+ EsSection.class,
+ PluginsSection.class,
+ SettingsSection.class,
+ SonarQubeSection.class,
+ SystemSection.class,
+
+ StandaloneInfoAction.class
+ };
+ }
+
+ public static Object[] forClusterMode() {
+ return new Object[] {
+ new JvmPropertiesSection("Web JVM Properties"),
+ new JvmStateSection("Web JVM State"),
+ DatabaseSection.class,
+ EsSection.class,
+ PluginsSection.class,
+
+ ClusterInfoAction.class
+ };
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 46d62bdf812..b69534bc605 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -108,19 +108,13 @@ import org.sonar.server.platform.BackendCleanup;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.platform.SettingsChangeNotifier;
-import org.sonar.server.platform.monitoring.DatabaseMonitor;
-import org.sonar.server.platform.monitoring.EsMonitor;
-import org.sonar.server.platform.monitoring.JvmPropsMonitor;
-import org.sonar.server.platform.monitoring.PluginsMonitor;
-import org.sonar.server.platform.monitoring.SettingsMonitor;
-import org.sonar.server.platform.monitoring.SonarQubeMonitor;
-import org.sonar.server.platform.monitoring.SystemMonitor;
+import org.sonar.server.platform.monitoring.WebSystemInfoModule;
import org.sonar.server.platform.web.WebPagesFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
import org.sonar.server.platform.ws.ChangeLogLevelAction;
import org.sonar.server.platform.ws.DbMigrationStatusAction;
import org.sonar.server.platform.ws.HealthActionModule;
-import org.sonar.server.platform.ws.InfoActionModule;
+import org.sonar.server.platform.ws.InfoAction;
import org.sonar.server.platform.ws.L10nWs;
import org.sonar.server.platform.ws.LogsAction;
import org.sonar.server.platform.ws.MigrateDbAction;
@@ -192,7 +186,9 @@ import org.sonar.server.source.ws.LinesAction;
import org.sonar.server.source.ws.RawAction;
import org.sonar.server.source.ws.ScmAction;
import org.sonar.server.source.ws.SourcesWs;
-import org.sonar.server.telemetry.TelemetryModule;
+import org.sonar.server.telemetry.TelemetryClient;
+import org.sonar.server.telemetry.TelemetryDaemon;
+import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.test.index.TestIndex;
import org.sonar.server.test.index.TestIndexDefinition;
import org.sonar.server.test.index.TestIndexer;
@@ -486,17 +482,9 @@ public class PlatformLevel4 extends PlatformLevel {
// System
ServerLogging.class,
RestartAction.class,
- InfoActionModule.class,
PingAction.class,
UpgradesAction.class,
StatusAction.class,
- SystemMonitor.class,
- SettingsMonitor.class,
- SonarQubeMonitor.class,
- EsMonitor.class,
- PluginsMonitor.class,
- JvmPropsMonitor.class,
- DatabaseMonitor.class,
MigrateDbAction.class,
LogsAction.class,
ChangeLogLevelAction.class,
@@ -548,9 +536,14 @@ public class PlatformLevel4 extends PlatformLevel {
RecoveryIndexer.class,
ProjectIndexersImpl.class);
- addIfStartupLeader(
- // Telemetry
- TelemetryModule.class);
+
+ // telemetry
+ add(TelemetryDataLoader.class);
+ addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);
+
+ // system info
+ add(InfoAction.class);
+ addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());
addAll(level4AddedComponents);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
index 3f543c4e8f3..447ec555b28 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
@@ -19,15 +19,15 @@
*/
package org.sonar.server.platform.ws;
-import java.util.Map;
+import java.util.Arrays;
import java.util.Optional;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.ce.http.CeHttpClient;
+import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.platform.monitoring.Monitor;
import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.user.UserSession;
@@ -40,14 +40,14 @@ public class InfoAction implements SystemWsAction {
private final UserSession userSession;
private final CeHttpClient ceHttpClient;
- private final Monitor[] monitors;
+ private final SystemInfoSection[] systemInfoSections;
private final TelemetryDataLoader statistics;
- public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, Monitor... monitors) {
+ public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, SystemInfoSection... systemInfoSections) {
this.userSession = userSession;
this.ceHttpClient = ceHttpClient;
this.statistics = statistics;
- this.monitors = monitors;
+ this.systemInfoSections = systemInfoSections;
}
@Override
@@ -66,32 +66,19 @@ public class InfoAction implements SystemWsAction {
public void handle(Request request, Response response) {
userSession.checkIsSystemAdministrator();
- JsonWriter json = response.newJsonWriter();
- writeJson(json);
- json.close();
+ try (JsonWriter json = response.newJsonWriter()) {
+ writeJson(json);
+ }
}
private void writeJson(JsonWriter json) {
json.beginObject();
- for (Monitor monitor : monitors) {
- Map<String, Object> attributes = monitor.attributes();
- json.name(monitor.name());
- json.beginObject();
- for (Map.Entry<String, Object> attribute : attributes.entrySet()) {
- json.name(attribute.getKey()).valueObject(attribute.getValue());
- }
- json.endObject();
- }
+ Arrays.stream(systemInfoSections)
+ .map(SystemInfoSection::toProtobuf)
+ .forEach(section -> sectionToJson(section, json));
Optional<ProtobufSystemInfo.SystemInfo> ceSysInfo = ceHttpClient.retrieveSystemInfo();
if (ceSysInfo.isPresent()) {
- for (ProtobufSystemInfo.Section section : ceSysInfo.get().getSectionsList()) {
- json.name(section.getName());
- json.beginObject();
- for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
- writeAttribute(json, attribute);
- }
- json.endObject();
- }
+ ceSysInfo.get().getSectionsList().forEach(section -> sectionToJson(section, json));
}
writeStatistics(json);
json.endObject();
@@ -102,19 +89,28 @@ public class InfoAction implements SystemWsAction {
writeTelemetryData(json, statistics.load());
}
- private static void writeAttribute(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
+ private static void sectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
+ json.name(section.getName());
+ json.beginObject();
+ for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+ attributeToJson(json, attribute);
+ }
+ json.endObject();
+ }
+
+ private static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
switch (attribute.getValueCase()) {
case BOOLEAN_VALUE:
- json.name(attribute.getKey()).valueObject(attribute.getBooleanValue());
+ json.prop(attribute.getKey(), attribute.getBooleanValue());
break;
case LONG_VALUE:
- json.name(attribute.getKey()).valueObject(attribute.getLongValue());
+ json.prop(attribute.getKey(), attribute.getLongValue());
break;
case DOUBLE_VALUE:
- json.name(attribute.getKey()).valueObject(attribute.getDoubleValue());
+ json.prop(attribute.getKey(), attribute.getDoubleValue());
break;
case STRING_VALUE:
- json.name(attribute.getKey()).valueObject(attribute.getStringValue());
+ json.prop(attribute.getKey(), attribute.getStringValue());
break;
case VALUE_NOT_SET:
break;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java
index 0218ac4e72c..31dc349c24f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java
@@ -28,9 +28,9 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
-public class BaseMonitorMBeanTest {
+public class BaseSectionMBeanTest {
- FakeMonitor underTest = new FakeMonitor();
+ private FakeSection underTest = new FakeSection();
@Test
public void test_registration() throws Exception {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java
index 5489e46b259..f4139c81f07 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java
@@ -19,25 +19,27 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-public class DatabaseMonitorTest {
+public class DatabaseSectionTest {
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
private DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
- private DatabaseMonitor underTest = new DatabaseMonitor(databaseVersion, dbTester.getDbClient());
+ private DatabaseSection underTest = new DatabaseSection(databaseVersion, dbTester.getDbClient());
@Before
public void setUp() throws Exception {
@@ -51,16 +53,16 @@ public class DatabaseMonitorTest {
@Test
public void db_info() {
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes.get("Database")).isEqualTo("H2");
- assertThat(attributes.get("Database Version").toString()).startsWith("1.");
- assertThat(attributes.get("Username")).isEqualTo("SONAR");
- assertThat(attributes.get("Driver Version").toString()).startsWith("1.");
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThatAttributeIs(section, "Database", "H2");
+ assertThat(attribute(section, "Database Version").getStringValue()).startsWith("1.");
+ assertThatAttributeIs(section, "Username", "SONAR");
+ assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("1.");
}
@Test
public void pool_info() {
- Map<String, Object> attributes = underTest.attributes();
- assertThat((int) attributes.get("Pool Max Connections")).isGreaterThan(0);
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java
index 345d23793cc..a55ae819df3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java
@@ -19,12 +19,12 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.Map;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.index.IssueIndexDefinition;
@@ -32,13 +32,15 @@ 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 EsMonitorTest {
+public class EsSectionTest {
@Rule
public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
- private EsMonitor underTest = new EsMonitor(esTester.client());
+ private EsSection underTest = new EsSection(esTester.client());
@Test
public void name() {
@@ -46,67 +48,54 @@ public class EsMonitorTest {
}
@Test
- public void cluster_attributes() {
- Map<String, Object> attributes = underTest.attributes();
+ public void es_state() {
assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name());
- assertThat(attributes.get("State")).isEqualTo(ClusterHealthStatus.GREEN);
- assertThat(attributes.get("Number of Nodes")).isEqualTo(1);
+ assertThatAttributeIs(underTest.toProtobuf(), "State", ClusterHealthStatus.GREEN.name());
}
@Test
public void node_attributes() {
- Map<String, Object> attributes = underTest.attributes();
- Map nodesAttributes = (Map) attributes.get("Nodes");
-
- // one node
- assertThat(nodesAttributes).hasSize(1);
- Map nodeAttributes = (Map) nodesAttributes.values().iterator().next();
- assertThat(nodeAttributes.get("Type")).isEqualTo("Master");
- assertThat(nodeAttributes.get("Store Size")).isNotNull();
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThat(attribute(section, "Store Size")).isNotNull();
}
@Test
public void index_attributes() {
- Map<String, Object> attributes = underTest.attributes();
- Map indicesAttributes = (Map) attributes.get("Indices");
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
// one index "issues"
- Map indexAttributes = (Map) indicesAttributes.get(IssueIndexDefinition.INDEX_TYPE_ISSUE.getIndex());
- assertThat(indexAttributes.get("Docs")).isEqualTo(0L);
- assertThat((int) indexAttributes.get("Shards")).isGreaterThan(0);
- assertThat(indexAttributes.get("Store Size")).isNotNull();
+ 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);
- EsMonitor underTest = new EsMonitor(esClientMock);
+ EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).hasSize(1);
- assertThat(attributes.get("State")).isEqualTo("RuntimeException with no cause");
+ 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);
- EsMonitor underTest = new EsMonitor(esClientMock);
+ EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).hasSize(1);
- assertThat(attributes.get("State")).isEqualTo("RuntimeException with cause not ES");
+ 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);
- EsMonitor underTest = new EsMonitor(esClientMock);
+ EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).hasSize(1);
- assertThat(attributes.get("State")).isEqualTo("some cause message");
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThatAttributeIs(section, "State", "some cause message");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java
index 959438b9ccf..7018c1e8aaa 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java
@@ -19,10 +19,9 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.Collections;
-import java.util.Map;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean {
+public class FakeSection extends BaseSectionMBean implements FakeSectionMBean {
@Override
public int getFake() {
@@ -35,7 +34,7 @@ public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean {
}
@Override
- public Map<String, Object> attributes() {
- return Collections.emptyMap();
+ public ProtobufSystemInfo.Section toProtobuf() {
+ return ProtobufSystemInfo.Section.newBuilder().build();
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java
index d355b62cde3..6421859d540 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java
@@ -19,6 +19,6 @@
*/
package org.sonar.server.platform.monitoring;
-public interface FakeMonitorMBean {
+public interface FakeSectionMBean {
int getFake();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java
index 278f8691e96..58783258e7e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java
@@ -20,49 +20,43 @@
package org.sonar.server.platform.monitoring;
import java.util.Arrays;
-import java.util.Map;
import org.junit.Test;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.updatecenter.common.Version;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-public class PluginsMonitorTest {
+public class PluginsSectionTest {
- PluginRepository repo = mock(PluginRepository.class);
- PluginsMonitor underTest = new PluginsMonitor(repo);
+ private PluginRepository repo = mock(PluginRepository.class);
+ private PluginsSection underTest = new PluginsSection(repo);
@Test
public void name() {
- assertThat(underTest.name()).isEqualTo("Plugins");
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("Plugins");
}
@Test
public void plugin_name_and_version() {
when(repo.getPluginInfos()).thenReturn(Arrays.asList(
new PluginInfo("key-1")
- .setName("plugin-1")
+ .setName("Plugin 1")
.setVersion(Version.create("1.1")),
new PluginInfo("key-2")
- .setName("plugin-2")
+ .setName("Plugin 2")
.setVersion(Version.create("2.2")),
new PluginInfo("no-version")
.setName("No Version")));
- Map<String, Object> attributes = underTest.attributes();
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
- assertThat(attributes).containsKeys("key-1", "key-2");
- assertThat((Map) attributes.get("key-1"))
- .containsEntry("Name", "plugin-1")
- .containsEntry("Version", "1.1");
- assertThat((Map) attributes.get("key-2"))
- .containsEntry("Name", "plugin-2")
- .containsEntry("Version", "2.2");
- assertThat((Map) attributes.get("no-version"))
- .containsEntry("Name", "No Version")
- .doesNotContainKey("Version");
+ assertThatAttributeIs(section, "key-1", "1.1 [Plugin 1]");
+ assertThatAttributeIs(section, "key-2", "2.2 [Plugin 2]");
+ assertThatAttributeIs(section, "no-version", "[No Version]");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java
index 9c1edb795a2..b5f5c14dadc 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java
@@ -19,53 +19,57 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.SortedMap;
import org.junit.Test;
import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import static org.apache.commons.lang.StringUtils.repeat;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-public class SettingsMonitorTest {
+public class SettingsSectionTest {
private static final String PASSWORD_PROPERTY = "sonar.password";
- PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
- Settings settings = new MapSettings(defs);
- SettingsMonitor underTest = new SettingsMonitor(settings);
+ private PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
+ private Settings settings = new MapSettings(defs);
+ private SettingsSection underTest = new SettingsSection(settings);
@Test
public void return_properties_and_sort_by_key() {
settings.setProperty("foo", "foo value");
settings.setProperty("bar", "bar value");
- SortedMap<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsExactly(entry("bar", "bar value"), entry("foo", "foo value"));
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "bar", "bar value");
+ assertThatAttributeIs(protobuf, "foo", "foo value");
}
@Test
public void truncate_long_property_values() {
settings.setProperty("foo", repeat("abcde", 1_000));
- String value = (String) underTest.attributes().get("foo");
- assertThat(value).hasSize(SettingsMonitor.MAX_VALUE_LENGTH).startsWith("abcde");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ String value = attribute(protobuf, "foo").getStringValue();
+ assertThat(value).hasSize(SettingsSection.MAX_VALUE_LENGTH).startsWith("abcde");
}
@Test
public void exclude_password_properties() {
settings.setProperty(PASSWORD_PROPERTY, "abcde");
- assertThat(underTest.attributes()).isEmpty();
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThat(attribute(protobuf, PASSWORD_PROPERTY)).isNull();
}
@Test
public void test_monitor_name() {
- assertThat(underTest.name()).isEqualTo("Settings");
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("Settings");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
index e967f10d8de..9817a2ec101 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
@@ -20,7 +20,6 @@
package org.sonar.server.platform.monitoring;
import java.io.File;
-import java.util.Map;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
@@ -31,6 +30,7 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.platform.Server;
import org.sonar.api.security.SecurityRealm;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.authentication.IdentityProviderRepositoryRule;
import org.sonar.server.authentication.TestIdentityProvider;
import org.sonar.server.platform.ServerId;
@@ -39,11 +39,12 @@ import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.SecurityRealmFactory;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-public class SonarQubeMonitorTest {
+public class SonarQubeSectionTest {
private static final String SERVER_ID_PROPERTY = "Server ID";
private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
@@ -54,13 +55,13 @@ public class SonarQubeMonitorTest {
@Rule
public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
- MapSettings settings = new MapSettings();
- Server server = mock(Server.class);
- ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
- ServerLogging serverLogging = mock(ServerLogging.class);
- SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
+ 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);
- SonarQubeMonitor underTest = new SonarQubeMonitor(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
+ private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
serverLogging, serverIdLoader);
@Before
@@ -80,7 +81,7 @@ public class SonarQubeMonitorTest {
when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
assertThat(underTest.getServerId()).isEqualTo("ABC");
- when(serverIdLoader.getRaw()).thenReturn(Optional.<String>empty());
+ when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
assertThat(underTest.getServerId()).isNull();
}
@@ -88,35 +89,38 @@ public class SonarQubeMonitorTest {
public void attributes_contain_information_about_valid_server_id() {
when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, true));
+ 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)));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, 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.<ServerId>empty());
+ when(serverIdLoader.get()).thenReturn(Optional.empty());
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).doesNotContainKeys(SERVER_ID_PROPERTY, SERVER_ID_VALIDATED_PROPERTY);
+ 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, SonarQubeMonitor.BRANDING_FILE_PATH), "1.2");
+ FileUtils.write(new File(rootDir, SonarQubeSection.BRANDING_FILE_PATH), "1.2");
when(server.getRootDir()).thenReturn(rootDir);
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("Official Distribution", Boolean.TRUE);
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Official Distribution", true);
}
@Test
@@ -125,14 +129,14 @@ public class SonarQubeMonitorTest {
// branding file is missing
when(server.getRootDir()).thenReturn(rootDir);
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("Official Distribution", Boolean.FALSE);
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Official Distribution", false);
}
@Test
public void get_log_level() throws Exception {
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("Logs Level", "DEBUG");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Logs Level", "DEBUG");
}
@Test
@@ -141,16 +145,16 @@ public class SonarQubeMonitorTest {
when(realm.getName()).thenReturn("LDAP");
when(securityRealmFactory.getRealm()).thenReturn(realm);
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("External User Authentication", "LDAP");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "External User Authentication", "LDAP");
}
@Test
public void no_realm() throws Exception {
when(securityRealmFactory.getRealm()).thenReturn(null);
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).doesNotContainKey("External User Authentication");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThat(attribute(protobuf, "External User Authentication")).isNull();
}
@Test
@@ -168,8 +172,8 @@ public class SonarQubeMonitorTest {
.setName("Disabled")
.setEnabled(false));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("Accepted external identity providers", "Bitbucket, GitHub");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub");
}
@Test
@@ -190,7 +194,7 @@ public class SonarQubeMonitorTest {
.setEnabled(false)
.setAllowsUsersToSignUp(true));
- Map<String, Object> attributes = underTest.attributes();
- assertThat(attributes).containsEntry("External identity providers whose users are allowed to sign themselves up", "GitHub");
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java
new file mode 100644
index 00000000000..bd02658896f
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class SystemInfoTesting {
+
+ private SystemInfoTesting() {
+ // do not instantiate
+ }
+
+ public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, String expectedValue) {
+ ProtobufSystemInfo.Attribute value = attribute(section, key);
+ assertThat(value).as(key).isNotNull();
+ assertThat(value.getStringValue()).isEqualTo(expectedValue);
+ }
+
+ public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, boolean expectedValue) {
+ ProtobufSystemInfo.Attribute value = attribute(section, key);
+ assertThat(value).as(key).isNotNull();
+ assertThat(value.getBooleanValue()).isEqualTo(expectedValue);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
index b48649679ac..94378667a59 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
@@ -19,24 +19,26 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.Map;
import org.junit.Test;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
-public class JvmPropsMonitorTest {
+public class SystemSectionTest {
- JvmPropsMonitor underTest = new JvmPropsMonitor();
+ private SystemSection underTest = new SystemSection();
@Test
- public void name_is_not_empty() {
- assertThat(underTest.name()).isNotEmpty();
+ public void name() {
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("System");
}
@Test
- public void attributes() {
- Map<String, Object> attributes = underTest.attributes();
+ public void system_properties() {
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
- assertThat(attributes).containsKeys("java.vm.vendor", "os.name");
+ assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty();
+ assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java
index 6c7b568cba1..a1bb583a67f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java
@@ -19,24 +19,23 @@
*/
package org.sonar.server.platform.monitoring;
-import java.util.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
-public class SystemMonitorTest {
-
- SystemMonitor underTest = new SystemMonitor();
+public class WebSystemInfoModuleTest {
@Test
- public void name() {
- assertThat(underTest.name()).isEqualTo("System");
+ public void test_forStandaloneMode() {
+ assertThat(WebSystemInfoModule.forStandaloneMode())
+ .isNotEmpty()
+ .doesNotContainNull();
}
@Test
- public void system_properties() {
- Map<String, Object> attributes = underTest.attributes();
-
- assertThat(attributes).containsKeys("System Date", "Processors");
+ public void test_forClusterMode() {
+ assertThat(WebSystemInfoModule.forClusterMode())
+ .isNotEmpty()
+ .doesNotContainNull();
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
index b90037f812f..9c02608fddd 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
@@ -19,8 +19,6 @@
*/
package org.sonar.server.platform.ws;
-import java.util.LinkedHashMap;
-import java.util.Map;
import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
@@ -28,8 +26,9 @@ import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.sonar.ce.http.CeHttpClient;
import org.sonar.ce.http.CeHttpClientImpl;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.platform.monitoring.Monitor;
import org.sonar.server.telemetry.TelemetryData;
import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.tester.UserSessionRule;
@@ -40,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
public class InfoActionTest {
@Rule
@@ -48,12 +48,12 @@ public class InfoActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private Monitor monitor1 = mock(Monitor.class);
- private Monitor monitor2 = mock(Monitor.class);
+ private SystemInfoSection section1 = mock(SystemInfoSection.class);
+ private SystemInfoSection section2 = mock(SystemInfoSection.class);
private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
private TelemetryDataLoader statistics = mock(TelemetryDataLoader.class);
- private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, monitor1, monitor2);
+ private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, section1, section2);
private WsActionTester ws = new WsActionTester(underTest);
@Test
@@ -84,22 +84,23 @@ public class InfoActionTest {
public void write_json() {
logInAsSystemAdministrator();
- Map<String, Object> attributes1 = new LinkedHashMap<>();
- attributes1.put("foo", "bar");
- Map<String, Object> attributes2 = new LinkedHashMap<>();
- attributes2.put("one", 1);
- attributes2.put("two", 2);
- when(monitor1.name()).thenReturn("Monitor One");
- when(monitor1.attributes()).thenReturn(attributes1);
- when(monitor2.name()).thenReturn("Monitor Two");
- when(monitor2.attributes()).thenReturn(attributes2);
+ ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder()
+ .setName("Section One");
+ setAttribute(attributes1, "foo", "bar");
+ when(section1.toProtobuf()).thenReturn(attributes1.build());
+
+ ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder()
+ .setName("Section Two");
+ setAttribute(attributes2, "one", 1);
+ setAttribute(attributes2, "two", 2);
+ when(section2.toProtobuf()).thenReturn(attributes2.build());
when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty());
when(statistics.load()).thenReturn(mock(TelemetryData.class));
TestResponse response = ws.newRequest().execute();
- // response does not contain empty "Monitor Three"
+ // response does not contain empty "Section Three"
verify(statistics).load();
- assertThat(response.getInput()).isEqualTo("{\"Monitor One\":{\"foo\":\"bar\"},\"Monitor Two\":{\"one\":1,\"two\":2}," +
+ assertThat(response.getInput()).isEqualTo("{\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
"\"Statistics\":{\"plugins\":{},\"userCount\":0,\"projectCount\":0,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":{},\"nclocByLanguage\":{}}}");
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java
deleted file mode 100644
index 3edc6524aa8..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.telemetry;
-
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
-
-public class TelemetryModuleTest {
- @Test
- public void verify_count_of_added_components() {
- ComponentContainer container = new ComponentContainer();
-
- new TelemetryModule().configure(container);
-
- assertThat(container.size()).isEqualTo(2 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
- }
-}
diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts
index 93eeaaf5b77..1b4fd125a97 100644
--- a/server/sonar-web/src/main/js/api/system.ts
+++ b/server/sonar-web/src/main/js/api/system.ts
@@ -18,21 +18,56 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, post } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
-export function setLogLevel(level: string): Promise<void> {
- return post('/api/system/change_log_level', { level });
+export type SysValue = boolean | string | number | HealthType | SysValueObject | SysValueArray;
+export interface SysValueObject {
+ [key: string]: SysValue;
}
+export interface SysValueArray extends Array<SysValue> {}
-export function getSystemInfo(): Promise<any> {
- return getJSON('/api/system/info');
+export interface SysInfoSection {
+ [sectionName: string]: SysValueObject;
+}
+
+export enum HealthType {
+ RED = 'RED',
+ YELLOW = 'YELLOW',
+ GREEN = 'GREEN'
+}
+
+export interface HealthCause extends SysValueObject {
+ message: string;
+}
+
+export interface NodeInfo extends SysValueObject {
+ Name: string;
+ Health: HealthType;
+ 'Health Causes': HealthCause[];
+}
+
+export interface SysInfo extends SysValueObject {
+ Cluster: boolean;
+ Health: HealthType;
+ 'Health Causes': HealthCause[];
+ 'Application Nodes': NodeInfo[];
+ 'Search Nodes': NodeInfo[];
+}
+
+export function setLogLevel(level: string): Promise<void | Response> {
+ return post('/api/system/change_log_level', { level }).catch(throwGlobalError);
+}
+
+export function getSystemInfo(): Promise<SysInfo> {
+ return getJSON('/api/system/info').catch(throwGlobalError);
}
export function getSystemStatus(): Promise<any> {
return getJSON('/api/system/status');
}
-export function restart(): Promise<void> {
- return post('/api/system/restart');
+export function restart(): Promise<void | Response> {
+ return post('/api/system/restart').catch(throwGlobalError);
}
const POLLING_INTERVAL = 2000;
diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
new file mode 100644
index 00000000000..cd10c605418
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as utils from '../utils';
+
+describe('parseQuery', () => {
+ it('should correctly parse the expand array', () => {
+ expect(utils.parseQuery({})).toEqual({ expandedCards: [] });
+ expect(utils.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] });
+ });
+});
+
+describe('serializeQuery', () => {
+ it('should correctly serialize the expand array', () => {
+ expect(utils.serializeQuery({ expandedCards: [] })).toEqual({});
+ expect(utils.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' });
+ });
+});
+
+describe('groupSections', () => {
+ it('should correctly group the root field into a main section', () => {
+ expect(utils.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
+ mainSection: { foo: 'Foo', bar: 3 },
+ sections: { baz: { a: 'a' } }
+ });
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx
new file mode 100644
index 00000000000..440b117929a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import Helmet from 'react-helmet';
+import ClusterSysInfos from './ClusterSysInfos';
+import PageHeader from './PageHeader';
+import StandAloneSysInfos from './StandAloneSysInfos';
+import { translate } from '../../../helpers/l10n';
+import { getSystemInfo, SysInfo } from '../../../api/system';
+import { isCluster, parseQuery, Query, serializeQuery } from '../utils';
+import { RawQuery } from '../../../helpers/query';
+import '../styles.css';
+
+interface Props {
+ location: { pathname: string; query: RawQuery };
+}
+
+interface State {
+ loading: boolean;
+ sysInfoData?: SysInfo;
+}
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchSysInfo();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchSysInfo = () => {
+ this.setState({ loading: true });
+ getSystemInfo().then(
+ (sysInfoData: SysInfo) => {
+ if (this.mounted) {
+ this.setState({ loading: false, sysInfoData });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ toggleSysInfoCards = (toggledCard: string) => {
+ const query = parseQuery(this.props.location.query);
+ let expandedCards;
+ if (query.expandedCards.includes(toggledCard)) {
+ expandedCards = query.expandedCards.filter(card => card !== toggledCard);
+ } else {
+ expandedCards = query.expandedCards.concat(toggledCard);
+ }
+ this.updateQuery({ expandedCards });
+ };
+
+ updateQuery = (newQuery: Query) => {
+ const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
+ this.context.router.push({ pathname: this.props.location.pathname, query });
+ };
+
+ renderSysInfo() {
+ const { sysInfoData } = this.state;
+ if (!sysInfoData) {
+ return null;
+ }
+
+ const query = parseQuery(this.props.location.query);
+ if (isCluster(sysInfoData)) {
+ return (
+ <ClusterSysInfos
+ sysInfoData={sysInfoData}
+ expandedCards={query.expandedCards}
+ toggleCard={this.toggleSysInfoCards}
+ />
+ );
+ }
+ return <StandAloneSysInfos sysInfoData={sysInfoData} />;
+ }
+
+ render() {
+ const { loading, sysInfoData } = this.state;
+ // TODO Correctly get logLevel, we are not sure yet how we want to do it for cluster mode
+ return (
+ <div className="page page-limited">
+ <Helmet title={translate('system_info.page')} />
+ <PageHeader
+ loading={loading}
+ isCluster={isCluster(sysInfoData)}
+ logLevel="INFO"
+ showActions={sysInfoData != undefined}
+ />
+ {this.renderSysInfo()}
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
new file mode 100644
index 00000000000..3065a0be66d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
@@ -0,0 +1,114 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Modal from 'react-modal';
+import { setLogLevel } from '../../../api/system';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ infoMsg: string;
+ logLevel: string;
+ onChange: (level: string) => void;
+ onClose: () => void;
+}
+
+interface State {
+ newLevel: string;
+ updating: boolean;
+}
+
+const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
+
+export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = { newLevel: props.logLevel, updating: false };
+ }
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ const { newLevel } = this.state;
+ if (!this.state.updating && newLevel !== this.props.logLevel) {
+ this.setState({ updating: true });
+ setLogLevel(newLevel).then(
+ () => this.props.onChange(newLevel),
+ () => this.setState({ updating: false })
+ );
+ }
+ };
+
+ handleLevelChange = (event: React.ChangeEvent<HTMLInputElement>) =>
+ this.setState({ newLevel: event.currentTarget.value });
+
+ render() {
+ const { updating, newLevel } = this.state;
+ const header = translate('system.set_log_level');
+ const disableSubmit = updating || newLevel === this.props.logLevel;
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <form id="set-log-level-form" onSubmit={this.handleFormSubmit}>
+ <div className="modal-head">
+ <h2>{header}</h2>
+ </div>
+ <div className="modal-body">
+ {LOG_LEVELS.map(level => (
+ <p key={level} className="spacer-bottom">
+ <input
+ type="radio"
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ value={level}
+ checked={level === newLevel}
+ onChange={this.handleLevelChange}
+ />
+ {level}
+ </p>
+ ))}
+ <div className="alert alert-info spacer-top">{this.props.infoMsg}</div>
+ {newLevel !== 'INFO' && (
+ <div className="alert alert-danger spacer-top">
+ {translate('system.log_level.warning')}
+ </div>
+ )}
+ </div>
+ <div className="modal-foot">
+ {updating && <i className="spinner spacer-right" />}
+ <button disabled={disableSubmit} id="set-log-level-submit">
+ {translate('save')}
+ </button>
+ <a href="#" id="set-log-level-cancel" onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </div>
+ </form>
+ </Modal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
new file mode 100644
index 00000000000..7f68dc6771a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import HealthCard from './info-items/HealthCard';
+import { translate } from '../../../helpers/l10n';
+import {
+ getAppNodes,
+ getHealth,
+ getHealthCauses,
+ getMainCardSection,
+ getNodeName,
+ getSearchNodes,
+ ignoreInfoFields
+} from '../utils';
+import { SysInfo } from '../../../api/system';
+
+interface Props {
+ expandedCards: string[];
+ sysInfoData: SysInfo;
+ toggleCard: (toggledCard: string) => void;
+}
+
+export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
+ const mainCardName = 'System';
+ return (
+ <ul>
+ <HealthCard
+ biggerHealth={true}
+ health={getHealth(sysInfoData)}
+ healthCauses={getHealthCauses(sysInfoData)}
+ name={mainCardName}
+ onClick={toggleCard}
+ open={expandedCards.includes(mainCardName)}
+ sysInfoData={ignoreInfoFields(getMainCardSection(sysInfoData))}
+ />
+ <li className="note system-info-health-title">
+ {translate('system.application_nodes_title')}
+ </li>
+ {sortBy(getAppNodes(sysInfoData), 'name').map(node => (
+ <HealthCard
+ key={getNodeName(node)}
+ health={getHealth(node)}
+ healthCauses={getHealthCauses(node)}
+ name={getNodeName(node)}
+ onClick={toggleCard}
+ open={expandedCards.includes(getNodeName(node))}
+ sysInfoData={ignoreInfoFields(node)}
+ />
+ ))}
+ <li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
+ {sortBy(getSearchNodes(sysInfoData), 'name').map(node => (
+ <HealthCard
+ key={getNodeName(node)}
+ health={getHealth(node)}
+ healthCauses={getHealthCauses(node)}
+ name={getNodeName(node)}
+ onClick={toggleCard}
+ open={expandedCards.includes(getNodeName(node))}
+ sysInfoData={ignoreInfoFields(node)}
+ />
+ ))}
+ </ul>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
new file mode 100644
index 00000000000..1b2ef92d34a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import ChangeLogLevelForm from './ChangeLogLevelForm';
+import RestartForm from '../../../components/common/RestartForm';
+import { getBaseUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ canDownloadLogs: boolean;
+ canRestart: boolean;
+ cluster: boolean;
+ logLevel: string;
+}
+
+interface State {
+ logLevel: string;
+ openLogsLevelForm: boolean;
+ openRestartForm: boolean;
+}
+
+export default class PageActions extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ openLogsLevelForm: false,
+ openRestartForm: false,
+ logLevel: props.logLevel
+ };
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.logLevel !== this.state.logLevel) {
+ this.setState({ logLevel: nextProps.logLevel });
+ }
+ }
+
+ handleLogsLevelOpen = (event: React.SyntheticEvent<HTMLElement>) => {
+ event.preventDefault();
+ this.setState({ openLogsLevelForm: true });
+ };
+
+ handleLogsLevelChange = (logLevel: string) => {
+ this.setState({ logLevel });
+ this.handleLogsLevelClose();
+ };
+
+ handleLogsLevelClose = () => this.setState({ openLogsLevelForm: false });
+
+ handleServerRestartOpen = () => this.setState({ openRestartForm: true });
+ handleServerRestartClose = () => this.setState({ openRestartForm: false });
+
+ render() {
+ const infoUrl = getBaseUrl() + '/api/system/info';
+ const logsUrl = getBaseUrl() + '/api/system/logs';
+ return (
+ <div className="page-actions">
+ <span>
+ {translate('system.logs_level')}
+ {':'}
+ <strong className="little-spacer-left">{this.state.logLevel}</strong>
+ <a
+ id="edit-logs-level-button"
+ className="spacer-left icon-edit"
+ href="#"
+ onClick={this.handleLogsLevelOpen}
+ />
+ </span>
+ {this.props.canDownloadLogs && (
+ <div className="display-inline-block dropdown spacer-left">
+ <button data-toggle="dropdown">
+ {translate('system.download_logs')}
+ <i className="icon-dropdown little-spacer-left" />
+ </button>
+ <ul className="dropdown-menu">
+ <li>
+ <a
+ href={logsUrl + '?process=app'}
+ id="logs-link"
+ download="sonarqube_app.log"
+ target="_blank">
+ Compute Engine
+ </a>
+ </li>
+ <li>
+ <a
+ href={logsUrl + '?process=ce'}
+ id="ce-logs-link"
+ download="sonarqube_ce.log"
+ target="_blank">
+ Main Process
+ </a>
+ </li>
+ <li>
+ <a
+ href={logsUrl + '?process=es'}
+ id="es-logs-link"
+ download="sonarqube_es.log"
+ target="_blank">
+ Elasticsearch
+ </a>
+ </li>
+ <li>
+ <a
+ href={logsUrl + '?process=web'}
+ id="web-logs-link"
+ download="sonarqube_web.log"
+ target="_blank">
+ Web Server
+ </a>
+ </li>
+ </ul>
+ </div>
+ )}
+ <a
+ href={infoUrl}
+ id="download-link"
+ className="button spacer-left"
+ download="sonarqube_system_info.json"
+ target="_blank">
+ {translate('system.download_system_info')}
+ </a>
+ {this.props.canRestart && (
+ <button
+ id="restart-server-button"
+ className="spacer-left"
+ onClick={this.handleServerRestartOpen}>
+ {translate('system.restart_server')}
+ </button>
+ )}
+ {this.props.canRestart &&
+ this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
+ {this.state.openLogsLevelForm && (
+ <ChangeLogLevelForm
+ infoMsg={translate(
+ this.props.cluster ? 'system.cluster_log_level.info' : 'system.log_level.info'
+ )}
+ logLevel={this.state.logLevel}
+ onChange={this.handleLogsLevelChange}
+ onClose={this.handleLogsLevelClose}
+ />
+ )}
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
index 48ae159208b..c9d84569e30 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java
+++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
@@ -17,25 +17,34 @@
* 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 * as React from 'react';
+import PageActions from './PageActions';
+import { translate } from '../../../helpers/l10n';
-import java.util.Map;
-import org.sonar.api.server.ServerSide;
-import org.sonar.server.platform.ws.InfoAction;
-
-/**
- * Any component that is involved in the information returned by the web service api/system/info
- */
-@ServerSide
-public interface Monitor {
- /**
- * Name of section in System Info page
- */
- String name();
+interface Props {
+ isCluster: boolean;
+ loading: boolean;
+ logLevel: string;
+ showActions: boolean;
+}
- /**
- * Type of attribute values must be supported by {@link org.sonar.api.utils.text.JsonWriter#valueObject(Object)}
- * because of JSON export by {@link InfoAction}.
- */
- Map<String, Object> attributes();
+export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">{translate('system_info.page')}</h1>
+ {showActions && (
+ <PageActions
+ canDownloadLogs={!isCluster}
+ canRestart={!isCluster}
+ cluster={isCluster}
+ logLevel={logLevel}
+ />
+ )}
+ {loading && (
+ <div className="page-actions">
+ <i className="spinner" />
+ </div>
+ )}
+ </header>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
new file mode 100644
index 00000000000..2a142c58a0e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+ sysInfoData: object;
+}
+
+export default class StandAloneSysInfos extends React.PureComponent<Props> {
+ render() {
+ return <div>StandAloneSysInfos</div>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx
new file mode 100644
index 00000000000..08c177e418b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ChangeLogLevelForm from '../ChangeLogLevelForm';
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <ChangeLogLevelForm infoMsg="Foo" logLevel="INFO" onChange={() => {}} onClose={() => {}} />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should display some warning messages for non INFO levels', () => {
+ expect(
+ shallow(
+ <ChangeLogLevelForm infoMsg="Foo" logLevel="DEBUG" onChange={() => {}} onClose={() => {}} />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
new file mode 100644
index 00000000000..5b703a1a214
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ClusterSysInfos from '../ClusterSysInfos';
+import { HealthType, SysInfo } from '../../../../api/system';
+
+const sysInfoData: SysInfo = {
+ Cluster: true,
+ Health: HealthType.RED,
+ Name: 'Foo',
+ 'Health Causes': [{ message: 'Database down' }],
+ 'Application Nodes': [{ Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [] }],
+ 'Search Nodes': [{ Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [] }]
+};
+
+it('should render correctly', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <ClusterSysInfos
+ expandedCards={['System', 'Foo']}
+ sysInfoData={sysInfoData}
+ toggleCard={() => {}}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
new file mode 100644
index 00000000000..098e897ec92
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PageActions from '../PageActions';
+import { click } from '../../../../helpers/testUtils';
+
+it('should render correctly', () => {
+ expect(
+ shallow(
+ <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should render without restart and log download', () => {
+ expect(
+ shallow(
+ <PageActions canDownloadLogs={false} canRestart={false} cluster={true} logLevel="INFO" />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should open restart modal', () => {
+ const wrapper = shallow(
+ <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ );
+ click(wrapper.find('#restart-server-button'));
+ expect(wrapper.find('RestartForm')).toHaveLength(1);
+});
+
+it('should open change log level modal', () => {
+ const wrapper = shallow(
+ <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ );
+ click(wrapper.find('#edit-logs-level-button'));
+ expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
new file mode 100644
index 00000000000..f5255c7228e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PageHeader from '../PageHeader';
+
+it('should render correctly', () => {
+ expect(
+ shallow(<PageHeader isCluster={true} loading={false} logLevel="INFO" showActions={true} />)
+ ).toMatchSnapshot();
+});
+
+it('should show a loading spinner and no actions', () => {
+ expect(
+ shallow(<PageHeader isCluster={true} loading={true} logLevel="INFO" showActions={false} />)
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
new file mode 100644
index 00000000000..04cfc11d49b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
@@ -0,0 +1,194 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display some warning messages for non INFO levels 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="system.set_log_level"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <form
+ id="set-log-level-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ system.set_log_level
+ </h2>
+ </div>
+ <div
+ className="modal-body"
+ >
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={false}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="INFO"
+ />
+ INFO
+ </p>
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={true}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="DEBUG"
+ />
+ DEBUG
+ </p>
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={false}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="TRACE"
+ />
+ TRACE
+ </p>
+ <div
+ className="alert alert-info spacer-top"
+ >
+ Foo
+ </div>
+ <div
+ className="alert alert-danger spacer-top"
+ >
+ system.log_level.warning
+ </div>
+ </div>
+ <div
+ className="modal-foot"
+ >
+ <button
+ disabled={true}
+ id="set-log-level-submit"
+ >
+ save
+ </button>
+ <a
+ href="#"
+ id="set-log-level-cancel"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </div>
+ </form>
+</Modal>
+`;
+
+exports[`should render correctly 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="system.set_log_level"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <form
+ id="set-log-level-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ system.set_log_level
+ </h2>
+ </div>
+ <div
+ className="modal-body"
+ >
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={true}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="INFO"
+ />
+ INFO
+ </p>
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={false}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="DEBUG"
+ />
+ DEBUG
+ </p>
+ <p
+ className="spacer-bottom"
+ >
+ <input
+ checked={false}
+ className="spacer-right text-middle"
+ name="system.log_levels"
+ onChange={[Function]}
+ type="radio"
+ value="TRACE"
+ />
+ TRACE
+ </p>
+ <div
+ className="alert alert-info spacer-top"
+ >
+ Foo
+ </div>
+ </div>
+ <div
+ className="modal-foot"
+ >
+ <button
+ disabled={true}
+ id="set-log-level-submit"
+ >
+ save
+ </button>
+ <a
+ href="#"
+ id="set-log-level-cancel"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </div>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
new file mode 100644
index 00000000000..20ed8d541f6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ul>
+ <HealthCard
+ biggerHealth={true}
+ health="RED"
+ healthCauses={
+ Array [
+ Object {
+ "message": "Database down",
+ },
+ ]
+ }
+ name="System"
+ onClick={[Function]}
+ open={true}
+ sysInfoData={
+ Object {
+ "Name": "Foo",
+ }
+ }
+ />
+ <li
+ className="note system-info-health-title"
+ >
+ system.application_nodes_title
+ </li>
+ <HealthCard
+ health="GREEN"
+ healthCauses={Array []}
+ name="Bar"
+ onClick={[Function]}
+ open={false}
+ sysInfoData={
+ Object {
+ "Name": "Bar",
+ }
+ }
+ />
+ <li
+ className="note system-info-health-title"
+ >
+ system.search_nodes_title
+ </li>
+ <HealthCard
+ health="YELLOW"
+ healthCauses={Array []}
+ name="Baz"
+ onClick={[Function]}
+ open={false}
+ sysInfoData={
+ Object {
+ "Name": "Baz",
+ }
+ }
+ />
+</ul>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
new file mode 100644
index 00000000000..9d84700539f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
@@ -0,0 +1,126 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="page-actions"
+>
+ <span>
+ system.logs_level
+ :
+ <strong
+ className="little-spacer-left"
+ >
+ INFO
+ </strong>
+ <a
+ className="spacer-left icon-edit"
+ href="#"
+ id="edit-logs-level-button"
+ onClick={[Function]}
+ />
+ </span>
+ <div
+ className="display-inline-block dropdown spacer-left"
+ >
+ <button
+ data-toggle="dropdown"
+ >
+ system.download_logs
+ <i
+ className="icon-dropdown little-spacer-left"
+ />
+ </button>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <a
+ download="sonarqube_app.log"
+ href="/api/system/logs?process=app"
+ id="logs-link"
+ target="_blank"
+ >
+ Compute Engine
+ </a>
+ </li>
+ <li>
+ <a
+ download="sonarqube_ce.log"
+ href="/api/system/logs?process=ce"
+ id="ce-logs-link"
+ target="_blank"
+ >
+ Main Process
+ </a>
+ </li>
+ <li>
+ <a
+ download="sonarqube_es.log"
+ href="/api/system/logs?process=es"
+ id="es-logs-link"
+ target="_blank"
+ >
+ Elasticsearch
+ </a>
+ </li>
+ <li>
+ <a
+ download="sonarqube_web.log"
+ href="/api/system/logs?process=web"
+ id="web-logs-link"
+ target="_blank"
+ >
+ Web Server
+ </a>
+ </li>
+ </ul>
+ </div>
+ <a
+ className="button spacer-left"
+ download="sonarqube_system_info.json"
+ href="/api/system/info"
+ id="download-link"
+ target="_blank"
+ >
+ system.download_system_info
+ </a>
+ <button
+ className="spacer-left"
+ id="restart-server-button"
+ onClick={[Function]}
+ >
+ system.restart_server
+ </button>
+</div>
+`;
+
+exports[`should render without restart and log download 1`] = `
+<div
+ className="page-actions"
+>
+ <span>
+ system.logs_level
+ :
+ <strong
+ className="little-spacer-left"
+ >
+ INFO
+ </strong>
+ <a
+ className="spacer-left icon-edit"
+ href="#"
+ id="edit-logs-level-button"
+ onClick={[Function]}
+ />
+ </span>
+ <a
+ className="button spacer-left"
+ download="sonarqube_system_info.json"
+ href="/api/system/info"
+ id="download-link"
+ target="_blank"
+ >
+ system.download_system_info
+ </a>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
new file mode 100644
index 00000000000..2945b6df932
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ system_info.page
+ </h1>
+ <PageActions
+ canDownloadLogs={false}
+ canRestart={false}
+ cluster={true}
+ logLevel="INFO"
+ />
+</header>
+`;
+
+exports[`should show a loading spinner and no actions 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ system_info.page
+ </h1>
+ <div
+ className="page-actions"
+ >
+ <i
+ className="spinner"
+ />
+ </div>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
new file mode 100644
index 00000000000..b45205dccba
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { map } from 'lodash';
+import HealthItem from './HealthItem';
+import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon';
+import Section from './Section';
+import { HealthType, HealthCause, SysValueObject } from '../../../../api/system';
+import { groupSections } from '../../utils';
+
+interface Props {
+ biggerHealth?: boolean;
+ health: HealthType;
+ healthCauses: HealthCause[];
+ onClick: (toggledCard: string) => void;
+ open: boolean;
+ name: string;
+ sysInfoData: SysValueObject;
+}
+
+interface State {
+ hoveringDetail: boolean;
+}
+
+export default class HealthCard extends React.PureComponent<Props, State> {
+ state: State = { hoveringDetail: false };
+
+ handleClick = () => this.props.onClick(this.props.name);
+ onDetailEnter = () => this.setState({ hoveringDetail: true });
+ onDetailLeave = () => this.setState({ hoveringDetail: false });
+
+ render() {
+ const { open, sysInfoData } = this.props;
+ const { mainSection, sections } = groupSections(sysInfoData);
+ const showFields = open && mainSection && Object.keys(mainSection).length > 0;
+ const showSections = open && sections;
+ return (
+ <li
+ className={classNames('boxed-group system-info-health-card', {
+ 'no-hover': this.state.hoveringDetail
+ })}>
+ <div className="boxed-group-header" onClick={this.handleClick}>
+ <span className="system-info-health-card-title">
+ <OpenCloseIcon className="little-spacer-right" open={open} />
+ {this.props.name}
+ </span>
+ <HealthItem
+ className={classNames('pull-right', { 'big-dot': this.props.biggerHealth })}
+ health={this.props.health}
+ healthCauses={this.props.healthCauses}
+ />
+ </div>
+ {open && (
+ <div
+ className="boxed-group-inner"
+ onMouseEnter={this.onDetailEnter}
+ onMouseLeave={this.onDetailLeave}>
+ {showFields && <Section items={mainSection} />}
+ {showSections &&
+ map(sections, (section, name) => <Section key={name} items={section} name={name} />)}
+ </div>
+ )}
+ </li>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
new file mode 100644
index 00000000000..49b22b717bb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { HealthCause, HealthType } from '../../../../api/system';
+
+interface Props {
+ className?: string;
+ health: HealthType;
+ healthCause: HealthCause;
+}
+
+export default function HealthCauseItem({ className, health, healthCause }: Props) {
+ return (
+ <span
+ className={classNames(
+ 'alert',
+ health === HealthType.RED ? 'alert-danger' : 'alert-warning',
+ className
+ )}>
+ {healthCause.message}
+ </span>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
new file mode 100644
index 00000000000..a0793a81831
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import HealthCauseItem from './HealthCauseItem';
+import { HealthType, HealthCause } from '../../../../api/system';
+
+interface Props {
+ className?: string;
+ health: HealthType;
+ healthCauses?: HealthCause[];
+}
+
+export default function HealthItem({ className, health, healthCauses }: Props) {
+ const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
+ return (
+ <div className={classNames('system-info-health-info', className)}>
+ {hasHealthCauses &&
+ healthCauses!.map((cause, idx) => (
+ <HealthCauseItem key={idx} className="spacer-right" health={health} healthCause={cause} />
+ ))}
+ <span className={classNames('system-info-health-dot', health)} />
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
new file mode 100644
index 00000000000..3da339cbdf2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import SysInfoItem from './SysInfoItem';
+import { SysValueObject } from '../../../../api/system';
+
+interface Props {
+ name?: string;
+ items: SysValueObject;
+}
+
+export default function Section({ name, items }: Props) {
+ return (
+ <div className="system-info-section">
+ {name && <h4 className="spacer-bottom">{name}</h4>}
+ <table className="data zebra" id={name}>
+ <tbody>
+ {map(items, (value, name) => {
+ return (
+ <tr key={name}>
+ <td className="thin">
+ <div className="system-info-section-item-name">{name}</div>
+ </td>
+ <td style={{ wordBreak: 'break-all' }}>
+ <SysInfoItem name={name} value={value} />
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
new file mode 100644
index 00000000000..12876d75940
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import HealthItem from './HealthItem';
+import { HealthType, SysValue, SysValueObject } from '../../../../api/system';
+import { HEALTH_FIELD } from '../../utils';
+
+interface Props {
+ name: string;
+ value: SysValue;
+}
+
+export default function SysInfoItem({ name, value }: Props): JSX.Element {
+ if (name === HEALTH_FIELD) {
+ return <HealthItem className="no-margin" health={value as HealthType} />;
+ }
+ if (value instanceof Array) {
+ return <code>{JSON.stringify(value)}</code>;
+ }
+ switch (typeof value) {
+ case 'boolean':
+ return <BooleanItem value={value as boolean} />;
+ case 'object':
+ return <ObjectItem value={value as SysValueObject} />;
+ default:
+ return <code>{value}</code>;
+ }
+}
+
+function BooleanItem({ value }: { value: boolean }) {
+ if (value) {
+ return <i className="icon-check" />;
+ } else {
+ return <i className="icon-delete" />;
+ }
+}
+
+function ObjectItem({ value }: { value: SysValueObject }) {
+ return (
+ <table className="data">
+ <tbody>
+ {map(value, (value, name) => (
+ <tr key={name}>
+ <td className="thin nowrap">{name}</td>
+ <td>
+ <SysInfoItem name={name} value={value} />
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
new file mode 100644
index 00000000000..c080f444dec
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthCard from '../HealthCard';
+import { click } from '../../../../../helpers/testUtils';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+ expect(getShallowWrapper()).toMatchSnapshot();
+});
+
+it('should display the sysinfo detail', () => {
+ expect(getShallowWrapper({ biggerHealth: true, open: true })).toMatchSnapshot();
+});
+
+it('should show the sysinfo detail when the card is clicked', () => {
+ const onClick = jest.fn();
+ click(getShallowWrapper({ onClick }).find('.boxed-group-header'));
+ expect(onClick).toBeCalled();
+ expect(onClick).toBeCalledWith('Foobar');
+});
+
+it('should show a main section and multiple sub sections', () => {
+ const sysInfoData = {
+ Name: 'foo',
+ bar: 'Bar',
+ Database: { db: 'test' },
+ Elasticseach: { Elastic: 'search' }
+ };
+ expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot();
+});
+
+function getShallowWrapper(props = {}) {
+ return shallow(
+ <HealthCard
+ biggerHealth={false}
+ health={HealthType.RED}
+ healthCauses={[{ message: 'foo' }]}
+ name="Foobar"
+ onClick={() => {}}
+ open={false}
+ sysInfoData={{}}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
new file mode 100644
index 00000000000..24504f09a91
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthCauseItem from '../HealthCauseItem';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+ expect(
+ shallow(<HealthCauseItem health={HealthType.RED} healthCause={{ message: 'foo' }} />)
+ ).toMatchSnapshot();
+ expect(
+ shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause={{ message: 'foo' }} />)
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
new file mode 100644
index 00000000000..f77622b9e92
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import HealthItem from '../HealthItem';
+import { HealthType } from '../../../../../api/system';
+
+it('should render correctly', () => {
+ expect(
+ shallow(<HealthItem health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />)
+ ).toMatchSnapshot();
+});
+
+it('should not render health causes', () => {
+ expect(
+ shallow(<HealthItem health={HealthType.GREEN} healthCauses={[{ message: 'foo' }]} />)
+ ).toMatchSnapshot();
+ expect(shallow(<HealthItem health={HealthType.YELLOW} healthCauses={[]} />)).toMatchSnapshot();
+});
+
+it('should render multiple health causes', () => {
+ expect(
+ shallow(
+ <HealthItem
+ health={HealthType.YELLOW}
+ healthCauses={[{ message: 'foo' }, { message: 'bar' }]}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx
new file mode 100644
index 00000000000..2778dc855b8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Section from '../Section';
+
+it('should render correctly', () => {
+ expect(
+ shallow(<Section name="foo" items={{ foo: 1, bar: 'Bar', baz: false }} />)
+ ).toMatchSnapshot();
+});
+
+it('should not render a title', () => {
+ expect(shallow(<Section items={{ foo: 'bar' }} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx
new file mode 100644
index 00000000000..2761a2ddbf9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow, mount } from 'enzyme';
+import SysInfoItem from '../SysInfoItem';
+
+it('should render string', () => {
+ const wrapper = shallow(<SysInfoItem name="foo" value="/some/path/as/an/example" />);
+ expect(wrapper.find('code').text()).toBe('/some/path/as/an/example');
+});
+
+it('should render object', () => {
+ const wrapper = shallow(<SysInfoItem name="foo" value={{ bar: 'baz' }} />);
+ expect(wrapper.find('ObjectItem').prop('value')).toEqual({ bar: 'baz' });
+});
+
+it('should render boolean', () => {
+ const wrapper = shallow(<SysInfoItem name="foo" value={true} />);
+ expect(wrapper.find('BooleanItem').prop('value')).toBe(true);
+});
+
+it('should render health item', () => {
+ const wrapper = shallow(<SysInfoItem name="Health" value="GREEN" />);
+ expect(wrapper.find('HealthItem').prop('health')).toBe('GREEN');
+});
+
+it('should render object correctly', () => {
+ expect(
+ mount(
+ <SysInfoItem name="test" value={{ foo: 'Far', bar: { a: 1, b: 'b' }, baz: true }} />
+ ).find('ObjectItem')
+ ).toMatchSnapshot();
+});
+
+it('should render `true`', () => {
+ const wrapper = mount(<SysInfoItem name="test" value={true} />);
+ expect(wrapper.find('.icon-check')).toHaveLength(1);
+});
+
+it('should render `false`', () => {
+ const wrapper = mount(<SysInfoItem name="test" value={false} />);
+ expect(wrapper.find('.icon-delete')).toHaveLength(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
new file mode 100644
index 00000000000..724001990fa
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
@@ -0,0 +1,132 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the sysinfo detail 1`] = `
+<li
+ className="boxed-group system-info-health-card"
+>
+ <div
+ className="boxed-group-header"
+ onClick={[Function]}
+ >
+ <span
+ className="system-info-health-card-title"
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={true}
+ />
+ Foobar
+ </span>
+ <HealthItem
+ className="pull-right big-dot"
+ health="RED"
+ healthCauses={
+ Array [
+ Object {
+ "message": "foo",
+ },
+ ]
+ }
+ />
+ </div>
+ <div
+ className="boxed-group-inner"
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ />
+</li>
+`;
+
+exports[`should render correctly 1`] = `
+<li
+ className="boxed-group system-info-health-card"
+>
+ <div
+ className="boxed-group-header"
+ onClick={[Function]}
+ >
+ <span
+ className="system-info-health-card-title"
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={false}
+ />
+ Foobar
+ </span>
+ <HealthItem
+ className="pull-right"
+ health="RED"
+ healthCauses={
+ Array [
+ Object {
+ "message": "foo",
+ },
+ ]
+ }
+ />
+ </div>
+</li>
+`;
+
+exports[`should show a main section and multiple sub sections 1`] = `
+<li
+ className="boxed-group system-info-health-card"
+>
+ <div
+ className="boxed-group-header"
+ onClick={[Function]}
+ >
+ <span
+ className="system-info-health-card-title"
+ >
+ <OpenCloseIcon
+ className="little-spacer-right"
+ open={true}
+ />
+ Foobar
+ </span>
+ <HealthItem
+ className="pull-right"
+ health="RED"
+ healthCauses={
+ Array [
+ Object {
+ "message": "foo",
+ },
+ ]
+ }
+ />
+ </div>
+ <div
+ className="boxed-group-inner"
+ onMouseEnter={[Function]}
+ onMouseLeave={[Function]}
+ >
+ <Section
+ items={
+ Object {
+ "Name": "foo",
+ "bar": "Bar",
+ }
+ }
+ />
+ <Section
+ items={
+ Object {
+ "db": "test",
+ }
+ }
+ name="Database"
+ />
+ <Section
+ items={
+ Object {
+ "Elastic": "search",
+ }
+ }
+ name="Elasticseach"
+ />
+ </div>
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap
new file mode 100644
index 00000000000..3202387cf1c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<span
+ className="alert alert-danger"
+>
+ foo
+</span>
+`;
+
+exports[`should render correctly 2`] = `
+<span
+ className="alert alert-warning"
+>
+ foo
+</span>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
new file mode 100644
index 00000000000..d9fa53a2956
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
@@ -0,0 +1,68 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render health causes 1`] = `
+<div
+ className="system-info-health-info"
+>
+ <span
+ className="system-info-health-dot GREEN"
+ />
+</div>
+`;
+
+exports[`should not render health causes 2`] = `
+<div
+ className="system-info-health-info"
+>
+ <span
+ className="system-info-health-dot YELLOW"
+ />
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="system-info-health-info"
+>
+ <HealthCauseItem
+ className="spacer-right"
+ health="RED"
+ healthCause={
+ Object {
+ "message": "foo",
+ }
+ }
+ />
+ <span
+ className="system-info-health-dot RED"
+ />
+</div>
+`;
+
+exports[`should render multiple health causes 1`] = `
+<div
+ className="system-info-health-info"
+>
+ <HealthCauseItem
+ className="spacer-right"
+ health="YELLOW"
+ healthCause={
+ Object {
+ "message": "foo",
+ }
+ }
+ />
+ <HealthCauseItem
+ className="spacer-right"
+ health="YELLOW"
+ healthCause={
+ Object {
+ "message": "bar",
+ }
+ }
+ />
+ <span
+ className="system-info-health-dot YELLOW"
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap
new file mode 100644
index 00000000000..20fce92a9b7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render a title 1`] = `
+<div
+ className="system-info-section"
+>
+ <table
+ className="data zebra"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin"
+ >
+ <div
+ className="system-info-section-item-name"
+ >
+ foo
+ </div>
+ </td>
+ <td
+ style={
+ Object {
+ "wordBreak": "break-all",
+ }
+ }
+ >
+ <SysInfoItem
+ name="foo"
+ value="bar"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="system-info-section"
+>
+ <h4
+ className="spacer-bottom"
+ >
+ foo
+ </h4>
+ <table
+ className="data zebra"
+ id="foo"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin"
+ >
+ <div
+ className="system-info-section-item-name"
+ >
+ foo
+ </div>
+ </td>
+ <td
+ style={
+ Object {
+ "wordBreak": "break-all",
+ }
+ }
+ >
+ <SysInfoItem
+ name="foo"
+ value={1}
+ />
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin"
+ >
+ <div
+ className="system-info-section-item-name"
+ >
+ bar
+ </div>
+ </td>
+ <td
+ style={
+ Object {
+ "wordBreak": "break-all",
+ }
+ }
+ >
+ <SysInfoItem
+ name="bar"
+ value="Bar"
+ />
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin"
+ >
+ <div
+ className="system-info-section-item-name"
+ >
+ baz
+ </div>
+ </td>
+ <td
+ style={
+ Object {
+ "wordBreak": "break-all",
+ }
+ }
+ >
+ <SysInfoItem
+ name="baz"
+ value={false}
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap
new file mode 100644
index 00000000000..a1ff67c3506
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap
@@ -0,0 +1,180 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render object correctly 1`] = `
+Array [
+ <ObjectItem
+ value={
+ Object {
+ "bar": Object {
+ "a": 1,
+ "b": "b",
+ },
+ "baz": true,
+ "foo": "Far",
+ }
+ }
+>
+ <table
+ className="data"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ foo
+ </td>
+ <td>
+ <SysInfoItem
+ name="foo"
+ value="Far"
+ >
+ <code>
+ Far
+ </code>
+ </SysInfoItem>
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ bar
+ </td>
+ <td>
+ <SysInfoItem
+ name="bar"
+ value={
+ Object {
+ "a": 1,
+ "b": "b",
+ }
+ }
+ >
+ <ObjectItem
+ value={
+ Object {
+ "a": 1,
+ "b": "b",
+ }
+ }
+ >
+ <table
+ className="data"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ a
+ </td>
+ <td>
+ <SysInfoItem
+ name="a"
+ value={1}
+ >
+ <code>
+ 1
+ </code>
+ </SysInfoItem>
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ b
+ </td>
+ <td>
+ <SysInfoItem
+ name="b"
+ value="b"
+ >
+ <code>
+ b
+ </code>
+ </SysInfoItem>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </ObjectItem>
+ </SysInfoItem>
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ baz
+ </td>
+ <td>
+ <SysInfoItem
+ name="baz"
+ value={true}
+ >
+ <BooleanItem
+ value={true}
+ >
+ <i
+ className="icon-check"
+ />
+ </BooleanItem>
+ </SysInfoItem>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</ObjectItem>,
+ <ObjectItem
+ value={
+ Object {
+ "a": 1,
+ "b": "b",
+ }
+ }
+>
+ <table
+ className="data"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ a
+ </td>
+ <td>
+ <SysInfoItem
+ name="a"
+ value={1}
+ >
+ <code>
+ 1
+ </code>
+ </SysInfoItem>
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="thin nowrap"
+ >
+ b
+ </td>
+ <td>
+ <SysInfoItem
+ name="b"
+ value="b"
+ >
+ <code>
+ b
+ </code>
+ </SysInfoItem>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</ObjectItem>,
+]
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/main.js b/server/sonar-web/src/main/js/apps/system/main.js
index 411170e6f75..150bd042adf 100644
--- a/server/sonar-web/src/main/js/apps/system/main.js
+++ b/server/sonar-web/src/main/js/apps/system/main.js
@@ -23,7 +23,7 @@ import { sortBy } from 'lodash';
import { getSystemInfo } from '../../api/system';
import Section from './section';
import { translate } from '../../helpers/l10n';
-import RestartModal from '../../components/RestartModal';
+import RestartForm from '../../components/common/RestartForm';
const SECTIONS_ORDER = [
'SonarQube',
@@ -38,6 +38,8 @@ const SECTIONS_ORDER = [
];
export default class Main extends React.PureComponent {
+ state = { openRestartForm: false };
+
componentDidMount() {
getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) }));
}
@@ -60,9 +62,8 @@ export default class Main extends React.PureComponent {
orderItems = items => sortBy(items, 'name');
- handleServerRestart = () => {
- new RestartModal().render();
- };
+ handleServerRestartOpen = () => this.setState({ openRestartForm: true });
+ handleServerRestartClose = () => this.setState({ openRestartForm: false });
render() {
let sections = null;
@@ -113,9 +114,10 @@ export default class Main extends React.PureComponent {
<button
id="restart-server-button"
className="big-spacer-left"
- onClick={this.handleServerRestart}>
+ onClick={this.handleServerRestartOpen}>
Restart Server
</button>
+ {this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
</div>
</header>
{sections}
diff --git a/server/sonar-web/src/main/js/apps/system/routes.ts b/server/sonar-web/src/main/js/apps/system/routes.ts
index 9f7f40c4cd2..1a8d6a5ab38 100644
--- a/server/sonar-web/src/main/js/apps/system/routes.ts
+++ b/server/sonar-web/src/main/js/apps/system/routes.ts
@@ -17,12 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RouterState, IndexRouteProps } from 'react-router';
+import { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- import('./main').then(i => callback(null, { component: (i as any).default }));
+ import('./components/App').then(i => callback(null, { component: i.default }));
+ }
+ },
+ {
+ path: 'old',
+ getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+ import('./main').then(i => callback(null, (i as any).default));
}
}
];
diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css
new file mode 100644
index 00000000000..f1156609add
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/styles.css
@@ -0,0 +1,81 @@
+.system-info-health-title {
+ margin-top: 24px;
+ margin-bottom: 16px;
+}
+
+.system-info-health-card {
+ margin-bottom: 8px;
+ transition: border-color 0.3s ease;
+}
+
+.system-info-health-card:not(.no-hover):hover {
+ border-color: #4b9fd5;
+}
+
+.system-info-health-card:not(.no-hover):hover .system-info-health-card-title {
+ color: #4b9fd5;
+}
+
+.system-info-health-card .boxed-group-header {
+ cursor: pointer;
+ padding-bottom: 15px;
+}
+
+.system-info-health-card .boxed-group-inner {
+ padding-top: 0;
+}
+
+.system-info-health-card-title {
+ font-weight: bold;
+}
+
+.system-info-health-info {
+ margin-top: -4px;
+}
+
+.system-info-health-dot {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ margin: 4px;
+ border-radius: 16px;
+ box-sizing: border-box;
+}
+
+.system-info-health-dot.GREEN {
+ background-color: #00aa00;
+}
+
+.system-info-health-dot.YELLOW {
+ background-color: #eabe06;
+}
+.system-info-health-dot.RED {
+ background-color: #d4333f;
+}
+
+.system-info-health-info .alert {
+ display: inline-block;
+ position: relative;
+ top: -8px;
+}
+
+.system-info-health-info.big-dot .system-info-health-dot {
+ width: 24px;
+ height: 24px;
+ margin: 0;
+ border-radius: 24px;
+}
+
+.system-info-health-info.no-margin .system-info-health-dot {
+ margin: 0;
+}
+
+.system-info-section ~ .system-info-section {
+ margin-top: 16px;
+}
+
+.system-info-section-item-name {
+ width: 25vw;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts
new file mode 100644
index 00000000000..41ad5223544
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/utils.ts
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { each, omit, memoize } from 'lodash';
+import {
+ cleanQuery,
+ parseAsArray,
+ parseAsString,
+ RawQuery,
+ serializeStringArray
+} from '../../helpers/query';
+import {
+ HealthCause,
+ HealthType,
+ NodeInfo,
+ SysInfo,
+ SysInfoSection,
+ SysValueObject
+} from '../../api/system';
+
+export interface Query {
+ expandedCards: string[];
+}
+
+export const HEALTH_FIELD = 'Health';
+export const HEALTHCAUSES_FIELD = 'Health Causes';
+
+export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject {
+ return omit(sysInfoObject, ['Cluster', HEALTH_FIELD, HEALTHCAUSES_FIELD]);
+}
+
+export function getAppNodes(sysInfoData: SysInfo): NodeInfo[] {
+ return sysInfoData['Application Nodes'];
+}
+
+export function getHealth(sysInfoObject: SysValueObject): HealthType {
+ return sysInfoObject[HEALTH_FIELD] as HealthType;
+}
+
+export function getHealthCauses(sysInfoObject: SysValueObject): HealthCause[] {
+ return sysInfoObject[HEALTHCAUSES_FIELD] as HealthCause[];
+}
+
+export function getMainCardSection(sysInfoData: SysInfo): SysValueObject {
+ return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
+}
+
+export function getNodeName(nodeInfo: NodeInfo): string {
+ return nodeInfo['Name'];
+}
+
+export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] {
+ return sysInfoData['Search Nodes'];
+}
+
+export function groupSections(sysInfoData: SysValueObject) {
+ let mainSection: SysValueObject = {};
+ let sections: SysInfoSection = {};
+ each(sysInfoData, (item, key) => {
+ if (typeof item !== 'object' || item instanceof Array) {
+ mainSection[key] = item;
+ } else {
+ sections[key] = item;
+ }
+ });
+ return { mainSection, sections };
+}
+
+export function isCluster(sysInfoData?: SysInfo): boolean {
+ return sysInfoData != undefined && sysInfoData['Cluster'];
+}
+
+export const parseQuery = memoize((urlQuery: RawQuery): Query => {
+ return {
+ expandedCards: parseAsArray(urlQuery.expand, parseAsString)
+ };
+});
+
+export const serializeQuery = memoize((query: Query): RawQuery => {
+ return cleanQuery({
+ expand: serializeStringArray(query.expandedCards)
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/common/RestartForm.tsx b/server/sonar-web/src/main/js/components/common/RestartForm.tsx
new file mode 100644
index 00000000000..43e12ab31f1
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/RestartForm.tsx
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import Modal from 'react-modal';
+import { restartAndWait } from '../../api/system';
+import { translate } from '../../helpers/l10n';
+
+interface Props {
+ onClose: () => void;
+}
+
+interface State {
+ restarting: boolean;
+}
+
+export default class RestartForm extends React.PureComponent<Props, State> {
+ state: State = { restarting: false };
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ if (!this.state.restarting) {
+ this.setState({ restarting: true });
+ restartAndWait().then(
+ () => document.location.reload(),
+ () => this.setState({ restarting: false })
+ );
+ }
+ };
+
+ render() {
+ const { restarting } = this.state;
+ const header = translate('system.restart_server');
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <form id="restart-form" onSubmit={this.handleFormSubmit}>
+ <div className="modal-head">
+ <h2>{header}</h2>
+ </div>
+ <div className="modal-body">
+ <p className={classNames('spacer-top spacer-bottom', { 'text-center': restarting })}>
+ {translate(restarting ? 'system.is_restarting' : 'system.are_you_sure_to_restart')}
+ </p>
+ {restarting && (
+ <p className="big-spacer-top spacer-bottom text-center">
+ <i className="spinner" />
+ </p>
+ )}
+ </div>
+ {!restarting && (
+ <div className="modal-foot">
+ <button id="restart-server-submit">{translate('restart')}</button>
+ <a
+ href="#"
+ className="js-modal-close"
+ id="restart-server-cancel"
+ onClick={this.handleCancelClick}>
+ {translate('cancel')}
+ </a>
+ </div>
+ )}
+ </form>
+ </Modal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.js b/server/sonar-web/src/main/js/components/facet/FacetHeader.js
index 7950444a2e0..befa67b9223 100644
--- a/server/sonar-web/src/main/js/components/facet/FacetHeader.js
+++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.js
@@ -20,8 +20,9 @@
// @flow
/* eslint-disable max-len */
import React from 'react';
-import Tooltip from '../controls/Tooltip';
+import OpenCloseIcon from '../icons-components/OpenCloseIcon';
import HelpIcon from '../icons-components/HelpIcon';
+import Tooltip from '../controls/Tooltip';
import { translate } from '../../helpers/l10n';
/*::
@@ -58,29 +59,6 @@ export default class FacetHeader extends React.PureComponent {
}
};
- renderCheckbox() {
- return (
- <svg
- className="little-spacer-right"
- viewBox="0 0 1792 1792"
- width="10"
- height="10"
- style={{ paddingTop: 3 }}>
- {this.props.open ? (
- <path
- style={{ fill: 'currentColor ' }}
- d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
- />
- ) : (
- <path
- style={{ fill: 'currentColor ' }}
- d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
- />
- )}
- </svg>
- );
- }
-
renderHelper() {
if (!this.props.helper) {
return null;
@@ -119,7 +97,7 @@ export default class FacetHeader extends React.PureComponent {
{this.props.onClick ? (
<span className="search-navigator-facet-header">
<a href="#" onClick={this.handleClick}>
- {this.renderCheckbox()}
+ <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
{this.props.name}
</a>
{this.renderHelper()}
diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap
index 7def87dae3c..dc22f069760 100644
--- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap
+++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap
@@ -15,26 +15,10 @@ exports[`should clear 1`] = `
href="#"
onClick={[Function]}
>
- <svg
+ <OpenCloseIcon
className="little-spacer-right"
- height="10"
- style={
- Object {
- "paddingTop": 3,
- }
- }
- viewBox="0 0 1792 1792"
- width="10"
- >
- <path
- d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
- style={
- Object {
- "fill": "currentColor ",
- }
- }
- />
- </svg>
+ open={false}
+ />
foo
</a>
<span
@@ -55,26 +39,10 @@ exports[`should render closed facet with value 1`] = `
href="#"
onClick={[Function]}
>
- <svg
+ <OpenCloseIcon
className="little-spacer-right"
- height="10"
- style={
- Object {
- "paddingTop": 3,
- }
- }
- viewBox="0 0 1792 1792"
- width="10"
- >
- <path
- d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
- style={
- Object {
- "fill": "currentColor ",
- }
- }
- />
- </svg>
+ open={false}
+ />
foo
</a>
<span
@@ -95,26 +63,10 @@ exports[`should render closed facet without value 1`] = `
href="#"
onClick={[Function]}
>
- <svg
+ <OpenCloseIcon
className="little-spacer-right"
- height="10"
- style={
- Object {
- "paddingTop": 3,
- }
- }
- viewBox="0 0 1792 1792"
- width="10"
- >
- <path
- d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
- style={
- Object {
- "fill": "currentColor ",
- }
- }
- />
- </svg>
+ open={false}
+ />
foo
</a>
</span>
@@ -130,26 +82,10 @@ exports[`should render open facet with value 1`] = `
href="#"
onClick={[Function]}
>
- <svg
+ <OpenCloseIcon
className="little-spacer-right"
- height="10"
- style={
- Object {
- "paddingTop": 3,
- }
- }
- viewBox="0 0 1792 1792"
- width="10"
- >
- <path
- d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
- style={
- Object {
- "fill": "currentColor ",
- }
- }
- />
- </svg>
+ open={true}
+ />
foo
</a>
</span>
@@ -165,26 +101,10 @@ exports[`should render open facet without value 1`] = `
href="#"
onClick={[Function]}
>
- <svg
+ <OpenCloseIcon
className="little-spacer-right"
- height="10"
- style={
- Object {
- "paddingTop": 3,
- }
- }
- viewBox="0 0 1792 1792"
- width="10"
- >
- <path
- d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
- style={
- Object {
- "fill": "currentColor ",
- }
- }
- />
- </svg>
+ open={true}
+ />
foo
</a>
</span>
diff --git a/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
new file mode 100644
index 00000000000..739bc6027e7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+
+interface Props {
+ className?: string;
+ open: boolean;
+ size?: number;
+}
+
+export default function OpenCloseIcon({ className, open, size = 14 }: Props) {
+ return (
+ <svg
+ className={className}
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 16 16"
+ width={size}
+ height={size}
+ style={{ fill: 'currentColor' }}>
+ {open ? (
+ <path d="M13.506 8.539l-5.191 5.184q-0.133 0.133-0.315 0.133t-0.315-0.133l-5.191-5.184q-0.133-0.133-0.133-0.318t0.133-0.318l1.161-1.154q0.133-0.133 0.315-0.133t0.315 0.133l3.715 3.715 3.715-3.715q0.133-0.133 0.315-0.133t0.315 0.133l1.161 1.154q0.133 0.133 0.133 0.318t-0.133 0.318z" />
+ ) : (
+ <path d="M13.527 8.318l-5.244 5.244q-0.134 0.134-0.318 0.134t-0.318-0.134l-1.173-1.173q-0.134-0.134-0.134-0.318t0.134-0.318l3.753-3.753-3.753-3.753q-0.134-0.134-0.134-0.318t0.134-0.318l1.173-1.173q0.134-0.134 0.318-0.134t0.318 0.134l5.244 5.244q0.134 0.134 0.134 0.318t-0.134 0.318z" />
+ )}
+ </svg>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/icons.js b/server/sonar-web/src/main/js/components/icons-components/icons.js
index 890a7c87ebc..c50065921f4 100644
--- a/server/sonar-web/src/main/js/components/icons-components/icons.js
+++ b/server/sonar-web/src/main/js/components/icons-components/icons.js
@@ -30,6 +30,7 @@ import _HelpIcon from './HelpIcon';
import _HistoryIcon from './HistoryIcon';
import _LinkIcon from './LinkIcon';
import _ListIcon from './ListIcon';
+import _OpenCloseIcon from './OpenCloseIcon';
import _OrganizationIcon from './OrganizationIcon';
import _ProjectEventIcon from './ProjectEventIcon';
import _QualifierIcon from './QualifierIcon';
@@ -51,6 +52,7 @@ export const HelpIcon = _HelpIcon;
export const HistoryIcon = _HistoryIcon;
export const LinkIcon = _LinkIcon;
export const ListIcon = _ListIcon;
+export const OpenCloseIcon = _OpenCloseIcon;
export const OrganizationIcon = _OrganizationIcon;
export const ProjectEventIcon = _ProjectEventIcon;
export const QualifierIcon = _QualifierIcon;
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index 8deba26fcad..773448fad23 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -31,21 +31,23 @@ interface Location {
query?: Query;
}
+export function getBaseUrl(): string {
+ return (window as any).baseUrl;
+}
+
/**
* Generate URL for a component's home page
*/
export function getComponentUrl(componentKey: string, branch?: string): string {
const branchQuery = branch ? `&branch=${encodeURIComponent(branch)}` : '';
- return (
- (window as any).baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery
- );
+ return getBaseUrl() + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery;
}
export function getProjectUrl(key: string, branch?: string): Location {
return { pathname: '/dashboard', query: { id: key, branch } };
}
-export function getProjectBranchUrl(key: string, branch: Branch) {
+export function getProjectBranchUrl(key: string, branch: Branch): Location {
if (isShortLivingBranch(branch)) {
return {
pathname: '/project/issues',
@@ -74,13 +76,17 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca
export function getComponentIssuesUrlAsString(componentKey: string, query?: Query): string {
const path = getComponentIssuesUrl(componentKey, query);
- return `${(window as any).baseUrl}${path.pathname}?${stringify(path.query)}`;
+ return `${getBaseUrl()}${path.pathname}?${stringify(path.query)}`;
}
/**
* Generate URL for a component's drilldown page
*/
-export function getComponentDrilldownUrl(componentKey: string, metric: string, branch?: string) {
+export function getComponentDrilldownUrl(
+ componentKey: string,
+ metric: string,
+ branch?: string
+): Location {
return { pathname: '/component_measures', query: { id: componentKey, metric, branch } };
}
@@ -159,9 +165,9 @@ export function getDeprecatedActiveRulesUrl(query = {}, organization?: string |
}
export function getProjectsUrl(): string {
- return (window as any).baseUrl + '/projects';
+ return getBaseUrl() + '/projects';
}
export function getMarkdownHelpUrl(): string {
- return (window as any).baseUrl + '/markdown/help';
+ return getBaseUrl() + '/markdown/help';
}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index aca17c1f279..ef96a4360d5 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -146,6 +146,7 @@ remove=Remove
rename=Rename
reset_verb=Reset
resolution=Resolution
+restart=Restart
restore=Restore
result=Result
results=Results
@@ -2840,8 +2841,18 @@ background_tasks.add_more_with_governance=Add more with Governance
# SYSTEM
#
#------------------------------------------------------------------------------
+system.application_nodes_title=Application Nodes
+system.are_you_sure_to_restart=Are you sure you want to restart the server?
+system.cluster_log_level.info=Changes apply to all Application nodes but not to Search nodes.
+system.download_logs=Download Logs
+system.download_system_info=Download System Info
+system.is_restarting=Server is restarting. This page will be automatically refreshed.
system.log_level.warning=Current level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties.
-
+system.log_level.info=Changes don't apply to Search.
+system.logs_level=Logs level
+system.restart_server=Restart Server
+system.search_nodes_title=Search Nodes
+system.set_log_level=Set logs level
#------------------------------------------------------------------------------