Browse Source

reroute on success/failure

main
Stephanie Gredell 4 months ago
parent
commit
44fce817ef
  1. 133
      router/handlers/results.go
  2. 117
      router/handlers/simulation.go
  3. 2
      router/router.go
  4. 9
      static/commands.js
  5. 28
      static/failure.html
  6. 17
      static/success.html

133
router/handlers/results.go

@ -3,20 +3,145 @@ package handlers
import ( import (
"html/template" "html/template"
"net/http" "net/http"
"strconv"
"strings"
) )
type ResultHandler struct { type ResultHandler struct {
Tmpl *template.Template Tmpl *template.Template
} }
type SuccessData struct {
LevelName string
Score int
TargetRPS int
AchievedRPS int
TargetLatency int
ActualLatency float64
Availability float64
Feedback []string
LevelID string
}
type FailureData struct {
LevelName string
Reason string
TargetRPS int
AchievedRPS int
TargetLatency int
ActualLatency float64
TargetAvail float64
ActualAvail float64
FailedReqs []string
LevelID string
}
func (r *ResultHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *ResultHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
data := struct { // Default to success page for backward compatibility
Title string data := SuccessData{
}{ LevelName: "Demo Level",
Title: "Title", Score: 85,
TargetRPS: 10000,
AchievedRPS: 10417,
TargetLatency: 200,
ActualLatency: 87,
Availability: 99.9,
Feedback: []string{"All requirements met successfully!"},
LevelID: "demo",
} }
if err := r.Tmpl.ExecuteTemplate(w, "success.html", data); err != nil { if err := r.Tmpl.ExecuteTemplate(w, "success.html", data); err != nil {
http.Error(w, "Template Error", http.StatusInternalServerError) http.Error(w, "Template Error", http.StatusInternalServerError)
} }
} }
type SuccessHandler struct {
Tmpl *template.Template
}
func (h *SuccessHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
data := SuccessData{
LevelName: req.URL.Query().Get("level"),
Score: parseInt(req.URL.Query().Get("score"), 85),
TargetRPS: parseInt(req.URL.Query().Get("targetRPS"), 10000),
AchievedRPS: parseInt(req.URL.Query().Get("achievedRPS"), 10417),
TargetLatency: parseInt(req.URL.Query().Get("targetLatency"), 200),
ActualLatency: parseFloat(req.URL.Query().Get("actualLatency"), 87),
Availability: parseFloat(req.URL.Query().Get("availability"), 99.9),
Feedback: parseStringSlice(req.URL.Query().Get("feedback")),
LevelID: req.URL.Query().Get("levelId"),
}
if data.LevelName == "" {
data.LevelName = "System Design Challenge"
}
if len(data.Feedback) == 0 {
data.Feedback = []string{"All requirements met successfully!"}
}
if err := h.Tmpl.ExecuteTemplate(w, "success.html", data); err != nil {
http.Error(w, "Template Error", http.StatusInternalServerError)
}
}
type FailureHandler struct {
Tmpl *template.Template
}
func (h *FailureHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
data := FailureData{
LevelName: req.URL.Query().Get("level"),
Reason: req.URL.Query().Get("reason"),
TargetRPS: parseInt(req.URL.Query().Get("targetRPS"), 10000),
AchievedRPS: parseInt(req.URL.Query().Get("achievedRPS"), 2847),
TargetLatency: parseInt(req.URL.Query().Get("targetLatency"), 200),
ActualLatency: parseFloat(req.URL.Query().Get("actualLatency"), 1247),
TargetAvail: parseFloat(req.URL.Query().Get("targetAvail"), 99.9),
ActualAvail: parseFloat(req.URL.Query().Get("actualAvail"), 87.3),
FailedReqs: parseStringSlice(req.URL.Query().Get("failedReqs")),
LevelID: req.URL.Query().Get("levelId"),
}
if data.LevelName == "" {
data.LevelName = "System Design Challenge"
}
if data.Reason == "" {
data.Reason = "performance"
}
if len(data.FailedReqs) == 0 {
data.FailedReqs = []string{"Latency exceeded target", "Availability below requirement"}
}
if err := h.Tmpl.ExecuteTemplate(w, "failure.html", data); err != nil {
http.Error(w, "Template Error", http.StatusInternalServerError)
}
}
// Helper functions
func parseInt(s string, defaultValue int) int {
if s == "" {
return defaultValue
}
if val, err := strconv.Atoi(s); err == nil {
return val
}
return defaultValue
}
func parseFloat(s string, defaultValue float64) float64 {
if s == "" {
return defaultValue
}
if val, err := strconv.ParseFloat(s, 64); err == nil {
return val
}
return defaultValue
}
func parseStringSlice(s string) []string {
if s == "" {
return []string{}
}
// Split by pipe character for multiple values
return strings.Split(s, "|")
}

