1
0

#11 Metrics widget

This commit is contained in:
2022-08-13 15:09:05 +02:00
parent 16fe2c4a93
commit 4134160b97
15 changed files with 334 additions and 5 deletions

View File

@@ -1,19 +1,26 @@
package de.nbscloud.dashboard;
import de.nbscloud.dashboard.widget.MachineMetricsWidget;
import de.nbscloud.webcontainer.registry.App;
import de.nbscloud.webcontainer.registry.AppRegistry;
import de.nbscloud.webcontainer.registry.Widget;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
@Component
public class DashboardApp implements App, InitializingBean {
public static final String ID = "dashboard";
@Autowired
private AppRegistry appRegistry;
@Override
public String getId() {
return "dashboard";
return ID;
}
@Override
@@ -31,6 +38,11 @@ public class DashboardApp implements App, InitializingBean {
return 0;
}
@Override
public Collection<Widget> getWidgets() {
return List.of(new MachineMetricsWidget());
}
@Override
public void afterPropertiesSet() throws Exception {
this.appRegistry.registerApp(this);

View File

@@ -0,0 +1,49 @@
package de.nbscloud.dashboard.config;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.UpdateScrapers;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "nbs-cloud.dashboard")
@PropertySource("classpath:/config/dashboard-application.properties")
public class DashboardConfig {
@Configuration
public static class Metrics {
@Configuration
public static class Updates {
private List<UpdateScrapers> scrapers;
public List<UpdateScrapers> getScrapers() {
return scrapers;
}
public void setScrapers(List<UpdateScrapers> scrapers) {
this.scrapers = scrapers;
}
}
private Updates updates;
public Updates getUpdates() {
return updates;
}
public void setUpdates(Updates updates) {
this.updates = updates;
}
}
private Metrics metrics;
public Metrics getMetrics() {
return metrics;
}
public void setMetrics(Metrics metrics) {
this.metrics = metrics;
}
}

View File

@@ -0,0 +1,34 @@
package de.nbscloud.dashboard.controller;
import de.nbscloud.dashboard.config.DashboardConfig;
import de.nbscloud.dashboard.widget.service.MetricService;
import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class DashboardWidgetController {
private static final Logger logger = LoggerFactory.getLogger(DashboardWidgetController.class);
@Autowired
private WebContainerSharedConfig webContainerSharedConfig;
@Autowired
private MetricService metricService;
@Autowired
private DashboardConfig dashboardConfig;
@GetMapping("dashboard/widgets/machineMetrics")
public String getDiskUsage(HttpServletRequest request, HttpServletResponse response, Model model) {
model.addAttribute("interfaces", metricService.getInterfaces());
model.addAttribute("updates", metricService.getUpdates());
return "dashboard/widgets/machineMetrics :: dashboard-machine-metrics";
}
}

View File

@@ -0,0 +1,11 @@
package de.nbscloud.dashboard.widget;
import de.nbscloud.dashboard.DashboardApp;
import de.nbscloud.webcontainer.registry.Widget;
public class MachineMetricsWidget implements Widget {
@Override
public String getPath() {
return DashboardApp.ID + "/widgets/machineMetrics";
}
}

View File

@@ -0,0 +1,9 @@
package de.nbscloud.dashboard.widget.metrics.updates.scraper;
import de.nbscloud.dashboard.widget.service.MetricService;
import java.util.Map;
public interface UpdateScraper {
public MetricService.UpdateContainer scrape();
}

View File

@@ -0,0 +1,25 @@
package de.nbscloud.dashboard.widget.metrics.updates.scraper;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.impl.ArchAuracleScraperImpl;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.impl.ArchCheckupdatesScraperImpl;
import java.lang.reflect.InvocationTargetException;
public enum UpdateScrapers {
ARCH_CHECKUPDATES(ArchCheckupdatesScraperImpl.class),
ARCH_AURACLE(ArchAuracleScraperImpl.class);
private Class<? extends UpdateScraper> scraperClass;
UpdateScrapers(Class<? extends UpdateScraper> scraperClass) {
this.scraperClass = scraperClass;
}
public UpdateScraper getUpdateScraper() {
try {
return this.scraperClass.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,36 @@
package de.nbscloud.dashboard.widget.metrics.updates.scraper.impl;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.UpdateScraper;
import de.nbscloud.dashboard.widget.service.MetricService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ArchAuracleScraperImpl implements UpdateScraper {
private static final Logger logger = LoggerFactory.getLogger(ArchAuracleScraperImpl.class);
private static final String KEY = "AUR";
@Override
public MetricService.UpdateContainer scrape() {
try {
final Process process = Runtime.getRuntime()
.exec(new String[]{"sh", "-c", "auracle outdated | wc -l"});
final String updateCount = new BufferedReader(new InputStreamReader(process.getInputStream())).readLine();
process.destroy();
if(updateCount != null && !updateCount.isBlank()) {
return new MetricService.UpdateContainer(KEY, updateCount);
}
} catch (IOException e) {
logger.error("Could not get AUR update count", e);
return new MetricService.UpdateContainer(KEY, "error");
}
return new MetricService.UpdateContainer(KEY, "0");
}
}

View File

@@ -0,0 +1,36 @@
package de.nbscloud.dashboard.widget.metrics.updates.scraper.impl;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.UpdateScraper;
import de.nbscloud.dashboard.widget.service.MetricService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ArchCheckupdatesScraperImpl implements UpdateScraper {
private static final Logger logger = LoggerFactory.getLogger(ArchCheckupdatesScraperImpl.class);
private static final String KEY = "pacman";
@Override
public MetricService.UpdateContainer scrape() {
try {
final Process process = Runtime.getRuntime()
.exec(new String[]{"sh", "-c", "checkupdates | wc -l"});
final String updateCount = new BufferedReader(new InputStreamReader(process.getInputStream())).readLine();
process.destroy();
if(updateCount != null && !updateCount.isBlank()) {
return new MetricService.UpdateContainer(KEY, updateCount);
}
} catch (IOException e) {
logger.error("Could not get pacman update count", e);
return new MetricService.UpdateContainer(KEY, "error");
}
return new MetricService.UpdateContainer(KEY, "0");
}
}

View File

@@ -0,0 +1,78 @@
package de.nbscloud.dashboard.widget.service;
import de.nbscloud.dashboard.config.DashboardConfig;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.UpdateScraper;
import de.nbscloud.dashboard.widget.metrics.updates.scraper.UpdateScrapers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class MetricService {
private static final Logger logger = LoggerFactory.getLogger(MetricService.class);
public static record InterfaceContainer(String name, String ip) {
}
public static record UpdateContainer(String key, String count) {
}
@Autowired
private DashboardConfig dashboardConfig;
public List<InterfaceContainer> getInterfaces() {
try {
final Iterator<NetworkInterface> networkInterfaceIterator = NetworkInterface.getNetworkInterfaces()
.asIterator();
final List<InterfaceContainer> retList = new ArrayList<>();
while (networkInterfaceIterator.hasNext()) {
final NetworkInterface networkInterface = networkInterfaceIterator.next();
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
retList.add(new InterfaceContainer(networkInterface.getDisplayName(), removeInterfaceName(removeSlash(interfaceAddress.getAddress()
.toString()))));
}
}
retList.sort(Comparator.comparing(InterfaceContainer::name));
return retList;
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
private static final String removeSlash(String address) {
return address.startsWith("/") ? address.substring(1) : address;
}
private static final String removeInterfaceName(String address) {
// IPv6 addresses have the interface name in them like
// abc:...:xyz%myInterface
return address.contains("%") ? address.substring(0, address.indexOf("%")) : address;
}
public List<UpdateContainer> getUpdates() {
return this.dashboardConfig.getMetrics()
.getUpdates()
.getScrapers()
.stream()
.map(UpdateScrapers::getUpdateScraper)
.map(UpdateScraper::scrape)
.sorted(Comparator.comparing(UpdateContainer::key))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,7 @@
spring.profiles.active=@activeProfiles@
info.app.name=NoBullShit Cloud - Dashboard app
info.app.description=A simple dashboard app with widget support
nbs-cloud.dashboard.metrics.updates.scrapers[0]=ARCH_CHECKUPDATES
nbs-cloud.dashboard.metrics.updates.scrapers[1]=ARCH_AURACLE

View File

@@ -1,3 +1,7 @@
nbscloud.dashboard.greeting=Welcome to nbscloud
nbscloud.dashboard.index.title=nbscloud\: dashboard
nbscloud.dashboard.index.title=nbscloud\: dashboard
nbscloud.dashboard.dashboard-machine-metrics-widget.heading=dashboard machine metrics
nbscloud.dashboard.dashboard-machine-metrics-widget-table.interfaces=Interfaces\:
nbscloud.dashboard.dashboard-machine-metrics-widget-table.updates=Updates\:

View File

@@ -1,3 +1,7 @@
nbscloud.dashboard.greeting=Willkommen bei nbscloud
nbscloud.dashboard.index.title=nbscloud\: \u00DCbersicht
nbscloud.dashboard.index.title=nbscloud\: \u00DCbersicht
nbscloud.dashboard.dashboard-machine-metrics-widget.heading=dashboard Metriken
nbscloud.dashboard.dashboard-machine-metrics-widget-table.interfaces=Schnittstellen\:
nbscloud.dashboard.dashboard-machine-metrics-widget-table.updates=Updates\:

View File

@@ -18,6 +18,7 @@
flex-grow: 1;
background-color: #1d2121;
padding: 1em;
align-self: stretch;
}
.widget-heading {
@@ -32,4 +33,8 @@
margin-left: 0;
margin-right: 0;
font-weight: bold;
}
.widget > table > tbody > tr > td {
padding-inline-end: 1em;
}

View File

@@ -0,0 +1,19 @@
<div id="dashboard-machine-metrics-widget" class="widget" th:fragment="dashboard-machine-metrics">
<p class="widget-heading" th:text="#{nbscloud.dashboard.dashboard-machine-metrics-widget.heading}"/>
<table id="dashboard-machine-metrics-widget-table">
<tr th:each="interface, i : ${interfaces}">
<td th:if="${i.count == 1}"
th:text="#{nbscloud.dashboard.dashboard-machine-metrics-widget-table.interfaces}" />
<td th:if="${i.count > 1}"></td>
<td th:text="${interface.name}" />
<td th:text="${interface.ip}" />
</tr>
<tr th:each="update, i : ${updates}">
<td th:if="${i.count == 1}"
th:text="#{nbscloud.dashboard.dashboard-machine-metrics-widget-table.updates}" />
<td th:if="${i.count > 1}"></td>
<td th:text="${update.key}" />
<td th:text="${update.count}" />
</tr>
</table>
</div>