2021-10-23 04:56:24 +00:00
|
|
|
package configui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"embed"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
2021-11-03 19:35:51 +00:00
|
|
|
"os"
|
2021-11-12 09:25:44 +00:00
|
|
|
"path/filepath"
|
2021-11-12 15:09:32 +00:00
|
|
|
"runtime"
|
2021-11-09 03:27:58 +00:00
|
|
|
"strings"
|
2021-11-05 03:10:13 +00:00
|
|
|
"sync"
|
2021-11-03 10:08:37 +00:00
|
|
|
"time"
|
2021-10-23 04:56:24 +00:00
|
|
|
|
2021-11-03 19:35:51 +00:00
|
|
|
"kumoly.io/lib/klog"
|
2021-11-04 05:55:45 +00:00
|
|
|
"kumoly.io/lib/ksrv/engine"
|
2021-10-23 04:56:24 +00:00
|
|
|
"kumoly.io/tools/configui/public"
|
|
|
|
)
|
|
|
|
|
2021-11-12 15:09:32 +00:00
|
|
|
const UNIX_SHELL = "/usr/bin/sh"
|
|
|
|
const WIN_SHELL = "C:\\Windows\\System32\\cmd"
|
|
|
|
const DARWIN_SHELL = "/bin/bash"
|
2021-10-23 04:56:24 +00:00
|
|
|
|
2021-11-12 07:08:34 +00:00
|
|
|
const version = "v0.1.12"
|
2021-10-23 16:49:44 +00:00
|
|
|
|
2021-10-23 04:56:24 +00:00
|
|
|
//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",
|
2021-10-24 06:32:55 +00:00
|
|
|
"md": "markdown",
|
2021-10-23 04:56:24 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 03:27:58 +00:00
|
|
|
type Action struct {
|
2021-11-12 15:09:32 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Cmd string `json:"cmd"`
|
|
|
|
Dir string `json:"dir"`
|
2021-11-12 09:25:44 +00:00
|
|
|
|
|
|
|
run chan struct{} `json:"-"`
|
2021-11-12 15:09:32 +00:00
|
|
|
pid int
|
2021-11-09 03:27:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Integration struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Cmd func() (string, error) `json:"-"`
|
2021-11-11 01:58:36 +00:00
|
|
|
run chan struct{} `json:"-"`
|
2021-11-09 03:27:58 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 04:56:24 +00:00
|
|
|
type ConfigUI struct {
|
|
|
|
AppName string `json:"app_name"`
|
2021-11-03 19:35:51 +00:00
|
|
|
Prod bool `json:"production"`
|
2021-10-24 07:06:02 +00:00
|
|
|
BaseUrl string `json:"base_url"`
|
2021-10-23 04:56:24 +00:00
|
|
|
ConfigPath string `json:"config_path"`
|
2021-11-12 15:09:32 +00:00
|
|
|
SHELL string `json:"shell"`
|
2021-10-23 04:56:24 +00:00
|
|
|
|
|
|
|
NoReconfig bool `json:"no_reconfig"`
|
|
|
|
AllowIP string `json:"allow_ip"`
|
2021-11-03 10:08:37 +00:00
|
|
|
CmdTimeout string `json:"timeout"`
|
|
|
|
cmdTimeout time.Duration
|
2021-10-23 04:56:24 +00:00
|
|
|
|
2021-11-09 03:27:58 +00:00
|
|
|
Files []*File `json:"files"`
|
|
|
|
fileIndex map[string]int
|
|
|
|
Actions []Action `json:"actions"`
|
2021-11-09 03:41:41 +00:00
|
|
|
Integrations []*Integration `json:"integrations,omitempty"`
|
2021-10-23 04:56:24 +00:00
|
|
|
|
|
|
|
ResultBellow bool `json:"result_bellow"`
|
|
|
|
HideConfig bool `json:"hide_config"`
|
|
|
|
|
|
|
|
// Should be in main app
|
2021-11-05 16:09:51 +00:00
|
|
|
LogPath string `json:"log_path"`
|
|
|
|
LogLevel klog.Llevel `json:"log_level"`
|
2021-10-23 04:56:24 +00:00
|
|
|
|
2021-11-12 09:25:44 +00:00
|
|
|
// Running commands
|
2021-11-13 04:59:08 +00:00
|
|
|
Onitachi map[string]*Oni `json:"onitachi"`
|
2021-11-12 09:25:44 +00:00
|
|
|
|
2021-11-05 03:10:13 +00:00
|
|
|
TmplFS embed.FS `json:"-"`
|
|
|
|
tmpl *engine.Engine
|
2021-11-09 03:27:58 +00:00
|
|
|
PublicFS embed.FS `json:"-"`
|
|
|
|
log *klog.Logger
|
|
|
|
ksrv_log *klog.Logger
|
|
|
|
f *os.File
|
|
|
|
configLock sync.Mutex
|
2021-10-23 04:56:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func New() *ConfigUI {
|
2021-11-04 05:55:45 +00:00
|
|
|
tmpl := engine.Must(engine.New("").Funcs(template.FuncMap{
|
2021-10-23 04:56:24 +00:00
|
|
|
"step": func(from, to, step uint) []uint {
|
|
|
|
items := []uint{}
|
|
|
|
for i := from; i <= to; i += step {
|
|
|
|
items = append(items, i)
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
},
|
2021-11-09 03:27:58 +00:00
|
|
|
"normal": func(name string) string {
|
|
|
|
return strings.ReplaceAll(name, " ", "-")
|
|
|
|
},
|
2021-11-12 15:09:32 +00:00
|
|
|
"state_class": func(state State) string {
|
|
|
|
switch state {
|
|
|
|
case STARTED:
|
|
|
|
return "is-success"
|
|
|
|
case ERROR:
|
|
|
|
return "is-danger"
|
|
|
|
case ENDED:
|
|
|
|
return "has-background-grey-light"
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"state_icon": func(state State) string {
|
|
|
|
switch state {
|
|
|
|
case STARTED:
|
|
|
|
return "play_arrow"
|
|
|
|
case ERROR:
|
|
|
|
return "error"
|
|
|
|
case ENDED:
|
|
|
|
return "stop"
|
|
|
|
default:
|
|
|
|
return "pending"
|
|
|
|
}
|
|
|
|
},
|
2021-10-23 04:56:24 +00:00
|
|
|
}).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl"))
|
2021-11-12 15:09:32 +00:00
|
|
|
sh := ""
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "darwin":
|
|
|
|
sh = DARWIN_SHELL
|
|
|
|
case "windows":
|
|
|
|
sh = WIN_SHELL
|
|
|
|
default:
|
|
|
|
sh = UNIX_SHELL
|
|
|
|
}
|
2021-10-23 04:56:24 +00:00
|
|
|
return &ConfigUI{
|
2021-11-03 10:08:37 +00:00
|
|
|
fileIndex: map[string]int{},
|
2021-11-12 15:09:32 +00:00
|
|
|
SHELL: sh,
|
2021-11-03 19:35:51 +00:00
|
|
|
Prod: true,
|
2021-11-03 10:08:37 +00:00
|
|
|
AppName: "ConfigUI",
|
|
|
|
BaseUrl: "/",
|
2021-11-09 03:27:58 +00:00
|
|
|
Actions: []Action{{Name: "User", Cmd: "whoami"}},
|
2021-11-03 10:08:37 +00:00
|
|
|
PublicFS: public.FS,
|
|
|
|
TmplFS: tmplFS,
|
|
|
|
tmpl: tmpl,
|
|
|
|
CmdTimeout: "10s",
|
|
|
|
cmdTimeout: time.Second * 10,
|
2021-11-03 19:35:51 +00:00
|
|
|
LogLevel: klog.Lerror | klog.Linfo,
|
|
|
|
log: klog.Sub("ConfigUI"),
|
2021-11-13 04:59:08 +00:00
|
|
|
Onitachi: make(map[string]*Oni),
|
2021-10-23 04:56:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cui *ConfigUI) File(file_name string) (*File, error) {
|
|
|
|
if file_name == cui.AppName {
|
|
|
|
return &File{
|
2021-11-03 19:35:51 +00:00
|
|
|
Path: cui.ConfigPath,
|
|
|
|
Name: cui.AppName,
|
|
|
|
Lang: "json",
|
|
|
|
owner: cui,
|
2021-10-23 04:56:24 +00:00
|
|
|
}, 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 {
|
2021-11-13 04:59:08 +00:00
|
|
|
cui.configLock.Lock()
|
|
|
|
defer cui.configLock.Unlock()
|
2021-10-23 04:56:24 +00:00
|
|
|
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
|
|
|
|
}
|
2021-11-03 19:35:51 +00:00
|
|
|
f.owner = cui
|
2021-10-23 04:56:24 +00:00
|
|
|
tmpIndex[f.Name] = i
|
2021-11-11 02:21:10 +00:00
|
|
|
|
|
|
|
// deprecated fix
|
|
|
|
if f.Cmd == "" && f.Action != "" {
|
|
|
|
f.Cmd = f.Action
|
|
|
|
f.Action = ""
|
|
|
|
}
|
2021-10-23 04:56:24 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 04:59:08 +00:00
|
|
|
// del oni dry run
|
|
|
|
for k, v := range cui.Onitachi {
|
|
|
|
_, ok := tmpConf.Onitachi[k]
|
|
|
|
if !ok && v.State == STARTED {
|
|
|
|
return ErrorNoDeleteRunning.New(k)
|
2021-11-12 15:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 04:56:24 +00:00
|
|
|
// copy
|
|
|
|
cui.fileIndex = tmpIndex
|
|
|
|
cui.Files = tmpConf.Files
|
|
|
|
cui.AllowIP = tmpConf.AllowIP
|
2021-11-03 19:35:51 +00:00
|
|
|
cui.Prod = tmpConf.Prod
|
2021-11-03 19:45:06 +00:00
|
|
|
klog.PROD = cui.Prod
|
2021-10-23 04:56:24 +00:00
|
|
|
cui.ConfigPath = tmpConf.ConfigPath
|
|
|
|
cui.HideConfig = tmpConf.HideConfig
|
|
|
|
cui.NoReconfig = tmpConf.NoReconfig
|
2021-11-05 03:10:13 +00:00
|
|
|
cui.ResultBellow = tmpConf.ResultBellow
|
2021-11-12 15:09:32 +00:00
|
|
|
cui.SHELL = tmpConf.SHELL
|
2021-11-03 19:35:51 +00:00
|
|
|
|
2021-11-13 04:59:08 +00:00
|
|
|
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()
|
|
|
|
|
|
|
|
// construct oni - update existing, del
|
|
|
|
for k, v := range cui.Onitachi {
|
|
|
|
n, ok := tmpConf.Onitachi[k]
|
|
|
|
if !ok {
|
|
|
|
delete(cui.Onitachi, k)
|
|
|
|
} else {
|
|
|
|
v.Name = k
|
|
|
|
v.Cmd = n.Cmd
|
|
|
|
v.Args = n.Args
|
|
|
|
v.Envs = n.Envs
|
|
|
|
v.Dir = n.Dir
|
|
|
|
v.Policy = n.Policy
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// construct oni - append new
|
|
|
|
for k, v := range tmpConf.Onitachi {
|
|
|
|
_, ok := cui.Onitachi[k]
|
|
|
|
if !ok {
|
|
|
|
v.Name = k
|
|
|
|
v.Init(cui)
|
|
|
|
cui.Onitachi[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-09 03:27:58 +00:00
|
|
|
cui.Actions = tmpConf.Actions
|
|
|
|
for i := range cui.Actions {
|
|
|
|
if cui.Actions[i].Name == "" {
|
|
|
|
cui.Actions[i].Name = cui.Actions[i].Cmd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 04:56:24 +00:00
|
|
|
cui.AppName = tmpConf.AppName
|
2021-11-03 19:35:51 +00:00
|
|
|
if cui.AppName == "" {
|
|
|
|
cui.AppName = "ConfigUI"
|
|
|
|
}
|
|
|
|
|
2021-10-24 07:06:02 +00:00
|
|
|
cui.BaseUrl = tmpConf.BaseUrl
|
2021-10-24 07:15:21 +00:00
|
|
|
if cui.BaseUrl == "" {
|
|
|
|
cui.BaseUrl = "/"
|
|
|
|
}
|
2021-11-03 19:35:51 +00:00
|
|
|
|
2021-11-03 10:08:37 +00:00
|
|
|
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
|
|
|
|
}
|
2021-10-23 04:56:24 +00:00
|
|
|
|
|
|
|
// fmt.Printf("%+v", cui)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-03 19:35:51 +00:00
|
|
|
func (cui *ConfigUI) setLog() {
|
|
|
|
var err error
|
|
|
|
if cui.f != nil {
|
|
|
|
cui.f.Close()
|
|
|
|
}
|
|
|
|
if cui.LogPath != "" {
|
2021-11-12 09:25:44 +00:00
|
|
|
mkdir(filepath.Dir(cui.LogPath))
|
2021-11-03 19:35:51 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 04:56:24 +00:00
|
|
|
func (cui *ConfigUI) Config() ([]byte, error) {
|
|
|
|
return json.MarshalIndent(cui, "", " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cui *ConfigUI) AppendFile(file *File) error {
|
|
|
|
if file.Name == "" {
|
|
|
|
file.Name = file.Path
|
|
|
|
}
|
2021-11-03 19:35:51 +00:00
|
|
|
file.owner = cui
|
2021-10-23 04:56:24 +00:00
|
|
|
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
|
|
|
|
}
|