180 lines
5.2 KiB
Go
180 lines
5.2 KiB
Go
|
package config
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"net/http"
|
||
|
"reflect"
|
||
|
"time"
|
||
|
|
||
|
"github.com/BurntSushi/toml"
|
||
|
"github.com/gorilla/mux"
|
||
|
"github.com/invopop/jsonschema"
|
||
|
|
||
|
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
||
|
"github.com/filecoin-project/lotus/curiosrc/web/api/apihelper"
|
||
|
"github.com/filecoin-project/lotus/node/config"
|
||
|
)
|
||
|
|
||
|
type cfg struct {
|
||
|
*deps.Deps
|
||
|
}
|
||
|
|
||
|
func Routes(r *mux.Router, deps *deps.Deps) {
|
||
|
c := &cfg{deps}
|
||
|
// At menu.html:
|
||
|
r.Methods("GET").Path("/layers").HandlerFunc(c.getLayers)
|
||
|
r.Methods("GET").Path("/topo").HandlerFunc(c.topo)
|
||
|
|
||
|
// At edit.html:
|
||
|
r.Methods("GET").Path("/schema").HandlerFunc(getSch)
|
||
|
r.Methods("GET").Path("/layers/{layer}").HandlerFunc(c.getLayer)
|
||
|
r.Methods("POST").Path("/layers/{layer}").HandlerFunc(c.setLayer)
|
||
|
r.Methods("GET").Path("/default").HandlerFunc(c.def)
|
||
|
}
|
||
|
func getSch(w http.ResponseWriter, r *http.Request) {
|
||
|
ref := jsonschema.Reflector{
|
||
|
Mapper: func(i reflect.Type) *jsonschema.Schema {
|
||
|
if i == reflect.TypeOf(config.Duration(time.Second)) {
|
||
|
return &jsonschema.Schema{
|
||
|
Type: "string",
|
||
|
Format: "duration",
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
},
|
||
|
}
|
||
|
sch := ref.Reflect(config.CurioConfig{})
|
||
|
// add comments
|
||
|
for k, doc := range config.Doc {
|
||
|
item, ok := sch.Definitions[k]
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
for _, line := range doc {
|
||
|
item, ok := item.Properties.Get(line.Name)
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
if line.Comment != "" {
|
||
|
extra := make(map[string]any)
|
||
|
type options struct {
|
||
|
InfoText string `json:"infoText"`
|
||
|
}
|
||
|
opt := options{
|
||
|
InfoText: line.Comment,
|
||
|
}
|
||
|
extra["options"] = opt
|
||
|
item.Extras = extra
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var allOpt func(s *jsonschema.Schema)
|
||
|
allOpt = func(s *jsonschema.Schema) {
|
||
|
s.Required = []string{}
|
||
|
for _, v := range s.Definitions {
|
||
|
v.Required = []string{}
|
||
|
|
||
|
allOpt(v)
|
||
|
}
|
||
|
}
|
||
|
allOpt(sch)
|
||
|
|
||
|
apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(sch))
|
||
|
}
|
||
|
|
||
|
func (c *cfg) getLayers(w http.ResponseWriter, r *http.Request) {
|
||
|
var layers []string
|
||
|
apihelper.OrHTTPFail(w, c.DB.Select(context.Background(), &layers, `SELECT title FROM harmony_config ORDER BY title`))
|
||
|
apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(layers))
|
||
|
}
|
||
|
|
||
|
func (c *cfg) getLayer(w http.ResponseWriter, r *http.Request) {
|
||
|
var layer string
|
||
|
apihelper.OrHTTPFail(w, c.DB.QueryRow(context.Background(), `SELECT config FROM harmony_config WHERE title = $1`, mux.Vars(r)["layer"]).Scan(&layer))
|
||
|
|
||
|
// Read the TOML into a struct
|
||
|
configStruct := map[string]any{} // NOT CurioConfig b/c we want to preserve unsets
|
||
|
_, err := toml.Decode(layer, &configStruct)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
// Encode the struct as JSON
|
||
|
jsonData, err := json.Marshal(configStruct)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
// Write the JSON response
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
_, err = w.Write(jsonData)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
}
|
||
|
|
||
|
func (c *cfg) setLayer(w http.ResponseWriter, r *http.Request) {
|
||
|
layer := mux.Vars(r)["layer"]
|
||
|
var configStruct map[string]any
|
||
|
dec := json.NewDecoder(r.Body)
|
||
|
dec.UseNumber() // JSON lib by default treats number is float64()
|
||
|
apihelper.OrHTTPFail(w, dec.Decode(&configStruct))
|
||
|
|
||
|
//Encode the struct as TOML
|
||
|
var tomlData bytes.Buffer
|
||
|
err := toml.NewEncoder(&tomlData).Encode(configStruct)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
configStr := tomlData.String()
|
||
|
|
||
|
// Generate a full commented string if this is base layer
|
||
|
if layer == "base" {
|
||
|
// Parse the into CurioConfig TOML
|
||
|
curioCfg := config.DefaultCurioConfig()
|
||
|
_, err = deps.LoadConfigWithUpgrades(tomlData.String(), curioCfg)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
cb, err := config.ConfigUpdate(curioCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv())
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
configStr = string(cb)
|
||
|
}
|
||
|
|
||
|
//Write the TOML to the database
|
||
|
_, err = c.DB.Exec(context.Background(), `INSERT INTO harmony_config (title, config) VALUES ($1, $2) ON CONFLICT (title) DO UPDATE SET config = $2`, layer, configStr)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
}
|
||
|
|
||
|
func (c *cfg) topo(w http.ResponseWriter, r *http.Request) {
|
||
|
var topology []struct {
|
||
|
Server string `db:"server"`
|
||
|
CPU int `db:"cpu"`
|
||
|
GPU int `db:"gpu"`
|
||
|
RAM int `db:"ram"`
|
||
|
LayersCSV string `db:"layers"`
|
||
|
TasksCSV string `db:"tasks"`
|
||
|
}
|
||
|
apihelper.OrHTTPFail(w, c.DB.Select(context.Background(), &topology, `
|
||
|
SELECT
|
||
|
m.host_and_port as server,
|
||
|
cpu, gpu, ram, layers, tasks
|
||
|
FROM harmony_machines m JOIN harmony_machine_details d ON m.id=d.machine_id
|
||
|
ORDER BY server`))
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
apihelper.OrHTTPFail(w, json.NewEncoder(w).Encode(topology))
|
||
|
}
|
||
|
|
||
|
func (c *cfg) def(w http.ResponseWriter, r *http.Request) {
|
||
|
cb, err := config.ConfigUpdate(config.DefaultCurioConfig(), nil, config.Commented(false), config.DefaultKeepUncommented(), config.NoEnv())
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
// Read the TOML into a struct
|
||
|
configStruct := map[string]any{} // NOT CurioConfig b/c we want to preserve unsets
|
||
|
_, err = toml.Decode(string(cb), &configStruct)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
// Encode the struct as JSON
|
||
|
jsonData, err := json.Marshal(configStruct)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
|
||
|
// Write the JSON response
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
_, err = w.Write(jsonData)
|
||
|
apihelper.OrHTTPFail(w, err)
|
||
|
}
|