diff --git a/cmd/configui/main.go b/cmd/configui/main.go index af888f9..f321a04 100644 --- a/cmd/configui/main.go +++ b/cmd/configui/main.go @@ -36,7 +36,7 @@ func init() { flag.StringVar(&flagAction, "c", "", "cmd to apply") flag.StringVar(&flagLogFile, "log", "", "log to file") flag.StringVar(&flagAllow, "allow", "", "IPs to allow, blank to allow all") - flag.StringVar(&flagBind, "bind", "0.0.0.0:8000", "address to bind") + flag.StringVar(&flagBind, "bind", "127.0.0.1:8000", "address to bind") flag.BoolVar(&flagNoReconfig, "static", false, "disable config api") flag.BoolVar(&flagVer, "v", false, "show version") flag.Usage = func() { diff --git a/configui.go b/configui.go index 6115856..9fe9bed 100644 --- a/configui.go +++ b/configui.go @@ -35,13 +35,12 @@ var Ext2Mode map[string]string = map[string]string{ } type Action struct { - Name string `json:"name"` - Cmd string `json:"cmd"` - Dir string `json:"dir"` - Args []string `json:"args"` - Envs []string `json:"envs"` + Name string `json:"name"` + Cmd string `json:"cmd"` + Dir string `json:"dir"` run chan struct{} `json:"-"` + pid int } type Integration struct { @@ -75,7 +74,8 @@ type ConfigUI struct { LogLevel klog.Llevel `json:"log_level"` // Running commands - Onitachi map[string]*Oni `json:"-"` + Onitachi []*Oni `json:"-"` + OniIndex map[string]int TmplFS embed.FS `json:"-"` tmpl *engine.Engine @@ -98,6 +98,30 @@ func New() *ConfigUI { "normal": func(name string) string { return strings.ReplaceAll(name, " ", "-") }, + "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" + } + }, }).ParseFS(tmplFS, "templates/*.tmpl", "templates/**/*.tmpl")) return &ConfigUI{ fileIndex: map[string]int{}, @@ -112,7 +136,7 @@ func New() *ConfigUI { cmdTimeout: time.Second * 10, LogLevel: klog.Lerror | klog.Linfo, log: klog.Sub("ConfigUI"), - Onitachi: make(map[string]*Oni), + OniIndex: make(map[string]int), } } diff --git a/errors.go b/errors.go index 26d1fcb..e61cc6b 100644 --- a/errors.go +++ b/errors.go @@ -13,9 +13,9 @@ var ErrorOniHasNoPID = ksrv.Error{ } var ErrorOniNotStarted = ksrv.Error{ - Code: http.StatusConflict, - ID: "ErrorOniNotStarted", - Message: "oni hasn't start", + Code: http.StatusConflict, + ID: "ErrorOniNotStarted", + Tmpl: "%s hasn't start", } var ErrorOniHasStarted = ksrv.Error{ @@ -25,9 +25,9 @@ var ErrorOniHasStarted = ksrv.Error{ } var ErrorOniNotFound = ksrv.Error{ - Code: http.StatusNotFound, - ID: "ErrorOniNotFound", - Message: "oni not found", + Code: http.StatusNotFound, + ID: "ErrorOniNotFound", + Tmpl: "%s not found", } var ErrorOniNotValid = ksrv.Error{ diff --git a/file.go b/file.go index 604bd02..1ff9588 100644 --- a/file.go +++ b/file.go @@ -58,7 +58,7 @@ func (f *File) Write(data []byte) error { return os.WriteFile(f.Path, data, info.Mode()) } -func (f *File) Do(CmdTimeout time.Duration) (string, error) { +func (f *File) Do(CmdTimeout time.Duration, report chan int) (string, error) { if f.Cmd == "" { return "", nil } @@ -96,6 +96,7 @@ func (f *File) Do(CmdTimeout time.Duration) (string, error) { } go func() { f.pid = cmd.Process.Pid + report <- cmd.Process.Pid cmd.Wait() done <- b.String() }() diff --git a/handler.go b/handler.go index 0d4b1b3..52b86c6 100644 --- a/handler.go +++ b/handler.go @@ -121,7 +121,7 @@ func (cui *ConfigUI) Apply(w http.ResponseWriter, r *http.Request) { ksrv.Response(w, 404, []byte("file not found")) return } - result, err := file.Do(cui.cmdTimeout) + result, err := file.Do(cui.cmdTimeout, make(chan int)) if err != nil { panic(err) } @@ -141,23 +141,20 @@ func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) { case cui.Actions[i].run <- struct{}{}: defer func() { <-cui.Actions[i].run }() default: - panic(fmt.Errorf("another task of %s is running with", name)) + panic(fmt.Errorf("another task of %s is running with pid: %d", name, v.pid)) } - oni, ok := cui.Onitachi[name] - if !ok { - oni = NewOni(name, cui) - oni.Cmd = cui.Actions[i].Cmd - oni.Args = cui.Actions[i].Args - oni.Envs = cui.Actions[i].Envs - oni.Dir = cui.Actions[i].Dir - cui.Onitachi[name] = oni - } - err := oni.Start() + file := &File{Name: name, Cmd: v.Cmd, owner: cui} + pid := make(chan int) + go func() { + cui.Actions[i].pid = <-pid + }() + result, err := file.Do(cui.cmdTimeout, pid) if err != nil { panic(err) } - w.Write([]byte("ok")) + w.Write([]byte(result)) + return } } @@ -266,3 +263,37 @@ func (cui *ConfigUI) GetConfig(w http.ResponseWriter, r *http.Request) { func (cui *ConfigUI) GetRunning(w http.ResponseWriter, r *http.Request) { ksrv.JSON(w, cui.Onitachi) } + +func (cui *ConfigUI) OniStart(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + i, ok := cui.OniIndex[name] + if !ok { + panic(ErrorOniNotFound.New(name)) + } + oni := cui.Onitachi[i] + switch oni.State { + case "": + oni.Init(cui) + case STARTED: + panic(ErrorOniHasStarted) + } + oni.Start() + w.Write([]byte("ok")) +} + +func (cui *ConfigUI) OniStop(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + i, ok := cui.OniIndex[name] + if !ok { + panic(ErrorOniNotFound.New(name)) + } + oni := cui.Onitachi[i] + switch oni.State { + case STARTED: + oni.Stop() + default: + panic(ErrorOniNotStarted.New(name)) + } + oni.Start() + w.Write([]byte("ok")) +} diff --git a/muzan.go b/muzan.go index fdb238e..afccc3f 100644 --- a/muzan.go +++ b/muzan.go @@ -42,33 +42,30 @@ type Oni struct { Error string `json:"error"` ManStop bool `json:"manual_stop"` - parent *ConfigUI - cmd *exec.Cmd - start chan struct{} - buff bytes.Buffer - log io.Writer + parent *ConfigUI + cmd *exec.Cmd + running chan struct{} + buff bytes.Buffer + log io.Writer listeners []io.Writer } -func NewOni(name string, cui *ConfigUI) *Oni { +func (oni *Oni) Init(cui *ConfigUI) error { var err error - oni := &Oni{ - Policy: NO, - State: READY, - parent: cui, - start: make(chan struct{}, 1), - listeners: make([]io.Writer, 0), - } + oni.parent = cui + oni.running = make(chan struct{}, 1) + oni.listeners = make([]io.Writer, 0) + oni.StateChange(READY) if cui.LogPath != "" { - logpath := filepath.Join(filepath.Dir(cui.LogPath), name+".log") + logpath := filepath.Join(filepath.Dir(cui.LogPath), oni.Name+".log") oni.LogFile = logpath oni.log, err = os.OpenFile(logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - panic(err) + return err } } - return oni + return err } func (oni *Oni) Start() error { @@ -76,7 +73,7 @@ func (oni *Oni) Start() error { return ErrorOniNotValid } select { - case oni.start <- struct{}{}: + case oni.running <- struct{}{}: defer func() { err := recover() if err != nil { @@ -105,7 +102,7 @@ func (oni *Oni) Start() error { } go oni.read() oni.PID = cmd.Process.Pid - oni.State = STARTED + oni.StateChange(STARTED) go func() { oni.end(cmd.Wait()) }() @@ -122,13 +119,13 @@ func (oni *Oni) Stop() error { func (oni *Oni) end(v interface{}) { if v == nil { - oni.State = ENDED + oni.StateChange(ENDED) } else { oni.Error = fmt.Sprint(v) - oni.State = ERROR + oni.StateChange(ERROR) } - if len(oni.start) == 1 { - <-oni.start + if len(oni.running) == 1 { + <-oni.running } } @@ -144,3 +141,12 @@ func (oni *Oni) read() { } }() } + +func (oni *Oni) StateChange(state State) { + oni.State = state + // for i := range oni.parent.Actions { + // if oni.parent.Actions[i].Name == oni.Name { + // oni.parent.Actions[i].State = state + // } + // } +} diff --git a/templates/action.tmpl b/templates/action.tmpl new file mode 100644 index 0000000..1324ed4 --- /dev/null +++ b/templates/action.tmpl @@ -0,0 +1,7 @@ +{{define "action"}} +{{template "base/header" .}} +{{template "components/error" .}} + + +{{template "base/footer" .}} +{{end}} \ No newline at end of file diff --git a/templates/base/nav.tmpl b/templates/base/nav.tmpl index d21b024..4c4a553 100644 --- a/templates/base/nav.tmpl +++ b/templates/base/nav.tmpl @@ -14,6 +14,7 @@