236 lines
5.0 KiB
Go
236 lines
5.0 KiB
Go
package configui
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"kumoly.io/lib/klog"
|
|
"kumoly.io/lib/ksrv/engine"
|
|
"kumoly.io/tools/configui/public"
|
|
)
|
|
|
|
var UNIX_SHELL = "sh"
|
|
var WIN_SHELL = "cmd"
|
|
|
|
const version = "v0.1.9"
|
|
|
|
//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 Action struct {
|
|
Name string `json:"name"`
|
|
Cmd string `json:"cmd"`
|
|
}
|
|
|
|
type Integration struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Cmd func() (string, error) `json:"-"`
|
|
}
|
|
|
|
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
|
|
Actions []Action `json:"actions"`
|
|
Integrations []*Integration `json:"integrations"`
|
|
|
|
ResultBellow bool `json:"result_bellow"`
|
|
HideConfig bool `json:"hide_config"`
|
|
|
|
// Should be in main app
|
|
LogPath string `json:"log_path"`
|
|
LogLevel klog.Llevel `json:"log_level"`
|
|
|
|
TmplFS embed.FS `json:"-"`
|
|
tmpl *engine.Engine
|
|
PublicFS embed.FS `json:"-"`
|
|
log *klog.Logger
|
|
ksrv_log *klog.Logger
|
|
f *os.File
|
|
configLock sync.Mutex
|
|
}
|
|
|
|
func New() *ConfigUI {
|
|
tmpl := engine.Must(engine.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
|
|
},
|
|
"normal": func(name string) string {
|
|
return strings.ReplaceAll(name, " ", "-")
|
|
},
|
|
}).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
|
|
return &ConfigUI{
|
|
fileIndex: map[string]int{},
|
|
Prod: true,
|
|
AppName: "ConfigUI",
|
|
BaseUrl: "/",
|
|
Actions: []Action{{Name: "User", Cmd: "whoami"}},
|
|
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.configLock.Lock()
|
|
defer cui.configLock.Unlock()
|
|
cui.fileIndex = tmpIndex
|
|
cui.Files = tmpConf.Files
|
|
cui.AllowIP = tmpConf.AllowIP
|
|
cui.Prod = tmpConf.Prod
|
|
klog.PROD = cui.Prod
|
|
cui.ConfigPath = tmpConf.ConfigPath
|
|
cui.HideConfig = tmpConf.HideConfig
|
|
cui.NoReconfig = tmpConf.NoReconfig
|
|
cui.ResultBellow = tmpConf.ResultBellow
|
|
|
|
cui.Actions = tmpConf.Actions
|
|
for i := range cui.Actions {
|
|
if cui.Actions[i].Name == "" {
|
|
cui.Actions[i].Name = cui.Actions[i].Cmd
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
cui.LogLevel = tmpConf.LogLevel
|
|
if cui.LogLevel == 0 {
|
|
cui.LogLevel = klog.Lerror | klog.Linfo
|
|
}
|
|
klog.LEVEL = cui.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)
|
|
if cui.ksrv_log != nil {
|
|
cui.ksrv_log.SetErrOutput(cui.f)
|
|
cui.ksrv_log.SetOutput(cui.f)
|
|
}
|
|
} else {
|
|
cui.log.SetErrOutput(os.Stderr)
|
|
cui.log.SetOutput(os.Stderr)
|
|
if cui.ksrv_log != nil {
|
|
cui.ksrv_log.SetErrOutput(os.Stderr)
|
|
cui.ksrv_log.SetOutput(os.Stderr)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|