add muzan
parent
4b1ad5b791
commit
31c141b6f9
11
configui.go
11
configui.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -36,8 +37,11 @@ var Ext2Mode map[string]string = map[string]string{
|
||||||
type Action struct {
|
type Action struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Cmd string `json:"cmd"`
|
Cmd string `json:"cmd"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Args []string `json:"args"`
|
||||||
|
Envs []string `json:"envs"`
|
||||||
|
|
||||||
run chan struct{} `json:"-"`
|
run chan struct{} `json:"-"`
|
||||||
pid int `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Integration struct {
|
type Integration struct {
|
||||||
|
@ -70,6 +74,9 @@ type ConfigUI struct {
|
||||||
LogPath string `json:"log_path"`
|
LogPath string `json:"log_path"`
|
||||||
LogLevel klog.Llevel `json:"log_level"`
|
LogLevel klog.Llevel `json:"log_level"`
|
||||||
|
|
||||||
|
// Running commands
|
||||||
|
Onitachi map[string]*Oni `json:"-"`
|
||||||
|
|
||||||
TmplFS embed.FS `json:"-"`
|
TmplFS embed.FS `json:"-"`
|
||||||
tmpl *engine.Engine
|
tmpl *engine.Engine
|
||||||
PublicFS embed.FS `json:"-"`
|
PublicFS embed.FS `json:"-"`
|
||||||
|
@ -105,6 +112,7 @@ func New() *ConfigUI {
|
||||||
cmdTimeout: time.Second * 10,
|
cmdTimeout: time.Second * 10,
|
||||||
LogLevel: klog.Lerror | klog.Linfo,
|
LogLevel: klog.Lerror | klog.Linfo,
|
||||||
log: klog.Sub("ConfigUI"),
|
log: klog.Sub("ConfigUI"),
|
||||||
|
Onitachi: make(map[string]*Oni),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +213,7 @@ func (cui *ConfigUI) setLog() {
|
||||||
cui.f.Close()
|
cui.f.Close()
|
||||||
}
|
}
|
||||||
if cui.LogPath != "" {
|
if cui.LogPath != "" {
|
||||||
|
mkdir(filepath.Dir(cui.LogPath))
|
||||||
cui.f, err = os.OpenFile(cui.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
cui.f, err = os.OpenFile(cui.LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cui.log.Error(err)
|
cui.log.Error(err)
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package configui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"kumoly.io/lib/ksrv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrorOniHasNoPID = ksrv.Error{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
ID: "ErrorOniHasNoPID",
|
||||||
|
Message: "oni has no pid",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorOniNotStarted = ksrv.Error{
|
||||||
|
Code: http.StatusConflict,
|
||||||
|
ID: "ErrorOniNotStarted",
|
||||||
|
Message: "oni hasn't start",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorOniHasStarted = ksrv.Error{
|
||||||
|
Code: http.StatusConflict,
|
||||||
|
ID: "ErrorOniHasStarted",
|
||||||
|
Message: "oni has started",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorOniNotFound = ksrv.Error{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
ID: "ErrorOniNotFound",
|
||||||
|
Message: "oni not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorOniNotValid = ksrv.Error{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
ID: "ErrorOniNotValid",
|
||||||
|
Message: "oni no name or cmd",
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
kumoly.io/lib/klog v0.0.8
|
kumoly.io/lib/klog v0.0.8
|
||||||
kumoly.io/lib/ksrv v0.0.1
|
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -8,3 +8,5 @@ kumoly.io/lib/klog v0.0.8 h1:6hTfDlZh7KGnPrd2tUrauCKRImSnyyN9DHXpey3Czn8=
|
||||||
kumoly.io/lib/klog v0.0.8/go.mod h1:Snm+c1xRrh/RbXsxQf7UGYbAJGPcIa6bEEN+CmzJh7M=
|
kumoly.io/lib/klog v0.0.8/go.mod h1:Snm+c1xRrh/RbXsxQf7UGYbAJGPcIa6bEEN+CmzJh7M=
|
||||||
kumoly.io/lib/ksrv v0.0.1 h1:JfWwJ9GeiTtDfGoeG7YxJwsckralbhsLKEPLQb20Uzo=
|
kumoly.io/lib/ksrv v0.0.1 h1:JfWwJ9GeiTtDfGoeG7YxJwsckralbhsLKEPLQb20Uzo=
|
||||||
kumoly.io/lib/ksrv v0.0.1/go.mod h1:ykHXeAPjNvA5jEZo5rp32edzkugLf0e+2pspct3FOFQ=
|
kumoly.io/lib/ksrv v0.0.1/go.mod h1:ykHXeAPjNvA5jEZo5rp32edzkugLf0e+2pspct3FOFQ=
|
||||||
|
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298 h1:0raqoIXmNpD6s1SrJbieAyIIkDyhe+aqfaXvx8wenrI=
|
||||||
|
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298/go.mod h1:pwd+NspxnoxPJAETRY2V4i2qZc+orKLxvWzGUBiqBW8=
|
||||||
|
|
25
handler.go
25
handler.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"kumoly.io/lib/ksrv"
|
"kumoly.io/lib/ksrv"
|
||||||
)
|
)
|
||||||
|
@ -142,19 +141,23 @@ func (cui *ConfigUI) DoAction(w http.ResponseWriter, r *http.Request) {
|
||||||
case cui.Actions[i].run <- struct{}{}:
|
case cui.Actions[i].run <- struct{}{}:
|
||||||
defer func() { <-cui.Actions[i].run }()
|
defer func() { <-cui.Actions[i].run }()
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("another task of %s is running with pid: %d", name, v.pid))
|
panic(fmt.Errorf("another task of %s is running with", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
file := &File{Name: name, Cmd: v.Cmd, owner: cui}
|
oni, ok := cui.Onitachi[name]
|
||||||
go func() {
|
if !ok {
|
||||||
<-time.After(time.Millisecond * 10)
|
oni = NewOni(name, cui)
|
||||||
cui.Actions[i].pid = file.pid
|
oni.Cmd = cui.Actions[i].Cmd
|
||||||
}()
|
oni.Args = cui.Actions[i].Args
|
||||||
result, err := file.Do(cui.cmdTimeout)
|
oni.Envs = cui.Actions[i].Envs
|
||||||
|
oni.Dir = cui.Actions[i].Dir
|
||||||
|
cui.Onitachi[name] = oni
|
||||||
|
}
|
||||||
|
err := oni.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
w.Write([]byte(result))
|
w.Write([]byte("ok"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,3 +262,7 @@ func (cui *ConfigUI) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cui *ConfigUI) GetRunning(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ksrv.JSON(w, cui.Onitachi)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
package configui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State string
|
||||||
|
|
||||||
|
const (
|
||||||
|
READY State = "ready"
|
||||||
|
ERROR State = "error"
|
||||||
|
STARTED State = "started"
|
||||||
|
ENDED State = "ended"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Policy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NO Policy = "no"
|
||||||
|
ONFAIL Policy = "on-failure"
|
||||||
|
ALWAYS Policy = "always"
|
||||||
|
UNLESSSTOP Policy = "unless-stopped"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Oni struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Cmd string `json:"cmd"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Args []string `json:"args"`
|
||||||
|
Envs []string `json:"envs"`
|
||||||
|
Policy Policy `json:"policy"`
|
||||||
|
|
||||||
|
PID int `json:"pid"`
|
||||||
|
LogFile string `json:"log_file"`
|
||||||
|
State State `json:"state"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ManStop bool `json:"manual_stop"`
|
||||||
|
|
||||||
|
parent *ConfigUI
|
||||||
|
cmd *exec.Cmd
|
||||||
|
start chan struct{}
|
||||||
|
buff bytes.Buffer
|
||||||
|
log io.Writer
|
||||||
|
|
||||||
|
listeners []io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOni(name string, cui *ConfigUI) *Oni {
|
||||||
|
var err error
|
||||||
|
oni := &Oni{
|
||||||
|
Policy: NO,
|
||||||
|
State: READY,
|
||||||
|
parent: cui,
|
||||||
|
start: make(chan struct{}, 1),
|
||||||
|
listeners: make([]io.Writer, 0),
|
||||||
|
}
|
||||||
|
if cui.LogPath != "" {
|
||||||
|
logpath := filepath.Join(filepath.Dir(cui.LogPath), 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 oni
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oni *Oni) Start() error {
|
||||||
|
if oni.Cmd == "" {
|
||||||
|
return ErrorOniNotValid
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case oni.start <- struct{}{}:
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
oni.end(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
return ErrorOniHasStarted
|
||||||
|
}
|
||||||
|
cmd := exec.Command(oni.Cmd, oni.Args...)
|
||||||
|
cmd.Env = oni.Envs
|
||||||
|
cmd.Dir = oni.Dir
|
||||||
|
var out io.Writer
|
||||||
|
if oni.LogFile != "" {
|
||||||
|
out = io.MultiWriter(oni.log, &oni.buff)
|
||||||
|
} else {
|
||||||
|
out = &oni.buff
|
||||||
|
}
|
||||||
|
cmd.Stderr = out
|
||||||
|
cmd.Stdout = out
|
||||||
|
oni.cmd = cmd
|
||||||
|
err := oni.cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
oni.end(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go oni.read()
|
||||||
|
oni.PID = cmd.Process.Pid
|
||||||
|
oni.State = STARTED
|
||||||
|
go func() {
|
||||||
|
oni.end(cmd.Wait())
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oni *Oni) Stop() error {
|
||||||
|
if oni.cmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return oni.cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oni *Oni) end(v interface{}) {
|
||||||
|
if v == nil {
|
||||||
|
oni.State = ENDED
|
||||||
|
} else {
|
||||||
|
oni.Error = fmt.Sprint(v)
|
||||||
|
oni.State = ERROR
|
||||||
|
}
|
||||||
|
if len(oni.start) == 1 {
|
||||||
|
<-oni.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oni *Oni) read() {
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(&oni.buff)
|
||||||
|
for scanner.Scan() {
|
||||||
|
if len(oni.listeners) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w := io.MultiWriter(oni.listeners...)
|
||||||
|
w.Write([]byte(scanner.Text()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
|
@ -98,6 +98,13 @@ func (cui *ConfigUI) mux() *http.ServeMux {
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
cuiR.HandleFunc("/api/running", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
cui.GetRunning(w, r)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS))))
|
cuiR.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.FS(cui.PublicFS))))
|
||||||
cuiR.HandleFunc("/", cui.App)
|
cuiR.HandleFunc("/", cui.App)
|
||||||
return cuiR
|
return cuiR
|
||||||
|
|
16
util.go
16
util.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,3 +66,18 @@ func bundle(buf io.Writer, paths []string, rootDir string, flat bool) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mkdir(args ...interface{}) error {
|
||||||
|
var path string
|
||||||
|
var mode os.FileMode
|
||||||
|
mode = 0755
|
||||||
|
for _, arg := range args {
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case string:
|
||||||
|
path = filepath.Join(path, arg)
|
||||||
|
case os.FileMode:
|
||||||
|
mode = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.MkdirAll(path, mode)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue