package main import ( "context" "flag" "fmt" "os" "text/tabwriter" "time" ) func runStatus(args []string) error { fs := flag.NewFlagSet("status", flag.ExitOnError) g := addGlobalFlags(fs) fs.Usage = func() { fmt.Fprint(os.Stderr, "Usage: tinyforge status []\n\nWith no app: server health and the logged-in user.\nWith an app: that app's containers.\n") fs.PrintDefaults() } if err := fs.Parse(args); err != nil { return err } sess, err := newSession(g) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if fs.NArg() == 0 { return serverStatus(ctx, sess) } return appStatus(ctx, sess.client, fs.Arg(0)) } func serverStatus(ctx context.Context, sess *session) error { fmt.Printf("Server: %s\n", sess.client.baseURL) var me User if err := sess.client.doJSON(ctx, "GET", "/api/auth/me", nil, &me); err != nil { fmt.Printf("User: not logged in (%v)\n", err) } else { fmt.Printf("User: %s (%s)\n", me.Username, me.Role) } if exp := friendlyExpiry(sess.cfg.ExpiresAt); exp != "" { fmt.Printf("Token: valid until %s\n", exp) } var health map[string]any if err := sess.client.doJSON(ctx, "GET", "/api/health", nil, &health); err != nil { return err } fmt.Printf("DB: %s\n", connState(health, "database")) docker := connState(health, "docker") if v := nestedString(health, "docker", "version"); v != "" { docker += " (v" + v + ")" } fmt.Printf("Docker: %s\n", docker) if _, ok := health["proxy"]; ok { fmt.Printf("Proxy: %s\n", connState(health, "proxy")) } return nil } func appStatus(ctx context.Context, c *Client, ref string) error { app, err := resolveApp(ctx, c, ref) if err != nil { return err } var containers []Container if err := c.doJSON(ctx, "GET", "/api/workloads/"+app.ID+"/containers", nil, &containers); err != nil { return err } fmt.Printf("%s (%s, %s)\n", app.Name, app.SourceKind, idShort(app.ID)) if len(containers) == 0 { fmt.Println("No containers — not deployed yet.") return nil } tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0) fmt.Fprintln(tw, "ROLE\tSTATE\tIMAGE\tPORT\tSUBDOMAIN\tCONTAINER") for _, c := range containers { role := c.Role if role == "" { role = "(default)" } port := "" if c.Port != 0 { port = fmt.Sprintf("%d", c.Port) } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", role, c.State, c.ImageRef, port, c.Subdomain, idShort(c.ID)) } return tw.Flush() } // connState reads health[section].connected and renders connected/disconnected, // appending the section's error string when present. func connState(health map[string]any, section string) string { m, ok := health[section].(map[string]any) if !ok { return "unknown" } connected, _ := m["connected"].(bool) if connected { return "connected" } if msg, ok := m["error"].(string); ok && msg != "" { return "disconnected (" + msg + ")" } return "disconnected" } func nestedString(m map[string]any, section, key string) string { sub, ok := m[section].(map[string]any) if !ok { return "" } s, _ := sub[key].(string) return s }