1package nightsky23import (4 "strings"5 "time"67 "gno.land/p/jeronimoalbi/mdform/v0"8 "gno.land/p/moul/md/v0"9 "gno.land/p/nt/ufmt/v0"10)1112// RenderNightsky generates the markdown output for the telescope realm.13// Call this from your realm's Render(path string) string function.14func (r *TelescopeRealm) RenderNightsky(path string) string {15 switch path {16 case "status":17 return r.renderStatus()18 case "commands":19 return r.renderCommandQueue()20 case "captures":21 return r.renderCaptureHistory()22 case "access":23 return r.renderAccessRules()24 case "commandData":25 return r.renderCommandData()26 case "control":27 return r.renderControl()28 default:29 return r.renderMainPage()30 }31}3233func (r *TelescopeRealm) renderMainPage() string {34 out := "# " + r.Config.Name + " 🔭\n\n"3536 statusIcon := "🔴"37 switch r.Config.Status {38 case "online":39 statusIcon = "🟢"40 case "busy":41 statusIcon = "🟡"42 case "error":43 statusIcon = "🔴"44 }4546 out += statusIcon + " **Status:** " + r.Config.Status + "\n\n"47 out += "**Model:** " + r.Config.Model + "\n\n"48 out += ufmt.Sprintf("**Position:** %.2f°, %.2f°\n\n", r.Config.Latitude, r.Config.Longitude)4950 out += "## Quick Stats\n\n"51 out += ufmt.Sprintf("📋 Commands in queue: %d\n\n", len(r.CommandQueue))52 out += ufmt.Sprintf("📸 Total captures: %d\n\n", r.Captures.Len())53 out += ufmt.Sprintf("👥 Authorized users: %d\n\n", r.AccessRules.Size())5455 out += "## Quick Links\n\n"56 out += md.Link("🎮 Control Telescope", r.path(":control")) + " - Submit commands\n\n"57 out += md.Link("👥 Access Rules", r.path(":access")) + " - View and manage user access\n\n"58 out += md.Link("📋 Command Queue", r.path(":commands")) + "\n\n"59 out += md.Link("📸 Capture History", r.path(":captures")) + "\n\n"6061 if latest, ok := r.Captures.Latest(); ok {62 out += "## Latest Capture\n\n"63 out += "\n\n"64 out += ufmt.Sprintf("_RA %.8fh · Dec %.8f° · %ds_\n\n", latest.TargetRA, latest.TargetDec, latest.Exposure)65 }6667 out += "---\n\n"68 out += "## CLI Usage\n\n"69 out += "```\n"70 out += "gnokey maketx call -pkgpath \"gno.land" + r.Config.RealmPath + "\" \\\n"71 out += " -func \"SubmitCommand\" \\\n"72 out += " -args \"capture\" -args \"5.5\" -args \"22.5\" -args \"60\" \\\n"73 out += " -gas-fee 1000000ugnot -gas-wanted 2000000 \\\n"74 out += " -broadcast YOUR_ADDRESS\n"75 out += "```\n\n"7677 out += md.Link("← Back to NightSky Network", "/r/nym-vikbez000/nightsky/v1") + "\n\n"78 return out79}8081func (r *TelescopeRealm) renderControl() string {82 out := "# 🎮 Control Telescope\n\n"83 out += "**Note:** You must have access to submit commands.\n\n"84 out += "---\n\n"8586 form := mdform.87 New("exec", "SubmitCommand").88 Select("commandType", "capture", "description", "Command type").89 Select("commandType", "stop").90 Input("targetRA", "placeholder", "0.0", "description", "Right Ascension (0–24h) — leave empty for stop").91 Input("targetDec", "placeholder", "0.0", "description", "Declination (−90–90°) — leave empty for stop").92 Input("exposure", "placeholder", "60", "description", "Exposure time in seconds (1–300) — leave empty for stop")9394 out += form.String() + "\n\n"95 out += "---\n\n"96 out += md.Link("← Back", r.path("")) + "\n\n"97 return out98}99100func (r *TelescopeRealm) renderCommandQueue() string {101 out := "# Command Queue\n\n"102103 if len(r.CommandQueue) == 0 {104 out += "No pending commands.\n\n"105 } else {106 out += ufmt.Sprintf("%d command(s) pending:\n\n", len(r.CommandQueue))107 for i, cmd := range r.CommandQueue {108 out += ufmt.Sprintf("### Command #%d\n\n", i+1)109 out += "**Type:** " + cmd.CommandType + "\n\n"110 if cmd.CommandType == "capture" {111 out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", cmd.TargetRA, cmd.TargetDec)112 out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", cmd.Exposure)113 }114 out += "**Requested by:** " + cmd.RequestedBy + "\n\n"115 out += "---\n\n"116 }117 }118119 out += md.Link("← Back", r.path("")) + "\n\n"120 return out121}122123func (r *TelescopeRealm) renderCaptureHistory() string {124 out := "# Capture History\n\n"125126 if r.Captures.Len() == 0 {127 out += "No captures yet.\n\n"128 } else {129 out += ufmt.Sprintf("Showing %d most recent captures:\n\n", r.Captures.Len())130 i := 0131 r.Captures.IterateNewest(func(capture CaptureResult) bool {132 i++133 out += ufmt.Sprintf("### Capture #%d\n\n", i)134 out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", capture.TargetRA, capture.TargetDec)135 out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", capture.Exposure)136 out += "**Captured by:** " + capture.CapturedBy + "\n\n"137 out += "**Image:** " + md.Link("View", capture.ImageURL) + "\n\n"138 out += "---\n\n"139 return false140 })141 }142143 out += md.Link("← Back", r.path("")) + "\n\n"144 return out145}146147func (r *TelescopeRealm) renderAccessRules() string {148 out := "# 👥 Access Rules\n\n"149150 if r.AccessRules.Size() == 0 {151 out += "No access rules configured. Only the owner can use the telescope.\n\n"152 } else {153 out += ufmt.Sprintf("%d user(s) have access:\n\n", r.AccessRules.Size())154 r.AccessRules.Iterate("", "", func(_ string, value any) bool {155 rule := value.(AccessRule)156 out += "**User:** " + rule.AllowedAddress157 if rule.ExpiresAt.IsZero() {158 out += " (Expires: Never)\n\n"159 } else {160 out += " (Expires: " + rule.ExpiresAt.Format(time.RFC1123) + ")\n\n"161 }162 out += "---\n\n"163 return false164 })165 }166167 out += "## Grant Access\n\n"168 out += mdform.169 New("exec", "GrantAccess").170 Input("address", "placeholder", "g1...", "description", "Gno address to grant access to").171 Input("durationDays", "placeholder", "0", "description", "Duration in days (0 = never expires)").172 String() + "\n\n"173174 out += "## Revoke Access\n\n"175 out += mdform.176 New("exec", "RevokeAccess").177 Input("address", "placeholder", "g1...", "description", "Gno address to revoke").178 String() + "\n\n"179180 out += "---\n\n"181 out += md.Link("← Back", r.path("")) + "\n\n"182 return out183}184185// renderStatus returns a machine-readable status block for the telescope controller.186func (r *TelescopeRealm) renderStatus() string {187 out := ufmt.Sprintf("commands=%d\n", len(r.CommandQueue))188 out += "status=" + r.Config.Status189 return "```\n" + out + "\n```"190}191192// renderCommandData returns the first queued command in a machine-readable block.193func (r *TelescopeRealm) renderCommandData() string {194 if len(r.CommandQueue) == 0 {195 return "```\nno_commands\n```"196 }197 cmd := r.CommandQueue[0]198 data := "type=" + cmd.CommandType + "\n"199 data += ufmt.Sprintf("ra=%.8f\n", cmd.TargetRA)200 data += ufmt.Sprintf("dec=%.8f\n", cmd.TargetDec)201 data += ufmt.Sprintf("exposure=%d\n", cmd.Exposure)202 data += "requester=" + cmd.RequestedBy203 return "```\n" + data + "\n```"204}205206// path builds an absolute URL path for this realm with an optional suffix,207// using the pkgPath stored on the config at construction time. This avoids a208// runtime realm lookup entirely (no chain/runtime/unsafe needed).209func (r *TelescopeRealm) path(addon string) string {210 return strings.TrimPrefix(r.Config.RealmPath, "gno.land") + addon211}212Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.