package configui import ( "bytes" "embed" "encoding/json" "errors" "fmt" "html/template" "net/http" "os" "time" "kumoly.io/lib/klog" "kumoly.io/tools/configui/public" ) var UNIX_SHELL = "sh" var WIN_SHELL = "cmd" const version = "v0.1.3" //go:embed templates var tmplFS embed.FS var Ext2Mode map[string]string = map[string]string{ "go": "golang", "log": "sh", "txt": "text", "yml": "yaml", "conf": "ini", "md": "markdown", } type ConfigUI struct { AppName string `json:"app_name"` Prod bool `json:"production"` BaseUrl string `json:"base_url"` ConfigPath string `json:"config_path"` NoReconfig bool `json:"no_reconfig"` AllowIP string `json:"allow_ip"` CmdTimeout string `json:"timeout"` cmdTimeout time.Duration Files []*File `json:"files"` fileIndex map[string]int ResultBellow bool `json:"result_bellow"` HideConfig bool `json:"hide_config"` // Should be in main app LogPath string `json:"log_path"` LogLevel int `json:"log_level"` TmplFS embed.FS `json:"-"` tmpl *template.Template PublicFS embed.FS `json:"-"` log *klog.Logger `json:"-"` ksrv_log *klog.Logger `json:"-"` f *os.File `json:"-"` } func New() *ConfigUI { tmpl := template.Must(template.New("").Funcs(template.FuncMap{ "step": func(from, to, step uint) []uint { items := []uint{} for i := from; i <= to; i += step { items = append(items, i) } return items }, }).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl")) return &ConfigUI{ fileIndex: map[string]int{}, Prod: true, AppName: "ConfigUI", BaseUrl: "/", PublicFS: public.FS, TmplFS: tmplFS, tmpl: tmpl, CmdTimeout: "10s", cmdTimeout: time.Second * 10, LogLevel: klog.Lerror | klog.Linfo, log: klog.Sub("ConfigUI"), } } func (cui *ConfigUI) File(file_name string) (*File, error) { if file_name == cui.AppName { return &File{ Path: cui.ConfigPath, Name: cui.AppName, Lang: "json", owner: cui, }, nil } index, ok := cui.fileIndex[file_name] if !ok { return nil, errors.New("no file found") } return cui.Files[index], nil } func (cui *ConfigUI) LoadConfig(confstr string) error { tmpConf := &ConfigUI{} err := json.Unmarshal([]byte(confstr), tmpConf) if err != nil { return nil } // construct fileIndex tmpIndex := map[string]int{} for i, f := range tmpConf.Files { if f.Name == "" { f.Name = f.Path } f.owner = cui tmpIndex[f.Name] = i } // copy cui.fileIndex = tmpIndex cui.Files = tmpConf.Files cui.AllowIP = tmpConf.AllowIP cui.Prod = tmpConf.Prod cui.ConfigPath = tmpConf.ConfigPath cui.HideConfig = tmpConf.HideConfig cui.NoReconfig = tmpConf.NoReconfig cui.AppName = tmpConf.AppName if cui.AppName == "" { cui.AppName = "ConfigUI" } cui.BaseUrl = tmpConf.BaseUrl if cui.BaseUrl == "" { cui.BaseUrl = "/" } ct, err := time.ParseDuration(tmpConf.CmdTimeout) if err != nil || cui.CmdTimeout == "" { cui.CmdTimeout = "10s" cui.cmdTimeout = time.Second * 10 } else { cui.CmdTimeout = tmpConf.CmdTimeout cui.cmdTimeout = ct } cui.log = klog.Sub(cui.AppName) // NOT implemented cui.ResultBellow = tmpConf.ResultBellow cui.LogLevel = tmpConf.LogLevel cui.LogPath = tmpConf.LogPath cui.setLog() // fmt.Printf("%+v", cui) return nil } func (cui *ConfigUI) setLog() { var err error if cui.f != nil { cui.f.Close() } if cui.LogPath != "" { cui.f, err = os.OpenFile(cui.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { cui.log.Error(err) } cui.log.SetErrOutput(cui.f) cui.log.SetOutput(cui.f) cui.log.Reload() if cui.ksrv_log != nil { cui.ksrv_log.SetErrOutput(cui.f) cui.ksrv_log.SetOutput(cui.f) cui.ksrv_log.Reload() } } else { cui.log.SetErrOutput(os.Stderr) cui.log.SetOutput(os.Stderr) cui.log.Reload() if cui.ksrv_log != nil { cui.ksrv_log.SetErrOutput(os.Stderr) cui.ksrv_log.SetOutput(os.Stderr) cui.ksrv_log.Reload() } } } func (cui *ConfigUI) Config() ([]byte, error) { return json.MarshalIndent(cui, "", " ") } func (cui *ConfigUI) AppendFile(file *File) error { if file.Name == "" { file.Name = file.Path } file.owner = cui i, ok := cui.fileIndex[file.Name] if ok { return fmt.Errorf("%v already exists at %d", file.Name, i) } cui.fileIndex[file.Name] = len(cui.Files) cui.Files = append(cui.Files, file) return nil } func (cui *ConfigUI) Parse(w http.ResponseWriter, name string, data interface{}) error { buf := &bytes.Buffer{} err := cui.tmpl.ExecuteTemplate(buf, "home", data) if err != nil { panic(err) } _, err = w.Write(buf.Bytes()) return err }