117
router/handlers/simulation.go

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"systemdesigngame/internal/design" "systemdesigngame/internal/design"
"systemdesigngame/internal/level" "systemdesigngame/internal/level"
"systemdesigngame/internal/simulation" "systemdesigngame/internal/simulation"
@ -113,21 +114,18 @@ func (h *SimulationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
response := SimulationResponse{ // Build redirect URL based on success/failure
Success: true, var redirectURL string
Metrics: metrics, if passed {
Timeline: timeline, // Success page
Passed: passed, redirectURL = buildSuccessURL(levelName, score, metrics, feedback, requestBody.LevelID)
Score: score, } else {
Feedback: feedback, // Failure page
LevelName: levelName, redirectURL = buildFailureURL(levelName, metrics, feedback, requestBody.LevelID)
} }
w.Header().Set("Content-Type", "application/json") // Redirect to appropriate result page
if err := json.NewEncoder(w).Encode(response); err != nil { http.Redirect(w, r, redirectURL, http.StatusSeeOther)
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
return
}
} }
// calculateMetrics computes key performance metrics from simulation snapshots // calculateMetrics computes key performance metrics from simulation snapshots
@ -457,3 +455,96 @@ func min(a, b int) int {
} }
return b return b
} }
// buildSuccessURL creates a URL for the success page with simulation results
func buildSuccessURL(levelName string, score int, metrics map[string]interface{}, feedback []string, levelID string) string {
baseURL := "/success"
// Get level data if available
var targetRPS, targetLatency int
var targetAvail float64
if levelID != "" {
if lvl, err := level.GetLevelByID(levelID); err == nil {
targetRPS = lvl.TargetRPS
targetLatency = lvl.MaxP95LatencyMs
targetAvail = lvl.RequiredAvailabilityPct
}
}
// Use defaults if level not found
if targetRPS == 0 {
targetRPS = 10000
}
if targetLatency == 0 {
targetLatency = 200
}
if targetAvail == 0 {
targetAvail = 99.9
}
params := []string{
fmt.Sprintf("level=%s", levelName),
fmt.Sprintf("score=%d", score),
fmt.Sprintf("targetRPS=%d", targetRPS),
fmt.Sprintf("achievedRPS=%d", int(metrics["throughput"].(float64))),
fmt.Sprintf("targetLatency=%d", targetLatency),
fmt.Sprintf("actualLatency=%.1f", metrics["latency_avg"].(float64)),
fmt.Sprintf("availability=%.1f", metrics["availability"].(float64)),
fmt.Sprintf("levelId=%s", levelID),
}
// Add feedback as pipe-separated values
if len(feedback) > 0 {
feedbackStr := strings.Join(feedback, "|")
params = append(params, fmt.Sprintf("feedback=%s", feedbackStr))
}
return baseURL + "?" + strings.Join(params, "&")
}
// buildFailureURL creates a URL for the failure page with simulation results
func buildFailureURL(levelName string, metrics map[string]interface{}, feedback []string, levelID string) string {
baseURL := "/failure"
// Get level data if available
var targetRPS, targetLatency int
var targetAvail float64
if levelID != "" {
if lvl, err := level.GetLevelByID(levelID); err == nil {
targetRPS = lvl.TargetRPS
targetLatency = lvl.MaxP95LatencyMs
targetAvail = lvl.RequiredAvailabilityPct
}
}
// Use defaults if level not found
if targetRPS == 0 {
targetRPS = 10000
}
if targetLatency == 0 {
targetLatency = 200
}
if targetAvail == 0 {
targetAvail = 99.9
}
params := []string{
fmt.Sprintf("level=%s", levelName),
fmt.Sprintf("reason=performance"),
fmt.Sprintf("targetRPS=%d", targetRPS),
fmt.Sprintf("achievedRPS=%d", int(metrics["throughput"].(float64))),
fmt.Sprintf("targetLatency=%d", targetLatency),
fmt.Sprintf("actualLatency=%.1f", metrics["latency_avg"].(float64)),
fmt.Sprintf("targetAvail=%.1f", targetAvail),
fmt.Sprintf("actualAvail=%.1f", metrics["availability"].(float64)),
fmt.Sprintf("levelId=%s", levelID),
}
// Add failed requirements as pipe-separated values
if len(feedback) > 0 {
failedReqsStr := strings.Join(feedback, "|")
params = append(params, fmt.Sprintf("failedReqs=%s", failedReqsStr))
}
return baseURL + "?" + strings.Join(params, "&")
}

2
router/router.go

