Merge branch 'fix/g11' into fix/integrated

# Conflicts:
#	internal/store/migrations.go
This commit is contained in:
kami
2026-08-01 14:20:24 +04:00
19 changed files with 1070 additions and 266 deletions
+16 -1
View File
@@ -8,6 +8,8 @@ import (
"net/http"
"sync"
"time"
"github.com/kami/maven/internal/ipc"
)
// The three sibling services Maven coordinates are headless JSON APIs (no web UI
@@ -84,9 +86,13 @@ type ecoData struct {
Nexus ecoPanel[ecoEntity]
Praxis ecoPanel[ecoItem]
Hexis ecoPanel[ecoCap]
Calls ecoPanel[ipc.EcosystemTrace]
}
func handleEcosystem(w http.ResponseWriter, r *http.Request, urls ecoURLs) {
// handleEcosystem renders the three sibling panels plus Maven's own log of the
// calls she made to them. The call log comes from core, not from the siblings:
// it is what Maven saw, including the hops that never got an answer.
func handleEcosystem(w http.ResponseWriter, r *http.Request, urls ecoURLs, core ipc.CoreAPI) {
ctx := r.Context()
var d ecoData
var wg sync.WaitGroup
@@ -102,6 +108,15 @@ func handleEcosystem(w http.ResponseWriter, r *http.Request, urls ecoURLs) {
go func() { defer wg.Done(); d.Hexis.Err = getEco(ctx, urls.hexis, "/api/v1/capabilities", &d.Hexis.Rows) }()
wg.Wait()
if core == nil {
d.Calls.Err = "not configured"
} else if rows, err := core.RecentEcosystemTraces(ctx, 50); err != nil {
log.Printf("ecosystem traces: %v", err)
d.Calls.Err = "core read failed"
} else {
d.Calls.Rows = rows
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := ecosystemTmpl.Execute(w, d); err != nil {
log.Printf("ecosystem render: %v", err)
+14 -1
View File
@@ -41,11 +41,24 @@
{{end}}
</section>
<section class=card id=eco-calls>
<div class=section-header>
<h2>Calls <span class=card-sub>what Maven asked them</span></h2>
</div>
{{with .Calls}}
{{if .Err}}<div class=empty>calls — {{.Err}}</div>
{{else if not .Rows}}<div class=empty>no ecosystem calls yet.</div>
{{else}}<div class=scroll><table class=mono><tr><th>when<th>service<th>operation<th>status<th>ms<th>http<th>correlation</tr>
{{range .Rows}}<tr><td>{{ago .Ts}}<td><span class=badge>{{.Service}}</span><td class=en>{{.Operation}}<td>{{if eq .Status "ok"}}<span class="badge badge-ok">ok</span>{{else}}<span class="badge badge-warn">{{.Status}}</span>{{end}}<td>{{.DurationMs}}<td>{{if .HTTPStatus}}{{.HTTPStatus}}{{else}}—{{end}}<td class=key>{{.CorrelationID}}</tr>{{end}}
</table></div>{{end}}
{{end}}
</section>
{{template "shellBottom"}}
<script>
setInterval(() => fetch('/ecosystem').then(r => r.text()).then(html => {
const d = new DOMParser().parseFromString(html, 'text/html');
for (const id of ['eco-nexus', 'eco-praxis', 'eco-hexis']) {
for (const id of ['eco-nexus', 'eco-praxis', 'eco-hexis', 'eco-calls']) {
const old = document.getElementById(id), nu = d.getElementById(id);
if (old && nu) old.replaceWith(nu);
}
+1 -1
View File
@@ -441,7 +441,7 @@ func main() {
})
ecoURLsCfg := ecoURLs{nexus: *nexusURL, praxis: *praxisURL, hexis: *hexisURL}
mux.HandleFunc("/ecosystem", func(w http.ResponseWriter, r *http.Request) {
handleEcosystem(w, r, ecoURLsCfg)
handleEcosystem(w, r, ecoURLsCfg, core)
})
// ----- passkey (WebAuthn) endpoints -----
// Wired when both -core and a configured origin are present. The origin