diff --git a/apps/rest-api-app/src/main/java/bisq/rest_api/RestApiApplicationService.java b/apps/rest-api-app/src/main/java/bisq/rest_api/RestApiApplicationService.java index fc24868d83..396434e9b6 100644 --- a/apps/rest-api-app/src/main/java/bisq/rest_api/RestApiApplicationService.java +++ b/apps/rest-api-app/src/main/java/bisq/rest_api/RestApiApplicationService.java @@ -25,6 +25,7 @@ import bisq.chat.ChatService; import bisq.common.application.Service; import bisq.common.network.Address; +import bisq.rest_api.dto.AddressInfoDto; import bisq.common.network.TransportType; import bisq.common.observable.Observable; import bisq.common.platform.OS; @@ -46,18 +47,17 @@ import bisq.support.SupportService; import bisq.trade.TradeService; import bisq.user.UserService; +import bisq.user.profile.UserProfile; import bisq.wallets.core.BitcoinWalletSelection; import bisq.wallets.core.WalletService; 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.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkArgument; import static java.util.concurrent.CompletableFuture.supplyAsync; @@ -271,7 +271,6 @@ public KeyBundleService getKeyPairService() { return securityService.getKeyBundleService(); } - private void setState(State newState) { checkArgument(state.get().ordinal() < newState.ordinal(), "New state %s must have a higher ordinal as the current state %s", newState, state.get()); @@ -298,7 +297,7 @@ private Optional findSystemNotificationDelegate() } } - public List getAddressList() { + public List getAddresses() { Set
bannedAddresses = bondedRolesService.getAuthorizedBondedRolesService().getBondedRoles().stream() .filter(BondedRole::isBanned) @@ -310,7 +309,7 @@ public List getAddressList() { .collect(Collectors.toSet()); Map> seedAddressesByTransport = networkService.getSeedAddressesByTransportFromConfig(); Set supportedTransportTypes = networkService.getSupportedTransportTypes(); - List addresslist = seedAddressesByTransport.entrySet().stream() + List addresses = seedAddressesByTransport.entrySet().stream() .filter(entry -> supportedTransportTypes.contains(entry.getKey())) .flatMap(entry -> entry.getValue().stream()) .filter(address -> !bannedAddresses.contains(address)) @@ -318,9 +317,30 @@ public List getAddressList() { .collect(Collectors.toList()); // Oracle Nodes - addresslist.add("kr4yvzlhwt5binpw7js2tsfqv6mjd4klmslmcxw3c5izsaqh5vvsp6ad.onion:36185"); - addresslist.add("s2yxxqvyofzud32mxliya3dihj5rdlowagkblqqtntxhi7cbdaufqkid.onion:54467"); + addresses.add("kr4yvzlhwt5binpw7js2tsfqv6mjd4klmslmcxw3c5izsaqh5vvsp6ad.onion:36185"); + addresses.add("s2yxxqvyofzud32mxliya3dihj5rdlowagkblqqtntxhi7cbdaufqkid.onion:54467"); + + return addresses; + } - return addresslist; + /** + * @return The result set may be smaller than the list of addresses in the parameter. + */ + public List getAddressInfos(List addresses) { + Set bondedRoles = bondedRolesService.getAuthorizedBondedRolesService().getBondedRoles(); + return bondedRoles.stream() + .flatMap(bondedRole -> bondedRole.getAuthorizedBondedRole().getAddressByTransportTypeMap() + .map(addressMap -> addressMap.entrySet().stream() + .filter(entry -> addresses.contains(entry.getValue().toString())) + .map(entry -> AddressInfoDto.getBuilder() + .address(entry.getValue()) + .bondedRoleType(bondedRole.getAuthorizedBondedRole().getBondedRoleType()) + .nickNameOrBondUserName(userService.getUserProfileService() + .findUserProfile(bondedRole.getAuthorizedBondedRole().getProfileId()) + .map(UserProfile::getNickName) + .orElse(bondedRole.getAuthorizedBondedRole().getBondUserName()) + ).build()) + ).orElse(Stream.empty())) + .collect(Collectors.toList()); } } diff --git a/apps/rest-api-app/src/main/java/bisq/rest_api/dto/AddressInfoDto.java b/apps/rest-api-app/src/main/java/bisq/rest_api/dto/AddressInfoDto.java new file mode 100644 index 0000000000..bd83d4a8dd --- /dev/null +++ b/apps/rest-api-app/src/main/java/bisq/rest_api/dto/AddressInfoDto.java @@ -0,0 +1,109 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq 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 Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.rest_api.dto; + +import bisq.bonded_roles.BondedRoleType; +import bisq.common.network.Address; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Schema(name = "AddressMeta") +@Getter +@EqualsAndHashCode +public final class AddressInfoDto { + private final String address; + private final String bondedRoleType; + private final String nickNameOrBondUserName; + + public AddressInfoDto() { + this.address = null; + this.bondedRoleType = null; + this.nickNameOrBondUserName = null; + } + + public AddressInfoDto(String address, String bondedRoleType, String nickNameOrBondUserName) { + this.address = address; + this.bondedRoleType = bondedRoleType; + this.nickNameOrBondUserName = nickNameOrBondUserName; + } + + private AddressInfoDto(Builder builder) { + this.address = builder.address; + this.bondedRoleType = builder.bondedRoleType; + this.nickNameOrBondUserName = builder.nickNameOrBondUserName; + } + + public static AddressInfoDto.Builder getBuilder() { + return new AddressInfoDto.Builder(); + } + + public static AddressInfoDto fromBuilder(AddressInfoDto.Builder builder) { + return builder.build(); + } + + public static final class Builder { + private String address; + private String bondedRoleType; + private String nickNameOrBondUserName; + + private Builder() { + } + + public Builder address(Address address) { + this.address = address.toString(); + return this; + } + + public Builder bondedRoleType(BondedRoleType bondedRoleType) { + this.bondedRoleType = bondedRoleType.name(); + return this; + } + + public Builder nickNameOrBondUserName(String nickNameOrBondUserName) { + this.nickNameOrBondUserName = nickNameOrBondUserName; + return this; + } + + public AddressInfoDto build() { + validate(); + return new AddressInfoDto(this); + } + + private void validate() { + if (address == null || address.isEmpty()) { + throw new IllegalStateException("Member address must not be null or empty"); + } + if (bondedRoleType == null || bondedRoleType.isEmpty()) { + throw new IllegalStateException("Member bondedRoleType must not be null or empty"); + } + if (nickNameOrBondUserName == null || nickNameOrBondUserName.isEmpty()) { + throw new IllegalStateException("Member nickNameOrBondUserName must not be null or empty"); + } + } + } + + @Override + public String toString() { + return "AddressMetaDto{" + + "address='" + address + '\'' + + ", bondedRoleType='" + bondedRoleType + '\'' + + ", nickNameOrBondUserName='" + nickNameOrBondUserName + '\'' + + '}'; + } +} diff --git a/apps/rest-api-app/src/main/java/bisq/rest_api/endpoints/ReportApi.java b/apps/rest-api-app/src/main/java/bisq/rest_api/endpoints/ReportApi.java index ac033e8304..eeb44eedcb 100644 --- a/apps/rest-api-app/src/main/java/bisq/rest_api/endpoints/ReportApi.java +++ b/apps/rest-api-app/src/main/java/bisq/rest_api/endpoints/ReportApi.java @@ -23,6 +23,7 @@ import bisq.network.NetworkService; import bisq.rest_api.JaxRsApplication; import bisq.rest_api.RestApiApplicationService; +import bisq.rest_api.dto.AddressInfoDto; import bisq.rest_api.dto.ReportDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -30,10 +31,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -64,12 +62,12 @@ public ReportApi(@Context Application application) { )} ) @GET - @Path("get-address-list") - public List getAddressList() { + @Path("get-addresses") + public List getAddresses() { try { - return applicationService.getAddressList(); + return applicationService.getAddresses(); } catch (Exception e) { - throw new RuntimeException("Failed to get the node address list"); + throw new RuntimeException("Failed to get the node address list."); } } @@ -87,7 +85,7 @@ public ReportDto getReport( @Parameter(description = "address from which we request the report") @PathParam("address") String address) { try { - return fetchReportForAddress(address).join(); + return requestReportForAddress(address).join(); } catch (Exception e) { throw new RuntimeException("Failed to get report for address: " + address); } @@ -115,7 +113,7 @@ public List getReports( } List> futures = addressList.stream() - .map(this::fetchReportForAddress) + .map(this::requestReportForAddress) .toList(); CompletableFuture> allFutures = CompletableFutureUtils.allOf(futures); @@ -123,7 +121,32 @@ public List getReports( return allFutures.join(); } - private CompletableFuture fetchReportForAddress(String addressString) { + @POST + @Path("get-address-infos") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Operation(description = "Get address info for a set of host:port addresses") + @ApiResponse(responseCode = "200", description = "The set of address info (host, role type, nickname or bond name)", + content = { + @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = String[][].class) // String[][] because each entry is String[] + ) + }) + public List getAddressInfos( + @Parameter(description = "Set of addresses in host:port format") + List addresses) { + try { + log.info("Received request to get address infos for: {}", addresses); + List addressInfos = applicationService.getAddressInfos(addresses); + return addressInfos; + } catch (Exception e) { + log.error("Failed to get address infos for provided addresses: {}. Nested: {}", addresses, e.getMessage()); + throw new RuntimeException("Failed to retrieve address infos."); + } + } + + private CompletableFuture requestReportForAddress(String addressString) { try { Address address = Address.fromFullAddress(addressString); return networkService.requestReport(address) diff --git a/apps/rest-api-app/src/main/resources/node-monitor/index.html b/apps/rest-api-app/src/main/resources/node-monitor/index.html index d5b82b191a..ce445d0eb0 100644 --- a/apps/rest-api-app/src/main/resources/node-monitor/index.html +++ b/apps/rest-api-app/src/main/resources/node-monitor/index.html @@ -1,8 +1,10 @@ + + Bisq Node Monitor @@ -10,52 +12,75 @@ + -

Bisq Node Monitor

+

Bisq Node Monitor

-
- ☰ -
+
+ ☰ +
-