aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2017-09-19 16:13:55 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2017-09-26 23:49:38 +0200
commit8fa40905cc8afa06f384ed455e06871126804751 (patch)
treeb3bc5deab119ce93cc9be327ad7a59e6bb42243d /server
parent9c2a9a75fff56e56799273e4a61364ed51870b7e (diff)
downloadsonarqube-8fa40905cc8afa06f384ed455e06871126804751.tar.gz
sonarqube-8fa40905cc8afa06f384ed455e06871126804751.zip
SONAR-9802 add information to System Info page
Diffstat (limited to 'server')
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java5
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java28
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java)40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.java64
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java156
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java169
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSectionMBean.java (renamed from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java)2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java15
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStateSectionTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java)23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java88
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java217
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java187
-rw-r--r--server/sonar-web/src/main/js/api/system.ts3
-rw-r--r--server/sonar-web/src/main/js/apps/system/__tests__/system-test.js101
-rw-r--r--server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts28
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/App.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageActions.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx62
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx (renamed from server/sonar-web/src/main/js/apps/system/item-value.js)51
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap42
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap66
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/system/item-boolean.js30
-rw-r--r--server/sonar-web/src/main/js/apps/system/item-log-level.js60
-rw-r--r--server/sonar-web/src/main/js/apps/system/item-object.js41
-rw-r--r--server/sonar-web/src/main/js/apps/system/main.js127
-rw-r--r--server/sonar-web/src/main/js/apps/system/routes.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/system/section.js49
-rw-r--r--server/sonar-web/src/main/js/apps/system/styles.css38
-rw-r--r--server/sonar-web/src/main/js/apps/system/utils.ts83
-rw-r--r--server/sonar-web/src/main/js/components/common/BranchStatus.css16
-rw-r--r--server/sonar-web/src/main/js/components/common/BranchStatus.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/common/StatusIndicator.css34
-rw-r--r--server/sonar-web/src/main/js/components/common/StatusIndicator.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx)28
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap15
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx4
51 files changed, 1017 insertions, 1114 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
index 575083280b1..656fba7e75b 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
@@ -57,8 +57,11 @@ public class JvmStateSection implements SystemInfoSection {
addAttributeInMb(protobuf, "Non Heap Init (MB)", nonHeap.getInit());
addAttributeInMb(protobuf, "Non Heap Max (MB)", nonHeap.getMax());
addAttributeInMb(protobuf, "Non Heap Used (MB)", nonHeap.getUsed());
+
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
- setAttribute(protobuf, "Thread Count", thread.getThreadCount());
+ setAttribute(protobuf, "Threads", thread.getThreadCount());
+
+ setAttribute(protobuf,"Processors", Runtime.getRuntime().availableProcessors());
return protobuf.build();
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
index 5276a524353..3057e9933e9 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.when;
public class JvmStateSectionTest {
- public static final String PROCESS_NAME = "the process name";
+ private static final String PROCESS_NAME = "the process name";
@Test
public void toSystemInfoSection() {
@@ -39,7 +39,7 @@ public class JvmStateSectionTest {
assertThat(section.getName()).isEqualTo(PROCESS_NAME);
assertThat(section.getAttributesCount()).isGreaterThan(0);
- assertThat(section.getAttributesList()).extracting("key").contains("Thread Count");
+ assertThat(section.getAttributesList()).extracting("key").contains("Threads", "Processors");
}
@Test
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
deleted file mode 100644
index 6358f68593f..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-/**
- * The public attributes of {@link EsSection}
- * to be exported in JMX bean.
- */
-public interface EsSectionMBean {
- String getState();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java
index 2e86e6ef854..d6f5f0df07d 100644
--- 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/EsStateSection.java
@@ -19,44 +19,28 @@
*/
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.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.es.EsClient;
import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
-public class EsSection extends BaseSectionMBean implements EsSectionMBean {
+public class EsStateSection implements SystemInfoSection {
private final EsClient esClient;
- public EsSection(EsClient esClient) {
+ public EsStateSection(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();
}
@@ -64,29 +48,17 @@ public class EsSection extends BaseSectionMBean implements EsSectionMBean {
@Override
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
- protobuf.setName(name());
+ protobuf.setName("Search State");
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);
+ } catch (Exception es) {
+ Loggers.get(EsStateSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
setAttribute(protobuf, "State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
}
return protobuf.build();
}
- private void 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()) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.java
new file mode 100644
index 00000000000..12656ae4f3e
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStatisticsSection.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.server.platform.monitoring;
+
+import java.util.Map;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.indices.stats.IndexStats;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+
+import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class EsStatisticsSection implements SystemInfoSection {
+
+ private final EsClient esClient;
+
+ public EsStatisticsSection(EsClient esClient) {
+ this.esClient = esClient;
+ }
+
+ @Override
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName("Search Statistics");
+ try {
+ completeIndexAttributes(protobuf);
+ } catch (Exception es) {
+ Loggers.get(EsStatisticsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"Error\" attribute.", es);
+ setAttribute(protobuf, "Error", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
+ }
+ return protobuf.build();
+ }
+
+ private void completeIndexAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
+ IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
+ for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
+ String prefix = "Index " + indexStats.getKey() + " - ";
+ setAttribute(protobuf, prefix + "Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
+ setAttribute(protobuf, prefix + "Shards", indexStats.getValue().getShards().length);
+ setAttribute(protobuf, prefix + "Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
deleted file mode 100644
index ead5a7475fd..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import com.google.common.base.Joiner;
-import java.io.File;
-import java.util.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.platform.Server;
-import org.sonar.api.security.SecurityRealm;
-import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.authentication.IdentityProviderRepository;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.HealthChecker;
-import org.sonar.server.platform.ServerIdLoader;
-import org.sonar.server.platform.ServerLogging;
-import org.sonar.server.user.SecurityRealmFactory;
-
-import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
-
-public class SonarQubeSection extends BaseSectionMBean implements SonarQubeSectionMBean {
-
- private static final Joiner COMMA_JOINER = Joiner.on(", ");
-
- static final String BRANDING_FILE_PATH = "web/WEB-INF/classes/com/sonarsource/branding";
-
- private final Configuration config;
- private final SecurityRealmFactory securityRealmFactory;
- private final IdentityProviderRepository identityProviderRepository;
- private final Server server;
- private final ServerLogging serverLogging;
- private final ServerIdLoader serverIdLoader;
- private final HealthChecker healthChecker;
-
- public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory,
- IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
- ServerIdLoader serverIdLoader, HealthChecker healthChecker) {
- this.config = config;
- this.securityRealmFactory = securityRealmFactory;
- this.identityProviderRepository = identityProviderRepository;
- this.server = server;
- this.serverLogging = serverLogging;
- this.serverIdLoader = serverIdLoader;
- this.healthChecker = healthChecker;
- }
-
- @Override
- public String getServerId() {
- return serverIdLoader.getRaw().orElse(null);
- }
-
- @Override
- public String getVersion() {
- return server.getVersion();
- }
-
- @Override
- public String getLogLevel() {
- return serverLogging.getRootLoggerLevel().name();
- }
-
- @CheckForNull
- private String getExternalUserAuthentication() {
- SecurityRealm realm = securityRealmFactory.getRealm();
- return realm == null ? null : realm.getName();
- }
-
- private List<String> getEnabledIdentityProviders() {
- return identityProviderRepository.getAllEnabledAndSorted()
- .stream()
- .filter(IdentityProvider::isEnabled)
- .map(IdentityProvider::getName)
- .collect(MoreCollectors.toList());
- }
-
- private List<String> getAllowsToSignUpEnabledIdentityProviders() {
- return identityProviderRepository.getAllEnabledAndSorted()
- .stream()
- .filter(IdentityProvider::isEnabled)
- .filter(IdentityProvider::allowsUsersToSignUp)
- .map(IdentityProvider::getName)
- .collect(MoreCollectors.toList());
- }
-
- private boolean getForceAuthentication() {
- return config.getBoolean(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false);
- }
-
- private boolean isOfficialDistribution() {
- // the dependency com.sonarsource:sonarsource-branding is shaded to webapp
- // during release (see sonar-web pom)
- File brandingFile = new File(server.getRootDir(), BRANDING_FILE_PATH);
- // no need to check that the file exists. java.io.File#length() returns zero in this case.
- return brandingFile.length() > 0L;
- }
-
- @Override
- public String name() {
- return "SonarQube";
- }
-
- @Override
- public ProtobufSystemInfo.Section toProtobuf() {
- ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
- protobuf.setName(name());
-
- serverIdLoader.get().ifPresent(serverId -> {
- setAttribute(protobuf, "Server ID", serverId.getId());
- setAttribute(protobuf, "Server ID validated", serverId.isValid());
- });
- Health health = healthChecker.checkNode();
- setAttribute(protobuf, "Health", health.getStatus().name());
- setAttribute(protobuf, "Health Causes", health.getCauses());
- setAttribute(protobuf, "Version", getVersion());
- setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
- addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
- addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders());
- setAttribute(protobuf, "Force authentication", getForceAuthentication());
- setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
- setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
- setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
- setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
- setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
- setAttribute(protobuf, "Logs Level", getLogLevel());
- return protobuf.build();
- }
-
- private static void addIfNotEmpty(ProtobufSystemInfo.Section.Builder protobuf, String key, @Nullable List<String> values) {
- if (values != null && !values.isEmpty()) {
- setAttribute(protobuf, key, COMMA_JOINER.join(values));
- }
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
index bbd623f52b9..d19f7162ae4 100644
--- 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
@@ -19,85 +19,140 @@
*/
package org.sonar.server.platform.monitoring;
-import java.lang.management.ClassLoadingMXBean;
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.RuntimeMXBean;
-import java.lang.management.ThreadMXBean;
-import java.util.Date;
-import org.sonar.api.utils.System2;
-import org.sonar.process.systeminfo.SystemInfoSection;
+import com.google.common.base.Joiner;
+import java.io.File;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.Server;
+import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.process.ProcessProperties;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.authentication.IdentityProviderRepository;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.user.SecurityRealmFactory;
-import static java.lang.String.format;
-import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
-/**
- * JVM runtime information. Not exported as a MXBean because these information
- * are natively provided.
- */
-public class SystemSection implements SystemInfoSection {
- private final System2 system;
+public class SystemSection extends BaseSectionMBean implements SystemSectionMBean {
+
+ private static final Joiner COMMA_JOINER = Joiner.on(", ");
+
+ static final String BRANDING_FILE_PATH = "web/WEB-INF/classes/com/sonarsource/branding";
- public SystemSection() {
- this(System2.INSTANCE);
+ private final Configuration config;
+ private final SecurityRealmFactory securityRealmFactory;
+ private final IdentityProviderRepository identityProviderRepository;
+ private final Server server;
+ private final ServerLogging serverLogging;
+ private final ServerIdLoader serverIdLoader;
+ private final HealthChecker healthChecker;
+
+ public SystemSection(Configuration config, SecurityRealmFactory securityRealmFactory,
+ IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
+ ServerIdLoader serverIdLoader, HealthChecker healthChecker) {
+ this.config = config;
+ this.securityRealmFactory = securityRealmFactory;
+ this.identityProviderRepository = identityProviderRepository;
+ this.server = server;
+ this.serverLogging = serverLogging;
+ this.serverIdLoader = serverIdLoader;
+ this.healthChecker = healthChecker;
}
- SystemSection(System2 system) {
- this.system = system;
+ @Override
+ public String getServerId() {
+ return serverIdLoader.getRaw().orElse(null);
}
@Override
- public ProtobufSystemInfo.Section toProtobuf() {
- ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
- protobuf.setName("System");
+ public String getVersion() {
+ return server.getVersion();
+ }
- setAttribute(protobuf, "System Date", formatDateTime(new Date(system.now())));
- setAttribute(protobuf, "Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
- setAttribute(protobuf, "JVM Vendor", runtimeMXBean().getVmVendor());
- setAttribute(protobuf, "JVM Name", runtimeMXBean().getVmName());
- setAttribute(protobuf, "JVM Version", runtimeMXBean().getVmVersion());
- setAttribute(protobuf, "Processors", runtime().availableProcessors());
- setAttribute(protobuf, "System Classpath", runtimeMXBean().getClassPath());
- setAttribute(protobuf, "BootClassPath", runtimeMXBean().getBootClassPath());
- setAttribute(protobuf, "Library Path", runtimeMXBean().getLibraryPath());
- setAttribute(protobuf, "Total Memory", formatMemory(runtime().totalMemory()));
- setAttribute(protobuf, "Free Memory", formatMemory(runtime().freeMemory()));
- setAttribute(protobuf, "Max Memory", formatMemory(runtime().maxMemory()));
- setAttribute(protobuf, "Heap", memoryMXBean().getHeapMemoryUsage().toString());
- setAttribute(protobuf, "Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
- setAttribute(protobuf, "System Load Average", format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
- setAttribute(protobuf, "Loaded Classes", classLoadingMXBean().getLoadedClassCount());
- setAttribute(protobuf, "Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
- setAttribute(protobuf, "Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
- setAttribute(protobuf, "Threads", threadMXBean().getThreadCount());
- setAttribute(protobuf, "Threads Peak", threadMXBean().getPeakThreadCount());
- setAttribute(protobuf, "Daemon Thread", threadMXBean().getDaemonThreadCount());
- return protobuf.build();
+ @Override
+ public String getLogLevel() {
+ return serverLogging.getRootLoggerLevel().name();
}
- private static RuntimeMXBean runtimeMXBean() {
- return ManagementFactory.getRuntimeMXBean();
+ @CheckForNull
+ private String getExternalUserAuthentication() {
+ SecurityRealm realm = securityRealmFactory.getRealm();
+ return realm == null ? null : realm.getName();
}
- private static Runtime runtime() {
- return Runtime.getRuntime();
+ private List<String> getEnabledIdentityProviders() {
+ return identityProviderRepository.getAllEnabledAndSorted()
+ .stream()
+ .filter(IdentityProvider::isEnabled)
+ .map(IdentityProvider::getName)
+ .collect(MoreCollectors.toList());
}
- private static MemoryMXBean memoryMXBean() {
- return ManagementFactory.getMemoryMXBean();
+ private List<String> getAllowsToSignUpEnabledIdentityProviders() {
+ return identityProviderRepository.getAllEnabledAndSorted()
+ .stream()
+ .filter(IdentityProvider::isEnabled)
+ .filter(IdentityProvider::allowsUsersToSignUp)
+ .map(IdentityProvider::getName)
+ .collect(MoreCollectors.toList());
}
- private static ClassLoadingMXBean classLoadingMXBean() {
- return ManagementFactory.getClassLoadingMXBean();
+ private boolean getForceAuthentication() {
+ return config.getBoolean(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false);
}
- private static ThreadMXBean threadMXBean() {
- return ManagementFactory.getThreadMXBean();
+ private boolean isOfficialDistribution() {
+ // the dependency com.sonarsource:sonarsource-branding is shaded to webapp
+ // during release (see sonar-web pom)
+ File brandingFile = new File(server.getRootDir(), BRANDING_FILE_PATH);
+ // no need to check that the file exists. java.io.File#length() returns zero in this case.
+ return brandingFile.length() > 0L;
+ }
+
+ @Override
+ public String name() {
+ // JMX name
+ return "SonarQube";
+ }
+
+ @Override
+ public ProtobufSystemInfo.Section toProtobuf() {
+ ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+ protobuf.setName("System");
+
+ serverIdLoader.get().ifPresent(serverId -> {
+ setAttribute(protobuf, "Server ID", serverId.getId());
+ setAttribute(protobuf, "Server ID validated", serverId.isValid());
+ });
+ Health health = healthChecker.checkNode();
+ setAttribute(protobuf, "Health", health.getStatus().name());
+ setAttribute(protobuf, "Health Causes", health.getCauses());
+ setAttribute(protobuf, "Version", getVersion());
+ setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
+ addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
+ addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders());
+ setAttribute(protobuf, "High Availability", false);
+ setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
+ setAttribute(protobuf, "Force authentication", getForceAuthentication());
+ setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
+ setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
+ setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
+ setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
+ setAttribute(protobuf, "Logs Level", getLogLevel());
+ return protobuf.build();
}
- private static String formatMemory(long memoryInBytes) {
- return format("%d MB", memoryInBytes / 1_000_000);
+ private static void addIfNotEmpty(ProtobufSystemInfo.Section.Builder protobuf, String key, @Nullable List<String> values) {
+ if (values != null && !values.isEmpty()) {
+ setAttribute(protobuf, key, COMMA_JOINER.join(values));
+ }
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSectionMBean.java
index 1a562a6bc30..7c3f8f01aa9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSectionMBean.java
@@ -21,7 +21,7 @@ package org.sonar.server.platform.monitoring;
import javax.annotation.CheckForNull;
-public interface SonarQubeSectionMBean {
+public interface SystemSectionMBean {
@CheckForNull
String getServerId();
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
index c565219240f..09c40749fc8 100644
--- 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
@@ -35,10 +35,10 @@ public class WebSystemInfoModule {
new JvmPropertiesSection("Web JVM Properties"),
new JvmStateSection("Web JVM State"),
DatabaseSection.class,
- EsSection.class,
+ EsStateSection.class,
+ EsStatisticsSection.class,
PluginsSection.class,
SettingsSection.class,
- SonarQubeSection.class,
SystemSection.class,
StandaloneInfoAction.class
@@ -50,7 +50,7 @@ public class WebSystemInfoModule {
new JvmPropertiesSection("Web JVM Properties"),
new JvmStateSection("Web JVM State"),
DatabaseSection.class,
- EsSection.class,
+ EsStateSection.class,
PluginsSection.class,
ClusterInfoAction.class
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
index 6f9125f40cc..d298d99c03a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
@@ -19,6 +19,7 @@
*/
package org.sonar.server.platform.ws;
+import java.util.Arrays;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -50,14 +51,16 @@ public class ClusterInfoAction implements SystemWsAction {
try (JsonWriter json = response.newJsonWriter()) {
json.beginObject();
- // global section
- json.prop("Cluster", true);
- json.prop("Cluster Name", "foo");
+ json.name("System");
+ json.beginObject();
+ json.prop("High Availability", true);
+ json.prop("Cluster Name", "fooo");
json.prop("Server Id", "ABC123");
json.prop("Health", "RED");
json
.name("Health Causes")
- .beginArray().beginObject().prop("message", "Requires at least two search nodes").endObject().endArray();
+ .beginArray().values(Arrays.asList("foo", "bar")).endArray();
+ json.endObject();
json.name("Settings");
json.beginObject();
@@ -65,8 +68,6 @@ public class ClusterInfoAction implements SystemWsAction {
json.prop("sonar.externalIdentityProviders", "GitHub, BitBucket");
json.endObject();
-
-
json.name("Database");
json
.beginObject()
@@ -83,7 +84,7 @@ public class ClusterInfoAction implements SystemWsAction {
.prop("workersPerNode", 4)
.endObject();
- json.name("Elasticsearch");
+ json.name("Search");
json
.beginObject()
.prop("Health", "GREEN")
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStateSectionTest.java
index a55ae819df3..af248d63640 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStateSectionTest.java
@@ -35,21 +35,20 @@ import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-public class EsSectionTest {
+public class EsStateSectionTest {
@Rule
public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
- private EsSection underTest = new EsSection(esTester.client());
+ private EsStateSection underTest = new EsStateSection(esTester.client());
@Test
public void name() {
- assertThat(underTest.name()).isEqualTo("Elasticsearch");
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("Search State");
}
@Test
public void es_state() {
- assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name());
assertThatAttributeIs(underTest.toProtobuf(), "State", ClusterHealthStatus.GREEN.name());
}
@@ -60,19 +59,9 @@ public class EsSectionTest {
}
@Test
- public void index_attributes() {
- ProtobufSystemInfo.Section section = underTest.toProtobuf();
-
- // one index "issues"
- assertThat(attribute(section, "Index issues - Docs").getLongValue()).isEqualTo(0L);
- assertThat(attribute(section, "Index issues - Shards").getLongValue()).isGreaterThan(0);
- assertThat(attribute(section, "Index issues - Store Size").getStringValue()).isNotNull();
- }
-
- @Test
public void attributes_displays_exception_message_when_cause_null_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
- EsSection underTest = new EsSection(esClientMock);
+ EsStateSection underTest = new EsStateSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
ProtobufSystemInfo.Section section = underTest.toProtobuf();
@@ -82,7 +71,7 @@ public class EsSectionTest {
@Test
public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
- EsSection underTest = new EsSection(esClientMock);
+ EsStateSection underTest = new EsStateSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
ProtobufSystemInfo.Section section = underTest.toProtobuf();
@@ -92,7 +81,7 @@ public class EsSectionTest {
@Test
public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
- EsSection underTest = new EsSection(esClientMock);
+ EsStateSection underTest = new EsStateSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
ProtobufSystemInfo.Section section = underTest.toProtobuf();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java
new file mode 100644
index 00000000000..44c583ebb2e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsStatisticsSectionTest.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import org.elasticsearch.ElasticsearchException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
+
+public class EsStatisticsSectionTest {
+
+ @Rule
+ public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
+
+ private EsStatisticsSection underTest = new EsStatisticsSection(esTester.client());
+
+ @Test
+ public void name() {
+ assertThat(underTest.toProtobuf().getName()).isEqualTo("Search Statistics");
+ }
+
+ @Test
+ public void index_attributes() {
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+
+ // one index "issues"
+ assertThat(attribute(section, "Index issues - Docs").getLongValue()).isEqualTo(0L);
+ assertThat(attribute(section, "Index issues - Shards").getLongValue()).isGreaterThan(0);
+ assertThat(attribute(section, "Index issues - Store Size").getStringValue()).isNotNull();
+ }
+
+ @Test
+ public void attributes_displays_exception_message_when_cause_null_when_client_fails() {
+ EsClient esClientMock = mock(EsClient.class);
+ EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+ when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));
+
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThatAttributeIs(section, "Error", "RuntimeException with no cause");
+ }
+
+ @Test
+ public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
+ EsClient esClientMock = mock(EsClient.class);
+ EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+ when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));
+
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThatAttributeIs(section, "Error", "RuntimeException with cause not ES");
+ }
+
+ @Test
+ public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
+ EsClient esClientMock = mock(EsClient.class);
+ EsStatisticsSection underTest = new EsStatisticsSection(esClientMock);
+ when(esClientMock.prepareStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));
+
+ ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ assertThatAttributeIs(section, "Error", "some cause message");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
deleted file mode 100644
index fac46e81c31..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.io.File;
-import java.util.Optional;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.Server;
-import org.sonar.api.security.SecurityRealm;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.authentication.IdentityProviderRepositoryRule;
-import org.sonar.server.authentication.TestIdentityProvider;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.TestStandaloneHealthChecker;
-import org.sonar.server.platform.ServerId;
-import org.sonar.server.platform.ServerIdLoader;
-import org.sonar.server.platform.ServerLogging;
-import org.sonar.server.user.SecurityRealmFactory;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
-import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
-
-public class SonarQubeSectionTest {
-
- private static final String SERVER_ID_PROPERTY = "Server ID";
- private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- @Rule
- public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
-
- private MapSettings settings = new MapSettings();
- private Server server = mock(Server.class);
- private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
- private ServerLogging serverLogging = mock(ServerLogging.class);
- private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
- private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
-
- private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
- serverLogging, serverIdLoader, healthChecker);
-
- @Before
- public void setUp() throws Exception {
- when(serverLogging.getRootLoggerLevel()).thenReturn(LoggerLevel.DEBUG);
- when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
- when(serverIdLoader.get()).thenReturn(Optional.empty());
- }
-
- @Test
- public void name_is_not_empty() {
- assertThat(underTest.name()).isNotEmpty();
- }
-
- @Test
- public void test_getServerId() {
- when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
- assertThat(underTest.getServerId()).isEqualTo("ABC");
-
- when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
- assertThat(underTest.getServerId()).isNull();
- }
-
- @Test
- public void attributes_contain_information_about_valid_server_id() {
- when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
- assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, true);
- }
-
- @Test
- public void attributes_contain_information_about_non_valid_server_id() {
- when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false)));
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
- assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, false);
- }
-
- @Test
- public void attributes_do_not_contain_information_about_server_id_if_absent() {
- when(serverIdLoader.get()).thenReturn(Optional.empty());
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThat(attribute(protobuf, SERVER_ID_PROPERTY)).isNull();
- assertThat(attribute(protobuf, SERVER_ID_VALIDATED_PROPERTY)).isNull();
- }
-
- @Test
- public void official_distribution() throws Exception {
- File rootDir = temp.newFolder();
- FileUtils.write(new File(rootDir, SonarQubeSection.BRANDING_FILE_PATH), "1.2");
-
- when(server.getRootDir()).thenReturn(rootDir);
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "Official Distribution", true);
- }
-
- @Test
- public void not_an_official_distribution() throws Exception {
- File rootDir = temp.newFolder();
- // branding file is missing
- when(server.getRootDir()).thenReturn(rootDir);
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "Official Distribution", false);
- }
-
- @Test
- public void get_log_level() throws Exception {
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "Logs Level", "DEBUG");
- }
-
- @Test
- public void get_realm() throws Exception {
- SecurityRealm realm = mock(SecurityRealm.class);
- when(realm.getName()).thenReturn("LDAP");
- when(securityRealmFactory.getRealm()).thenReturn(realm);
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "External User Authentication", "LDAP");
- }
-
- @Test
- public void no_realm() throws Exception {
- when(securityRealmFactory.getRealm()).thenReturn(null);
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThat(attribute(protobuf, "External User Authentication")).isNull();
- }
-
- @Test
- public void get_enabled_identity_providers() throws Exception {
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("github")
- .setName("GitHub")
- .setEnabled(true));
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("bitbucket")
- .setName("Bitbucket")
- .setEnabled(true));
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("disabled")
- .setName("Disabled")
- .setEnabled(false));
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub");
- }
-
- @Test
- public void get_enabled_identity_providers_allowing_users_to_signup() throws Exception {
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("github")
- .setName("GitHub")
- .setEnabled(true)
- .setAllowsUsersToSignUp(true));
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("bitbucket")
- .setName("Bitbucket")
- .setEnabled(true)
- .setAllowsUsersToSignUp(false));
- identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
- .setKey("disabled")
- .setName("Disabled")
- .setEnabled(false)
- .setAllowsUsersToSignUp(true));
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
- }
-
- @Test
- public void return_health() {
- healthChecker.setHealth(Health.newHealthCheckBuilder()
- .setStatus(Health.Status.YELLOW)
- .addCause("foo")
- .addCause("bar")
- .build());
-
- ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
- assertThatAttributeIs(protobuf, "Health", "YELLOW");
- SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
index 94378667a59..c1ae9b3305d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
@@ -19,26 +19,199 @@
*/
package org.sonar.server.platform.monitoring;
+import java.io.File;
+import java.util.Optional;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.api.security.SecurityRealm;
+import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.authentication.IdentityProviderRepositoryRule;
+import org.sonar.server.authentication.TestIdentityProvider;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.TestStandaloneHealthChecker;
+import org.sonar.server.platform.ServerId;
+import org.sonar.server.platform.ServerIdLoader;
+import org.sonar.server.platform.ServerLogging;
+import org.sonar.server.user.SecurityRealmFactory;
+import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;
public class SystemSectionTest {
- private SystemSection underTest = new SystemSection();
+ private static final String SERVER_ID_PROPERTY = "Server ID";
+ private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();
+
+ private MapSettings settings = new MapSettings();
+ private Server server = mock(Server.class);
+ private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
+ private ServerLogging serverLogging = mock(ServerLogging.class);
+ private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
+ private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
+
+ private SystemSection underTest = new SystemSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
+ serverLogging, serverIdLoader, healthChecker);
+
+ @Before
+ public void setUp() throws Exception {
+ when(serverLogging.getRootLoggerLevel()).thenReturn(LoggerLevel.DEBUG);
+ when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
+ when(serverIdLoader.get()).thenReturn(Optional.empty());
+ }
+
+ @Test
+ public void name_is_not_empty() {
+ assertThat(underTest.name()).isNotEmpty();
+ }
+
+ @Test
+ public void test_getServerId() {
+ when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
+ assertThat(underTest.getServerId()).isEqualTo("ABC");
+
+ when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
+ assertThat(underTest.getServerId()).isNull();
+ }
+
+ @Test
+ public void attributes_contain_information_about_valid_server_id() {
+ when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
+ assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, true);
+ }
+
+ @Test
+ public void attributes_contain_information_about_non_valid_server_id() {
+ when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false)));
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
+ assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, false);
+ }
+
+ @Test
+ public void attributes_do_not_contain_information_about_server_id_if_absent() {
+ when(serverIdLoader.get()).thenReturn(Optional.empty());
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThat(attribute(protobuf, SERVER_ID_PROPERTY)).isNull();
+ assertThat(attribute(protobuf, SERVER_ID_VALIDATED_PROPERTY)).isNull();
+ }
@Test
- public void name() {
- assertThat(underTest.toProtobuf().getName()).isEqualTo("System");
+ public void official_distribution() throws Exception {
+ File rootDir = temp.newFolder();
+ FileUtils.write(new File(rootDir, SystemSection.BRANDING_FILE_PATH), "1.2");
+
+ when(server.getRootDir()).thenReturn(rootDir);
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Official Distribution", true);
+ }
+
+ @Test
+ public void not_an_official_distribution() throws Exception {
+ File rootDir = temp.newFolder();
+ // branding file is missing
+ when(server.getRootDir()).thenReturn(rootDir);
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Official Distribution", false);
+ }
+
+ @Test
+ public void get_log_level() throws Exception {
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Logs Level", "DEBUG");
+ }
+
+ @Test
+ public void get_realm() throws Exception {
+ SecurityRealm realm = mock(SecurityRealm.class);
+ when(realm.getName()).thenReturn("LDAP");
+ when(securityRealmFactory.getRealm()).thenReturn(realm);
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "External User Authentication", "LDAP");
+ }
+
+ @Test
+ public void no_realm() throws Exception {
+ when(securityRealmFactory.getRealm()).thenReturn(null);
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThat(attribute(protobuf, "External User Authentication")).isNull();
+ }
+
+ @Test
+ public void get_enabled_identity_providers() throws Exception {
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("github")
+ .setName("GitHub")
+ .setEnabled(true));
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("bitbucket")
+ .setName("Bitbucket")
+ .setEnabled(true));
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("disabled")
+ .setName("Disabled")
+ .setEnabled(false));
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub");
+ }
+
+ @Test
+ public void get_enabled_identity_providers_allowing_users_to_signup() throws Exception {
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("github")
+ .setName("GitHub")
+ .setEnabled(true)
+ .setAllowsUsersToSignUp(true));
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("bitbucket")
+ .setName("Bitbucket")
+ .setEnabled(true)
+ .setAllowsUsersToSignUp(false));
+ identityProviderRepository.addIdentityProvider(new TestIdentityProvider()
+ .setKey("disabled")
+ .setName("Disabled")
+ .setEnabled(false)
+ .setAllowsUsersToSignUp(true));
+
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
}
@Test
- public void system_properties() {
- ProtobufSystemInfo.Section section = underTest.toProtobuf();
+ public void return_health() {
+ healthChecker.setHealth(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause("foo")
+ .addCause("bar")
+ .build());
- assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty();
- assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0);
+ ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
+ assertThatAttributeIs(protobuf, "Health", "YELLOW");
+ SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
}
}
diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts
index 1b4fd125a97..df9da694e40 100644
--- a/server/sonar-web/src/main/js/api/system.ts
+++ b/server/sonar-web/src/main/js/api/system.ts
@@ -44,14 +44,13 @@ export interface NodeInfo extends SysValueObject {
Name: string;
Health: HealthType;
'Health Causes': HealthCause[];
+ 'Logs Level': string;
}
export interface SysInfo extends SysValueObject {
Cluster: boolean;
Health: HealthType;
'Health Causes': HealthCause[];
- 'Application Nodes': NodeInfo[];
- 'Search Nodes': NodeInfo[];
}
export function setLogLevel(level: string): Promise<void | Response> {
diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/system-test.js b/server/sonar-web/src/main/js/apps/system/__tests__/system-test.js
deleted file mode 100644
index 0ad07b2794a..00000000000
--- a/server/sonar-web/src/main/js/apps/system/__tests__/system-test.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import ItemValue from '../item-value';
-import ItemBoolean from '../item-boolean';
-import ItemObject from '../item-object';
-import ItemLogLevel from '../item-log-level';
-
-describe('Item Value', () => {
- it('should render string', () => {
- const result = shallow(<ItemValue value="/some/path/as/an/example" />);
- expect(result.find('code').text()).toBe('/some/path/as/an/example');
- });
-});
-
-describe('ItemBoolean', () => {
- it('should render `true`', () => {
- const result = shallow(<ItemBoolean value={true} />);
- expect(result.find('.icon-check').length).toBe(1);
- });
-
- it('should render `false`', () => {
- const result = shallow(<ItemBoolean value={false} />);
- expect(result.find('.icon-delete').length).toBe(1);
- });
-});
-
-describe('ItemObject', () => {
- it('should render object', () => {
- const result = shallow(<ItemObject value={{ name: 'Java', version: '3.2' }} />);
- expect(result.find('table').length).toBe(1);
- expect(result.find('tr').length).toBe(2);
- });
-
- it('should render `true` inside object', () => {
- const result = shallow(<ItemObject value={{ isCool: true }} />);
- const itemValue = result.find(ItemValue);
- expect(itemValue.length).toBe(1);
- expect(itemValue.prop('value')).toBe(true);
- });
-
- it('should render object inside object', () => {
- const result = shallow(
- <ItemObject value={{ users: { docs: 1, shards: 5 }, tests: { docs: 68, shards: 5 } }} />
- );
- expect(result.find(ItemValue).length).toBe(2);
- expect(
- result
- .find(ItemValue)
- .at(0)
- .prop('value')
- ).toEqual({ docs: 1, shards: 5 });
- expect(
- result
- .find(ItemValue)
- .at(1)
- .prop('value')
- ).toEqual({ docs: 68, shards: 5 });
- });
-});
-
-describe('Log Level', () => {
- it('should render select box', () => {
- const result = shallow(<ItemLogLevel value="INFO" />);
- expect(result.find('select').length).toBe(1);
- expect(result.find('option').length).toBe(3);
- });
-
- it('should set initial value', () => {
- const result = shallow(<ItemLogLevel value="DEBUG" />);
- expect(result.find('select').prop('value')).toBe('DEBUG');
- });
-
- it('should render warning', () => {
- const result = shallow(<ItemLogLevel value="DEBUG" />);
- expect(result.find('.alert').length).toBe(1);
- });
-
- it('should not render warning', () => {
- const result = shallow(<ItemLogLevel value="INFO" />);
- expect(result.find('.alert').length).toBe(0);
- });
-});
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
index cd10c605418..0c550fd943f 100644
--- 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
@@ -17,27 +17,43 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import * as utils from '../utils';
+import * as u from '../utils';
describe('parseQuery', () => {
it('should correctly parse the expand array', () => {
- expect(utils.parseQuery({})).toEqual({ expandedCards: [] });
- expect(utils.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] });
+ expect(u.parseQuery({})).toEqual({ expandedCards: [] });
+ expect(u.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] });
});
});
describe('serializeQuery', () => {
it('should correctly serialize the expand array', () => {
- expect(utils.serializeQuery({ expandedCards: [] })).toEqual({});
- expect(utils.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' });
+ expect(u.serializeQuery({ expandedCards: [] })).toEqual({});
+ expect(u.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' });
});
});
describe('groupSections', () => {
it('should correctly group the root field into a main section', () => {
- expect(utils.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
+ expect(u.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
mainSection: { foo: 'Foo', bar: 3 },
sections: { baz: { a: 'a' } }
});
});
});
+
+describe('getSystemLogsLevel', () => {
+ it('should correctly return log level for standalone mode', () => {
+ expect(u.getSystemLogsLevel({ 'Logs Level': 'FOO' } as u.StandaloneSysInfo)).toBe('FOO');
+ expect(u.getSystemLogsLevel({} as u.StandaloneSysInfo)).toBe('INFO');
+ expect(u.getSystemLogsLevel()).toBe('INFO');
+ });
+ it('should return the worst log level for cluster mode', () => {
+ expect(
+ u.getSystemLogsLevel({
+ Cluster: true,
+ 'Application Nodes': [{ 'Logs Level': 'INFO' }, { 'Logs Level': 'DEBUG' }]
+ } as u.ClusterSysInfo)
+ ).toBe('DEBUG');
+ });
+});
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
index 440b117929a..1cd3752de88 100644
--- a/server/sonar-web/src/main/js/apps/system/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx
@@ -22,10 +22,18 @@ import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import ClusterSysInfos from './ClusterSysInfos';
import PageHeader from './PageHeader';
-import StandAloneSysInfos from './StandAloneSysInfos';
+import StandaloneSysInfos from './StandaloneSysInfos';
import { translate } from '../../../helpers/l10n';
import { getSystemInfo, SysInfo } from '../../../api/system';
-import { isCluster, parseQuery, Query, serializeQuery } from '../utils';
+import {
+ ClusterSysInfo,
+ getSystemLogsLevel,
+ isCluster,
+ parseQuery,
+ Query,
+ serializeQuery,
+ StandaloneSysInfo
+} from '../utils';
import { RawQuery } from '../../../helpers/query';
import '../styles.css';
@@ -84,7 +92,7 @@ export default class App extends React.PureComponent<Props, State> {
updateQuery = (newQuery: Query) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
- this.context.router.push({ pathname: this.props.location.pathname, query });
+ this.context.router.replace({ pathname: this.props.location.pathname, query });
};
renderSysInfo() {
@@ -97,26 +105,32 @@ export default class App extends React.PureComponent<Props, State> {
if (isCluster(sysInfoData)) {
return (
<ClusterSysInfos
- sysInfoData={sysInfoData}
expandedCards={query.expandedCards}
+ sysInfoData={sysInfoData as ClusterSysInfo}
toggleCard={this.toggleSysInfoCards}
/>
);
}
- return <StandAloneSysInfos sysInfoData={sysInfoData} />;
+ return (
+ <StandaloneSysInfos
+ expandedCards={query.expandedCards}
+ sysInfoData={sysInfoData as StandaloneSysInfo}
+ toggleCard={this.toggleSysInfoCards}
+ />
+ );
}
render() {
const { loading, sysInfoData } = this.state;
- // TODO Correctly get logLevel, we are not sure yet how we want to do it for cluster mode
return (
<div className="page page-limited">
<Helmet title={translate('system_info.page')} />
<PageHeader
loading={loading}
isCluster={isCluster(sysInfoData)}
- logLevel="INFO"
+ logLevel={getSystemLogsLevel(sysInfoData)}
showActions={sysInfoData != undefined}
+ onLogLevelChange={this.fetchSysInfo}
/>
{this.renderSysInfo()}
</div>
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
index 3065a0be66d..6118fe2d956 100644
--- a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
@@ -21,6 +21,7 @@ import * as React from 'react';
import Modal from 'react-modal';
import { setLogLevel } from '../../../api/system';
import { translate } from '../../../helpers/l10n';
+import { LOGS_LEVELS } from '../utils';
interface Props {
infoMsg: string;
@@ -34,8 +35,6 @@ interface State {
updating: boolean;
}
-const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
-
export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
@@ -78,9 +77,10 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
<h2>{header}</h2>
</div>
<div className="modal-body">
- {LOG_LEVELS.map(level => (
+ {LOGS_LEVELS.map(level => (
<p key={level} className="spacer-bottom">
<input
+ id={`loglevel-${level}`}
type="radio"
className="spacer-right text-middle"
name="system.log_levels"
@@ -88,7 +88,7 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
checked={level === newLevel}
onChange={this.handleLevelChange}
/>
- {level}
+ <label htmlFor={`loglevel-${level}`}>{level}</label>
</p>
))}
<div className="alert alert-info spacer-top">{this.props.infoMsg}</div>
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
index 7f68dc6771a..cb3000dea32 100644
--- a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
@@ -22,19 +22,19 @@ import { sortBy } from 'lodash';
import HealthCard from './info-items/HealthCard';
import { translate } from '../../../helpers/l10n';
import {
+ ClusterSysInfo,
getAppNodes,
getHealth,
getHealthCauses,
- getMainCardSection,
+ getClusterMainCardSection,
getNodeName,
getSearchNodes,
ignoreInfoFields
} from '../utils';
-import { SysInfo } from '../../../api/system';
interface Props {
expandedCards: string[];
- sysInfoData: SysInfo;
+ sysInfoData: ClusterSysInfo;
toggleCard: (toggledCard: string) => void;
}
@@ -49,7 +49,7 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
name={mainCardName}
onClick={toggleCard}
open={expandedCards.includes(mainCardName)}
- sysInfoData={ignoreInfoFields(getMainCardSection(sysInfoData))}
+ sysInfoData={ignoreInfoFields(getClusterMainCardSection(sysInfoData))}
/>
<li className="note system-info-health-title">
{translate('system.application_nodes_title')}
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
index 1b2ef92d34a..4e26c343f02 100644
--- a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
@@ -28,6 +28,7 @@ interface Props {
canRestart: boolean;
cluster: boolean;
logLevel: string;
+ onLogLevelChange: () => void;
}
interface State {
@@ -40,9 +41,9 @@ export default class PageActions extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
+ logLevel: props.logLevel,
openLogsLevelForm: false,
- openRestartForm: false,
- logLevel: props.logLevel
+ openRestartForm: false
};
}
@@ -59,6 +60,7 @@ export default class PageActions extends React.PureComponent<Props, State> {
handleLogsLevelChange = (logLevel: string) => {
this.setState({ logLevel });
+ this.props.onLogLevelChange();
this.handleLogsLevelClose();
};
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
index c9d84569e30..97c4f1077c0 100644
--- a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
@@ -26,21 +26,23 @@ interface Props {
loading: boolean;
logLevel: string;
showActions: boolean;
+ onLogLevelChange: () => void;
}
-export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) {
+export default function PageHeader(props: Props) {
return (
<header className="page-header">
<h1 className="page-title">{translate('system_info.page')}</h1>
- {showActions && (
+ {props.showActions && (
<PageActions
- canDownloadLogs={!isCluster}
- canRestart={!isCluster}
- cluster={isCluster}
- logLevel={logLevel}
+ canDownloadLogs={!props.isCluster}
+ canRestart={!props.isCluster}
+ cluster={props.isCluster}
+ logLevel={props.logLevel}
+ onLogLevelChange={props.onLogLevelChange}
/>
)}
- {loading && (
+ {props.loading && (
<div className="page-actions">
<i className="spinner" />
</div>
diff --git a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
new file mode 100644
index 00000000000..4ed444cc66d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { map } from 'lodash';
+import HealthCard from './info-items/HealthCard';
+import {
+ getHealth,
+ getHealthCauses,
+ getStandaloneMainSections,
+ getStandaloneSecondarySections,
+ ignoreInfoFields,
+ StandaloneSysInfo
+} from '../utils';
+
+interface Props {
+ expandedCards: string[];
+ sysInfoData: StandaloneSysInfo;
+ toggleCard: (toggledCard: string) => void;
+}
+
+export default function StandAloneSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
+ const mainCardName = 'System';
+ return (
+ <ul>
+ <HealthCard
+ biggerHealth={true}
+ health={getHealth(sysInfoData)}
+ healthCauses={getHealthCauses(sysInfoData)}
+ name={mainCardName}
+ onClick={toggleCard}
+ open={expandedCards.includes(mainCardName)}
+ sysInfoData={ignoreInfoFields(getStandaloneMainSections(sysInfoData))}
+ />
+ {map(getStandaloneSecondarySections(sysInfoData), (section, name) => (
+ <HealthCard
+ key={name}
+ name={name}
+ onClick={toggleCard}
+ open={expandedCards.includes(name)}
+ sysInfoData={ignoreInfoFields(section)}
+ />
+ ))}
+ </ul>
+ );
+}
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
index 5b703a1a214..21c60cbd6fc 100644
--- 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
@@ -20,18 +20,38 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import ClusterSysInfos from '../ClusterSysInfos';
-import { HealthType, SysInfo } from '../../../../api/system';
+import { HealthType } from '../../../../api/system';
+import { ClusterSysInfo } from '../../utils';
-const sysInfoData: SysInfo = {
+const sysInfoData: ClusterSysInfo = {
Cluster: true,
Health: HealthType.RED,
Name: 'Foo',
'Health Causes': [{ message: 'Database down' }],
- 'Application Nodes': [{ Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [] }],
- 'Search Nodes': [{ Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [] }]
+ 'Application Nodes': [
+ { Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' }
+ ],
+ 'Search Nodes': [
+ { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'INFO' }
+ ]
};
it('should render correctly', () => {
+ expect(
+ getWrapper({
+ sysInfoData: {
+ ...sysInfoData,
+ 'Application Nodes': [
+ { Name: 'Foo', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' },
+ { Name: 'Bar', Health: HealthType.RED, 'Health Causes': [], 'Logs Level': 'DEBUG' },
+ { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'TRACE' }
+ ]
+ }
+ }).find('HealthCard')
+ ).toHaveLength(5);
+});
+
+it('should support more than two nodes', () => {
expect(getWrapper()).toMatchSnapshot();
});
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
index 098e897ec92..627347f8886 100644
--- 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
@@ -25,7 +25,13 @@ import { click } from '../../../../helpers/testUtils';
it('should render correctly', () => {
expect(
shallow(
- <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ <PageActions
+ canDownloadLogs={true}
+ canRestart={true}
+ cluster={false}
+ logLevel="INFO"
+ onLogLevelChange={() => {}}
+ />
)
).toMatchSnapshot();
});
@@ -33,14 +39,26 @@ it('should render correctly', () => {
it('should render without restart and log download', () => {
expect(
shallow(
- <PageActions canDownloadLogs={false} canRestart={false} cluster={true} logLevel="INFO" />
+ <PageActions
+ canDownloadLogs={false}
+ canRestart={false}
+ cluster={true}
+ logLevel="INFO"
+ onLogLevelChange={() => {}}
+ />
)
).toMatchSnapshot();
});
it('should open restart modal', () => {
const wrapper = shallow(
- <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ <PageActions
+ canDownloadLogs={true}
+ canRestart={true}
+ cluster={false}
+ logLevel="INFO"
+ onLogLevelChange={() => {}}
+ />
);
click(wrapper.find('#restart-server-button'));
expect(wrapper.find('RestartForm')).toHaveLength(1);
@@ -48,7 +66,13 @@ it('should open restart modal', () => {
it('should open change log level modal', () => {
const wrapper = shallow(
- <PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
+ <PageActions
+ canDownloadLogs={true}
+ canRestart={true}
+ cluster={false}
+ logLevel="INFO"
+ onLogLevelChange={() => {}}
+ />
);
click(wrapper.find('#edit-logs-level-button'));
expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1);
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
index f5255c7228e..a101f213406 100644
--- 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
@@ -23,12 +23,28 @@ import PageHeader from '../PageHeader';
it('should render correctly', () => {
expect(
- shallow(<PageHeader isCluster={true} loading={false} logLevel="INFO" showActions={true} />)
+ shallow(
+ <PageHeader
+ isCluster={true}
+ loading={false}
+ logLevel="INFO"
+ showActions={true}
+ onLogLevelChange={() => {}}
+ />
+ )
).toMatchSnapshot();
});
it('should show a loading spinner and no actions', () => {
expect(
- shallow(<PageHeader isCluster={true} loading={true} logLevel="INFO" showActions={false} />)
+ shallow(
+ <PageHeader
+ isCluster={true}
+ loading={true}
+ logLevel="INFO"
+ showActions={false}
+ onLogLevelChange={() => {}}
+ />
+ )
).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/system/item-value.js b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx
index 5461aa95e8b..c70202e3219 100644
--- a/server/sonar-web/src/main/js/apps/system/item-value.js
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx
@@ -17,29 +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.
*/
-import React from 'react';
-import ItemBoolean from './item-boolean';
-import ItemObject from './item-object';
-import ItemLogLevel from './item-log-level';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import StandaloneSysInfos from '../StandaloneSysInfos';
+import { HealthType } from '../../../../api/system';
+import { StandaloneSysInfo } from '../../utils';
-export default class ItemValue extends React.PureComponent {
- render() {
- if (this.props.name === 'Logs Level') {
- return <ItemLogLevel value={this.props.value} />;
- }
+const sysInfoData: StandaloneSysInfo = {
+ Cluster: true,
+ Health: HealthType.RED,
+ 'Logs Level': 'DEBUG',
+ Name: 'Foo',
+ 'Health Causes': [{ message: 'Database down' }],
+ 'Web JVM': { 'Max Memory': '2Gb' },
+ 'Compute Engine': { Pending: 4 },
+ Elasticsearch: { 'Number of Nodes': 1 }
+};
- const rawValue = this.props.value;
- let formattedValue;
- switch (typeof this.props.value) {
- case 'boolean':
- formattedValue = <ItemBoolean value={rawValue} />;
- break;
- case 'object':
- formattedValue = <ItemObject value={rawValue} />;
- break;
- default:
- formattedValue = <code>{rawValue}</code>;
- }
- return formattedValue;
- }
+it('should render correctly', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <StandaloneSysInfos
+ expandedCards={['Compute Engine', 'Foo']}
+ sysInfoData={sysInfoData}
+ toggleCard={() => {}}
+ {...props}
+ />
+ );
}
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
index 04cfc11d49b..eeaa775462d 100644
--- 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
@@ -34,12 +34,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
<input
checked={false}
className="spacer-right text-middle"
+ id="loglevel-INFO"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="INFO"
/>
- INFO
+ <label
+ htmlFor="loglevel-INFO"
+ >
+ INFO
+ </label>
</p>
<p
className="spacer-bottom"
@@ -47,12 +52,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
<input
checked={true}
className="spacer-right text-middle"
+ id="loglevel-DEBUG"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="DEBUG"
/>
- DEBUG
+ <label
+ htmlFor="loglevel-DEBUG"
+ >
+ DEBUG
+ </label>
</p>
<p
className="spacer-bottom"
@@ -60,12 +70,17 @@ exports[`should display some warning messages for non INFO levels 1`] = `
<input
checked={false}
className="spacer-right text-middle"
+ id="loglevel-TRACE"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="TRACE"
/>
- TRACE
+ <label
+ htmlFor="loglevel-TRACE"
+ >
+ TRACE
+ </label>
</p>
<div
className="alert alert-info spacer-top"
@@ -133,12 +148,17 @@ exports[`should render correctly 1`] = `
<input
checked={true}
className="spacer-right text-middle"
+ id="loglevel-INFO"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="INFO"
/>
- INFO
+ <label
+ htmlFor="loglevel-INFO"
+ >
+ INFO
+ </label>
</p>
<p
className="spacer-bottom"
@@ -146,12 +166,17 @@ exports[`should render correctly 1`] = `
<input
checked={false}
className="spacer-right text-middle"
+ id="loglevel-DEBUG"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="DEBUG"
/>
- DEBUG
+ <label
+ htmlFor="loglevel-DEBUG"
+ >
+ DEBUG
+ </label>
</p>
<p
className="spacer-bottom"
@@ -159,12 +184,17 @@ exports[`should render correctly 1`] = `
<input
checked={false}
className="spacer-right text-middle"
+ id="loglevel-TRACE"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="TRACE"
/>
- TRACE
+ <label
+ htmlFor="loglevel-TRACE"
+ >
+ TRACE
+ </label>
</p>
<div
className="alert alert-info spacer-top"
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
index 20ed8d541f6..abd7d3a63ac 100644
--- 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
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should support more than two nodes 1`] = `
<ul>
<HealthCard
biggerHealth={true}
@@ -34,6 +34,7 @@ exports[`should render correctly 1`] = `
open={false}
sysInfoData={
Object {
+ "Logs Level": "INFO",
"Name": "Bar",
}
}
@@ -51,6 +52,7 @@ exports[`should render correctly 1`] = `
open={false}
sysInfoData={
Object {
+ "Logs Level": "INFO",
"Name": "Baz",
}
}
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
index 2945b6df932..39f93da4322 100644
--- 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
@@ -14,6 +14,7 @@ exports[`should render correctly 1`] = `
canRestart={false}
cluster={true}
logLevel="INFO"
+ onLogLevelChange={[Function]}
/>
</header>
`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap
new file mode 100644
index 00000000000..f4961aeac1e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ul>
+ <HealthCard
+ biggerHealth={true}
+ health="RED"
+ healthCauses={
+ Array [
+ Object {
+ "message": "Database down",
+ },
+ ]
+ }
+ name="System"
+ onClick={[Function]}
+ open={false}
+ sysInfoData={
+ Object {
+ "Logs Level": "DEBUG",
+ "Name": "Foo",
+ }
+ }
+ />
+ <HealthCard
+ name="Web"
+ onClick={[Function]}
+ open={false}
+ sysInfoData={
+ Object {
+ "Web Database Connectivity": undefined,
+ "Web JVM": Object {
+ "Max Memory": "2Gb",
+ },
+ "Web JVM Properties": undefined,
+ }
+ }
+ />
+ <HealthCard
+ name="Compute Engine"
+ onClick={[Function]}
+ open={true}
+ sysInfoData={
+ Object {
+ "Compute Engine JVM": undefined,
+ "Compute Engine JVM Properties": undefined,
+ "Pending": 4,
+ }
+ }
+ />
+ <HealthCard
+ name="Search"
+ onClick={[Function]}
+ open={false}
+ sysInfoData={
+ Object {
+ "Elasticsearch": Object {
+ "Number of Nodes": 1,
+ },
+ "Search JVM": undefined,
+ "Search JVM Properties": undefined,
+ }
+ }
+ />
+</ul>
+`;
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
index b45205dccba..459f26ee721 100644
--- 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
@@ -24,12 +24,13 @@ import HealthItem from './HealthItem';
import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon';
import Section from './Section';
import { HealthType, HealthCause, SysValueObject } from '../../../../api/system';
-import { groupSections } from '../../utils';
+import { LOGS_LEVELS, groupSections, getLogsLevel } from '../../utils';
+import { translate } from '../../../../helpers/l10n';
interface Props {
biggerHealth?: boolean;
- health: HealthType;
- healthCauses: HealthCause[];
+ health?: HealthType;
+ healthCauses?: HealthCause[];
onClick: (toggledCard: string) => void;
open: boolean;
name: string;
@@ -48,10 +49,12 @@ export default class HealthCard extends React.PureComponent<Props, State> {
onDetailLeave = () => this.setState({ hoveringDetail: false });
render() {
- const { open, sysInfoData } = this.props;
+ const { health, open, sysInfoData } = this.props;
const { mainSection, sections } = groupSections(sysInfoData);
const showFields = open && mainSection && Object.keys(mainSection).length > 0;
const showSections = open && sections;
+ const logLevel = getLogsLevel(sysInfoData);
+ const showLogLevelWarning = logLevel && logLevel !== LOGS_LEVELS[0];
return (
<li
className={classNames('boxed-group system-info-health-card', {
@@ -62,11 +65,19 @@ export default class HealthCard extends React.PureComponent<Props, State> {
<OpenCloseIcon className="little-spacer-right" open={open} />
{this.props.name}
</span>
- <HealthItem
- className={classNames('pull-right', { 'big-dot': this.props.biggerHealth })}
- health={this.props.health}
- healthCauses={this.props.healthCauses}
- />
+ {health && (
+ <HealthItem
+ biggerHealth={this.props.biggerHealth}
+ className="pull-right spacer-left"
+ health={health}
+ healthCauses={this.props.healthCauses}
+ />
+ )}
+ {showLogLevelWarning && (
+ <span className="pull-right alert alert-danger">
+ {translate('system.log_level.warning.short')}
+ </span>
+ )}
</div>
{open && (
<div
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
index a0793a81831..86618c34c8a 100644
--- 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
@@ -20,15 +20,17 @@
import * as React from 'react';
import * as classNames from 'classnames';
import HealthCauseItem from './HealthCauseItem';
+import StatusIndicator from '../../../../components/common/StatusIndicator';
import { HealthType, HealthCause } from '../../../../api/system';
interface Props {
+ biggerHealth?: boolean;
className?: string;
health: HealthType;
healthCauses?: HealthCause[];
}
-export default function HealthItem({ className, health, healthCauses }: Props) {
+export default function HealthItem({ biggerHealth, className, health, healthCauses }: Props) {
const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
return (
<div className={classNames('system-info-health-info', className)}>
@@ -36,7 +38,7 @@ export default function HealthItem({ className, health, healthCauses }: Props) {
healthCauses!.map((cause, idx) => (
<HealthCauseItem key={idx} className="spacer-right" health={health} healthCause={cause} />
))}
- <span className={classNames('system-info-health-dot', health)} />
+ <StatusIndicator color={health.toLowerCase()} size={biggerHealth ? 'big' : undefined} />
</div>
);
}
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
index c080f444dec..98b33bd31ee 100644
--- 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
@@ -48,6 +48,12 @@ it('should show a main section and multiple sub sections', () => {
expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot();
});
+it('should display the log level alert', () => {
+ expect(
+ getShallowWrapper({ sysInfoData: { 'Logs Level': 'DEBUG' } }).find('.alert')
+ ).toMatchSnapshot();
+});
+
function getShallowWrapper(props = {}) {
return shallow(
<HealthCard
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
index f77622b9e92..9f1bd198352 100644
--- 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
@@ -24,7 +24,9 @@ import { HealthType } from '../../../../../api/system';
it('should render correctly', () => {
expect(
- shallow(<HealthItem health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />)
+ shallow(
+ <HealthItem biggerHealth={true} health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />
+ )
).toMatchSnapshot();
});
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
index 724001990fa..7390ef8020e 100644
--- 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
@@ -1,5 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should display the log level alert 1`] = `
+<span
+ className="pull-right alert alert-danger"
+>
+ system.log_level.warning.short
+</span>
+`;
+
exports[`should display the sysinfo detail 1`] = `
<li
className="boxed-group system-info-health-card"
@@ -18,7 +26,8 @@ exports[`should display the sysinfo detail 1`] = `
Foobar
</span>
<HealthItem
- className="pull-right big-dot"
+ biggerHealth={true}
+ className="pull-right spacer-left"
health="RED"
healthCauses={
Array [
@@ -55,7 +64,8 @@ exports[`should render correctly 1`] = `
Foobar
</span>
<HealthItem
- className="pull-right"
+ biggerHealth={false}
+ className="pull-right spacer-left"
health="RED"
healthCauses={
Array [
@@ -87,7 +97,8 @@ exports[`should show a main section and multiple sub sections 1`] = `
Foobar
</span>
<HealthItem
- className="pull-right"
+ biggerHealth={false}
+ className="pull-right spacer-left"
health="RED"
healthCauses={
Array [
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
index d9fa53a2956..f962437404c 100644
--- 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
@@ -4,8 +4,8 @@ exports[`should not render health causes 1`] = `
<div
className="system-info-health-info"
>
- <span
- className="system-info-health-dot GREEN"
+ <StatusIndicator
+ color="green"
/>
</div>
`;
@@ -14,8 +14,8 @@ exports[`should not render health causes 2`] = `
<div
className="system-info-health-info"
>
- <span
- className="system-info-health-dot YELLOW"
+ <StatusIndicator
+ color="yellow"
/>
</div>
`;
@@ -33,8 +33,9 @@ exports[`should render correctly 1`] = `
}
}
/>
- <span
- className="system-info-health-dot RED"
+ <StatusIndicator
+ color="red"
+ size="big"
/>
</div>
`;
@@ -61,8 +62,8 @@ exports[`should render multiple health causes 1`] = `
}
}
/>
- <span
- className="system-info-health-dot YELLOW"
+ <StatusIndicator
+ color="yellow"
/>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/system/item-boolean.js b/server/sonar-web/src/main/js/apps/system/item-boolean.js
deleted file mode 100644
index aeac6e88390..00000000000
--- a/server/sonar-web/src/main/js/apps/system/item-boolean.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-
-export default class ItemBoolean extends React.PureComponent {
- render() {
- if (this.props.value) {
- return <i className="icon-check" />;
- } else {
- return <i className="icon-delete" />;
- }
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/item-log-level.js b/server/sonar-web/src/main/js/apps/system/item-log-level.js
deleted file mode 100644
index 89364f164d8..00000000000
--- a/server/sonar-web/src/main/js/apps/system/item-log-level.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { setLogLevel } from '../../api/system';
-import { translate } from '../../helpers/l10n';
-
-const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
-
-export default class ItemLogLevel extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = { level: props.value };
- }
-
- onChange = () => {
- const newValue = this.refs.select.value;
- setLogLevel(newValue).then(() => {
- this.setState({ level: newValue });
- });
- };
-
- render() {
- const options = LOG_LEVELS.map(level => (
- <option key={level} value={level}>
- {level}
- </option>
- ));
- const warning =
- this.state.level !== 'INFO' ? (
- <div className="alert alert-danger spacer-top" style={{ wordBreak: 'normal' }}>
- {translate('system.log_level.warning')}
- </div>
- ) : null;
- return (
- <div>
- <select ref="select" onChange={this.onChange} value={this.state.level}>
- {options}
- </select>
- {warning}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/item-object.js b/server/sonar-web/src/main/js/apps/system/item-object.js
deleted file mode 100644
index d9386cbce8d..00000000000
--- a/server/sonar-web/src/main/js/apps/system/item-object.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import ItemValue from './item-value';
-
-export default class ItemObject extends React.PureComponent {
- render() {
- const rows = Object.keys(this.props.value).map(key => {
- return (
- <tr key={key}>
- <td className="thin nowrap">{key}</td>
- <td>
- <ItemValue value={this.props.value[key]} />
- </td>
- </tr>
- );
- });
- return (
- <table className="data">
- <tbody>{rows}</tbody>
- </table>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/main.js b/server/sonar-web/src/main/js/apps/system/main.js
deleted file mode 100644
index 150bd042adf..00000000000
--- a/server/sonar-web/src/main/js/apps/system/main.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import Helmet from 'react-helmet';
-import { sortBy } from 'lodash';
-import { getSystemInfo } from '../../api/system';
-import Section from './section';
-import { translate } from '../../helpers/l10n';
-import RestartForm from '../../components/common/RestartForm';
-
-const SECTIONS_ORDER = [
- 'SonarQube',
- 'Database',
- 'System',
- 'Elasticsearch State',
- 'Elasticsearch',
- 'Compute Engine Tasks',
- 'Compute Engine State',
- 'Compute Engine Database Connection',
- 'JvmProperties'
-];
-
-export default class Main extends React.PureComponent {
- state = { openRestartForm: false };
-
- componentDidMount() {
- getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) }));
- }
-
- parseSections = data => {
- const sections = Object.keys(data).map(section => {
- return { name: section, items: this.parseItems(data[section]) };
- });
- return this.orderSections(sections);
- };
-
- orderSections = sections => sortBy(sections, section => SECTIONS_ORDER.indexOf(section.name));
-
- parseItems = data => {
- const items = Object.keys(data).map(item => {
- return { name: item, value: data[item] };
- });
- return this.orderItems(items);
- };
-
- orderItems = items => sortBy(items, 'name');
-
- handleServerRestartOpen = () => this.setState({ openRestartForm: true });
- handleServerRestartClose = () => this.setState({ openRestartForm: false });
-
- render() {
- let sections = null;
- if (this.state && this.state.sections) {
- sections = this.state.sections
- .filter(section => SECTIONS_ORDER.indexOf(section.name) >= 0)
- .map(section => (
- <Section key={section.name} section={section.name} items={section.items} />
- ));
- }
-
- return (
- <div className="page page-limited">
- <Helmet title={translate('system_info.page')} />
- <header className="page-header">
- <h1 className="page-title">{translate('system_info.page')}</h1>
- <div className="page-actions">
- <a href={window.baseUrl + '/api/system/info'} id="download-link">
- Download
- </a>
- <div className="display-inline-block dropdown big-spacer-left">
- <button data-toggle="dropdown">
- Logs <i className="icon-dropdown" />
- </button>
- <ul className="dropdown-menu">
- <li>
- <a href={window.baseUrl + '/api/system/logs?process=app'} id="logs-link">
- Main Process
- </a>
- </li>
- <li>
- <a href={window.baseUrl + '/api/system/logs?process=ce'} id="ce-logs-link">
- Compute Engine
- </a>
- </li>
- <li>
- <a href={window.baseUrl + '/api/system/logs?process=es'} id="es-logs-link">
- Elasticsearch
- </a>
- </li>
- <li>
- <a href={window.baseUrl + '/api/system/logs?process=web'} id="web-logs-link">
- Web Server
- </a>
- </li>
- </ul>
- </div>
- <button
- id="restart-server-button"
- className="big-spacer-left"
- onClick={this.handleServerRestartOpen}>
- Restart Server
- </button>
- {this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
- </div>
- </header>
- {sections}
- </div>
- );
- }
-}
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 1a8d6a5ab38..fcffeff191c 100644
--- a/server/sonar-web/src/main/js/apps/system/routes.ts
+++ b/server/sonar-web/src/main/js/apps/system/routes.ts
@@ -17,19 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+import { RouterState, IndexRouteProps } from 'react-router';
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/App').then(i => callback(null, { component: i.default }));
}
- },
- {
- path: 'old',
- getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
- import('./main').then(i => callback(null, (i as any).default));
- }
}
];
diff --git a/server/sonar-web/src/main/js/apps/system/section.js b/server/sonar-web/src/main/js/apps/system/section.js
deleted file mode 100644
index 89b90e99c5d..00000000000
--- a/server/sonar-web/src/main/js/apps/system/section.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import ItemValue from './item-value';
-
-export default class Section extends React.PureComponent {
- render() {
- const items = this.props.items.map(item => {
- return (
- <tr key={item.name}>
- <td className="thin">
- <div style={{ width: '25vw', overflow: 'hidden', textOverflow: 'ellipsis' }}>
- {item.name}
- </div>
- </td>
- <td style={{ wordBreak: 'break-all' }}>
- <ItemValue name={item.name} value={item.value} />
- </td>
- </tr>
- );
- });
-
- return (
- <div className="big-spacer-bottom">
- <h3 className="spacer-bottom">{this.props.section}</h3>
- <table className="data zebra" id={this.props.section}>
- <tbody>{items}</tbody>
- </table>
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css
index f1156609add..f34dc3fa394 100644
--- a/server/sonar-web/src/main/js/apps/system/styles.css
+++ b/server/sonar-web/src/main/js/apps/system/styles.css
@@ -21,6 +21,10 @@
padding-bottom: 15px;
}
+.system-info-health-card .boxed-group-header > .alert {
+ margin-top: -6px;
+}
+
.system-info-health-card .boxed-group-inner {
padding-top: 0;
}
@@ -30,43 +34,19 @@
}
.system-info-health-info {
- margin-top: -4px;
-}
-
-.system-info-health-dot {
- display: inline-block;
- width: 16px;
- height: 16px;
- margin: 4px;
- border-radius: 16px;
- box-sizing: border-box;
-}
-
-.system-info-health-dot.GREEN {
- background-color: #00aa00;
+ margin-top: -12px;
}
-.system-info-health-dot.YELLOW {
- background-color: #eabe06;
-}
-.system-info-health-dot.RED {
- background-color: #d4333f;
+.system-info-health-info .status-indicator {
+ position: relative;
+ top: 8px;
}
.system-info-health-info .alert {
display: inline-block;
- position: relative;
- top: -8px;
-}
-
-.system-info-health-info.big-dot .system-info-health-dot {
- width: 24px;
- height: 24px;
- margin: 0;
- border-radius: 24px;
}
-.system-info-health-info.no-margin .system-info-health-dot {
+.system-info-health-info.no-margin .status-indicator {
margin: 0;
}
diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts
index 41ad5223544..ae8818edf9d 100644
--- a/server/sonar-web/src/main/js/apps/system/utils.ts
+++ b/server/sonar-web/src/main/js/apps/system/utils.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { each, omit, memoize } from 'lodash';
+import { each, omit, memoize, sortBy } from 'lodash';
import {
cleanQuery,
parseAsArray,
@@ -38,6 +38,16 @@ export interface Query {
expandedCards: string[];
}
+export interface ClusterSysInfo extends SysInfo {
+ 'Application Nodes': NodeInfo[];
+ 'Search Nodes': NodeInfo[];
+}
+
+export interface StandaloneSysInfo extends SysInfo {
+ 'Logs Level': string;
+}
+
+export const LOGS_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
export const HEALTH_FIELD = 'Health';
export const HEALTHCAUSES_FIELD = 'Health Causes';
@@ -45,10 +55,6 @@ export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject
return omit(sysInfoObject, ['Cluster', HEALTH_FIELD, HEALTHCAUSES_FIELD]);
}
-export function getAppNodes(sysInfoData: SysInfo): NodeInfo[] {
- return sysInfoData['Application Nodes'];
-}
-
export function getHealth(sysInfoObject: SysValueObject): HealthType {
return sysInfoObject[HEALTH_FIELD] as HealthType;
}
@@ -57,15 +63,74 @@ export function getHealthCauses(sysInfoObject: SysValueObject): HealthCause[] {
return sysInfoObject[HEALTHCAUSES_FIELD] as HealthCause[];
}
-export function getMainCardSection(sysInfoData: SysInfo): SysValueObject {
- return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
+export function getLogsLevel(sysInfoObject: SysValueObject): string {
+ return (sysInfoObject['Logs Level'] || LOGS_LEVELS[0]) as string;
+}
+
+export function getSystemLogsLevel(sysInfoData?: SysInfo): string {
+ const defaultLevel = LOGS_LEVELS[0];
+ if (!sysInfoData) {
+ return defaultLevel;
+ }
+ if (isCluster(sysInfoData)) {
+ const nodes = sortBy(getAppNodes(sysInfoData as ClusterSysInfo), node =>
+ LOGS_LEVELS.indexOf(getLogsLevel(node))
+ );
+ return nodes.length > 0 ? getLogsLevel(nodes[nodes.length - 1]) : defaultLevel;
+ } else {
+ return getLogsLevel(sysInfoData);
+ }
}
export function getNodeName(nodeInfo: NodeInfo): string {
return nodeInfo['Name'];
}
-export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] {
+export function getClusterMainCardSection(sysInfoData: ClusterSysInfo): SysValueObject {
+ return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
+}
+
+export function getStandaloneMainSections(sysInfoData: StandaloneSysInfo): SysValueObject {
+ return omit(sysInfoData, [
+ 'Settings',
+ 'Statistics',
+ 'Compute Engine',
+ 'Compute Engine JVM',
+ 'Compute Engine JVM Properties',
+ 'Elasticsearch',
+ 'Search JVM',
+ 'Search JVM Properties',
+ 'Web Database Connectivity',
+ 'Web JVM',
+ 'Web JVM Properties'
+ ]);
+}
+
+export function getStandaloneSecondarySections(sysInfoData: StandaloneSysInfo): SysInfoSection {
+ return {
+ Web: {
+ 'Web Database Connectivity': sysInfoData['Web Database Connectivity'],
+ 'Web JVM': sysInfoData['Web JVM'],
+ 'Web JVM Properties': sysInfoData['Web JVM Properties']
+ },
+ 'Compute Engine': {
+ ...sysInfoData['Compute Engine'] as SysValueObject,
+ 'Compute Engine JVM': sysInfoData['Compute Engine JVM'],
+ 'Compute Engine JVM Properties': sysInfoData['Compute Engine JVM Properties']
+ },
+ Search: {
+ Elasticsearch: sysInfoData['Elasticsearch'] as SysValueObject,
+ 'Search JVM': sysInfoData['Search JVM'],
+ 'Search JVM Properties': sysInfoData['Search JVM Properties']
+ }
+ };
+}
+
+export function getAppNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
+ return sysInfoData['Application Nodes'];
+}
+
+export function getSearchNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
return sysInfoData['Search Nodes'];
}
@@ -83,7 +148,7 @@ export function groupSections(sysInfoData: SysValueObject) {
}
export function isCluster(sysInfoData?: SysInfo): boolean {
- return sysInfoData != undefined && sysInfoData['Cluster'];
+ return sysInfoData != undefined && sysInfoData['Cluster'] === true;
}
export const parseQuery = memoize((urlQuery: RawQuery): Query => {
diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.css b/server/sonar-web/src/main/js/components/common/BranchStatus.css
index 74278d67573..ccfccb351f1 100644
--- a/server/sonar-web/src/main/js/components/common/BranchStatus.css
+++ b/server/sonar-web/src/main/js/components/common/BranchStatus.css
@@ -3,18 +3,6 @@
text-align: right;
}
-.branch-status-indicator {
- display: block;
- width: 8px;
- height: 8px;
- border-radius: 8px;
- margin: 4px 0;
-}
-
-.branch-status-indicator.is-failed {
- background-color: #d4333f;
-}
-
-.branch-status-indicator.is-passed {
- background-color: #00aa00;
+.branch-status .status-indicator {
+ margin: 0;
}
diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
index 40688087cf0..bb9069bc234 100644
--- a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
+++ b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx
@@ -18,11 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as classNames from 'classnames';
import { Branch } from '../../app/types';
import Level from '../ui/Level';
import BugIcon from '../icons-components/BugIcon';
import CodeSmellIcon from '../icons-components/CodeSmellIcon';
+import StatusIndicator from './StatusIndicator';
import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
import { isShortLivingBranch } from '../../helpers/branches';
import './BranchStatus.css';
@@ -41,22 +41,19 @@ export default function BranchStatus({ branch, concise = false }: Props) {
const totalIssues =
branch.status.bugs + branch.status.vulnerabilities + branch.status.codeSmells;
- const indicatorClassName = classNames('branch-status-indicator', {
- 'is-failed': totalIssues > 0,
- 'is-passed': totalIssues === 0
- });
+ const indicatorColor = totalIssues > 0 ? 'red' : 'green';
return concise ? (
<ul className="list-inline branch-status">
<li>{totalIssues}</li>
<li className="spacer-left">
- <i className={indicatorClassName} />
+ <StatusIndicator color={indicatorColor} size="small" />
</li>
</ul>
) : (
<ul className="list-inline branch-status">
<li className="spacer-right">
- <i className={indicatorClassName} />
+ <StatusIndicator color={indicatorColor} size="small" />
</li>
<li>
{branch.status.bugs}
diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.css b/server/sonar-web/src/main/js/components/common/StatusIndicator.css
new file mode 100644
index 00000000000..26008838cd2
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/StatusIndicator.css
@@ -0,0 +1,34 @@
+.status-indicator {
+ display: inline-block;
+ box-sizing: border-box;
+ width: 16px;
+ height: 16px;
+ border-radius: 16px;
+ margin: 4px;
+}
+
+.status-indicator.small-status-indicator {
+ width: 8px;
+ height: 8px;
+ border-radius: 8px;
+ margin: 8px;
+}
+
+.status-indicator.big-status-indicator {
+ width: 24px;
+ height: 24px;
+ border-radius: 24px;
+ margin: 0;
+}
+
+.status-indicator.red {
+ background-color: #d4333f;
+}
+
+.sstatus-indicator.yellow {
+ background-color: #eabe06;
+}
+
+.status-indicator.green {
+ background-color: #00aa00;
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx b/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx
index 2a142c58a0e..6fc96354033 100644
--- a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
+++ b/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx
@@ -1,7 +1,7 @@
/*
* SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -18,13 +18,27 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
+import './StatusIndicator.css';
interface Props {
- sysInfoData: object;
+ className?: string;
+ color?: string;
+ size?: string;
}
-export default class StandAloneSysInfos extends React.PureComponent<Props> {
- render() {
- return <div>StandAloneSysInfos</div>;
- }
+export default function StatusIndicator({ className, color, size }: Props) {
+ return (
+ <i
+ className={classNames(
+ 'status-indicator',
+ color,
+ {
+ 'small-status-indicator': size === 'small',
+ 'big-status-indicator': size === 'big'
+ },
+ className
+ )}
+ />
+ );
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
index b106929fbe4..32c0d0a2afa 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
@@ -23,8 +23,9 @@ exports[`renders status of short-living branches 1`] = `
<li
className="spacer-right"
>
- <i
- className="branch-status-indicator is-passed"
+ <StatusIndicator
+ color="green"
+ size="small"
/>
</li>
<li>
@@ -49,8 +50,9 @@ exports[`renders status of short-living branches 2`] = `
<li
className="spacer-right"
>
- <i
- className="branch-status-indicator is-failed"
+ <StatusIndicator
+ color="red"
+ size="small"
/>
</li>
<li>
@@ -75,8 +77,9 @@ exports[`renders status of short-living branches 3`] = `
<li
className="spacer-right"
>
- <i
- className="branch-status-indicator is-failed"
+ <StatusIndicator
+ color="red"
+ size="small"
/>
</li>
<li>
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
index 739bc6027e7..ba583b3de7c 100644
--- a/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
+++ b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
@@ -35,9 +35,9 @@ export default function OpenCloseIcon({ className, open, size = 14 }: Props) {
height={size}
style={{ fill: 'currentColor' }}>
{open ? (
- <path d="M13.506 8.539l-5.191 5.184q-0.133 0.133-0.315 0.133t-0.315-0.133l-5.191-5.184q-0.133-0.133-0.133-0.318t0.133-0.318l1.161-1.154q0.133-0.133 0.315-0.133t0.315 0.133l3.715 3.715 3.715-3.715q0.133-0.133 0.315-0.133t0.315 0.133l1.161 1.154q0.133 0.133 0.133 0.318t-0.133 0.318z" />
+ <path d="M13.506 9.289l-5.191 5.184q-0.133 0.133-0.315 0.133t-0.315-0.133l-5.191-5.184q-0.133-0.133-0.133-0.318t0.133-0.318l1.161-1.154q0.133-0.133 0.315-0.133t0.315 0.133l3.715 3.715 3.715-3.715q0.133-0.133 0.315-0.133t0.315 0.133l1.161 1.154q0.133 0.133 0.133 0.318t-0.133 0.318z" />
) : (
- <path d="M13.527 8.318l-5.244 5.244q-0.134 0.134-0.318 0.134t-0.318-0.134l-1.173-1.173q-0.134-0.134-0.134-0.318t0.134-0.318l3.753-3.753-3.753-3.753q-0.134-0.134-0.134-0.318t0.134-0.318l1.173-1.173q0.134-0.134 0.318-0.134t0.318 0.134l5.244 5.244q0.134 0.134 0.134 0.318t-0.134 0.318z" />
+ <path d="M13.527 9.318l-5.244 5.244q-0.134 0.134-0.318 0.134t-0.318-0.134l-1.173-1.173q-0.134-0.134-0.134-0.318t0.134-0.318l3.753-3.753-3.753-3.753q-0.134-0.134-0.134-0.318t0.134-0.318l1.173-1.173q0.134-0.134 0.318-0.134t0.318 0.134l5.244 5.244q0.134 0.134 0.134 0.318t-0.134 0.318z" />
)}
</svg>
);