Skip to content

Commit 55d3e8b

Browse files
committed
Implement server control API and enhance server management UI
1 parent 3fa9af4 commit 55d3e8b

12 files changed

Lines changed: 301 additions & 99 deletions

File tree

client/src/header/header.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<link rel="stylesheet" href="header.css" />
22
<header class="dashboard-header">
3-
<button id="dashboard" onclick="window.location.href='dashboard.html'" {{"disabled" if page.name == "dashboard" else ""}}>
3+
<button id="dashboard" onclick="window.location.href='dashboard'" {{"disabled" if page.name == "dashboard" else ""}}>
44
Dashboard
55
</button>
6-
<button id="account" onclick="window.location.href='account.html'" {{"disabled" if page.name == "account" else ""}}>
6+
<button id="account" onclick="window.location.href='account'" {{"disabled" if page.name == "account" else ""}}>
77
Account Management
88
</button>
99
<button id="disconnect">

client/src/scripts/api.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,58 @@ export default class API {
308308
}
309309
}
310310

311+
public static async start_server(server_name: string) {
312+
if (!Cookies.has('token')) {
313+
console.error('No token found in cookies');
314+
return false;
315+
}
316+
const {data, status} = await API.post(`/api/start_server/${server_name}`, {});
317+
if (status === 200) {
318+
return true;
319+
}
320+
else if (status === 500) {
321+
console.error('Error starting server:', data['message']);
322+
throw new Error('Error starting server');
323+
}
324+
else{
325+
return false;
326+
}
327+
}
328+
329+
public static async stop_server(server_name: string) {
330+
if (!Cookies.has('token')) {
331+
console.error('No token found in cookies');
332+
return false;
333+
}
334+
const {data, status} = await API.post(`/api/stop_server/${server_name}`, {});
335+
if (status === 200) {
336+
return true;
337+
}
338+
else if (status === 500) {
339+
console.error('Error stopping server:', data['message']);
340+
throw new Error('Error stopping server');
341+
}
342+
else{
343+
return false;
344+
}
345+
}
346+
347+
public static async restart_server(server_name: string) {
348+
if (!Cookies.has('token')) {
349+
console.error('No token found in cookies');
350+
return false;
351+
}
352+
const {data, status} = await API.post(`/api/restart_server/${server_name}`, {});
353+
if (status === 200) {
354+
return true;
355+
}
356+
else if (status === 500) {
357+
console.error('Error restarting server:', data['message']);
358+
throw new Error('Error restarting server');
359+
}
360+
else{
361+
return false;
362+
}
363+
}
364+
311365
}

client/src/server/index.template.html

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@
1010
{{ insert("header/header") }}
1111
<main>
1212
<h1 id="server-name"></h1>
13-
<div>
14-
<span id="mc-version"></span>
13+
<div class="elements">
14+
Minecraft <span id="mc-version"></span> <span id="modloader-version"></span>
1515
</div>
16-
<div>
17-
<span id="modloader-version"></span>
16+
<div class="elements">
17+
Allocated RAM: <span id="ram"></span> MB
1818
</div>
19-
<div>
20-
<span id="ram"></span>
19+
<div class="elements">
20+
Started at: <span id="started-at"></span> (<span id="running-for"></span>)
2121
</div>
22-
<div>
23-
<span id="started-at"></span>
24-
</div>
25-
<div>
26-
<span id="running-for"></span>
22+
<div class="elements">
23+
<button disabled id="start-server">Start Server</button>
24+
<button disabled id="stop-server">Stop Server</button>
25+
<button disabled id="restart-server">Restart Server</button>
26+
<input type="checkbox" id="auto-start" />
27+
<label for="auto-start">Auto Start</label>
2728
</div>
2829
</main>
2930
<script type="module" src="server.js"></script>

client/src/server/server.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
11
@use '../common.scss';
2+
3+
h1 {
4+
text-align: center;
5+
}
6+
7+
.elements {
8+
display: inline-block;
9+
margin: 10px;
10+
text-align: center;
11+
}

client/src/server/server.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@ async function load_data(){
1919
serverNameElement.textContent = server_info.name;
2020

2121
const mcVersionElement = document.getElementById("mc-version") as HTMLSpanElement;
22-
mcVersionElement.textContent = `Minecraft Version: ${server_info.mc_version}`;
22+
mcVersionElement.textContent = server_info.mc_version;
2323

24+
2425
const modloaderVersionElement = document.getElementById("modloader-version") as HTMLSpanElement;
25-
modloaderVersionElement.textContent = `Modloader Version: ${server_info.modloader_version}`;
26+
if (server_info.type === "vanilla") {
27+
modloaderVersionElement.textContent = "Vanilla";
28+
}
29+
else {
30+
modloaderVersionElement.textContent = `${server_info.type} ${server_info.modloader_version}`;
31+
}
2632

2733
const ramElement = document.getElementById("ram") as HTMLSpanElement;
28-
ramElement.textContent = `RAM: ${server_info.ram} MB`;
34+
ramElement.textContent = `${server_info.ram}`;
2935

3036
const startedAtElement = document.getElementById("started-at") as HTMLSpanElement;
31-
startedAtElement.textContent = server_info.started_at ? `Started At: ${new Date(server_info.started_at).toLocaleString()}` : "Not Started";
37+
startedAtElement.textContent = server_info.started_at ? `${new Date(server_info.started_at).toLocaleString()}` : "N/A";
3238

3339
const runningForElement = document.getElementById("running-for") as HTMLSpanElement;
3440
if (server_info.started_at) {
@@ -40,15 +46,66 @@ async function load_data(){
4046
const hours = Math.floor(diff / 3600);
4147
const minutes = Math.floor((diff % 3600) / 60);
4248
const seconds = diff % 60;
43-
runningForElement.textContent = `Running For: ${hours}h ${minutes}m ${seconds}s`;
49+
runningForElement.textContent = `${hours}h ${minutes}m ${seconds}s`;
4450
}
4551

4652
updateRunningFor();
4753
setInterval(updateRunningFor, 1000);
4854
}
4955
else {
50-
runningForElement.textContent = "Not Running";
56+
runningForElement.textContent = "Server is not running";
57+
}
58+
59+
const startServerButton = document.getElementById("start-server") as HTMLButtonElement;
60+
if (server_info.started_at) {
61+
startServerButton.disabled = true;
62+
startServerButton.textContent = "Server is running";
63+
} else {
64+
startServerButton.disabled = false;
65+
startServerButton.textContent = "Start Server";
66+
}
67+
startServerButton.addEventListener("click", () => {
68+
API.start_server(server_name as string).then(() => {
69+
window.location.reload();
70+
}).catch((error) => {
71+
console.error("Error starting server:", error);
72+
alert("Failed to start the server. Check the console for details.");
73+
});
74+
});
75+
76+
const stopServerButton = document.getElementById("stop-server") as HTMLButtonElement;
77+
if (!server_info.started_at) {
78+
stopServerButton.disabled = true;
79+
stopServerButton.textContent = "Server is not running";
80+
} else {
81+
stopServerButton.disabled = false;
82+
stopServerButton.textContent = "Stop Server";
83+
}
84+
stopServerButton.addEventListener("click", () => {
85+
API.stop_server(server_name as string).then(() => {
86+
window.location.reload();
87+
}).catch((error) => {
88+
console.error("Error stopping server:", error);
89+
alert("Failed to stop the server. Check the console for details.");
90+
});
91+
});
92+
93+
const restartServerButton = document.getElementById("restart-server") as HTMLButtonElement;
94+
if (!server_info.started_at) {
95+
restartServerButton.disabled = true;
96+
restartServerButton.textContent = "Server is not running";
97+
} else {
98+
restartServerButton.disabled = false;
99+
restartServerButton.textContent = "Restart Server";
51100
}
101+
restartServerButton.addEventListener("click", () => {
102+
API.restart_server(server_name as string).then(() => {
103+
window.location.reload();
104+
}).catch((error) => {
105+
console.error("Error restarting server:", error);
106+
alert("Failed to restart the server. Check the console for details.");
107+
});
108+
});
52109

53110
}).catch((error) => {
54111
console.error("Error loading server info:", error);

server/src/bus/bus.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def a():
231231
self.__exec_callback(event, prefix.source_id, **args)
232232
except Exception as e:
233233
Logger.error(f"Error processing event {event.name} with args {args}: {e.__class__.__name__} : {e}")
234-
Logger.trace(traceback.format_exc())
234+
Logger.debug(traceback.format_exc())
235235
t = th.Thread(target=a, daemon=True, name=f"BusCallback-{event.name}")
236236
Logger.trace(f"Starting thread for event {event.name} with args {args}\nthread hash: {t.__hash__()}\nthread name: {t.name}")
237237
t.start()
@@ -283,7 +283,7 @@ def start(self):
283283
except RuntimeError as e:
284284
Logger.error(f"Failed to start bus listener thread: {e}")
285285
Logger.trace(f"Thread:\n alive: {self.__thread.is_alive()}\n name: {self.__thread.name}\n hash: {self.__thread.__hash__()}\n repr: {self.__thread.__repr__()}")
286-
Logger.trace(traceback.format_exc())
286+
Logger.debug(traceback.format_exc())
287287
return
288288
Logger.info("Bus is now listening for events")
289289
else:

server/src/bus/bus_dispatcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def mainloop(self):
145145
self.__move_forward(rec_key)
146146
except Exception as e:
147147
Logger.error(f"Error processing message {msg} from {rec_key}: {e}")
148-
Logger.trace(traceback.format_exc())
148+
Logger.debug(traceback.format_exc())
149149
time.sleep(0.01)
150150

151151
def stop(self):

server/src/core/core.py

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,59 @@ def __stop_user_interfaces(self):
166166
Logger.info(f"User interface process {ui_name} stopped successfully.")
167167

168168

169+
def __start_server(self, server_name: str):
170+
"""
171+
Starts the specified server.
172+
:param server_name: Name of the server to start
173+
"""
174+
Logger.info(f"Initializing server {server_name}...")
175+
try:
176+
server_type = self._srv_config.get(f"{server_name}.type")
177+
server_path = self._srv_config.get(f"{server_name}.path")
178+
179+
Server = McServersModules[server_type]
180+
bus_data = self.__bus_dispatcher.get_bus_data(server_name)
181+
ram = self._srv_config.get(f"{server_name}.ram", default=1024, set_if_not_found=True)
182+
mc_version = Version.from_string(self._srv_config.get(f"{server_name}.mc_version"))
183+
def __start_mc_server():
184+
srv = Server(server_name, server_path, ram, mc_version, bus_data)
185+
srv.start()
186+
p = mp.Process(
187+
target=__start_mc_server,
188+
name=f"Server_{server_name}",
189+
daemon=True
190+
)
191+
Logger.info(f"Starting Minecraft server {server_name} of type {server_type} at {server_path} with RAM {ram}MB...")
192+
p.start() # Start the process
193+
self.__mc_servers[server_name] = p # Store the process
194+
except Exception as e:
195+
Logger.error(f"Failed to init server {server_name}: {e}")
196+
Logger.debug(traceback.format_exc())
197+
198+
def __stop_server(self, server_name: str):
199+
srv_process = self.__mc_servers[server_name]
200+
if srv_process.is_alive():
201+
Logger.info(f"Stopping Minecraft server {server_name} with PID {srv_process.pid}...")
202+
self.__bus.trigger(
203+
Events['SERVER.STOP'],
204+
server_name=server_name,
205+
timeout=30 # Wait for 30 seconds for the server to stop
206+
)
207+
srv_process.join(timeout=30)
208+
if srv_process.is_alive():
209+
Logger.warning(f"Minecraft server {server_name} did not stop gracefully. Terminating...")
210+
srv_process.terminate()
211+
srv_process.join()
212+
Logger.info(f"Minecraft server {server_name} stopped successfully.")
213+
214+
def __restart_server(self, server_name: str):
215+
"""
216+
Restarts the specified server.
217+
:param server_name: Name of the server to restart
218+
"""
219+
Logger.info(f"Restarting server {server_name}...")
220+
self.__stop_server(server_name)
221+
self.__start_server(server_name)
169222

170223
def __start_mc_servers(self):
171224
"""
@@ -175,29 +228,7 @@ def __start_mc_servers(self):
175228
Logger.info("Initializing Minecraft servers...")
176229
for server_name, srv_info in self._srv_config.items():
177230
if srv_info.get("autostart", False):
178-
Logger.info(f"Initializing server {server_name}...")
179-
try:
180-
server_type = self._srv_config.get(f"{server_name}.type")
181-
server_path = self._srv_config.get(f"{server_name}.path")
182-
183-
Server = McServersModules[server_type]
184-
bus_data = self.__bus_dispatcher.get_bus_data(server_name)
185-
ram = self._srv_config.get(f"{server_name}.ram", default=1024, set_if_not_found=True)
186-
mc_version = Version.from_string(self._srv_config.get(f"{server_name}.mc_version"))
187-
def __start_mc_server():
188-
srv = Server(server_name, server_path, ram, mc_version, bus_data)
189-
srv.start()
190-
p = mp.Process(
191-
target=__start_mc_server,
192-
name=f"Server_{server_name}",
193-
daemon=True
194-
)
195-
Logger.info(f"Starting Minecraft server {server_name} of type {server_type} at {server_path} with RAM {ram}MB...")
196-
p.start() # Start the process
197-
self.__mc_servers[server_name] = p # Store the process
198-
except Exception as e:
199-
Logger.error(f"Failed to init server {server_name}: {e}")
200-
Logger.debug(traceback.format_exc())
231+
self.__start_server(server_name)
201232
else:
202233
Logger.info(f"Server {server_name} is not set to autostart. Skipping.")
203234

@@ -207,15 +238,8 @@ def __stop_mc_servers(self):
207238
This method is called when the core is stopped.
208239
"""
209240
Logger.info("Stopping Minecraft servers...")
210-
for srv_name, srv_process in self.__mc_servers.items():
211-
if srv_process.is_alive():
212-
Logger.info(f"Stopping Minecraft server {srv_name} with PID {srv_process.pid}...")
213-
srv_process.join(timeout=30)
214-
if srv_process.is_alive():
215-
Logger.warning(f"Minecraft server {srv_name} did not stop gracefully. Terminating...")
216-
srv_process.terminate()
217-
srv_process.join()
218-
Logger.info(f"Minecraft server {srv_name} stopped successfully.")
241+
for srv_name in self.__mc_servers.keys():
242+
self.__stop_server(srv_name)
219243

220244

221245

@@ -317,24 +341,7 @@ def on_server_restart(self, timestamp : datetime, server_name: str):
317341
Logger.warning(f"Server {server_name} is not running. Cannot restart.")
318342
return
319343
Logger.info(f"Restarting server {server_name}...")
320-
self.__bus.trigger( # Will wait for the server to stop
321-
Events['SERVER.STOP'],
322-
server_name=server_name
323-
)
324-
if self.__get_server_status(server_name):
325-
Logger.error(f"Server {server_name} did not stop properly. Cannot restart.")
326-
return
327-
Logger.info(f"Server {server_name} stopped successfully. Starting it again...")
328-
srv_info = self._srv_config[server_name]
329-
server_type = srv_info['type']
330-
server_path = srv_info['path']
331-
if not self.__is_server_path_valid(server_path):
332-
Logger.error(f"Server path {server_path} is not valid. Cannot restart server {server_name}.")
333-
return
334-
if server_type not in McServersModules:
335-
Logger.error(f"Server type {server_type} is not recognized. Cannot restart server {server_name}.")
336-
return
337-
self.__start_server(server_name, server_type, server_path)
344+
self.__restart_server(server_name)
338345

339346
def on_server_create(self,
340347
timestamp : datetime,
@@ -395,6 +402,7 @@ def on_server_create(self,
395402
)
396403
except Exception as e:
397404
Logger.error(f"Failed to install server {server_name} of type {server_type}: {e}")
405+
Logger.debug(traceback.format_exc())
398406
return
399407
else:
400408
self._srv_config.set(server_name, {

server/src/minecraft/Base_mc_server.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ def on_started_at(self, timestamp: datetime, server_name: str) -> datetime:
186186
:return: The timestamp when the server started.
187187
"""
188188
if server_name == self.name:
189-
Logger.info(f"Server {self.name} started at {timestamp}.")
190189
return self.__started_at
191190
Logger.debug(f"Started at event received for server {server_name}, but this is not the current server ({self.name}). Ignoring.")
192191
return None

0 commit comments

Comments
 (0)