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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ changes the config rolls the pod.

The MCP endpoint is off in the chart too. `mcp.enabled` and `mcp.publicURL` turn it on; the chart refuses to render it with `authBypass`. See [`docs/mcp.md`](docs/mcp.md#with-the-helm-chart).

Setting `CRYPTOS_DEV_UI_ISSUE_COPY=true` adds a `<meta name="cryptos-dev-ui-issue-copy" content="true">` tag to the served index page, which lets a development console build show its "Copy for UI issue" button. Off by default; a release console build has no such button regardless of this setting.

## 🔌 MCP endpoint

The manager can serve a [Model Context Protocol](https://modelcontextprotocol.io) endpoint at `/mcp` for AI agents. It is off by default.
Expand Down
6 changes: 5 additions & 1 deletion cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,11 @@ func main() {
connect.WithInterceptors(apperr.Interceptor()),
)

web, err := webui.Handler()
devUIIssueCopy := os.Getenv("CRYPTOS_DEV_UI_ISSUE_COPY") == "true"
if devUIIssueCopy {
log.Printf("manager: CRYPTOS_DEV_UI_ISSUE_COPY is set, so the console's dev UI-issue button is enabled")
}
web, err := webui.Handler(webui.Options{DevUIIssueCopy: devUIIssueCopy})
if err != nil {
log.Fatalf("manager: webui: %v", err)
}
Expand Down
16 changes: 15 additions & 1 deletion internal/webui/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
*/

import (
"bytes"
"embed"
"io/fs"
"net/http"
Expand All @@ -25,9 +26,19 @@ import (
//go:embed all:dist
var distFS embed.FS

// Options tunes the web bundle handler.
type Options struct {
// DevUIIssueCopy adds the meta tag that lets a development console build
// show its "Copy for UI issue" button. Release builds don't contain the
// button, so the tag does nothing there.
DevUIIssueCopy bool
}

const devUIIssueMeta = `<meta name="cryptos-dev-ui-issue-copy" content="true">`

// Handler serves the embedded web bundle. Requests that don't map to a file
// fall back to index.html so the client-side router can handle the route.
func Handler() (http.Handler, error) {
func Handler(opts Options) (http.Handler, error) {
sub, err := fs.Sub(distFS, "dist")
if err != nil {
return nil, err
Expand All @@ -37,6 +48,9 @@ func Handler() (http.Handler, error) {
if err != nil {
return nil, err
}
if opts.DevUIIssueCopy {
index = bytes.Replace(index, []byte("</head>"), []byte(devUIIssueMeta+"</head>"), 1)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
serveIndex(w, index)
Expand Down
22 changes: 20 additions & 2 deletions internal/webui/embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

func TestHandler_ServesIndex(t *testing.T) {
h, err := Handler()
h, err := Handler(Options{})
if err != nil {
t.Fatalf("Handler: %v", err)
}
Expand All @@ -36,10 +36,28 @@ func TestHandler_ServesIndex(t *testing.T) {
}

func TestHandler_SPAFallback(t *testing.T) {
h, _ := Handler()
h, _ := Handler(Options{})
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/fleet", nil)) // no such file
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), "CryptOS Fleet Manager") {
t.Fatalf("GET /fleet (SPA) = %d, want index.html", rec.Code)
}
}

func TestHandlerInjectsDevUIIssueMetaOnlyWhenEnabled(t *testing.T) {
for _, tc := range []struct {
on bool
want bool
}{{false, false}, {true, true}} {
h, err := Handler(Options{DevUIIssueCopy: tc.on})
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/fleet", nil))
got := strings.Contains(rec.Body.String(), `<meta name="cryptos-dev-ui-issue-copy" content="true">`)
if got != tc.want {
t.Fatalf("on=%v: meta present=%v, want %v", tc.on, got, tc.want)
}
}
}
Loading