func addtrackHandler(w http.ResponseWriter, r *http.Request) { ctx := req2ctx(r) db := fdb.NewDB(r) icaoId := r.FormValue("icaoid") callsign := strings.TrimSpace(r.FormValue("callsign")) tStr := r.FormValue("track") // Validate it works before persisting t := ftype.Track{} if err := t.Base64Decode(tStr); err != nil { log.Errorf(ctx, " /mdb/addtrack: decode failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) } ftf := ftype.FrozenTrackFragment{ TrackBase64: tStr, Callsign: callsign, Icao24: icaoId, } if err := db.AddTrackFrgament(ftf); err != nil { log.Errorf(ctx, " /mdb/addtrack: db.AddTrackFragment failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) } // 3. Routine to merge track fragments ? Extra credit ? // c.Infof(" /mdb/addtrack: added %d points for [%s][%s]", len(t), icaoId, callsign) w.Write([]byte(fmt.Sprintf("Added %d for %s\n", len(t), icaoId))) }
func jobRetagHandler(r *http.Request, f *oldfdb.Flight) (string, error) { ctx := req2ctx(r) str := "" oldtags := f.TagList() f.Tags = map[string]bool{} f.AnalyseFlightPath() f.Analyse() if taglistsEqual(oldtags, f.TagList()) { return fmt.Sprintf("* no change to tags: %v", f.TagList()), nil } db := oldfgae.NewDB(r) if err := db.UpdateFlight(*f); err != nil { log.Errorf(ctx, "Persist Flight %s: %v", f, err) return str, err } log.Infof(ctx, "Updated flight %s", f) str += fmt.Sprintf("--\nFlight was updated\n") return str, nil }
func jobV2adsbHandler(r *http.Request, f *oldfdb.Flight) (string, error) { ctx := req2ctx(r) str := "" // Allow overwrite, for the (36,0) disastery // if f.HasTrack("ADSB") { return "", nil } // Already has one err, deb := f.GetV2ADSBTrack(urlfetch.Client(ctx)) str += fmt.Sprintf("*getv2ADSB [%v]:-\n", err, deb) if err != nil { return str, err } if !f.HasTrack("ADSB") { return "", nil } // Didn't find one f.Analyse() // Retrigger Class-B stuff db := oldfgae.NewDB(r) if err := db.UpdateFlight(*f); err != nil { log.Errorf(ctx, "Persist Flight %s: %v", f, err) return str, err } log.Infof(ctx, "Updated flight %s", f) str += fmt.Sprintf("--\nFlight was updated\n") return str, nil }
func jobTrackTimezoneHandler(r *http.Request, f *oldfdb.Flight) (string, error) { ctx := req2ctx(r) defaultTP := f.Track.ClosestTrackpoint(sfo.KFixes["EPICK"]) adsbTP := f.Tracks["ADSB"].ClosestTrackpoint(sfo.KFixes["EPICK"]) trackTimeDelta := defaultTP.TimestampUTC.Sub(adsbTP.TimestampUTC) str := fmt.Sprintf("OK, looked up %s\n Default: %s\n ADSB : %s\n delta: %s\n", f, defaultTP, adsbTP, trackTimeDelta) if trackTimeDelta < -4*time.Hour || trackTimeDelta > 4*time.Hour { str += fmt.Sprintf("* recoding\n* before: %s\n", f.Tracks["ADSB"]) for i, _ := range f.Tracks["ADSB"] { f.Tracks["ADSB"][i].TimestampUTC = f.Tracks["ADSB"][i].TimestampUTC.Add(time.Hour * -8) } str += fmt.Sprintf("* after : %s\n", f.Tracks["ADSB"]) db := oldfgae.NewDB(r) if err := db.UpdateFlight(*f); err != nil { log.Errorf(ctx, "Persist Flight %s: %v", f, err) return str, err } log.Infof(ctx, "Updated flight %s", f) str += fmt.Sprintf("--\nFlight was updated\n") } else { log.Debugf(ctx, "Skipped flight %s, delta=%s", f, trackTimeDelta) str += "--\nFlight was OK, left untouched\n" } return str, nil }
// Dequeue a single day, and enqueue a job for each flight on that day func batchFlightDayHandler(w http.ResponseWriter, r *http.Request) { ctx := req2ctx(r) tags := []string{} //"ADSB"} // Maybe make this configurable ... n := 0 str := "" job := r.FormValue("job") if job == "" { http.Error(w, "Missing argument: &job=foo", http.StatusInternalServerError) } day := date.ArbitraryDatestring2MidnightPdt(r.FormValue("day"), "2006/01/02") fdb := oldfgae.NewDB(r) dStart, dEnd := date.WindowForTime(day) dEnd = dEnd.Add(-1 * time.Second) keys, err := fdb.KeysInTimeRangeByTags(tags, dStart, dEnd) if err != nil { log.Errorf(ctx, "upgradeHandler: enqueue: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } singleFlightUrl := "/backend/fdb-batch/flight" for _, key := range keys { str += fmt.Sprintf("Enqueing day=%s: %s?job=%s&key=%s\n", day.Format("2006.01.02"), singleFlightUrl, job, key.Encode()) if r.FormValue("dryrun") == "" { t := taskqueue.NewPOSTTask(singleFlightUrl, map[string][]string{ // "date": {day.Format("2006.01.02")}, "key": {key.Encode()}, "job": {job}, }) if _, err := taskqueue.Add(ctx, t, "batch"); err != nil { log.Errorf(ctx, "upgradeHandler: enqueue: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } } n++ } log.Infof(ctx, "enqueued %d batch items for '%s'", n, job) w.Header().Set("Content-Type", "text/plain") w.Write([]byte(fmt.Sprintf("OK, batch, enqueued %d tasks for %s\n%s", n, job, str))) }
// A super widget, for all the batch jobs func formValueFlightByKey(r *http.Request) (*oldfdb.Flight, error) { fdb := oldfgae.NewDB(r) key, err := olddatastore.DecodeKey(r.FormValue("key")) if err != nil { return nil, fmt.Errorf("fdb-batch: %v", err) } f, err := fdb.KeyToFlight(key) if err != nil { return nil, fmt.Errorf("fdb-batch: %v", err) } return f, nil }
func debugHandler(w http.ResponseWriter, r *http.Request) { db := fdb.NewDB(r) id := r.FormValue("id") blob, f, err := db.GetBlobById(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else if blob == nil { http.Error(w, fmt.Sprintf("id=%s not found", id), http.StatusInternalServerError) return } blob.Flight = []byte{} // Zero this out s, e := f.Track.TimesInBox(sfo.KBoxSnarfingCatchment) log := blob.GestationLog blob.GestationLog = "" _ = log str := fmt.Sprintf("OK\n* Flight found: %s\n* Tracks: %q\n", f, f.TrackList()) str += fmt.Sprintf("* Points in default track: %d\n", len(f.Track)) str += fmt.Sprintf("* Default's start/end: %s, %s\n", s, e) // str += fmt.Sprintf("\n** Gestation log:-\n%s\n", log) // str += fmt.Sprintf("** Blob:-\n* Id=%s, Icao24=%s\n* Enter/Leave: %s -> %s\n* Tags: %v\n", // blob.Id, blob.Icao24, blob.EnterUTC, blob.LeaveUTC, blob.Tags) str += "** v2 snarf URL: " + f.GetV2JsonUrl() + "\n" for tName, t := range f.Tracks { str += fmt.Sprintf("\n---- Track %s ----\n", tName) for i, tp := range t { str += fmt.Sprintf(" - %03d %s\n", i, tp) } } if true { f2, _ := f.V2() str += fmt.Sprintf("** Ids\n* %s\n* %s\n* %#v\n", f2.FullString(), f2.IdentString(), f2.Identity) str += fmt.Sprintf("\n *** /backend/fdb-batch/flight?key=%s&job=retag\n", f.DatastoreKey()) } w.Header().Set("Content-Type", "text/plain") w.Write([]byte(str)) }
// We examine the tags CGI arg, which should be a pipe-delimited set of flight tags. func flightListHandler(w http.ResponseWriter, r *http.Request) { ctx := req2ctx(r) db := fdb.NewDB(r) tags := []string{} if r.FormValue("tags") != "" { tags = append(tags, strings.Split(r.FormValue("tags"), "|")...) } timeRange := regexp.MustCompile("/fdb/(.+)$").ReplaceAllString(r.URL.Path, "$1") var flights []ftype.Flight var err error switch timeRange { case "recent": flights, err = db.LookupRecentByTags(tags, 400) case "today": s, e := date.WindowForToday() flights, err = db.LookupTimeRangeByTags(tags, s, e) case "yesterday": s, e := date.WindowForYesterday() flights, err = db.LookupTimeRangeByTags(tags, s, e) } if err != nil { log.Errorf(ctx, " %s: %v", r.URL.Path, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } var params = map[string]interface{}{ "Tags": tags, "TimeRange": timeRange, "Flights": flights2params(flights), } if err := templates.ExecuteTemplate(w, "fdb-recentlist", params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
func decodetrackHandler(w http.ResponseWriter, r *http.Request) { db := fdb.NewDB(r) icao := r.FormValue("icaoid") callsign := strings.TrimSpace(r.FormValue("callsign")) if icao == "" || callsign == "" { http.Error(w, "need args {icaoid,callsign}", http.StatusInternalServerError) return } if tracks, err := db.ReadTrackFragments(icao, callsign); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else { var params = map[string]interface{}{ "Tracks": tracks, "Callsign": callsign, "Icao24": icao, } if err := templates.ExecuteTemplate(w, "fdb-decodetrack", params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } }
func queryHandler(w http.ResponseWriter, r *http.Request) { if r.FormValue("date") == "" && r.FormValue("epoch") == "" { var params = map[string]interface{}{ "TwoHoursAgo": date.NowInPdt().Add(-2 * time.Hour), } if err := templates.ExecuteTemplate(w, "fdb-queryform", params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } return } db := fdb.NewDB(r) db.Memcache = true var t time.Time if r.FormValue("epoch") != "" { if epoch, err := strconv.ParseInt(r.FormValue("epoch"), 10, 64); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } else { t = time.Unix(epoch, 0) } } else { var err2 error t, err2 = date.ParseInPdt("2006/01/02 15:04:05", r.FormValue("date")+" "+r.FormValue("time")) if err2 != nil { http.Error(w, err2.Error(), http.StatusInternalServerError) return } } var refPoint *geo.Latlong = nil if r.FormValue("lat") != "" { refPoint = &geo.Latlong{} var err error if refPoint.Lat, err = strconv.ParseFloat(r.FormValue("lat"), 64); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if refPoint.Long, err = strconv.ParseFloat(r.FormValue("long"), 64); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } if snapshots, err := db.LookupSnapshotsAtTimestampUTC(t.UTC(), refPoint, 1000); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { var params = map[string]interface{}{ "Legend": buildLegend(t), "SearchTimeUTC": t.UTC(), "SearchTime": date.InPdt(t), "Flights": snapshots2params(snapshots), "FlightsJS": ftype.FlightSnapshotSet(snapshots).ToJSVar(), "MapsAPIKey": kGoogleMapsAPIKey, "Center": sfo.KLatlongSERFR1, "Zoom": 9, // "CaptureArea": fdb.KBoxSnarfingCatchment, // comment out, as we don't want it in this view } if r.FormValue("resultformat") == "json" { for i, _ := range snapshots { snapshots[i].F.Track = nil snapshots[i].F.Tracks = nil } js, err := json.Marshal(snapshots) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(js) } else { templateName := "fdb-queryresults-map" if r.FormValue("resultformat") == "list" { templateName = "fdb-queryresults-list" } if err := templates.ExecuteTemplate(w, templateName, params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } } }
func lookupHandler(w http.ResponseWriter, r *http.Request) { db := fdb.NewDB(r) id := r.FormValue("id") if f, err2 := db.LookupById(id); err2 != nil { http.Error(w, err2.Error(), http.StatusInternalServerError) } else if f == nil { http.Error(w, fmt.Sprintf("id=%s not found", id), http.StatusInternalServerError) } else { _, classBTrack := f.SFOClassB("", nil) f.Analyse() // Repopulate the flight tags; useful when debugging new analysis stuff f.AnalyseFlightPath() // Repopulate the flight tags; useful when debugging new analysis stuff // Todo: collapse all these separate tracks down into the single point/line list thing fr24TrackJSVar := classBTrack.ToJSVar() // For Flightaware tracks //faClassBTrack := fdb.ClassBTrack{} faTrackJSVar := template.JS("{}") if _, exists := f.Tracks["FA"]; exists == true { _, faClassBTrack := f.SFOClassB("FA", nil) faTrackJSVar = faClassBTrack.ToJSVar() //fr24TrackJSVar = template.JS("{}") } // For ADS-B tracks ! adsbTrackJSVar := template.JS("{}") if _, exists := f.Tracks["ADSB"]; exists == true { _, adsbClassBTrack := f.SFOClassB("ADSB", nil) adsbTrackJSVar = adsbClassBTrack.ToJSVar() } skimTrackJSVar := template.JS("{}") if r.FormValue("skim") != "" { alttol, _ := strconv.ParseFloat(r.FormValue("alttol"), 64) mindist, _ := strconv.ParseFloat(r.FormValue("mindist"), 64) skimTrack, _ := f.BestTrack().SkimsToSFO(alttol, mindist, 15.0, 40.0) skimTrackJSVar = skimTrack.ToJSVar() fr24TrackJSVar = template.JS("{}") faTrackJSVar = template.JS("{}") adsbTrackJSVar = template.JS("{}") } mapPoints := []MapPoint{} mapLines := []MapLine{} // &waypoint=EPICK if waypoint := r.FormValue("waypoint"); waypoint != "" { //pos := geo.Latlong{37.060312, -121.990814} pos := sfo.KFixes[waypoint] if itp, err := f.BestTrack().PointOfClosestApproach(pos); err != nil { //log.Infof(" ** Error: %v", err) } else { mapPoints = append(mapPoints, MapPoint{Icon: "red", ITP: &itp}) mapPoints = append(mapPoints, MapPoint{Pos: &itp.Ref, Text: "** Reference point"}) mapLines = append(mapLines, MapLine{Line: &itp.Line, Color: "#ff8822"}) mapLines = append(mapLines, MapLine{Line: &itp.Perp, Color: "#ff2288"}) } } // &boxes=1 if r.FormValue("boxes") != "" { if true { // fr24 for _, box := range f.Track.AsContiguousBoxes() { mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#118811")...) } } if t, exists := f.Tracks["FA"]; exists == true { for _, box := range t.AsContiguousBoxes() { mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#1111aa")...) } } if t, exists := f.Tracks["ADSB"]; exists == true { for _, box := range t.AsContiguousBoxes() { mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#aaaa11")...) } } } pointsStr := "{\n" for i, mp := range mapPoints { pointsStr += fmt.Sprintf(" %d: {%s},\n", i, mp.ToJSStr("")) } pointsJS := template.JS(pointsStr + " }\n") linesStr := "{\n" for i, ml := range mapLines { linesStr += fmt.Sprintf(" %d: {%s},\n", i, ml.ToJSStr("")) } linesJS := template.JS(linesStr + " }\n") box := sfo.KBoxSnarfingCatchment if r.FormValue("report") == "level" { box = sfo.KBoxPaloAlto20K } else if r.FormValue("report") == "stack" { box = sfo.KBoxSFO10K } var params = map[string]interface{}{ "F": f, "Legend": f.Legend(), "Oneline": f.String(), "Text": fmt.Sprintf("%#v", f.Id), "ClassB": "", "MapsAPIKey": kGoogleMapsAPIKey, "Center": sfo.KLatlongSERFR1, "Zoom": 10, "CaptureArea": box, "MapsTrack": fr24TrackJSVar, "FlightawareTrack": faTrackJSVar, "ADSBTrack": adsbTrackJSVar, "SkimTrack": skimTrackJSVar, "Points": pointsJS, "Lines": linesJS, } templateName := "fdb-lookup" if r.FormValue("map") != "" { templateName = "fdb-lookup-map" } if err7 := templates.ExecuteTemplate(w, templateName, params); err7 != nil { http.Error(w, err7.Error(), http.StatusInternalServerError) } } }
func addflightHandler(w http.ResponseWriter, r *http.Request) { ctx := req2ctx(r) client := req2client(r) logstr := fmt.Sprintf("* addFlightHandler invoked: %s\n", time.Now().UTC()) fsStr := r.FormValue("flightsnapshot") fs := ftype.FlightSnapshot{} if err := fs.Base64Decode(fsStr); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } fr24Id := fs.F.Id.ForeignKeys["fr24"] // This is the only field we take from the logstr += fmt.Sprintf("* fr24 key: %s\n", fr24Id) db := fdb.NewDB(r) fr24db, err := fdb24.NewFlightDBFr24(client) if err != nil { log.Errorf(ctx, " /mdb/addflight: newdb: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } // Be idempotent - check to see if this flight has already been recorded /* Can't do this check now; the fs.F.Id we cached might be different from the * f.Id we get back from LookupPlayback(), because fr24 reuse their keys. * if exists,err := db.FlightExists(fs.F.Id.UniqueIdentifier()); err != nil { c.Errorf(" /mdb/addflight: FlightExists check failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } else if exists { c.Infof(" /mdb/addflight: already exists %s", fs) w.Write([]byte(fmt.Sprintf("Skipped %s\n", fs))) return } logstr += fmt.Sprintf("* FlightExists('%s') -> false\n", fs.F.Id.UniqueIdentifier()) */ // Now grab an initial flight (with track), from fr24. var f *ftype.Flight if f, err = fr24db.LookupPlayback(fr24Id); err != nil { // c.Errorf(" /mdb/addflight: lookup: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } logstr += fmt.Sprintf("* the fr24/default track has %d points\n", len(f.Track)) // Kludge: fr24 keys get reused, so the flight fr24 thinks it refers to might be // different than when we cached it. So we do the uniqueness check here, to avoid // dupes in the DB. Need a better solution to this. if exists, err := db.FlightExists(f.Id.UniqueIdentifier()); err != nil { log.Errorf(ctx, " /mdb/addflight: FlightExists check failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } else if exists { log.Infof(ctx, " /mdb/addflight: already exists %s", *f) w.Write([]byte(fmt.Sprintf("Skipped %s\n", *f))) return } logstr += fmt.Sprintf("* FlightExists('%s') -> false\n", f.Id.UniqueIdentifier()) // Fetch ADSB (and MLAT!) tracks from Skypi, via the new DB err, deb := f.GetV2ADSBTrack(client) logstr += "* fetchV2ADSB\n" + deb if err != nil { log.Errorf(ctx, "ADSB fetch err: %v", err) } // If we have any locally received ADSB fragments for this flight, add them in //if err := db.MaybeAddTrackFragmentsToFlight(f); err != nil { // c.Errorf(" /mdb/addflight: addTrackFrags(%s): %v", f.Id, err) //} f.AnalyseFlightPath() // Takes a coarse look at the flight path logstr += fmt.Sprintf("* Initial tags: %v\n", f.TagList()) // For flights on the SERFR1 or BRIXX1 approaches, fetch a flightaware track if f.HasTag(ftype.KTagSERFR1) || f.HasTag(ftype.KTagBRIXX) { u, p := kFlightawareAPIUsername, kFlightawareAPIKey if err := fdbfa.AddFlightAwareTrack(client, f, u, p); err != nil { log.Errorf(ctx, " /mdb/addflight: addflightaware: %v", err) } } f.Analyse() if err := db.PersistFlight(*f, logstr); err != nil { log.Errorf(ctx, " /mdb/addflight: persist: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } // Success ! w.Write([]byte(fmt.Sprintf("Added %s\n", f))) }
func report3Handler(w http.ResponseWriter, r *http.Request) { if r.FormValue("rep") == "" { var params = map[string]interface{}{ "Yesterday": date.NowInPdt().AddDate(0, 0, -1), "Reports": report.ListReports(), "FormUrl": "/report/", "Waypoints": sfo.ListWaypoints(), "Title": "Reports (DB v1)", } if err := templates.ExecuteTemplate(w, "report3-form", params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } return } rep, err := report.SetupReport(r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if r.FormValue("debug") != "" { str := fmt.Sprintf("Report Options\n\n%s\n", rep.Options) w.Header().Set("Content-Type", "text/plain") w.Write([]byte(fmt.Sprintf("OK\n\n%s\n", str))) return } //airframes := ref.NewAirframeCache(c) metars, err := metar.LookupArchive(req2ctx(r), "KSFO", rep.Start.AddDate(0, 0, -1), rep.End.AddDate(0, 0, 1)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } v1idspecs := []string{} v2idspecs := []string{} v1RejectByRestrict := []string{} v1RejectByReport := []string{} reportFunc := func(oldF *oldfdb.Flight) { newF, err := oldF.V2() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } newF.ComputeIndicatedAltitudes(metars) outcome, err := rep.Process(newF) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } switch outcome { case report.RejectedByGeoRestriction: v1RejectByRestrict = append(v1RejectByRestrict, oldF.UniqueIdentifier()) case report.RejectedByReport: v1RejectByReport = append(v1RejectByReport, oldF.UniqueIdentifier()) case report.Accepted: v1idspecs = append(v1idspecs, oldF.UniqueIdentifier()) v2idspecs = append(v2idspecs, newF.IdSpec().String()) } } tags := rep.Options.Tags for _, wp := range rep.Waypoints { tags = append(tags, fmt.Sprintf("%s%s", oldfdb.KWaypointTagPrefix, wp)) } db := oldfgae.NewDB(r) s, e := rep.Start, rep.End if err := db.LongIterWith(db.QueryTimeRangeByTags(tags, s, e), reportFunc); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } rep.FinishSummary() if rep.ResultsFormat == "csv" { rep.OutputAsCSV(w) return } postButtons := "" url := fmt.Sprintf("/fdb/trackset2?%s", rep.ToCGIArgs()) postButtons += maybeButtonPOST(v1RejectByRestrict, "Restriction Rejects as VectorMap", url) postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects as VectorMap", url) url = fmt.Sprintf("/fdb/descent2?%s", rep.ToCGIArgs()) postButtons += maybeButtonPOST(v1RejectByRestrict, "Restriction Rejects as DescentGraph", url) postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects DescentGraph", url) if rep.Name == "sfoclassb" { url = fmt.Sprintf("/fdb/approach2?%s", rep.ToCGIArgs()) postButtons += maybeButtonPOST(v1idspecs, "Matches as ClassB", url) postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects as ClassB", url) } // The only way to get embedded CGI args without them getting escaped is to submit a whole tag vizFormURL := "http://stop.jetnoise.net/fdb/visualize2?" + rep.ToCGIArgs() vizFormTag := "<form action=\"" + vizFormURL + "\" method=\"post\" target=\"_blank\">" var params = map[string]interface{}{ "R": rep, "Metadata": rep.MetadataTable(), "PostButtons": template.HTML(postButtons), "OptStr": template.HTML(fmt.Sprintf("<pre>%s</pre>\n", rep.Options)), "IdSpecs": template.HTML(strings.Join(v1idspecs, ",")), "VisualizationFormTag": template.HTML(vizFormTag), } if err := templates.ExecuteTemplate(w, "report3-results", params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }