mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:55:42 +00:00
Add action JSON and build info to admin server (#4927)
- Add action_json, faction_name, and game_date fields to GameHistoryEntry proto - Serialize ActionResult to JSON in EagleServiceImpl.getGameHistory() - Display faction name and date columns in history table - Show full JSON inline when clicking action rows (no separate AJAX call) - Add build timestamp and git commit to admin header on all pages - Create workspace_status.sh for Bazel build stamping - Enable --stamp in .bazelrc 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,3 +36,7 @@ common --java_language_version=17
|
||||
common --java_runtime_version=remotejdk_17
|
||||
common --tool_java_language_version=17
|
||||
common --tool_java_runtime_version=remotejdk_17
|
||||
|
||||
# Workspace status for build stamping (git commit, timestamp)
|
||||
common --workspace_status_command=tools/workspace_status.sh
|
||||
common --stamp
|
||||
|
||||
@@ -21,6 +21,10 @@ go_binary(
|
||||
embed = [":admin_server_lib"],
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
x_defs = {
|
||||
"main.buildTime": "{BUILD_TIMESTAMP}",
|
||||
"main.gitCommit": "{STABLE_GIT_COMMIT}",
|
||||
},
|
||||
)
|
||||
|
||||
# Linux x86_64 target for Docker deployment
|
||||
@@ -31,4 +35,8 @@ go_binary(
|
||||
goos = "linux",
|
||||
pure = "on",
|
||||
visibility = ["//visibility:public"],
|
||||
x_defs = {
|
||||
"main.buildTime": "{BUILD_TIMESTAMP}",
|
||||
"main.gitCommit": "{STABLE_GIT_COMMIT}",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -28,6 +28,25 @@ var templatesFS embed.FS
|
||||
//go:embed static/*
|
||||
var staticFS embed.FS
|
||||
|
||||
// Build info variables - set via ldflags
|
||||
var (
|
||||
buildTime = "unknown"
|
||||
gitCommit = "unknown"
|
||||
)
|
||||
|
||||
// BuildInfo holds version information displayed in the UI
|
||||
type BuildInfo struct {
|
||||
BuildTime string
|
||||
GitCommit string
|
||||
}
|
||||
|
||||
func getBuildInfo() BuildInfo {
|
||||
return BuildInfo{
|
||||
BuildTime: buildTime,
|
||||
GitCommit: gitCommit,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
eagleAddr = flag.String("eagle-addr", "localhost:40032", "Eagle gRPC server address")
|
||||
jfrSidecarAddr = flag.String("jfr-sidecar-addr", "localhost:8081", "JFR sidecar HTTP address")
|
||||
@@ -291,19 +310,24 @@ func fetchGames(ctx context.Context) ([]GameInfo, error) {
|
||||
|
||||
// Page data structures
|
||||
type GamesPageData struct {
|
||||
Title string
|
||||
Games []GameInfo
|
||||
Title string
|
||||
Games []GameInfo
|
||||
BuildInfo BuildInfo
|
||||
}
|
||||
|
||||
type GameDetailPageData struct {
|
||||
Title string
|
||||
Game GameInfo
|
||||
Title string
|
||||
Game GameInfo
|
||||
BuildInfo BuildInfo
|
||||
}
|
||||
|
||||
type HistoryEntry struct {
|
||||
Index int32
|
||||
ActionType string
|
||||
RoundId int32
|
||||
Index int32
|
||||
ActionType string
|
||||
RoundId int32
|
||||
FactionName string
|
||||
GameDate string
|
||||
ActionJSON string
|
||||
}
|
||||
|
||||
type HistoryRowsData struct {
|
||||
@@ -338,8 +362,9 @@ func handleGamesPage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := GamesPageData{
|
||||
Title: "Games",
|
||||
Games: games,
|
||||
Title: "Games",
|
||||
Games: games,
|
||||
BuildInfo: getBuildInfo(),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
@@ -426,8 +451,9 @@ func handleGameDetailPage(w http.ResponseWriter, r *http.Request, gameIDHex stri
|
||||
}
|
||||
|
||||
data := GameDetailPageData{
|
||||
Title: fmt.Sprintf("Game %s", gameIDHex),
|
||||
Game: *game,
|
||||
Title: fmt.Sprintf("Game %s", gameIDHex),
|
||||
Game: *game,
|
||||
BuildInfo: getBuildInfo(),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
@@ -489,9 +515,12 @@ func handleHistoryRows(w http.ResponseWriter, r *http.Request, gameIDHex string,
|
||||
entries := make([]HistoryEntry, len(resp.Entries))
|
||||
for i, e := range resp.Entries {
|
||||
entries[len(resp.Entries)-1-i] = HistoryEntry{
|
||||
Index: e.Index,
|
||||
ActionType: e.ActionType,
|
||||
RoundId: e.RoundId,
|
||||
Index: e.Index,
|
||||
ActionType: e.ActionType,
|
||||
RoundId: e.RoundId,
|
||||
FactionName: e.FactionName,
|
||||
GameDate: e.GameDate,
|
||||
ActionJSON: e.ActionJson,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,9 +710,10 @@ type SettingInfo struct {
|
||||
}
|
||||
|
||||
type SettingsPageData struct {
|
||||
Title string
|
||||
Settings []SettingInfo
|
||||
Filter string
|
||||
Title string
|
||||
Settings []SettingInfo
|
||||
Filter string
|
||||
BuildInfo BuildInfo
|
||||
}
|
||||
|
||||
type SettingsRowsData struct {
|
||||
@@ -738,9 +768,10 @@ func handleSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := SettingsPageData{
|
||||
Title: "Settings",
|
||||
Settings: settings,
|
||||
Filter: filter,
|
||||
Title: "Settings",
|
||||
Settings: settings,
|
||||
Filter: filter,
|
||||
BuildInfo: getBuildInfo(),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
@@ -548,3 +548,10 @@ dialog .modal-actions button {
|
||||
.rewind-result:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Build info in header */
|
||||
.build-info {
|
||||
font-size: 0.7rem;
|
||||
font-weight: normal;
|
||||
color: var(--pico-muted-color);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<tr class="action-detail-row">
|
||||
<td colspan="3">
|
||||
<td colspan="6">
|
||||
<div class="action-detail">
|
||||
<div class="action-detail-header">
|
||||
<strong>Action #{{.Index}}</strong> - {{.ActionType}} (Round {{.RoundId}})
|
||||
|
||||
@@ -171,10 +171,12 @@ function submitReassign() {
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 80px">#</th>
|
||||
<th style="width: 60px">#</th>
|
||||
<th>Action Type</th>
|
||||
<th style="width: 120px">Round</th>
|
||||
<th style="width: 100px">Actions</th>
|
||||
<th>Faction</th>
|
||||
<th style="width: 120px">Date</th>
|
||||
<th style="width: 80px">Round</th>
|
||||
<th style="width: 80px">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="history-body"
|
||||
@@ -182,7 +184,7 @@ function submitReassign() {
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<tr>
|
||||
<td colspan="4" class="htmx-indicator">Loading history...</td>
|
||||
<td colspan="6" class="htmx-indicator">Loading history...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
{{range .Entries}}
|
||||
<tr class="action-row"
|
||||
hx-get="/games/{{$.GameID}}/action/{{.Index}}"
|
||||
hx-trigger="click"
|
||||
hx-target="this"
|
||||
hx-swap="afterend">
|
||||
<tr class="action-row" onclick="toggleActionDetail(this)">
|
||||
<td>{{.Index}}</td>
|
||||
<td class="action-type">{{.ActionType}}</td>
|
||||
<td>{{.FactionName}}</td>
|
||||
<td>{{.GameDate}}</td>
|
||||
<td>{{.RoundId}}</td>
|
||||
<td class="rewind-cell">
|
||||
<button class="btn-rewind"
|
||||
@@ -19,16 +17,35 @@
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="action-detail-row" style="display: none;">
|
||||
<td colspan="6">
|
||||
<div class="action-detail">
|
||||
<div class="action-detail-header">
|
||||
<strong>Action #{{.Index}}</strong> - {{.ActionType}} (Round {{.RoundId}})
|
||||
<button class="close-btn" onclick="event.stopPropagation(); this.closest('tr').style.display='none'">×</button>
|
||||
</div>
|
||||
<pre class="action-json">{{.ActionJSON}}</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if .HasMore}}
|
||||
<tr hx-get="/games/{{.GameID}}/history?start={{.NextStart}}&limit=50"
|
||||
hx-trigger="revealed"
|
||||
hx-swap="outerHTML">
|
||||
<td colspan="4" class="htmx-indicator">Loading more...</td>
|
||||
<td colspan="6" class="htmx-indicator">Loading more...</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{if and (not .Entries) (not .HasMore)}}
|
||||
<tr>
|
||||
<td colspan="4" class="empty-state">No actions recorded yet.</td>
|
||||
<td colspan="6" class="empty-state">No actions recorded yet.</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<script>
|
||||
function toggleActionDetail(row) {
|
||||
const detailRow = row.nextElementSibling;
|
||||
if (detailRow && detailRow.classList.contains('action-detail-row')) {
|
||||
detailRow.style.display = detailRow.style.display === 'none' ? '' : 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li class="brand">Eagle Admin</li>
|
||||
<li class="brand">Eagle Admin <span class="build-info">{{if .BuildInfo.GitCommit}}({{.BuildInfo.GitCommit}}{{if .BuildInfo.BuildTime}}, {{.BuildInfo.BuildTime}}{{end}}){{end}}</span></li>
|
||||
<li><a href="/">Games</a></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
<li><a href="/health">Health</a></li>
|
||||
|
||||
@@ -422,6 +422,9 @@ message GameHistoryEntry {
|
||||
.google.protobuf.Int32Value acting_faction_id = 4;
|
||||
.google.protobuf.Int32Value province_id = 5;
|
||||
string summary = 6; // human-readable summary of the action
|
||||
string action_json = 7; // full action result as JSON
|
||||
string faction_name = 8; // name of the acting faction, if any
|
||||
string game_date = 9; // in-game date (e.g. "March 453")
|
||||
}
|
||||
|
||||
message GetActionDetailRequest {
|
||||
|
||||
@@ -54,6 +54,10 @@ scala_library(
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_resolution",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_update",
|
||||
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_update_receiver",
|
||||
"@maven//:com_thesamet_scalapb_scalapb_json4s_3",
|
||||
"@maven//:org_json4s_json4s_ast_3",
|
||||
"@maven//:org_json4s_json4s_core_3",
|
||||
"@maven//:org_json4s_json4s_native_3",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ import net.eagle0.eagle.library.settings.MaxSupportedPlayers
|
||||
import net.eagle0.eagle.library.EagleClientException
|
||||
import net.eagle0.eagle.service.new_game_creation.GameParametersUtils
|
||||
import net.eagle0.eagle.service.CustomBattleManager.CreateCustomBattleResponse
|
||||
import org.json4s.*
|
||||
import org.json4s.native.Serialization.writePretty
|
||||
import scalapb.json4s.JsonFormat
|
||||
|
||||
class EagleServiceImpl(
|
||||
gamesManager: GamesManager,
|
||||
@@ -652,19 +655,47 @@ class EagleServiceImpl(
|
||||
val startIndex = if request.startIndex > 0 then request.startIndex else 0
|
||||
val limit = if request.limit > 0 then request.limit else allResults.length
|
||||
|
||||
val entries = allResults.zipWithIndex
|
||||
implicit val formats: DefaultFormats.type = DefaultFormats
|
||||
val monthNames = Vector(
|
||||
"",
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
)
|
||||
val entries = allResults.zipWithIndex
|
||||
.drop(startIndex)
|
||||
.take(limit)
|
||||
.map {
|
||||
case (actionWithState, index) =>
|
||||
val ar = actionWithState.actionResult
|
||||
val ar = actionWithState.actionResult
|
||||
val gs = actionWithState.gameState
|
||||
val actionJson = writePretty(JsonFormat.toJson(ar))
|
||||
val factionName = ar.player
|
||||
.flatMap(fid => gs.factions.get(fid))
|
||||
.map(_.name)
|
||||
.getOrElse("")
|
||||
val gameDate = gs.currentDate
|
||||
.map(d => s"${monthNames(d.month)} ${d.year}")
|
||||
.getOrElse("")
|
||||
GameHistoryEntry(
|
||||
index = index,
|
||||
actionType = ar.`type`.name,
|
||||
roundId = actionWithState.gameState.currentRoundId,
|
||||
roundId = gs.currentRoundId,
|
||||
actingFactionId = ar.player,
|
||||
provinceId = ar.province,
|
||||
summary = s"${ar.`type`.name} action"
|
||||
summary = s"${ar.`type`.name} action",
|
||||
actionJson = actionJson,
|
||||
factionName = factionName,
|
||||
gameDate = gameDate
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Workspace status script for Bazel stamping
|
||||
# Outputs key-value pairs used by x_defs in BUILD files
|
||||
|
||||
# Get short git commit (7 characters)
|
||||
GIT_COMMIT=$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown")
|
||||
echo "STABLE_GIT_COMMIT ${GIT_COMMIT}"
|
||||
|
||||
# Get build timestamp in ISO format
|
||||
BUILD_TIME=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||
echo "BUILD_TIMESTAMP ${BUILD_TIME}"
|
||||
Reference in New Issue
Block a user