@ -16,6 +16,8 @@ func SetupRoutes(tmpl *template.Template) *http.ServeMux {
mux.Handle("/play/{levelId}", auth.RequireAuth(&handlers.PlayHandler{Tmpl: tmpl})) mux.Handle("/play/{levelId}", auth.RequireAuth(&handlers.PlayHandler{Tmpl: tmpl}))
mux.Handle("/simulate", auth.RequireAuth(&handlers.SimulationHandler{})) mux.Handle("/simulate", auth.RequireAuth(&handlers.SimulationHandler{}))
mux.Handle("/success", auth.RequireAuth(&handlers.SuccessHandler{Tmpl: tmpl}))
mux.Handle("/failure", auth.RequireAuth(&handlers.FailureHandler{Tmpl: tmpl}))
mux.HandleFunc("/login", auth.LoginHandler) mux.HandleFunc("/login", auth.LoginHandler)
mux.HandleFunc("/callback", auth.CallbackHandler) mux.HandleFunc("/callback", auth.CallbackHandler)
mux.HandleFunc("/ws", handlers.Messages) mux.HandleFunc("/ws", handlers.Messages)

9
static/commands.js

@ -285,8 +285,15 @@ export class RunSimulationCommand extends Command {
throw new Error(`HTTP ${response.status}: ${response.statusText}`); throw new Error(`HTTP ${response.status}: ${response.statusText}`);
} }
const result = await response.json(); // Check if response is a redirect (status 303)
if (response.redirected || response.status === 303) {
// Follow the redirect to the result page
window.location.href = response.url;
return;
}
// Fallback: try to parse as JSON for backward compatibility
const result = await response.json();
console.log('result', result); console.log('result', result);
if (result.passed && result.success) { if (result.passed && result.success) {
console.log('Simulation successful:', result); console.log('Simulation successful:', result);

28
static/failure.html

@ -641,29 +641,35 @@
<div class="failure-container"> <div class="failure-container">
<h1 class="failure-title glitch" data-text="SYSTEM OVERLOAD" id="failureTitle">SYSTEM OVERLOAD</h1> <h1 class="failure-title glitch" data-text="SYSTEM OVERLOAD" id="failureTitle">SYSTEM OVERLOAD</h1>
<p class="failure-subtitle" id="failureSubtitle">Your architecture couldn't handle the load</p> <p class="failure-subtitle" id="failureSubtitle">{{.LevelName}} - Your architecture couldn't handle the load</p>
<div class="failure-details"> <div class="failure-details">
<div class="failure-reason" id="failureReason"> <div class="failure-reason" id="failureReason">
Critical system failure detected. Your design exceeded operational limits. Critical system failure detected. Your design exceeded operational limits.
{{if .FailedReqs}}
<br><br>Failed requirements:
{{range .FailedReqs}}
<br>• {{.}}
{{end}}
{{end}}
</div> </div>
<div class="failure-metrics"> <div class="failure-metrics">
<div class="metric-item"> <div class="metric-item">
<div class="metric-label">Target RPS</div> <div class="metric-label">Target RPS</div>
<div class="metric-value">10,000</div> <div class="metric-value">{{.TargetRPS | printf "%d"}}</div>
</div> </div>
<div class="metric-item"> <div class="metric-item">
<div class="metric-label">Achieved RPS</div> <div class="metric-label">Achieved RPS</div>
<div class="metric-value exceeded">2,847</div> <div class="metric-value exceeded">{{.AchievedRPS | printf "%d"}}</div>
</div> </div>
<div class="metric-item"> <div class="metric-item">
<div class="metric-label">Max Latency</div> <div class="metric-label">Max Latency</div>
<div class="metric-value">200ms</div> <div class="metric-value">{{.TargetLatency}}ms</div>
</div> </div>
<div class="metric-item"> <div class="metric-item">
<div class="metric-label">Actual Latency</div> <div class="metric-label">Actual Latency</div>
<div class="metric-value exceeded">1,247ms</div> <div class="metric-value exceeded">{{.ActualLatency | printf "%.0f"}}ms</div>
</div> </div>
</div> </div>
@ -672,6 +678,9 @@
<div class="error-line">[ERROR] Load balancer timeout after 30s</div> <div class="error-line">[ERROR] Load balancer timeout after 30s</div>
<div class="error-line">[ERROR] Cache miss ratio: 89%</div> <div class="error-line">[ERROR] Cache miss ratio: 89%</div>
<div class="error-line">[FATAL] System unresponsive - shutting down</div> <div class="error-line">[FATAL] System unresponsive - shutting down</div>
{{range .FailedReqs}}
<div class="error-line">[ERROR] {{.}}</div>
{{end}}
</div> </div>
</div> </div>
@ -710,11 +719,20 @@
// Hide recovery message after transition // Hide recovery message after transition
setTimeout(() => { setTimeout(() => {
if (typeof recoveryMessage !== 'undefined') {
recoveryMessage.classList.remove('show'); recoveryMessage.classList.remove('show');
}
}, 3000); }, 3000);
}, 1000); }, 1000);
}, 15000); }, 15000);
// Add retry function
function retryLevel() {
const levelId = '{{.LevelID}}';
const retryUrl = levelId ? `/play/${levelId}?retry=true` : '/game?retry=true';
window.location.href = retryUrl;
}
// Add some random glitch effects (only during failure state) // Add some random glitch effects (only during failure state)
function addRandomGlitch() { function addRandomGlitch() {
if (!document.body.classList.contains('recovering')) { if (!document.body.classList.contains('recovering')) {

17
static/success.html

@ -591,25 +591,25 @@ input[name="tab"] {
<div style="max-width: 600px; width: 100%; background: var(--color-bg-component); border: 1px solid var(--color-border); border-radius: var(--radius-large); padding: 32px;"> <div style="max-width: 600px; width: 100%; background: var(--color-bg-component); border: 1px solid var(--color-border); border-radius: var(--radius-large); padding: 32px;">
<h2 style="color: var(--color-text-accent); font-size: 32px; text-align: center; margin-top: 0;">🏆 Mission Accomplished</h2> <h2 style="color: var(--color-text-accent); font-size: 32px; text-align: center; margin-top: 0;">🏆 Mission Accomplished</h2>
<p style="text-align: center; font-size: 16px; color: var(--color-text-muted); margin-bottom: 32px;"> <p style="text-align: center; font-size: 16px; color: var(--color-text-muted); margin-bottom: 32px;">
Your architecture scaled successfully and met all performance targets. Well done! {{.LevelName}} completed successfully! Your architecture scaled and met all performance targets. Well done!
</p> </p>
<div class="failure-metrics" style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;"> <div class="failure-metrics" style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;">
<div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;"> <div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;">
<div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Target RPS</div> <div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Target RPS</div>
<div class="metric-value" style="font-size: 20px; font-weight: bold;">10,000</div> <div class="metric-value" style="font-size: 20px; font-weight: bold;">{{.TargetRPS | printf "%d"}}</div>
</div> </div>
<div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;"> <div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;">
<div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Achieved RPS</div> <div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Achieved RPS</div>
<div class="metric-value" style="font-size: 20px; font-weight: bold; color: var(--color-text-accent);">10,417</div> <div class="metric-value" style="font-size: 20px; font-weight: bold; color: var(--color-text-accent);">{{.AchievedRPS | printf "%d"}}</div>
</div> </div>
<div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;"> <div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;">
<div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Max Latency</div> <div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Max Latency</div>
<div class="metric-value" style="font-size: 20px; font-weight: bold;">200ms</div> <div class="metric-value" style="font-size: 20px; font-weight: bold;">{{.TargetLatency}}ms</div>
</div> </div>
<div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;"> <div class="metric-item" style="background: var(--color-bg-dark); border: 1px solid var(--color-border); padding: 16px; border-radius: var(--radius-medium); text-align: center;">
<div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Actual Latency</div> <div class="metric-label" style="color: var(--color-text-muted); margin-bottom: 6px;">Actual Latency</div>
<div class="metric-value" style="font-size: 20px; font-weight: bold; color: var(--color-text-accent);">87ms</div> <div class="metric-value" style="font-size: 20px; font-weight: bold; color: var(--color-text-accent);">{{.ActualLatency | printf "%.0f"}}ms</div>
</div> </div>
</div> </div>
@ -618,6 +618,9 @@ input[name="tab"] {
<div>[INFO] Load balancer handled traffic with 0% errors</div> <div>[INFO] Load balancer handled traffic with 0% errors</div>
<div>[INFO] Cache hit ratio: 97%</div> <div>[INFO] Cache hit ratio: 97%</div>
<div>[SUCCESS] SLA met - all objectives achieved</div> <div>[SUCCESS] SLA met - all objectives achieved</div>
{{range .Feedback}}
<div>[SUCCESS] {{.}}</div>
{{end}}
</div> </div>
<div class="action-buttons" style="display: flex; justify-content: center; gap: 16px; margin-top: 32px;"> <div class="action-buttons" style="display: flex; justify-content: center; gap: 16px; margin-top: 32px;">
@ -636,8 +639,10 @@ input[name="tab"] {
const btn = document.getElementById('retry-button'); const btn = document.getElementById('retry-button');
btn.textContent = '⏳ Reloading...'; btn.textContent = '⏳ Reloading...';
btn.disabled = true; btn.disabled = true;
const levelId = '{{.LevelID}}';
const retryUrl = levelId ? `/play/${levelId}?retry=true` : '/game?retry=true';
setTimeout(() => { setTimeout(() => {
window.location.href = '/game?retry=true'; window.location.href = retryUrl;
}, 1500); }, 1500);
} }

Loading…
Cancel
Save