Updated responses and endpoint descriptions

This commit is contained in:
Malasaur 2025-12-14 15:00:42 +01:00
parent 44340f20b0
commit 17fc911290
No known key found for this signature in database
2 changed files with 169 additions and 20 deletions

162
main.py
View file

@ -1,5 +1,5 @@
from asyncio import create_task from asyncio import create_task
from typing import Annotated from typing import Annotated, Optional
import uvicorn import uvicorn
from classes import ProcessStatus from classes import ProcessStatus
@ -15,42 +15,77 @@ app = FastAPI()
@app.get("/start") @app.get("/start")
async def start() -> Responses.StartResponse: async def start() -> Responses.StartResponse:
"Starts the Server process." """Starts the Server's process if it is not already running.
Returns:
status: "started" or "running".
message: The Server's response.
"""
if Controllers.process.status() == ProcessStatus.RUNNING: if Controllers.process.status() == ProcessStatus.RUNNING:
return {"status": "running"} return {
"status": "running",
"message": "The Server was already running.",
}
if Controllers.maintainance.is_set(): if Controllers.maintainance.is_set():
Controllers.maintainance.unset() Controllers.maintainance.unset()
Controllers.process.start() Controllers.process.start()
return {"status": "started"} return {
"status": "started",
"message": "The Server was started.",
}
@app.get("/status") @app.get("/status")
async def status() -> Responses.StatusResponse: async def status() -> Responses.StatusResponse:
"Checks whether the Server is running and returns its information." """Checks whether the Server is running and returns its information.
Returns:
status: One of "online", "offline", "crashed", "maintainance", or "starting".
message: The Server's response.
reason: if status is "maintainance", contains the reason for the Server's maintainance state.
motd: if status is "online", contains the Server's MOTD as an HTML string.
icon: if status is "online", contains the Server's icon as a base64 string.
players: if status is "online", contains:
online: the number of online players.
max: the number of max allowed players.
list: the list of online players' usernames as strings.
"""
process_status = Controllers.process.status() process_status = Controllers.process.status()
if process_status != ProcessStatus.RUNNING: if process_status != ProcessStatus.RUNNING:
# Crashed # Crashed
if process_status == ProcessStatus.CRASHED: if process_status == ProcessStatus.CRASHED:
return {"status": "crashed"} return {
"status": "crashed",
"message": "The Server has crashed.",
}
# Maintainance # Maintainance
if Controllers.maintainance.is_set(): if Controllers.maintainance.is_set():
return {"status": "maintainance", "reason": Controllers.maintainance.get()} return {
"status": "maintainance",
"message": "The Server is offline due to maintainance.",
"reason": Controllers.maintainance.get(),
}
# Offline # Offline
return {"status": "offline"} return {
"status": "offline",
"message": "The Server is offline.",
}
server_status = Controllers.server.status() server_status = Controllers.server.status()
# Starting # Starting
if not server_status["online"]: if not server_status["online"]:
return {"status": "starting"} return {
"status": "starting",
"message": "The Server is starting.",
}
# Online # Online
return { return {
"status": "online", "status": "online",
"message": "The Server is online.",
"motd": server_status.get("motd", ""), "motd": server_status.get("motd", ""),
"icon": server_status.get("icon", None), "icon": server_status.get("icon", None),
"players": server_status.get( "players": server_status.get(
@ -65,15 +100,50 @@ async def status() -> Responses.StatusResponse:
@app.get("/stop") @app.get("/stop")
async def stop(data: Models.StopModel, authorization: Annotated[str, Header()]): async def stop(
"Stops the Server." data: Models.StopModel, authorization: Annotated[str, Header()]
) -> Responses.StopResponse:
"""Stops the Server.
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
Args:
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
if set to 0, the shutdown phase is started immediately. Defaults to 60.
reason: a brief message explaining why the Server is shutting down. Only shown if countdown is non-zero. Defaults to "".
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
Headers:
Authorization: the Authorization token.
Returns:
status: "stopping"
message: The Server's response.
"""
check_password(authorization) check_password(authorization)
create_task(stop_server("STOPPING", data.countdown, data.reason, data.timeout)) create_task(stop_server("STOPPING", data.countdown, data.reason, data.timeout))
return {
"status": "stopping",
"message": "The Server is stopping.",
}
@app.get("/restart") @app.get("/restart")
async def restart(data: Models.RestartModel, authorization: Annotated[str, Header()]): async def restart(
"Restarts the Server." data: Models.RestartModel, authorization: Annotated[str, Header()]
) -> Responses.RestartResponse:
"""Restarts the Server.
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
Then, it starts the Server again.
Args:
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
if set to 0, the shutdown phase is started immediately. Defaults to 60.
reason: a brief message explaining why the Server is restarting. Only shown if countdown is non-zero. Defaults to "".
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
Headers:
Authorization: the Authorization token.
Returns:
status: "restarting"
message: The Server's response.
"""
check_password(authorization) check_password(authorization)
create_task( create_task(
stop_server( stop_server(
@ -84,13 +154,30 @@ async def restart(data: Models.RestartModel, authorization: Annotated[str, Heade
Controllers.process.start, Controllers.process.start,
) )
) )
return {
"status": "restarting",
"message": "The Server is restarting.",
}
@app.get("/maintainance") @app.get("/maintainance")
async def maintainance( async def maintainance(
data: Models.MaintainanceModel, authorization: Annotated[str, Header()] data: Models.MaintainanceModel, authorization: Annotated[str, Header()]
): ) -> Responses.MaintainanceResponse:
"Stops the Server and sets it to maintainance status." """Stops the Server and sets it to maintainance status.
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
Args:
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
if set to 0, the shutdown phase is started immediately. Defaults to 60.
reason: a brief message explaining why the Server is entering maintanance mode. Only shown if countdown is non-zero. Defaults to "".
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
Headers:
Authorization: the Authorization token.
Returns:
status: "stopping"
message: The Server's response.
"""
check_password(authorization) check_password(authorization)
create_task( create_task(
stop_server( stop_server(
@ -101,29 +188,64 @@ async def maintainance(
Controllers.maintainance.set(data.reason), Controllers.maintainance.set(data.reason),
) )
) )
return {
"status": "stopping",
"message": "The Server is stopping for maintainance.",
}
@app.get("/command") @app.get("/command")
async def command( async def command(
data: Models.CommandModel, authorization: Annotated[str, Header()] data: Models.CommandModel, authorization: Annotated[str, Header()]
) -> str: ) -> Responses.CommandResponse:
"Runs a command on the Server and returns its output." """
Executes a command on the Server and returns its output.
Args:
command: The command to execute.
Headers:
Authorization: the Authorization token.
Returns:
status: "executed"
message: The Server's response.
output: The command's output.
"""
check_password(authorization) check_password(authorization)
return Controllers.server.command(data.command) return {
"status": "executed",
"message": "The command was executed.",
"output": Controllers.server.command(data.command),
}
@app.get("/logs")
@app.get("/logs/stream") @app.get("/logs/stream")
async def logs_stream(authorization: Annotated[str, Header()]) -> StreamingResponse: async def logs_stream(authorization: Annotated[str, Header()]) -> StreamingResponse:
"""Streams Server logs in real-time using SSE.
Headers:
Authorization: the Authorization token.
Returns: text/event-stream
"""
check_password(authorization) check_password(authorization)
return StreamingResponse(Controllers.logs.stream(), media_type="text/event-stream") return StreamingResponse(Controllers.logs.stream(), media_type="text/event-stream")
@app.get("/logs/tail") @app.get("/logs/tail")
async def logs_tail( async def logs_tail(
data: Models.LogsTailModel, authorization: Annotated[str, Header()] authorization: Annotated[str, Header()],
data: Optional[Models.LogsTailModel] = None,
) -> StreamingResponse: ) -> StreamingResponse:
"""Streams the last few lines of the Server's logs.
Args:
back: The number of lines to stream.
Headers:
Authorization: the Authorization token.
Returns: text/event-stream
"""
check_password(authorization) check_password(authorization)
return StreamingResponse(Controllers.logs.tail(data.back)) return StreamingResponse(Controllers.logs.tail(data.back if data else 10))
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -5,6 +5,7 @@ from typing_extensions import TypedDict
class StartResponse(TypedDict): class StartResponse(TypedDict):
status: Literal["running", "started"] status: Literal["running", "started"]
message: str
class StatusResponsePlayers(TypedDict): class StatusResponsePlayers(TypedDict):
@ -15,13 +16,39 @@ class StatusResponsePlayers(TypedDict):
class StatusResponse(TypedDict): class StatusResponse(TypedDict):
status: Literal["online", "offline", "crashed", "maintainance", "starting"] status: Literal["online", "offline", "crashed", "maintainance", "starting"]
message: str
reason: NotRequired[str] reason: NotRequired[str]
motd: NotRequired[str] motd: NotRequired[str]
icon: NotRequired[str | None] icon: NotRequired[str | None]
players: NotRequired[StatusResponsePlayers] players: NotRequired[StatusResponsePlayers]
class StopResponse(TypedDict):
status: Literal["stopping"]
message: str
class RestartResponse(TypedDict):
status: Literal["restarting"]
message: str
class MaintainanceResponse(TypedDict):
status: Literal["stopping"]
message: str
class CommandResponse(TypedDict):
status: Literal["executed"]
message: str
output: str
class Responses: class Responses:
StartResponse = StartResponse StartResponse = StartResponse
StatusResponsePlayers = StatusResponsePlayers StatusResponsePlayers = StatusResponsePlayers
StatusResponse = StatusResponse StatusResponse = StatusResponse
StopResponse = StopResponse
RestartResponse = RestartResponse
MaintainanceResponse = MaintainanceResponse
CommandResponse = CommandResponse