lotus/curiosrc/web/api/config/config.go
Andrew Jackson (Ajax) fd7f1a95e2
feat: curio: web based config edit (#11822)
* cfg edit 1

* jsonschema deps

* feat: lp mig - first few steps

* lp mig: default tasks

* code comments

* docs

* lp-mig-progress

* shared

* comments and todos

* fix: curio: rename lotus-provider to curio (#11645)

* rename provider to curio

* install gotext

* fix lint errors, mod tidy

* fix typo

* fix API_INFO and add gotext to circleCI

* add back gotext

* add gotext after remerge

* lp: channels doc

* finish easy-migration TODOs

* out generate

* merging and more renames

* avoid make-all

* minor doc stuff

* cu: make gen

* make gen fix

* make gen

* tryfix

* go mod tidy

* minor ez migration fixes

* ez setup - ui cleanups

* better error message

* guided setup colors

* better path to saveconfigtolayer

* loadconfigwithupgrades fix

* readMiner oops

* guided - homedir

* err if miner is running

* prompt error should exit

* process already running, miner_id sectors in migration

* dont prompt for language a second time

* check miner stopped

* unlock repo

* render and sql oops

* curio easyMig - some fixes

* easyMigration runs successfully

* lint

* part 2 of last

* message

* merge addtl

* fixing guided setup for myself

* warn-on-no-post

* EditorLoads

* cleanups and styles

* create info

* fix tests

* make gen

* change layout, add help button

* Duration custom json

* mjs naming

---------

Co-authored-by: LexLuthr <88259624+LexLuthr@users.noreply.github.com>
Co-authored-by: LexLuthr <lexluthr@protocol.ai>
Co-authored-by: LexLuthr <lexluthr@curiostorage.org>
2024-04-16 09:30:27 -05:00

181 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{})
//sch := jsonschema.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)
}