mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-31 16:20:45 +00:00
Merge branch 'eclipse-main' into translation
This commit is contained in:
commit
cd5def0111
28 files changed
+224
-59
No files matched your search
+3
@@ -13,6 +13,7 @@ public enum BoatClassMasterdata {
|
||||
_18Footer ("18Footer", true, 8.90, 2.00, BoatHullType.MONOHULL, true, "18.Footer", "18ft", "18ft Skiff", "18. Footer"),
|
||||
_2_4M ("2.4 Meter", true, 4.11, 0.81, BoatHullType.MONOHULL, false, "2.4M", "2.4mR", "2.4 Metre", "2.4-metre", "24MR"),
|
||||
_12M ("12 Meter", true, 21.50, 3.60, BoatHullType.MONOHULL, true, "12M", "12mR", "12SQM", "12-metre", "12 metre"),
|
||||
_15M2_SNS ("15m2 SNS", true, 7.00, 1.95, BoatHullType.MONOHULL, true, "15m2", "15er", "15 m2 SNS", "15m² SNS", "SNS 15"),
|
||||
_29ER ("29er", true, 4.45, 1.70, BoatHullType.MONOHULL, true, "29-ER"),
|
||||
_49ER ("49er", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49-ER"),
|
||||
_49ERFX ("49erFX", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49FX"),
|
||||
@@ -99,6 +100,7 @@ public enum BoatClassMasterdata {
|
||||
LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR", "Laser Standard", "ILCA 7", "ILCA7"),
|
||||
LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"),
|
||||
LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"),
|
||||
LACUSTRE ("Lacustre", true, 9.50, 1.81, BoatHullType.MONOHULL, true, "Lacustre Int."),
|
||||
LONGTZE ("Longtze", true, 6.85, 2.57, BoatHullType.MONOHULL, true, "Swiss Longtze Class"),
|
||||
M32 ("M32", false, 9.70, 5.50, BoatHullType.CATAMARAN, true, "M/32", "M32 Catamaran", "M/32 Catamaran"),
|
||||
MELGES_20 ("Melges 20", true, 6.10, 2.13, BoatHullType.MONOHULL, true, "Melges-20", "M20"),
|
||||
@@ -111,6 +113,7 @@ public enum BoatClassMasterdata {
|
||||
NACRA_17_FOIL ("Nacra 17 Foiling", true, 5.25, 2.59, BoatHullType.CATAMARAN, true, "N17F", "Nacra-17-Foiling"),
|
||||
O_JOLLE ("O-Jolle", true, 5.00, 1.66, BoatHullType.MONOHULL, false, "O Jolle", "OJolle", "Olympiajolle"),
|
||||
OK ("OK Dinghy", true, 5.25, 2.59, BoatHullType.MONOHULL, false, "OK-Dinghy", "OK-Jolle", "OK"),
|
||||
ONYX ("Onyx", true, 8.50, 2.20, BoatHullType.MONOHULL, true, "Onyx Yacht"),
|
||||
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC"),
|
||||
OPTIMIST ("Optimist", true, 2.34, 1.07, BoatHullType.MONOHULL, false, "Opti", "Optimist Dinghy"),
|
||||
PIRATE ("Pirate", true, 5.00, 1.61, BoatHullType.MONOHULL, false, "Pirat", "Piraten"),
|
||||
|
||||
+3
@@ -27,6 +27,7 @@ public class BoatClassImageResolver {
|
||||
boatClassIconsMap.put(BoatClassMasterdata.PHRF.getDisplayName(), imageResources.PHRFIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._18Footer.getDisplayName(), imageResources._18FooterIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._15M2_SNS.getDisplayName(), imageResources.SNS15m2Icon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._29ER.getDisplayName(), imageResources._29erIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._420.getDisplayName(), imageResources._420Icon());
|
||||
@@ -114,6 +115,7 @@ public class BoatClassImageResolver {
|
||||
boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.Laser47Icon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserRadialIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.LACUSTRE.getDisplayName(), imageResources.LacustreIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.LONGTZE.getDisplayName(), imageResources.LongtzeIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.M32.getDisplayName(), imageResources.M32Icon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.MELGES_20.getDisplayName(), imageResources.Melges20Icon());
|
||||
@@ -126,6 +128,7 @@ public class BoatClassImageResolver {
|
||||
boatClassIconsMap.put(BoatClassMasterdata.NACRA_17_FOIL.getDisplayName(), imageResources.Nacra17FoilIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.O_JOLLE.getDisplayName(), imageResources.OJolleIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.ONYX.getDisplayName(), imageResources.OnyxIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.PIRATE.getDisplayName(), imageResources.PiratIcon());
|
||||
|
||||
+12
@@ -191,6 +191,10 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/FAREAST28R.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource Fareast28RIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/15M2_SNS.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource SNS15m2Icon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
@@ -316,6 +320,10 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource Lago26Icon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LACUSTRE.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource LacustreIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LONGTZE.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource LongtzeIcon();
|
||||
@@ -359,6 +367,10 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/OK.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource OKIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/ONYX.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource OnyxIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
|
||||
+1
@@ -8,6 +8,7 @@
|
||||
<div class="innerContent">
|
||||
<h5 class="articleSubheadline">September 2026</h5>
|
||||
<ul class="bulletList">
|
||||
<li>Added boat classes Onyx, Lacustre and 15m² SNS.</li>
|
||||
<li>Fix for recently introduced issue in the simulator when used for live smartphone-tracked
|
||||
races. A time point from the mark passing detection turned out to be non-serializable.</li>
|
||||
</ul>
|
||||
|
||||
+16
-14
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
|
||||
import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel;
|
||||
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
|
||||
@@ -106,7 +107,7 @@ abstract class IPBlocklistTableWrapper
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
for (IpToTimedLockDTO e : getSelectionModel().getSelectedSet()) {
|
||||
unlockIP(e.getIp(), new AsyncCallback<Void>() {
|
||||
unlockIP(e.getIp(), new MarkedAsyncCallback<>(new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(errorMessageOnDataFailureString);
|
||||
@@ -116,26 +117,27 @@ abstract class IPBlocklistTableWrapper
|
||||
public void onSuccess(Void result) {
|
||||
filterField.remove(e);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadDataAndPopulateTable() {
|
||||
final AsyncCallback<ArrayList<IpToTimedLockDTO>> dataInitializationCallback = new AsyncCallback<ArrayList<IpToTimedLockDTO>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(errorMessageOnDataFailureString);
|
||||
}
|
||||
final AsyncCallback<ArrayList<IpToTimedLockDTO>> dataInitializationCallback = new MarkedAsyncCallback<>(
|
||||
new AsyncCallback<ArrayList<IpToTimedLockDTO>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(errorMessageOnDataFailureString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(ArrayList<IpToTimedLockDTO> result) {
|
||||
filterField.clear();
|
||||
clear();
|
||||
filterField.addAll(result);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void onSuccess(ArrayList<IpToTimedLockDTO> result) {
|
||||
filterField.clear();
|
||||
clear();
|
||||
filterField.addAll(result);
|
||||
}
|
||||
});
|
||||
fetchData(dataInitializationCallback);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -60,7 +60,9 @@ public class BoatClassVectorGraphicsResolver {
|
||||
BoatClassMasterdata.BRASSFAHRT_I, BoatClassMasterdata.BRASSFAHRT_II, BoatClassMasterdata.BRASSFAHRT_III,
|
||||
BoatClassMasterdata.BRASSFAHRT_IV, BoatClassMasterdata.BRASSFAHRT_V,
|
||||
BoatClassMasterdata.VO60, BoatClassMasterdata.VO65, BoatClassMasterdata.IMOCA,
|
||||
BoatClassMasterdata.HANSE_418, BoatClassMasterdata.SALONA_46, BoatClassMasterdata.SRS);
|
||||
BoatClassMasterdata.HANSE_418, BoatClassMasterdata.SALONA_46, BoatClassMasterdata.SRS,
|
||||
BoatClassMasterdata.ONYX, BoatClassMasterdata.LACUSTRE, BoatClassMasterdata._15M2_SNS,
|
||||
BoatClassMasterdata.FAREAST28R);
|
||||
BoatClassVectorGraphics dinghyWithSpinnaker = new DinghyWithSpinnakerVectorGraphics(BoatClassMasterdata._420,
|
||||
BoatClassMasterdata._470, BoatClassMasterdata._5O5, BoatClassMasterdata.CADET, BoatClassMasterdata.FLYING_DUTCHMAN,
|
||||
BoatClassMasterdata.FOLKBOAT, BoatClassMasterdata.DYAS, BoatClassMasterdata.DRAGON_INT,
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
+2
-1
@@ -514,7 +514,8 @@ public class PageObject {
|
||||
}
|
||||
|
||||
protected void waitUntil(Function<WebDriver, Boolean> predicate) {
|
||||
WebDriverWait webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(DEFAULT_LOOKUP_TIMEOUT));
|
||||
final WebDriverWait webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(DEFAULT_LOOKUP_TIMEOUT));
|
||||
webDriverWait.ignoring(StaleElementReferenceException.class);
|
||||
webDriverWait.until(predicate);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ public class AdminConsolePage extends HostPageWithAuthentication {
|
||||
public UserManagementPanelPO goToUserManagement() {
|
||||
goToTab(ADVANCED_PARENT_LABEL, ADVANCED_TAB_PARENT_IDENTIFIER, true);
|
||||
return new UserManagementPanelPO(this.driver,
|
||||
goToTab(USER_MANAGEMENT_PANEL_TAB_LABEL, USER_MANAGEMENT_PANEL_TAB_IDENTIFIER, false));
|
||||
goToTab(USER_MANAGEMENT_PANEL_TAB_LABEL, USER_MANAGEMENT_PANEL_TAB_IDENTIFIER, false), this);
|
||||
}
|
||||
|
||||
public UserGroupManagementPanelPO goToUserGroupDefinitions() {
|
||||
|
||||
+47
@@ -1,5 +1,8 @@
|
||||
package com.sap.sailing.selenium.pages.adminconsole.advanced;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
@@ -60,6 +63,7 @@ public class IpBlocklistPanelPO extends PageArea {
|
||||
|
||||
public void refresh() {
|
||||
refreshButton.click();
|
||||
waitForAjaxRequests();
|
||||
}
|
||||
|
||||
public boolean isIpInTable(final String ip) {
|
||||
@@ -68,8 +72,51 @@ public class IpBlocklistPanelPO extends PageArea {
|
||||
return wasFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblocks the given IP address and blocks until the server-side release is confirmed by the row disappearing from
|
||||
* a freshly reloaded table. The unlock GWT-RPC does not register with the pending-AJAX tracker (it uses a plain,
|
||||
* unmarked {@code AsyncCallback}), so {@link #waitForAjaxRequests()} cannot observe it and the removed row alone
|
||||
* only proves the client-side row was dropped, not that the lock was released. Refreshing (which does register)
|
||||
* and waiting for the row to be gone therefore ties completion to a server round-trip, preventing a subsequent
|
||||
* user creation from racing an as-yet-unreleased lock.
|
||||
*/
|
||||
public void unblockIP(String ip) {
|
||||
cellTable.getEntry(ip).select();
|
||||
unlockButton.click();
|
||||
waitUntil(() -> {
|
||||
refresh();
|
||||
return !isIpInTable(ip);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP addresses currently shown in the blocklist table, exactly as rendered in the "IP Address" column
|
||||
* (including any formatting such as brackets around an IPv6 literal). This avoids guessing the textual form under
|
||||
* which the server recorded a lock: the returned strings round-trip through {@link #unblockIP(String)} because they
|
||||
* are the very identifiers {@link IPLockEntry#getIdentifier()} compares against. The table is refreshed first so
|
||||
* that a lock recorded by a preceding operation is reflected before it is read.
|
||||
*/
|
||||
public List<String> getBlockedIps() {
|
||||
refresh();
|
||||
final List<String> result = new ArrayList<>();
|
||||
for (final IPLockEntry entry : cellTable.getEntries()) {
|
||||
result.add(entry.getIp());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every lock currently present in the blocklist table by unblocking each shown IP address. Because a
|
||||
* successful unblock removes the corresponding row and thereby invalidates the previously resolved page objects,
|
||||
* the set of present IPs is read once up front via {@link #getBlockedIps()} and each address is then unblocked by
|
||||
* re-resolving it against the (refreshed) table. Addresses that are no longer present when their turn comes are
|
||||
* skipped, so this is safe even if an unblock removes more than the targeted row.
|
||||
*/
|
||||
public void unblockAllPresentIps() {
|
||||
for (final String ip : getBlockedIps()) {
|
||||
if (isIpInTable(ip)) {
|
||||
unblockIP(ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -62,6 +62,12 @@ public class RoleDefinitionsPanelPO extends PageArea {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public RoleEntryPO findRoleWhenPresent(final String roleName) {
|
||||
waitForAjaxRequests();
|
||||
waitUntil(() -> findRole(roleName) != null);
|
||||
return findRole(roleName);
|
||||
}
|
||||
|
||||
public void selectRole(String name) {
|
||||
final CellTablePO<RoleEntryPO> table = getRoleTable();
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ public class AclActionInputPO extends PageArea {
|
||||
}
|
||||
|
||||
public void addAction(String name) {
|
||||
waitUntil(() -> inputSuggestBox.isDisplayed() && inputSuggestBox.isEnabled());
|
||||
SuggestBoxPO.create(driver, inputSuggestBox).appendText(name);
|
||||
addButton.click();
|
||||
}
|
||||
|
||||
+48
-10
@@ -13,6 +13,8 @@ import com.sap.sailing.selenium.core.BySeleniumId;
|
||||
import com.sap.sailing.selenium.core.FindBy;
|
||||
import com.sap.sailing.selenium.pages.PageArea;
|
||||
import com.sap.sailing.selenium.pages.adminconsole.ActionsHelper;
|
||||
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
|
||||
import com.sap.sailing.selenium.pages.adminconsole.advanced.LocalServerPO;
|
||||
import com.sap.sailing.selenium.pages.gwt.CellTablePO;
|
||||
import com.sap.sailing.selenium.pages.gwt.DataEntryPO;
|
||||
import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO;
|
||||
@@ -43,8 +45,15 @@ public class UserManagementPanelPO extends PageArea {
|
||||
|
||||
private TimePoint lastCreateUser;
|
||||
|
||||
private final AdminConsolePage adminConsole;
|
||||
|
||||
public UserManagementPanelPO(WebDriver driver, WebElement element) {
|
||||
this(driver, element, null);
|
||||
}
|
||||
|
||||
public UserManagementPanelPO(WebDriver driver, WebElement element, AdminConsolePage adminConsole) {
|
||||
super(driver, element);
|
||||
this.adminConsole = adminConsole;
|
||||
}
|
||||
|
||||
private CellTablePO<DataEntryPO> getUserTable() {
|
||||
@@ -92,17 +101,10 @@ public class UserManagementPanelPO extends PageArea {
|
||||
}
|
||||
|
||||
public CreateUserDialogPO getCreateUserDialog() {
|
||||
final TimePoint now = TimePoint.now();
|
||||
if (lastCreateUser != null && now.minus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION).before(lastCreateUser)) {
|
||||
try {
|
||||
final long sleepTimeMillis = now.until(lastCreateUser.plus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION)).asMillis();
|
||||
logger.info("Waiting "+sleepTimeMillis+"ms to create next user in "+this);
|
||||
Thread.sleep(sleepTimeMillis);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (adminConsole != null) {
|
||||
clearUserCreationLockForLocalIp();
|
||||
} else {
|
||||
logger.info("No wait required in dialog "+this);
|
||||
throttleClientSideForUserCreationLock();
|
||||
}
|
||||
createUserButton.click();
|
||||
final WebElement dialog = findElementBySeleniumId(this.driver, "CreateUserDialog");
|
||||
@@ -114,6 +116,42 @@ public class UserManagementPanelPO extends PageArea {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the server-side per-IP user-creation lock for the local client via the admin console's "User Creation
|
||||
* Abuse" blocklist panel, so a subsequent user creation is not rejected by the rate limit. The server keys the lock
|
||||
* by the connection's remote address, whose textual form for a loopback client depends on the stack (an IPv4 form
|
||||
* such as {@code 127.0.0.1} or an IPv6 loopback form such as {@code 0:0:0:0:0:0:0:1} or {@code ::1}) and may be
|
||||
* rendered with additional formatting in the table. Rather than guessing that literal, every currently locked
|
||||
* address shown in the panel is unblocked. This requires navigating to the Local Server panel and back to User
|
||||
* Management; the excursion is self-contained and leaves the User Management tab selected again for the caller.
|
||||
*/
|
||||
private void clearUserCreationLockForLocalIp() {
|
||||
final LocalServerPO localServer = adminConsole.goToLocalServerPanel();
|
||||
localServer.getUserCreationAbusePO().unblockAllPresentIps();
|
||||
adminConsole.goToUserManagement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy fallback used when this panel was created without an {@link AdminConsolePage} reference and therefore
|
||||
* cannot reach the blocklist panel to clear the server-side lock: pre-emptively sleeps for the remainder of the
|
||||
* {@link SecurityService#DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION} window since the last successful
|
||||
* creation so the server IP lock is never hit.
|
||||
*/
|
||||
private void throttleClientSideForUserCreationLock() {
|
||||
final TimePoint now = TimePoint.now();
|
||||
if (lastCreateUser != null && now.minus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION).before(lastCreateUser)) {
|
||||
try {
|
||||
final long sleepTimeMillis = now.until(lastCreateUser.plus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION)).asMillis();
|
||||
logger.info("Waiting "+sleepTimeMillis+"ms to create next user in "+this);
|
||||
Thread.sleep(sleepTimeMillis);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
logger.info("No wait required in dialog "+this);
|
||||
}
|
||||
}
|
||||
|
||||
public void createUserWithEqualUsernameAndPassword(String usernameAndPassword) {
|
||||
logger.info("Starting process to create user "+usernameAndPassword);
|
||||
|
||||
+2
-1
@@ -28,10 +28,11 @@ public class UserRoleDefinitionPanelPO extends PageArea {
|
||||
|
||||
public void deleteRole() {
|
||||
deleteButton.click();
|
||||
waitForAjaxRequests();
|
||||
}
|
||||
|
||||
public void deleteRoleAndExpectPermissionError() {
|
||||
deleteRole();
|
||||
deleteButton.click();
|
||||
waitForAlertContainingMessageAndAccept("You are not allowed to revoke this role from user");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -27,10 +27,11 @@ public class WildcardPermissionPanelPO extends PageArea {
|
||||
|
||||
public void deletePermission() {
|
||||
deleteButton.click();
|
||||
waitForAjaxRequests();
|
||||
}
|
||||
|
||||
public void deletePermissionAndExpectPermissionError() {
|
||||
deletePermission();
|
||||
deleteButton.click();
|
||||
waitForAlertContainingMessageAndAccept("Could not remove permission");
|
||||
}
|
||||
}
|
||||
@@ -65,6 +66,7 @@ public class WildcardPermissionPanelPO extends PageArea {
|
||||
public void addPermission(String permissionName) {
|
||||
enterNewPermissionValue(permissionName);
|
||||
clickAddButtonOrThrow();
|
||||
waitForAjaxRequests();
|
||||
waitUntil(() -> findPermission(permissionName) != null);
|
||||
}
|
||||
|
||||
|
||||
+16
-4
@@ -10,6 +10,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.StaleElementReferenceException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
@@ -175,11 +176,22 @@ public abstract class CellTablePO<T extends DataEntryPO> extends PageArea {
|
||||
}
|
||||
|
||||
public List<T> getEntries() {
|
||||
final List<T> entries = new ArrayList<>();
|
||||
for (WebElement row : getRows()) {
|
||||
entries.add(createDataEntry(row));
|
||||
final int maxAttempts = 5;
|
||||
List<T> result = null;
|
||||
for (int attempt = 0; result == null && attempt < maxAttempts; attempt++) {
|
||||
try {
|
||||
final List<T> entries = new ArrayList<>();
|
||||
for (final WebElement row : getRows()) {
|
||||
entries.add(createDataEntry(row));
|
||||
}
|
||||
result = entries;
|
||||
} catch (final StaleElementReferenceException e) {
|
||||
if (attempt == maxAttempts - 1) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
return result;
|
||||
}
|
||||
|
||||
public T getEntry(Object identifier) {
|
||||
|
||||
+29
-11
@@ -1,11 +1,13 @@
|
||||
package com.sap.sailing.selenium.pages.gwt;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.openqa.selenium.StaleElementReferenceException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
@@ -72,24 +74,40 @@ public class GenericCellTablePO<T extends DataEntryPO> extends CellTablePO<T> {
|
||||
@Override
|
||||
public <S extends CellTablePO<T>> T createEntry(S table, WebElement element) {
|
||||
Class<?> clazz = table.getClass();
|
||||
T result = null;
|
||||
final List<Exception> exceptionsCaught = new ArrayList<>();
|
||||
while (clazz != null) {
|
||||
while (clazz != null && result == null) {
|
||||
try {
|
||||
Constructor<T> constructor = this.type.getConstructor(clazz, WebElement.class);
|
||||
return constructor.newInstance(table, element);
|
||||
} catch (Exception exception) {
|
||||
final Constructor<T> constructor = this.type.getConstructor(clazz, WebElement.class);
|
||||
result = constructor.newInstance(table, element);
|
||||
} catch (final NoSuchMethodException exception) {
|
||||
clazz = clazz.getSuperclass();
|
||||
exceptionsCaught.add(exception);
|
||||
} catch (final InvocationTargetException exception) {
|
||||
if (exception.getCause() instanceof StaleElementReferenceException) {
|
||||
throw (StaleElementReferenceException) exception.getCause();
|
||||
} else {
|
||||
throw new RuntimeException("Can't create DataEntry of type " + this.type
|
||||
+ " on table of type " + table.getClass().getName() + " on web element " + element,
|
||||
exception);
|
||||
}
|
||||
} catch (final InstantiationException | IllegalAccessException exception) {
|
||||
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
|
||||
+ table.getClass().getName() + " on web element " + element, exception);
|
||||
}
|
||||
}
|
||||
logger.warning("Unable to construct a DataEntryPO of type " + this.type.getName() + " for table " + table
|
||||
+ " of type " + (table == null ? null : table.getClass().getName()) + " from web element "
|
||||
+ element);
|
||||
for (final Exception e : exceptionsCaught) {
|
||||
logger.log(Level.WARNING, "Exception caught while trying to create a DataEntryPO of type "+this.type.getName(), e);
|
||||
if (result == null) {
|
||||
logger.warning("Unable to construct a DataEntryPO of type " + this.type.getName() + " for table "
|
||||
+ table + " of type " + (table == null ? null : table.getClass().getName())
|
||||
+ " from web element " + element);
|
||||
for (final Exception e : exceptionsCaught) {
|
||||
logger.log(Level.WARNING,
|
||||
"Exception caught while trying to create a DataEntryPO of type " + this.type.getName(), e);
|
||||
}
|
||||
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
|
||||
+ table.getClass().getName() + " on web element " + element);
|
||||
}
|
||||
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
|
||||
+ table.getClass().getName() + " on web element " + element);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -339,7 +339,7 @@ public class TestNegativeAcls extends AbstractSeleniumTest {
|
||||
RoleDefinitionCreationAndUpdateDialogPO createRoleDialog = roleDefinitions.getCreateRoleDialog();
|
||||
createRoleDialog.setName(CUSTOM_ROLE);
|
||||
createRoleDialog.clickOkButtonOrThrow();
|
||||
AclPopupPO aclPopup = roleDefinitions.findRole(CUSTOM_ROLE).openAclPopup();
|
||||
AclPopupPO aclPopup = roleDefinitions.findRoleWhenPresent(CUSTOM_ROLE).openAclPopup();
|
||||
aclPopup.addUserGroup("");
|
||||
AclActionInputPO allowedActionsInput = aclPopup.getAllowedActionsInput();
|
||||
// adding this ACL ensures that other users may read and update the custom role
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<div class="innerContent">
|
||||
<h2 class="articleSubheadline">September 2026</h2>
|
||||
<ul class="bulletList">
|
||||
<li>Added boat classes Onyx, Lacustre and 15m² SNS.</li>
|
||||
<li>Bug fix: when allowing for competitor master data updates coming from TracTrac and the updates
|
||||
were only about competitor nationality changes, those updates were not properly applied in all
|
||||
cases. This is now fixed. <a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/6301">#6301</a></li>
|
||||
@@ -32,6 +33,9 @@
|
||||
<li>Warn landscape administrators when trying to remove or make read-only a replica set that
|
||||
currently has live events
|
||||
(see <a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/6288">issue #6288</a>)<li>
|
||||
<li>Bug fix: when loading a replica for a primary that already has a non-empty CORS configuration for its replica set,
|
||||
this CORS configuration was not properly applied on the replica, hence not serving the <tt>frame-ancestores</tt>
|
||||
HTTP response header correctly.</li>
|
||||
</ul>
|
||||
<h2 class="articleSubheadline">August 2026</h2>
|
||||
<ul class="bulletList">
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ import com.google.gwt.view.client.SetSelectionModel;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.util.NaturalComparator;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
|
||||
@@ -126,7 +127,7 @@ public class RoleDefinitionsPanel extends VerticalPanel {
|
||||
@Override
|
||||
public void ok(RoleDefinitionDTO editedObject) {
|
||||
userManagementWriteService.createRoleDefinition(editedObject.getId().toString(),
|
||||
editedObject.getName(), new AsyncCallback<RoleDefinitionDTO>() {
|
||||
editedObject.getName(), new MarkedAsyncCallback<RoleDefinitionDTO>(new AsyncCallback<RoleDefinitionDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(stringMessages.errorCreatingRole(editedObject.getName(), caught.getMessage()));
|
||||
@@ -146,7 +147,7 @@ public class RoleDefinitionsPanel extends VerticalPanel {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwt.view.client.MultiSelectionModel;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
||||
import com.sap.sse.security.shared.dto.RolesAndPermissionsForUserDTO;
|
||||
@@ -41,7 +42,8 @@ public class EditUserRolesAndPermissionsDialog extends DataEntryDialog<Void> {
|
||||
public void run() {
|
||||
selectionAdapter.clear();
|
||||
userService.getUserManagementService().getRolesAndPermissionsForUser(selectedUsername,
|
||||
new AsyncCallback<RolesAndPermissionsForUserDTO>() {
|
||||
new MarkedAsyncCallback<RolesAndPermissionsForUserDTO>(
|
||||
new AsyncCallback<RolesAndPermissionsForUserDTO>() {
|
||||
@Override
|
||||
public void onSuccess(RolesAndPermissionsForUserDTO result) {
|
||||
selectionAdapter.clear();
|
||||
@@ -55,7 +57,7 @@ public class EditUserRolesAndPermissionsDialog extends DataEntryDialog<Void> {
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(caught.getMessage());
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
};
|
||||
wildcardPermissionPanel = new WildcardPermissionPanel(userService, stringMessages, errorReporter,
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ import com.sap.sse.gwt.client.DateAndTimeFormatterUtil;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.Notification;
|
||||
import com.sap.sse.gwt.client.Notification.NotificationType;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
|
||||
@@ -347,7 +348,7 @@ extends TableWrapper<UserDTO, S, StringMessages, TR> {
|
||||
}
|
||||
}
|
||||
};
|
||||
getUserManagementService().getUserList(myCallback);
|
||||
getUserManagementService().getUserList(new MarkedAsyncCallback<>(myCallback));
|
||||
}
|
||||
|
||||
public void refreshUserList(final Callback<Iterable<UserDTO>, Throwable> callback) {
|
||||
|
||||
+3
-2
@@ -25,6 +25,7 @@ import com.google.gwt.view.client.SelectionChangeEvent;
|
||||
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
|
||||
import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
@@ -88,7 +89,7 @@ public class WildcardPermissionPanel extends HorizontalPanel
|
||||
UserDTO selectedUser = this.userSelectionModel.getSelectedObject();
|
||||
if (selectedUser != null) {
|
||||
userService.getUserManagementWriteService().addPermissionForUser(selectedUser.getName(),
|
||||
selectedPermission, new AsyncCallback<SuccessInfo>() {
|
||||
selectedPermission, new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
Window.alert(caught.getMessage());
|
||||
@@ -102,7 +103,7 @@ public class WildcardPermissionPanel extends HorizontalPanel
|
||||
Window.alert(result.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
suggestPermission.setText("");
|
||||
|
||||
+3
-2
@@ -18,6 +18,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
|
||||
@@ -78,7 +79,7 @@ public class WildcardPermissionWithSecurityDTOTableWrapper extends
|
||||
UserDTO selectedObject = userSelectionModel.getSelectedObject();
|
||||
if (selectedObject != null) {
|
||||
userService.getUserManagementWriteService().removePermissionFromUser(selectedObject.getName(),
|
||||
selectedPermission, new AsyncCallback<SuccessInfo>() {
|
||||
selectedPermission, new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
Window.alert(stringMessages.couldNotRemovePermissionFromUser(selectedObject.getName(),
|
||||
@@ -94,7 +95,7 @@ public class WildcardPermissionWithSecurityDTOTableWrapper extends
|
||||
Window.alert(result.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
Window.alert(stringMessages.pleaseSelect());
|
||||
}
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
|
||||
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
|
||||
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
|
||||
@@ -83,7 +84,7 @@ public class RoleWithSecurityDTOTableWrapper extends
|
||||
qualifiedForUser != null ? qualifiedForUser.getName() : null,
|
||||
selectedRole.getRoleDefinition().getId(),
|
||||
qualifiedForTenant == null ? null : qualifiedForTenant.getName(),
|
||||
selectedRole.isTransitive(), new AsyncCallback<SuccessInfo>() {
|
||||
selectedRole.isTransitive(), new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
Window.alert(stringMessages.couldNotRemoveRoleFromUser(selectedObject.getName(),
|
||||
@@ -99,7 +100,7 @@ public class RoleWithSecurityDTOTableWrapper extends
|
||||
Window.alert(result.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
Window.alert(stringMessages.pleaseSelect());
|
||||
}
|
||||
|
||||
@@ -364,6 +364,7 @@ implements ReplicableSecurityService, ClearStateTestSupport {
|
||||
this.hasPermissionsProvider = hasPermissionsProvider;
|
||||
this.cacheManager = loadReplicationCacheManagerContents();
|
||||
this.corsFilterConfigurationsByReplicaSetName = loadCORSFilterConfigurations();
|
||||
applyCORSFilterForLocalReplicaSet();
|
||||
logger.info("Loaded shiro.ini file from: classpath:shiro.ini");
|
||||
final StringBuilder logMessage = new StringBuilder("[urls] section from Shiro configuration:");
|
||||
final Section urlsSection = shiroConfiguration.getSection("urls");
|
||||
@@ -428,15 +429,18 @@ implements ReplicableSecurityService, ClearStateTestSupport {
|
||||
logger.info("Loading CORS filter configurations");
|
||||
final ConcurrentMap<String, Pair<Boolean, Set<String>>> result = new ConcurrentHashMap<>();
|
||||
result.putAll(PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory().loadCORSFilterConfigurationsForReplicaSetNames());
|
||||
if (result.containsKey(ServerInfo.getName())) {
|
||||
final Pair<Boolean, Set<String>> thisServersCORSFilterConfig = result.get(ServerInfo.getName());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void applyCORSFilterForLocalReplicaSet() {
|
||||
if (corsFilterConfigurationsByReplicaSetName.containsKey(ServerInfo.getName())) {
|
||||
final Pair<Boolean, Set<String>> thisServersCORSFilterConfig = corsFilterConfigurationsByReplicaSetName.get(ServerInfo.getName());
|
||||
if (thisServersCORSFilterConfig.getA()) {
|
||||
getCORSFilterConfiguration().setWildcard();
|
||||
} else {
|
||||
getCORSFilterConfiguration().setOrigins(thisServersCORSFilterConfig.getB());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2665,6 +2669,7 @@ implements ReplicableSecurityService, ClearStateTestSupport {
|
||||
final SecurityServiceInitialLoadExtensionsDTO initialLoadExtensions = (SecurityServiceInitialLoadExtensionsDTO) is.readObject();
|
||||
final ConcurrentMap<String, Pair<Boolean, Set<String>>> newCORSFilterConfigurations = initialLoadExtensions.getCorsFilterConfigurationsByReplicaSetName();
|
||||
corsFilterConfigurationsByReplicaSetName.putAll(newCORSFilterConfigurations);
|
||||
applyCORSFilterForLocalReplicaSet();
|
||||
if (initialLoadExtensions.getClientIPBasedTimedLocksForBearerTokenAuthentication() != null) {
|
||||
// checking for null for backward compatibility; an older primary/master may not have known this field yet
|
||||
clientIPBasedTimedLocksForBearerTokenAuthentication.putAll(initialLoadExtensions.getClientIPBasedTimedLocksForBearerTokenAuthentication());
|
||||
|
||||
Reference in new issue
Block a user