Skip to content

Commit

Permalink
Merge pull request #2978 from bsc7/feature/2634/Simple_HTML_NodeMonitor
Browse files Browse the repository at this point in the history
Simple HTML frontend for Bisq 2 monitor
  • Loading branch information
HenrikJannsen authored Nov 7, 2024
2 parents 45f8203 + 2bf5039 commit bbf8623
Show file tree
Hide file tree
Showing 17 changed files with 1,100 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public JaxRsApplication(String[] args, Supplier<RestApiApplicationService> appli
public CompletableFuture<Boolean> initialize() {
httpServer = JdkHttpServerFactory.createHttpServer(URI.create(BASE_URL), this);
httpServer.createContext("/doc", new StaticFileHandler("/doc/v1/"));
httpServer.createContext("/node-monitor", new StaticFileHandler("/node-monitor/"));
log.info("Server started at {}.", BASE_URL);
return CompletableFuture.completedFuture(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
import bisq.account.AccountService;
import bisq.bisq_easy.BisqEasyService;
import bisq.bonded_roles.BondedRolesService;
import bisq.bonded_roles.bonded_role.AuthorizedBondedRole;
import bisq.bonded_roles.bonded_role.BondedRole;
import bisq.chat.ChatService;
import bisq.common.application.Service;
import bisq.common.network.Address;
import bisq.common.network.TransportType;
import bisq.common.observable.Observable;
import bisq.common.platform.OS;
import bisq.common.util.CompletableFutureUtils;
Expand All @@ -47,9 +51,13 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.concurrent.CompletableFuture.supplyAsync;
Expand Down Expand Up @@ -269,7 +277,9 @@ private void setState(State newState) {
"New state %s must have a higher ordinal as the current state %s", newState, state.get());
state.set(newState);
log.info("New state {}", newState);
} private Optional<OsSpecificNotificationService> findSystemNotificationDelegate() {
}

private Optional<OsSpecificNotificationService> findSystemNotificationDelegate() {
try {
switch (OS.getOS()) {
case LINUX:
Expand All @@ -287,4 +297,30 @@ private void setState(State newState) {
return Optional.empty();
}
}

public List<String> getAddressList() {

Set<Address> bannedAddresses = bondedRolesService.getAuthorizedBondedRolesService().getBondedRoles().stream()
.filter(BondedRole::isBanned)
.map(BondedRole::getAuthorizedBondedRole)
.map(AuthorizedBondedRole::getAddressByTransportTypeMap)
.filter(Optional::isPresent)
.map(Optional::get)
.flatMap(map -> map.values().stream())
.collect(Collectors.toSet());
Map<TransportType, Set<Address>> seedAddressesByTransport = networkService.getSeedAddressesByTransportFromConfig();
Set<TransportType> supportedTransportTypes = networkService.getSupportedTransportTypes();
List<String> addresslist = seedAddressesByTransport.entrySet().stream()
.filter(entry -> supportedTransportTypes.contains(entry.getKey()))
.flatMap(entry -> entry.getValue().stream())
.filter(address -> !bannedAddresses.contains(address))
.map(Address::toString)
.collect(Collectors.toList());

// Oracle Nodes
addresslist.add("kr4yvzlhwt5binpw7js2tsfqv6mjd4klmslmcxw3c5izsaqh5vvsp6ad.onion:36185");
addresslist.add("s2yxxqvyofzud32mxliya3dihj5rdlowagkblqqtntxhi7cbdaufqkid.onion:54467");

return addresslist;
}
}
11 changes: 10 additions & 1 deletion apps/rest-api-app/src/main/java/bisq/rest_api/dto/ReportDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@
@Schema(name = "Report")
public final class ReportDto {
private Report report;
private String errorMessage;

public static ReportDto from(Report report) {
ReportDto dto = new ReportDto();
dto.report = report;
return dto;
}
}

public static ReportDto fromError(String errorMessage) {
ReportDto dto = new ReportDto();
dto.errorMessage = errorMessage;
return dto;
}

public boolean isSuccessful() {
return errorMessage == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@

package bisq.rest_api.endpoints;

import bisq.common.network.Address;
import bisq.common.util.CollectionUtil;
import bisq.common.util.CompletableFutureUtils;
import bisq.network.NetworkService;
import bisq.common.network.Address;
import bisq.network.p2p.services.reporting.Report;
import bisq.rest_api.JaxRsApplication;
import bisq.rest_api.RestApiApplicationService;
import bisq.rest_api.dto.ReportDto;
Expand Down Expand Up @@ -49,12 +48,31 @@
@Tag(name = "Report API")
public class ReportApi {
private final NetworkService networkService;
private final RestApiApplicationService applicationService;

public ReportApi(@Context Application application) {
RestApiApplicationService applicationService = ((JaxRsApplication) application).getApplicationService().get();
applicationService = ((JaxRsApplication) application).getApplicationService().get();
networkService = applicationService.getNetworkService();
}

@Operation(description = "Get a address list of seed and oracle nodes")
@ApiResponse(responseCode = "200", description = "the list of seed and oracle node addresses",
content = {
@Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(implementation = ReportDto.class)
)}
)
@GET
@Path("get-address-list")
public List<String> getAddressList() {
try {
return applicationService.getAddressList();
} catch (Exception e) {
throw new RuntimeException("Failed to get the node address list");
}
}

@Operation(description = "Get report for given address")
@ApiResponse(responseCode = "200", description = "the report for the given address",
content = {
Expand All @@ -65,14 +83,13 @@ public ReportApi(@Context Application application) {
)
@GET
@Path("get-report/{address}")
public ReportDto getReport(@Parameter(description = "address from which we request the report") @PathParam("address") String address) {
CompletableFuture<Report> future = networkService.requestReport(Address.fromFullAddress(address));
public ReportDto getReport(
@Parameter(description = "address from which we request the report")
@PathParam("address") String address) {
try {
Report report = future.get();
log.info(report.toString());
return ReportDto.from(report);
return fetchReportForAddress(address).join();
} catch (Exception e) {
throw new RuntimeException(e);
throw new RuntimeException("Failed to get report for address: " + address);
}
}

Expand All @@ -86,16 +103,43 @@ public ReportDto getReport(@Parameter(description = "address from which we reque
)
@GET
@Path("get-reports/{addresses}")
public List<ReportDto> getReports(@Parameter(description = "comma separated addresses from which we request the report") @PathParam("addresses") String addresses) {
List<CompletableFuture<Report>> futures = CollectionUtil.streamFromCsv(addresses)
.map(address -> networkService.requestReport(Address.fromFullAddress(address)))
public List<ReportDto> getReports(
@Parameter(description = "comma separated addresses from which we request the report")
@PathParam("addresses") String addresses) {

List<String> addressList;
try {
addressList = CollectionUtil.streamFromCsv(addresses).toList();
} catch (Exception e) {
throw new RuntimeException("Failed to parse addresses from CSV input: " + addresses);
}

List<CompletableFuture<ReportDto>> futures = addressList.stream()
.map(this::fetchReportForAddress)
.toList();

CompletableFuture<List<ReportDto>> allFutures = CompletableFutureUtils.allOf(futures);

return allFutures.join();
}

private CompletableFuture<ReportDto> fetchReportForAddress(String addressString) {
try {
List<Report> reports = CompletableFutureUtils.allOf(futures).get();
log.info(reports.toString());
return reports.stream().map(ReportDto::from).toList();
Address address = Address.fromFullAddress(addressString);
return networkService.requestReport(address)
.thenApply(report -> {
log.info("Report successfully created for address: {}", address);
return ReportDto.from(report);
})
.exceptionally(e -> {
log.error("Failed to get report for address: {}. Nested: {}", address, e.getMessage());
return ReportDto.fromError(e.getMessage());
});
} catch (Exception e) {
throw new RuntimeException(e);
log.error("Error creating report for address: {}. Nested: {}", addressString, e.getMessage());
return CompletableFuture.completedFuture(
ReportDto.fromError(e.getMessage())
);
}
}
}
36 changes: 36 additions & 0 deletions apps/rest-api-app/src/main/resources/node-monitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

# Bisq Node Monitor Application

## Overview

The **Bisq Node Monitor** is a web application designed to monitor Bisq nodes.
The application provides a user interface to input a list of hosts and ports, retrieves their status from an API, and displays the results in a structured and interactive format.

## Project Structure

The project is organized into different modules to ensure a clear separation of concerns and ease of maintenance. Each part of the application has a dedicated file or directory, as outlined below:

```
projekt-root/
├── index.html # Main HTML file that defines the application's structure
├── index.js # Main JavaScript file for initializing the application
├── README.md # Project README file with documentation
├── js/ # JavaScript files organized by functionality
│ ├── constants.js # Global constants used throughout the application
│ ├── controllers/ # Application controllers
│ │ └── appController.js # Main application controller for handling user input and API calls
│ ├── services/ # Application services for data and storage management
│ │ ├── dataService.js # Service handling API requests and data retrieval
│ │ └── storageService.js # Service for handling local storage interactions
│ └── views/ # View components for different sections
│ ├── settingsView.js # View handling settings display and input
│ └── reportView.js # View managing the display of node reports
└── styles/ # Directory containing CSS files for styling
├── global.css # Global styles, typography, colors, and basic element styling
├── page-layout.css # Layout and positioning of main areas, responsive styling
├── reportView.css # Styling for the report view section
└── settingsView.css # Styling for the settings view section
```
61 changes: 61 additions & 0 deletions apps/rest-api-app/src/main/resources/node-monitor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!-- ./index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bisq Node Monitor</title>
<link rel="stylesheet" href="styles/global.css">
<link rel="stylesheet" href="styles/settingsView.css">
<link rel="stylesheet" href="styles/reportView.css">
<link rel="stylesheet" href="styles/page-layout.css">
</head>
<body>

<h2>Bisq Node Monitor</h2>

<div class="hamburger-menu" id="hamburgerButton">
&#9776;
</div>

<div id="settingsPanel" style="display: none;">
<h3>Settings</h3>
<div>
<label>Host List (comma or newline separated):</label>
<textarea id="hostListInput" placeholder="Host:port list, separated by commas or new lines" rows="9" style="width: 100%;"></textarea>
</div>
<button id="fetchRemoteListButton" class="toggle-button" style="margin-top: 10px;">Fetch Remote Host List</button>
<div>
<label>Port List (comma or newline separated, optional):</label>
<textarea id="portListInput" placeholder="Port list for filtering, separated by commas or new lines" rows="3" style="width: 100%;"></textarea>
</div>
<button id="saveConfigButton" class="toggle-button" style="margin-top: 10px;">Save Configuration</button>
</div>

<div class="button-container">
<button id="reloadButton" class="button button--green">Reload Data</button>
<button id="toggleAllButton" class="button button--blue" style="display: none;">Expand All Details</button>
</div>

<div id="statusMessage" style="display: none; text-align: center; margin-top: 20px; color: grey;">
<!-- Dynamically generated message data will appear here -->
</div>

<div id="reportContainer">
<!-- Dynamically generated node data will appear here -->
</div>

<script>
window.App = window.App || {};
</script>

<script src="js/constants.js"></script>
<script src="js/services/storageService.js"></script>
<script src="js/services/dataService.js"></script>
<script src="js/views/settingsView.js"></script>
<script src="js/views/reportView.js"></script>
<script src="js/controllers/appController.js"></script>
<script src="index.js"></script>
</body>
</html>

11 changes: 11 additions & 0 deletions apps/rest-api-app/src/main/resources/node-monitor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// index.js
// Namespace for the application
window.App = window.App || {};

document.addEventListener("DOMContentLoaded", () => {
const dataService = new App.Services.DataService();
const storageService = new App.Services.StorageService();

const appController = new App.Controllers.AppController(dataService, storageService);
appController.initApp();
});
15 changes: 15 additions & 0 deletions apps/rest-api-app/src/main/resources/node-monitor/js/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// js/constants.js
App.Constants = {
API_URL_GET_REPORT: 'http://localhost:8082/api/v1/report/get-report',
API_URL_GET_ADDRESS_LIST: 'http://localhost:8082/api/v1/report/get-address-list',
STATUS_ERROR: "Failed to fetch data",
STATUS_ENTER_HOSTS: "Please enter a Host:port list in the settings to start fetching data.",
PLACEHOLDER_HOST_LIST: "Host:port list, separated by commas or new lines.\n# Comments and empty lines are alowed.",
PLACEHOLDER_PORT_LIST: "Port list, for filtering hosts. Separated by commas or new lines.\n# Comments and empty lines are alowed.",
BUTTON_EXPAND_ALL: "Expand All Details",
BUTTON_COLLAPSE_ALL: "Collapse All Details",
BUTTON_EXPAND_DETAILS: "Expand Details",
BUTTON_COLLAPSE_DETAILS: "Collapse Details",
HOSTS_COOKIE_KEY: 'hosts',
PORTS_COOKIE_KEY: 'ports',
};
Loading

0 comments on commit bbf8623

Please sign in to comment.