Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions UIMod/onboard_bundled/assets/css/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@
transition: opacity var(--transition-fast);
}

.uptime-display {
position: absolute;
right: 50px;
top: 22px;
font-family: 'Share Tech Mono', monospace;
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.75);
background-color: rgba(0, 0, 0, 0.35);
padding: 2px 8px;
border-radius: 4px;
letter-spacing: 0.5px;
white-space: nowrap;
transition: opacity 0.3s ease;
}
Comment on lines +24 to +37

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new .uptime-display is positioned at right: 50px, but the existing update icon is at right: 45px (and the status indicator at right: 25px). When the update button is visible, these elements will overlap and the uptime text will likely be obscured. Consider moving the uptime further left (or laying these items out in a container instead of absolute positioning) and/or setting explicit spacing/z-index.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will have a look next feature-friday


.status-indicator {
width: 16px;
height: 16px;
Expand Down
16 changes: 14 additions & 2 deletions UIMod/onboard_bundled/assets/js/server-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ function pollRecurringTasks() {
fetch('/api/v2/server/status')
.then(response => response.json())
.then(data => {
updateStatusIndicator(data.isRunning);
updateStatusIndicator(data.isRunning, false, data.uptime);
if (data.uuid) {
localStorage.setItem('gameserverrunID', data.uuid);
}
Expand Down Expand Up @@ -290,13 +290,15 @@ function pollRecurringTasks() {
}, 30000);
}

function updateStatusIndicator(isRunning, isError = false) {
function updateStatusIndicator(isRunning, isError = false, uptime = '') {
const indicator = document.getElementById('status-indicator');
const uptimeDisplay = document.getElementById('uptime-display');

if (isError) {
indicator.className = 'status-indicator error';
indicator.title = 'Error fetching server status';
window.gamserverstate = false;
if (uptimeDisplay) uptimeDisplay.style.display = 'none';
return;
}

Expand All @@ -309,4 +311,14 @@ function updateStatusIndicator(isRunning, isError = false) {
indicator.title = 'Server is offline';
window.gamserverstate = false;
}

// Show uptime only when server is running and uptime is not "0s"
if (uptimeDisplay) {
if (isRunning && uptime && uptime !== '0s') {
uptimeDisplay.textContent = uptime;
uptimeDisplay.style.display = 'inline-block';
} else {
uptimeDisplay.style.display = 'none';
}
Comment on lines +315 to +322

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateStatusIndicator hides the uptime whenever it equals "0s". That means the UI won’t show uptime for the first second(s) after the server starts (and also ties the UI to a backend string sentinel). If the intent is only to hide uptime when the server is offline, the isRunning check is already sufficient; consider removing the uptime !== '0s' condition (or switch to a numeric uptime value).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, thats fine

}
}
1 change: 1 addition & 0 deletions UIMod/onboard_bundled/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ <h2 id="popupTitle"></h2>
</header>
<main>

<span id="uptime-display" class="uptime-display" style="display:none;"></span>
<div id="status-indicator" class="status-indicator offline" title="Server status unknown"></div>
<button onclick="openUpdateModal()" id="update-button" class="update-icon" title="Update available">⭳</button>
<h1>Stationeers Server UI v{{.Version}}{{.SSUIIdentifier}}</h1>
Expand Down
38 changes: 37 additions & 1 deletion src/web/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"strings"
"time"

"github.com/JacksonTheMaster/StationeersServerUI/v5/src/config"
"github.com/JacksonTheMaster/StationeersServerUI/v5/src/localization"
Expand Down Expand Up @@ -49,8 +50,9 @@ func StopServer(w http.ResponseWriter, r *http.Request) {

func GetGameServerRunState(w http.ResponseWriter, r *http.Request) {
runState := config.GetIsGameServerRunning()
response := map[string]interface{}{
response := map[string]any{
"isRunning": runState,
"uptime": prettyUptime(gamemgr.GetServerUptime()),
Comment on lines +53 to +55

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns "uptime" as the string "0s" both when the server is offline (GetServerUptime() returns 0) and when the server has just started. This forces the frontend to treat "0s" as a special case and can hide valid uptimes. Consider omitting uptime (empty string/null) when isRunning is false, and only formatting uptime when runState is true (or return a numeric uptimeSeconds to avoid string sentinels).

Suggested change
response := map[string]any{
"isRunning": runState,
"uptime": prettyUptime(gamemgr.GetServerUptime()),
var uptime any
if runState {
uptime = prettyUptime(gamemgr.GetServerUptime())
} else {
uptime = nil
}
response := map[string]any{
"isRunning": runState,
"uptime": uptime,

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, fine.

"uuid": gamemgr.GameServerUUID.String(),
}
w.Header().Set("Content-Type", "application/json")
Expand All @@ -60,6 +62,40 @@ func GetGameServerRunState(w http.ResponseWriter, r *http.Request) {
}
}

// helper to format the uptime in a more human readable way, e.g. "1d2h3m4s" instead of "26h3m4s"
func prettyUptime(d time.Duration) string {
if d <= 0 {
return "0s"
}

d = d.Round(time.Second) // optional: round to nearest second
var parts []string

days := int64(d / (24 * time.Hour))
d -= time.Duration(days) * 24 * time.Hour

hours := int64(d / time.Hour)
d -= time.Duration(hours) * time.Hour

minutes := int64(d / time.Minute)
d -= time.Duration(minutes) * time.Minute

seconds := int64(d / time.Second)

if days > 0 {
parts = append(parts, fmt.Sprintf("%dd", days))
}
if hours > 0 || days > 0 {
parts = append(parts, fmt.Sprintf("%dh", hours))
}
if minutes > 0 || hours > 0 || days > 0 {
parts = append(parts, fmt.Sprintf("%dm", minutes))
}
parts = append(parts, fmt.Sprintf("%ds", seconds))

return strings.Join(parts, "")
Comment on lines +65 to +96

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prettyUptime duplicates uptime formatting logic that already exists in gamemgr (see gamemgr.FormatUptime in src/managers/gamemgr/uptime.go). Having multiple formatters risks inconsistent uptime display across CLI/Web. Prefer reusing a single formatter (and extend the existing one to support days if needed) instead of introducing a new one in the web layer.

Suggested change
// helper to format the uptime in a more human readable way, e.g. "1d2h3m4s" instead of "26h3m4s"
func prettyUptime(d time.Duration) string {
if d <= 0 {
return "0s"
}
d = d.Round(time.Second) // optional: round to nearest second
var parts []string
days := int64(d / (24 * time.Hour))
d -= time.Duration(days) * 24 * time.Hour
hours := int64(d / time.Hour)
d -= time.Duration(hours) * time.Hour
minutes := int64(d / time.Minute)
d -= time.Duration(minutes) * time.Minute
seconds := int64(d / time.Second)
if days > 0 {
parts = append(parts, fmt.Sprintf("%dd", days))
}
if hours > 0 || days > 0 {
parts = append(parts, fmt.Sprintf("%dh", hours))
}
if minutes > 0 || hours > 0 || days > 0 {
parts = append(parts, fmt.Sprintf("%dm", minutes))
}
parts = append(parts, fmt.Sprintf("%ds", seconds))
return strings.Join(parts, "")
// helper to format the uptime in a more human readable way; delegates to the shared gamemgr formatter
func prettyUptime(d time.Duration) string {
return gamemgr.FormatUptime(d)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair, but thats also fine for now.

}

// CommandHandler handles POST requests to execute commands via commandmgr.
// Expects a command in the request body. Returns 204 on success or error details.
func CommandHandler(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading