Skip to content

Commit 4cb0530

Browse files
committed
Fix: Server Resource And Lifecycle Polish.
1 parent 749cd3b commit 4cb0530

7 files changed

Lines changed: 1000 additions & 53 deletions

File tree

src/main/java/redxax/oxy/remotely/ui/server/ServerConfigurationScreen.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.nio.file.Path;
4343
import java.util.*;
4444
import java.util.concurrent.CompletableFuture;
45+
import java.util.function.Consumer;
4546
import java.util.function.Supplier;
4647

4748
import static restudio.rescreen.util.BrowserUtils.openBrowser;
@@ -59,6 +60,8 @@ private record InitialConfigLoad(List<String> extraFiles) {}
5960
private final RemotelyClient remotelyClient;
6061
private final boolean isReStudioCreation;
6162
private final String preselectedPlanName;
63+
private final Consumer<Instance> creationInitializer;
64+
private final Consumer<Instance> creationCallback;
6265

6366
private final Map<String, String> remoteVariables = new HashMap<>();
6467
private final Map<String, String> originalRemoteVariables = new HashMap<>();
@@ -83,6 +86,18 @@ public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost re
8386
}
8487

8588
public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost remoteHostContext, RemotelyClient remotelyClient, boolean isReStudioCreation, String preselectedPlanName) {
89+
this(parent, instance, remoteHostContext, remotelyClient, isReStudioCreation, preselectedPlanName, null, null, null);
90+
}
91+
92+
public ServerConfigurationScreen(Screen parent, RemoteHost remoteHostContext, RemotelyClient remotelyClient, ModLoader preset, Consumer<Instance> creationCallback) {
93+
this(parent, null, remoteHostContext, remotelyClient, false, null, preset, null, creationCallback);
94+
}
95+
96+
public ServerConfigurationScreen(Screen parent, RemoteHost remoteHostContext, RemotelyClient remotelyClient, ModLoader preset, Consumer<Instance> creationInitializer, Consumer<Instance> creationCallback) {
97+
this(parent, null, remoteHostContext, remotelyClient, false, null, preset, creationInitializer, creationCallback);
98+
}
99+
100+
private ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost remoteHostContext, RemotelyClient remotelyClient, boolean isReStudioCreation, String preselectedPlanName, ModLoader preset, Consumer<Instance> creationInitializer, Consumer<Instance> creationCallback) {
86101
super();
87102
this.parent = parent;
88103
this.isEditMode = instance != null;
@@ -91,6 +106,8 @@ public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost re
91106
this.remotelyClient = remotelyClient;
92107
this.isReStudioCreation = isReStudioCreation;
93108
this.preselectedPlanName = preselectedPlanName;
109+
this.creationInitializer = creationInitializer;
110+
this.creationCallback = creationCallback;
94111

95112
if (isEditMode) {
96113
this.tempInstance = new Instance(instance, instance.getName());
@@ -107,6 +124,9 @@ public ServerConfigurationScreen(Screen parent, Instance instance, RemoteHost re
107124
}
108125
} else {
109126
this.tempInstance = new Instance("New Server", remotelyClient.getHost().getGameVersion(), "");
127+
if (preset != null) {
128+
this.tempInstance.setModLoader(preset);
129+
}
110130
this.isReStudioBackend = false;
111131
if (remoteHostContext != null) {
112132
this.tempInstance.setBackendConfig(createBackendConfigForRemoteHost(remoteHostContext, null));
@@ -170,6 +190,9 @@ public void init() {
170190
if (screenClosed) {
171191
return;
172192
}
193+
if (!isEditMode && creationInitializer != null) {
194+
creationInitializer.accept(tempInstance);
195+
}
173196
setupSettingsUI(load.extraFiles());
174197
})).exceptionally(e -> {
175198
ScreenManager.getInstance().execute(() -> {
@@ -435,6 +458,9 @@ private void createNewLocalServer() {
435458
}).thenAccept(newInstance -> ScreenManager.getInstance().execute(() -> {
436459
handleOpMe(newInstance);
437460
newInstance.setState(InstanceState.STOPPED);
461+
if (creationCallback != null) {
462+
creationCallback.accept(newInstance);
463+
}
438464
})).exceptionally(ex -> {
439465
ScreenManager.getInstance().execute(() -> {
440466
Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
@@ -471,6 +497,9 @@ private void createNewRemoteServer() {
471497
.thenAccept(newInstance -> ScreenManager.getInstance().execute(() -> {
472498
handleOpMe(newInstance);
473499
newInstance.setState(InstanceState.STOPPED);
500+
if (creationCallback != null) {
501+
creationCallback.accept(newInstance);
502+
}
474503
notification.update().message("Remote Server Created").description(newInstance.getName()).type(Notification.Type.SUCCESS).loading(false).image(null).autoSlideOut(true);
475504
})).exceptionally(ex -> {
476505
ScreenManager.getInstance().execute(() -> {

src/main/java/redxax/oxy/remotely/ui/server/ServerDetailsScreen.java

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package redxax.oxy.remotely.ui.server;
22

33
import redxax.oxy.remotely.RemotelyClient;
4-
import redxax.oxy.remotely.discord.DiscordRpcBridge;
5-
import redxax.oxy.remotely.data.integrations.luckperms.LuckPermsService;
4+
import redxax.oxy.remotely.discord.DiscordRpcBridge;
5+
import redxax.oxy.remotely.data.integrations.luckperms.LuckPermsService;
6+
import redxax.oxy.remotely.network.NetworkDefinition;
7+
import redxax.oxy.remotely.network.NetworkMember;
8+
import redxax.oxy.remotely.network.NetworkRuntimeSnapshot;
69
import redxax.oxy.remotely.servers.QuickServerSyncManager;
710
import redxax.oxy.remotely.servers.ReProxyManager;
811
import redxax.oxy.remotely.session.TerminalSession;
@@ -79,7 +82,8 @@ public class ServerDetailsScreen extends InstanceDetailsScreen implements IDebug
7982
private final RemotelyClient remotelyClient;
8083
private final Object parent;
8184
private final Instance initialInstanceToOpen;
82-
private IconButton startIconButton;
85+
private IconButton startIconButton;
86+
private PopupWidget networkSummaryPopup;
8387
private Instance sidecarInstance;
8488

8589
private final Map<TabContext, TerminalSession> contextInfos = new HashMap<>();
@@ -1628,7 +1632,7 @@ private static String formatBytes(long bytes) {
16281632
return String.format(Locale.ROOT, "%.1f%s", b, units[idx]);
16291633
}
16301634

1631-
private static String formatUptime(long uptimeMs) {
1635+
private static String formatUptime(long uptimeMs) {
16321636
if (uptimeMs <= 0) return "-";
16331637
long totalSeconds = uptimeMs / 1000;
16341638
long days = totalSeconds / 86400;
@@ -1639,17 +1643,59 @@ private static String formatUptime(long uptimeMs) {
16391643
if (days > 0) return days + "d " + hours + "h";
16401644
if (hours > 0) return hours + "h " + minutes + "m";
16411645
if (minutes > 0) return minutes + "m " + seconds + "s";
1642-
return seconds + "s";
1643-
}
1644-
1645-
private static final class ServerTabStatusContext extends TabContext implements TabStatusContext {
1646+
return seconds + "s";
1647+
}
1648+
1649+
private void showNetworkSummary(Instance instance) {
1650+
if (instance == null || remotelyClient.getNetworkManager() == null) {
1651+
return;
1652+
}
1653+
NetworkDefinition network = remotelyClient.getNetworkManager().getNetworkForInstance(instance.getInstanceId()).orElse(null);
1654+
if (network == null) {
1655+
return;
1656+
}
1657+
NetworkMember member = network.members().stream().filter(candidate -> candidate.instanceId().equals(instance.getInstanceId())).findFirst().orElse(null);
1658+
NetworkRuntimeSnapshot snapshot = remotelyClient.getNetworkManager().getRuntimeSnapshot(network.networkId());
1659+
if (networkSummaryPopup != null) {
1660+
remove(networkSummaryPopup);
1661+
}
1662+
String role = member == null ? "Member" : member.isProxy() ? "Proxy" : formatNetworkRole(member.role().name());
1663+
String route = member == null ? "Unavailable" : member.routeName() + " • " + member.address() + ":" + member.port();
1664+
String presence = snapshot.connected() ? snapshot.players() + " Shared Players" : "ReSync " + formatNetworkRole(snapshot.state().name());
1665+
AnimatedButton identity = new AnimatedButton.Builder().label(role + " • " + network.members().size() + " Servers").active(false).accentType(ThemeManager.getAccent("calm")).build();
1666+
AnimatedButton endpoint = new AnimatedButton.Builder().label(route).active(false).accentType(ThemeManager.getAccent("calm")).build();
1667+
AnimatedButton runtime = new AnimatedButton.Builder().label(presence).active(false).accentType(ThemeManager.getAccent(snapshot.connected() ? "nice" : "warning")).build();
1668+
PopupWidget.Builder builder = new PopupWidget.Builder(network.name()).size(390, 135);
1669+
builder.addRow("identity", "Network", true, 22, identity);
1670+
builder.addRow("route", "Route", true, 22, endpoint);
1671+
builder.addRow("presence", "Presence", true, 22, runtime);
1672+
networkSummaryPopup = builder.build();
1673+
networkSummaryPopup.setX((width - networkSummaryPopup.getWidth()) / 2);
1674+
networkSummaryPopup.setY((height - networkSummaryPopup.getHeight()) / 2);
1675+
addDrawableChild(networkSummaryPopup);
1676+
networkSummaryPopup.show();
1677+
}
1678+
1679+
private static String formatNetworkRole(String value) {
1680+
String normalized = value == null ? "" : value.toLowerCase(Locale.ROOT).replace('_', ' ');
1681+
StringBuilder result = new StringBuilder(normalized.length());
1682+
boolean capitalize = true;
1683+
for (char character : normalized.toCharArray()) {
1684+
result.append(capitalize ? Character.toUpperCase(character) : character);
1685+
capitalize = character == ' ';
1686+
}
1687+
return result.toString();
1688+
}
1689+
1690+
private final class ServerTabStatusContext extends TabContext implements TabStatusContext {
16461691
private static final long CONNECTION_REFRESH_MS = 30000;
16471692
private static final long COPIED_DISPLAY_MS = 2000;
16481693

16491694
private IconButton connectionWidget;
1650-
private IconButton uptimeWidget;
1651-
private IconButton cpuWidget;
1652-
private IconButton ramWidget;
1695+
private IconButton uptimeWidget;
1696+
private IconButton cpuWidget;
1697+
private IconButton ramWidget;
1698+
private IconButton networkWidget;
16531699
private String connectionInfo = "Loading...";
16541700
private long copiedUntilMs;
16551701
private long copyActionToken;
@@ -1681,7 +1727,18 @@ public void setupStatusBar(StatusBarBuilder builder) {
16811727
.transparent(true).animateElevation(false).entranceAnimation(false).elevateOnFocused(false)
16821728
.build();
16831729
}
1684-
updateConnectionWidget();
1730+
updateConnectionWidget();
1731+
1732+
if (networkWidget == null) {
1733+
networkWidget = new IconButton.Builder()
1734+
.imagePath("velocity.png")
1735+
.autoWidthOnTextChange(true)
1736+
.size(0, 14)
1737+
.iconSize(12).iconPadding(2)
1738+
.transparent(true).animateElevation(false).entranceAnimation(false).elevateOnFocused(false)
1739+
.build();
1740+
}
1741+
updateNetworkWidget();
16851742

16861743
if (uptimeWidget == null) {
16871744
uptimeWidget = new IconButton.Builder()
@@ -1702,8 +1759,9 @@ public void setupStatusBar(StatusBarBuilder builder) {
17021759
.build();
17031760
}
17041761

1705-
builder.addLeft(connectionWidget);
1706-
builder.addRight(uptimeWidget);
1762+
builder.addLeft(connectionWidget);
1763+
builder.addLeft(networkWidget);
1764+
builder.addRight(uptimeWidget);
17071765
builder.addRight(cpuWidget);
17081766
builder.addRight(ramWidget);
17091767
}
@@ -1817,8 +1875,9 @@ private void finishRequest() {
18171875
requestInFlight = false;
18181876
}
18191877

1820-
private void update(ResourceUsageFeature.ResourceUsage usage) {
1821-
int players = 0;
1878+
private void update(ResourceUsageFeature.ResourceUsage usage) {
1879+
updateNetworkWidget();
1880+
int players = 0;
18221881
try {
18231882
players = PlayerManagerController.getOrCreate(instance).getOnlinePlayerCount();
18241883
} catch (Exception ignored) {
@@ -1846,10 +1905,30 @@ private void update(ResourceUsageFeature.ResourceUsage usage) {
18461905
if (usage.memoryLimitBytes() > 0) {
18471906
ramWidget.setMessage("RAM: " + formatBytes(usage.memoryBytes()) + "/" + formatBytes(usage.memoryLimitBytes()));
18481907
} else {
1849-
ramWidget.setMessage("RAM: " + formatBytes(usage.memoryBytes()));
1850-
}
1851-
}
1852-
}
1908+
ramWidget.setMessage("RAM: " + formatBytes(usage.memoryBytes()));
1909+
}
1910+
}
1911+
1912+
private void updateNetworkWidget() {
1913+
if (networkWidget == null || instance == null || remotelyClient.getNetworkManager() == null) {
1914+
return;
1915+
}
1916+
NetworkDefinition network = remotelyClient.getNetworkManager().getNetworkForInstance(instance.getInstanceId()).orElse(null);
1917+
if (network == null) {
1918+
networkWidget.setVisible(false);
1919+
networkWidget.setOnClick(null);
1920+
return;
1921+
}
1922+
NetworkMember member = network.members().stream().filter(candidate -> candidate.instanceId().equals(instance.getInstanceId())).findFirst().orElse(null);
1923+
NetworkRuntimeSnapshot snapshot = remotelyClient.getNetworkManager().getRuntimeSnapshot(network.networkId());
1924+
String role = member == null ? "Member" : member.isProxy() ? "Proxy" : formatNetworkRole(member.role().name());
1925+
String players = snapshot.connected() ? " • " + snapshot.players() + " Players" : "";
1926+
networkWidget.setMessage(network.name() + " • " + role + players);
1927+
networkWidget.setHint(snapshot.connected() ? "Network-Wide ReSync Presence" : "ReSync " + formatNetworkRole(snapshot.state().name()));
1928+
networkWidget.setOnClick(() -> showNetworkSummary(instance));
1929+
networkWidget.setVisible(true);
1930+
}
1931+
}
18531932

18541933
@Override
18551934
public List<String> getLeftLines() {

src/main/java/redxax/oxy/remotely/ui/server/ServerIconManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.concurrent.CompletableFuture;
2323
import java.util.concurrent.ConcurrentHashMap;
2424
import java.util.function.Consumer;
25+
import java.util.stream.Stream;
2526

2627
import static redxax.oxy.remotely.util.DevUtil.devPrint;
2728

@@ -393,7 +394,7 @@ private CompletableFuture<Boolean> uploadToRemote(Instance instance, BufferedIma
393394

394395
private void deleteDirectoryQuietly(Path dir) {
395396
if (dir == null || !Files.exists(dir)) return;
396-
try (java.util.stream.Stream<Path> walk = Files.walk(dir)) {
397+
try (Stream<Path> walk = Files.walk(dir)) {
397398
walk.sorted(Comparator.reverseOrder()).forEach(path -> {
398399
try {
399400
Files.deleteIfExists(path);

0 commit comments

Comments
 (0)