github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/dashboard/api/pkg/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "strings" 9 "time" 10 11 bacmodel "github.com/filecoin-project/bacalhau/pkg/model" 12 "github.com/rs/zerolog/log" 13 14 "github.com/filecoin-project/bacalhau/dashboard/api/pkg/model" 15 "github.com/filecoin-project/bacalhau/dashboard/api/pkg/types" 16 "github.com/filecoin-project/bacalhau/pkg/localdb" 17 "github.com/filecoin-project/bacalhau/pkg/system" 18 "github.com/gorilla/mux" 19 ) 20 21 type ServerOptions struct { 22 Host string 23 Port int 24 SwarmPort int 25 PeerConnect string 26 JWTSecret string 27 } 28 29 type DashboardAPIServer struct { 30 Options ServerOptions 31 API *model.ModelAPI 32 } 33 34 func NewServer( 35 options ServerOptions, 36 api *model.ModelAPI, 37 ) (*DashboardAPIServer, error) { 38 if options.Host == "" { 39 return nil, fmt.Errorf("host is required") 40 } 41 if options.Port == 0 { 42 return nil, fmt.Errorf("port is required") 43 } 44 if options.JWTSecret == "" { 45 return nil, fmt.Errorf("jwt secret is required") 46 } 47 return &DashboardAPIServer{ 48 Options: options, 49 API: api, 50 }, nil 51 } 52 53 func (apiServer *DashboardAPIServer) ListenAndServe(ctx context.Context, cm *system.CleanupManager) error { 54 router := mux.NewRouter() 55 subrouter := router.PathPrefix("/api/v1").Subrouter() 56 subrouter.HandleFunc("/nodes", apiServer.nodes).Methods("GET") 57 subrouter.HandleFunc("/run", apiServer.run).Methods("POST") 58 subrouter.HandleFunc("/stablediffusion", apiServer.stablediffusion).Methods("POST") 59 subrouter.HandleFunc("/jobs", apiServer.jobs).Methods("POST") 60 subrouter.HandleFunc("/jobs/count", apiServer.jobsCount).Methods("POST") 61 subrouter.HandleFunc("/job/{id}", apiServer.job).Methods("GET") 62 subrouter.HandleFunc("/job/{id}/info", apiServer.jobInfo).Methods("GET") 63 subrouter.HandleFunc("/summary/annotations", apiServer.annotations).Methods("GET") 64 subrouter.HandleFunc("/summary/jobmonths", apiServer.jobmonths).Methods("GET") 65 subrouter.HandleFunc("/summary/jobexecutors", apiServer.jobexecutors).Methods("GET") 66 subrouter.HandleFunc("/summary/totaljobs", apiServer.totaljobs).Methods("GET") 67 subrouter.HandleFunc("/summary/totaljobevents", apiServer.totaljobevents).Methods("GET") 68 subrouter.HandleFunc("/summary/totalusers", apiServer.totalusers).Methods("GET") 69 subrouter.HandleFunc("/summary/totalexecutors", apiServer.totalexecutors).Methods("GET") 70 71 subrouter.HandleFunc("/admin/login", apiServer.adminlogin).Methods("POST") 72 subrouter.HandleFunc("/admin/status", apiServer.adminstatus).Methods("GET") 73 subrouter.HandleFunc("/admin/moderate", apiServer.adminmoderate).Methods("POST") 74 75 srv := &http.Server{ 76 Addr: fmt.Sprintf("%s:%d", apiServer.Options.Host, apiServer.Options.Port), 77 WriteTimeout: time.Minute * 15, 78 ReadTimeout: time.Minute * 15, 79 ReadHeaderTimeout: time.Minute * 15, 80 IdleTimeout: time.Minute * 60, 81 Handler: router, 82 } 83 return srv.ListenAndServe() 84 } 85 86 type PromptParam struct { 87 Prompt string `json:"prompt"` 88 } 89 90 // TODO: factor commonality from following two funcs 91 func (apiServer *DashboardAPIServer) run(res http.ResponseWriter, req *http.Request) { 92 // any crazy mofo on the planet can build this into their web apps 93 res.Header().Set("Access-Control-Allow-Origin", "*") 94 95 spec := bacmodel.Spec{} 96 err := json.NewDecoder(req.Body).Decode(&spec) 97 if err != nil { 98 _, _ = res.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, strings.Trim(err.Error(), "\n")))) 99 return 100 } 101 102 cid, err := runGenericJob(spec) 103 if err != nil { 104 log.Ctx(req.Context()).Error().Err(err).Send() 105 _, _ = res.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, strings.Trim(err.Error(), "\n")))) 106 } else { 107 log.Ctx(req.Context()).Info().Str("CID", cid).Send() 108 _, _ = res.Write([]byte(fmt.Sprintf(`{"cid": "%s"}`, strings.Trim(cid, "\n")))) 109 } 110 } 111 112 func (apiServer *DashboardAPIServer) stablediffusion(res http.ResponseWriter, req *http.Request) { 113 // any crazy mofo on the planet can build this into their web apps 114 res.Header().Set("Access-Control-Allow-Origin", "*") 115 116 promptParam := PromptParam{} 117 err := json.NewDecoder(req.Body).Decode(&promptParam) 118 if err != nil { 119 _, _ = res.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, strings.Trim(err.Error(), "\n")))) 120 return 121 } 122 prompt := promptParam.Prompt 123 124 // user can pass ?testing=1 to bypass GPU and just return the prompt 125 testing := len(req.URL.Query()["testing"]) > 0 126 127 log.Ctx(req.Context()).Info().Msgf("--> testing=%t", testing) 128 129 cid, err := runStableDiffusion(prompt, testing) 130 if err != nil { 131 log.Ctx(req.Context()).Error().Err(err).Send() 132 _, _ = res.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, strings.Trim(err.Error(), "\n")))) 133 } else { 134 log.Ctx(req.Context()).Info().Str("CID", cid).Send() 135 _, _ = res.Write([]byte(fmt.Sprintf(`{"cid": "%s"}`, strings.Trim(cid, "\n")))) 136 } 137 } 138 139 func (apiServer *DashboardAPIServer) annotations(res http.ResponseWriter, req *http.Request) { 140 data, err := apiServer.API.GetAnnotationSummary(context.Background()) 141 if err != nil { 142 log.Ctx(req.Context()).Error().Msgf("error for annotations route: %s", err.Error()) 143 http.Error(res, err.Error(), http.StatusInternalServerError) 144 return 145 } 146 err = json.NewEncoder(res).Encode(data) 147 if err != nil { 148 log.Ctx(req.Context()).Error().Msgf("error for annotations route: %s", err.Error()) 149 http.Error(res, err.Error(), http.StatusInternalServerError) 150 return 151 } 152 } 153 154 func (apiServer *DashboardAPIServer) jobmonths(res http.ResponseWriter, req *http.Request) { 155 data, err := apiServer.API.GetJobMonthSummary(context.Background()) 156 if err != nil { 157 log.Ctx(req.Context()).Error().Msgf("error for job months route: %s", err.Error()) 158 http.Error(res, err.Error(), http.StatusInternalServerError) 159 return 160 } 161 err = json.NewEncoder(res).Encode(data) 162 if err != nil { 163 log.Ctx(req.Context()).Error().Msgf("error for job months route: %s", err.Error()) 164 http.Error(res, err.Error(), http.StatusInternalServerError) 165 return 166 } 167 } 168 169 func (apiServer *DashboardAPIServer) jobexecutors(res http.ResponseWriter, req *http.Request) { 170 data, err := apiServer.API.GetJobExecutorSummary(context.Background()) 171 if err != nil { 172 log.Ctx(req.Context()).Error().Msgf("error for job executors route: %s", err.Error()) 173 http.Error(res, err.Error(), http.StatusInternalServerError) 174 return 175 } 176 err = json.NewEncoder(res).Encode(data) 177 if err != nil { 178 log.Ctx(req.Context()).Error().Msgf("error for job executors route: %s", err.Error()) 179 http.Error(res, err.Error(), http.StatusInternalServerError) 180 return 181 } 182 } 183 184 func (apiServer *DashboardAPIServer) totaljobs(res http.ResponseWriter, req *http.Request) { 185 data, err := apiServer.API.GetTotalJobsCount(context.Background()) 186 if err != nil { 187 log.Ctx(req.Context()).Error().Msgf("error for job totals route: %s", err.Error()) 188 http.Error(res, err.Error(), http.StatusInternalServerError) 189 return 190 } 191 err = json.NewEncoder(res).Encode(data) 192 if err != nil { 193 log.Ctx(req.Context()).Error().Msgf("error for job totals route: %s", err.Error()) 194 http.Error(res, err.Error(), http.StatusInternalServerError) 195 return 196 } 197 } 198 199 func (apiServer *DashboardAPIServer) totaljobevents(res http.ResponseWriter, req *http.Request) { 200 data, err := apiServer.API.GetTotalEventCount(context.Background()) 201 if err != nil { 202 log.Ctx(req.Context()).Error().Msgf("error for job event totals route: %s", err.Error()) 203 http.Error(res, err.Error(), http.StatusInternalServerError) 204 return 205 } 206 err = json.NewEncoder(res).Encode(data) 207 if err != nil { 208 log.Ctx(req.Context()).Error().Msgf("error for job event totals route: %s", err.Error()) 209 http.Error(res, err.Error(), http.StatusInternalServerError) 210 return 211 } 212 } 213 214 func (apiServer *DashboardAPIServer) totalusers(res http.ResponseWriter, req *http.Request) { 215 data, err := apiServer.API.GetTotalUserCount(context.Background()) 216 if err != nil { 217 log.Ctx(req.Context()).Error().Msgf("error for job user totals route: %s", err.Error()) 218 http.Error(res, err.Error(), http.StatusInternalServerError) 219 return 220 } 221 err = json.NewEncoder(res).Encode(data) 222 if err != nil { 223 log.Ctx(req.Context()).Error().Msgf("error for job user totals route: %s", err.Error()) 224 http.Error(res, err.Error(), http.StatusInternalServerError) 225 return 226 } 227 } 228 229 func (apiServer *DashboardAPIServer) totalexecutors(res http.ResponseWriter, req *http.Request) { 230 data, err := apiServer.API.GetTotalExecutorCount(context.Background()) 231 if err != nil { 232 log.Ctx(req.Context()).Error().Msgf("error for job executors totals route: %s", err.Error()) 233 http.Error(res, err.Error(), http.StatusInternalServerError) 234 return 235 } 236 err = json.NewEncoder(res).Encode(data) 237 if err != nil { 238 log.Ctx(req.Context()).Error().Msgf("error for job executors totals route: %s", err.Error()) 239 http.Error(res, err.Error(), http.StatusInternalServerError) 240 return 241 } 242 } 243 244 func (apiServer *DashboardAPIServer) nodes(res http.ResponseWriter, req *http.Request) { 245 nodes, err := apiServer.API.GetNodes(context.Background()) 246 if err == nil { 247 err = json.NewEncoder(res).Encode(nodes) 248 } 249 if err != nil { 250 log.Ctx(req.Context()).Error().Msgf("error for nodes route: %s", err.Error()) 251 http.Error(res, err.Error(), http.StatusInternalServerError) 252 return 253 } 254 } 255 256 func (apiServer *DashboardAPIServer) jobs(res http.ResponseWriter, req *http.Request) { 257 query, err := GetRequestBody[localdb.JobQuery](res, req) 258 if err != nil { 259 log.Ctx(req.Context()).Error().Msgf("error for jobs route: %s", err.Error()) 260 http.Error(res, err.Error(), http.StatusInternalServerError) 261 return 262 } 263 264 results, err := apiServer.API.GetJobs(context.Background(), *query) 265 if err != nil { 266 log.Ctx(req.Context()).Error().Msgf("error for jobs route: %s", err.Error()) 267 http.Error(res, err.Error(), http.StatusInternalServerError) 268 return 269 } 270 271 err = json.NewEncoder(res).Encode(results) 272 if err != nil { 273 log.Ctx(req.Context()).Error().Msgf("error for jobs route: %s", err.Error()) 274 http.Error(res, err.Error(), http.StatusInternalServerError) 275 return 276 } 277 } 278 279 type jobsCountResponse struct { 280 Count int `json:"count"` 281 } 282 283 func (apiServer *DashboardAPIServer) jobsCount(res http.ResponseWriter, req *http.Request) { 284 query, err := GetRequestBody[localdb.JobQuery](res, req) 285 if err != nil { 286 log.Ctx(req.Context()).Error().Msgf("error for jobs route: %s", err.Error()) 287 http.Error(res, err.Error(), http.StatusInternalServerError) 288 return 289 } 290 291 count, err := apiServer.API.GetJobsCount(context.Background(), *query) 292 if err != nil { 293 log.Ctx(req.Context()).Error().Msgf("error for jobsCount route: %s", err.Error()) 294 http.Error(res, err.Error(), http.StatusInternalServerError) 295 return 296 } 297 298 err = json.NewEncoder(res).Encode(jobsCountResponse{ 299 Count: count, 300 }) 301 if err != nil { 302 log.Ctx(req.Context()).Error().Msgf("error for jobsCount route: %s", err.Error()) 303 http.Error(res, err.Error(), http.StatusInternalServerError) 304 return 305 } 306 } 307 308 func (apiServer *DashboardAPIServer) job(res http.ResponseWriter, req *http.Request) { 309 vars := mux.Vars(req) 310 id := vars["id"] 311 312 data, err := apiServer.API.GetJob(context.Background(), id) 313 if err != nil { 314 log.Ctx(req.Context()).Error().Msgf("error for job route: %s", err.Error()) 315 http.Error(res, err.Error(), http.StatusInternalServerError) 316 return 317 } 318 319 err = json.NewEncoder(res).Encode(data) 320 if err != nil { 321 log.Ctx(req.Context()).Error().Msgf("error for job route: %s", err.Error()) 322 http.Error(res, err.Error(), http.StatusInternalServerError) 323 return 324 } 325 } 326 327 func (apiServer *DashboardAPIServer) jobInfo(res http.ResponseWriter, req *http.Request) { 328 vars := mux.Vars(req) 329 id := vars["id"] 330 331 data, err := apiServer.API.GetJobInfo(context.Background(), id) 332 if err != nil { 333 log.Ctx(req.Context()).Error().Msgf("error for jobInfo route: %s", err.Error()) 334 http.Error(res, err.Error(), http.StatusInternalServerError) 335 return 336 } 337 338 err = json.NewEncoder(res).Encode(data) 339 if err != nil { 340 log.Ctx(req.Context()).Error().Msgf("error for jobInfo route: %s", err.Error()) 341 http.Error(res, err.Error(), http.StatusInternalServerError) 342 return 343 } 344 } 345 346 type loginResponse struct { 347 Token string `json:"token"` 348 } 349 350 func (apiServer *DashboardAPIServer) adminlogin(res http.ResponseWriter, req *http.Request) { 351 // decode the request body into a LoginRequest struct 352 var loginRequest types.LoginRequest 353 err := json.NewDecoder(req.Body).Decode(&loginRequest) 354 if err != nil { 355 log.Ctx(req.Context()).Error().Msgf("error for login route: %s", err.Error()) 356 http.Error(res, err.Error(), http.StatusInternalServerError) 357 return 358 } 359 user, err := apiServer.API.Login(context.Background(), loginRequest) 360 if err != nil { 361 log.Ctx(req.Context()).Error().Msgf("error for login route: %s", err.Error()) 362 http.Error(res, err.Error(), http.StatusInternalServerError) 363 return 364 } 365 token, err := generateJWT(apiServer.Options.JWTSecret, user.Username) 366 if err != nil { 367 log.Ctx(req.Context()).Error().Msgf("error for login route: %s", err.Error()) 368 http.Error(res, err.Error(), http.StatusInternalServerError) 369 return 370 } 371 err = json.NewEncoder(res).Encode(loginResponse{ 372 Token: token, 373 }) 374 if err != nil { 375 log.Ctx(req.Context()).Error().Msgf("error for login route: %s", err.Error()) 376 http.Error(res, err.Error(), http.StatusInternalServerError) 377 return 378 } 379 } 380 381 func (apiServer *DashboardAPIServer) adminstatus(res http.ResponseWriter, req *http.Request) { 382 user, err := getUserFromRequest(apiServer.API, req, apiServer.Options.JWTSecret) 383 if err != nil { 384 log.Ctx(req.Context()).Error().Msgf("error for adminstatus route: %s", err.Error()) 385 http.Error(res, fmt.Sprintf("error for adminstatus route: %s", err.Error()), http.StatusUnauthorized) 386 return 387 } 388 err = json.NewEncoder(res).Encode(user) 389 if err != nil { 390 log.Ctx(req.Context()).Error().Msgf("error for status route: %s", err.Error()) 391 http.Error(res, err.Error(), http.StatusInternalServerError) 392 return 393 } 394 } 395 396 func (apiServer *DashboardAPIServer) adminmoderate(res http.ResponseWriter, req *http.Request) { 397 user, err := getUserFromRequest(apiServer.API, req, apiServer.Options.JWTSecret) 398 if err != nil || user == nil { 399 log.Ctx(req.Context()).Error().Msgf("access denied: %s", err.Error()) 400 http.Error(res, fmt.Sprintf("access denied: %s", err.Error()), http.StatusUnauthorized) 401 return 402 } 403 data, err := GetRequestBody[types.JobModeration](res, req) 404 if err != nil { 405 log.Ctx(req.Context()).Error().Msgf("error for adminmoderate route: %s", err.Error()) 406 http.Error(res, err.Error(), http.StatusInternalServerError) 407 return 408 } 409 err = apiServer.API.CreateJobModeration(context.Background(), *data) 410 if err != nil { 411 log.Ctx(req.Context()).Error().Msgf("error for adminmoderate route: %s", err.Error()) 412 http.Error(res, err.Error(), http.StatusInternalServerError) 413 return 414 } 415 416 err = json.NewEncoder(res).Encode(struct { 417 Success bool `json:"success"` 418 }{ 419 Success: true, 420 }) 421 if err != nil { 422 log.Ctx(req.Context()).Error().Msgf("error for adminmoderate route: %s", err.Error()) 423 http.Error(res, err.Error(), http.StatusInternalServerError) 424 return 425 } 426 }