Compare commits
1 Commits
863b489412
...
bc443cc320
Author | SHA1 | Date |
---|---|---|
Evan Chen | bc443cc320 |
|
@ -0,0 +1,118 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchIPGlob(ip, pattern string) bool {
|
||||||
|
parts := strings.Split(pattern, ".")
|
||||||
|
seg := strings.Split(ip, ".")
|
||||||
|
for i, part := range parts {
|
||||||
|
|
||||||
|
// normalize pattern to 3 digits
|
||||||
|
switch len(part) {
|
||||||
|
case 1:
|
||||||
|
if part == "*" {
|
||||||
|
part = "***"
|
||||||
|
} else {
|
||||||
|
part = "00" + part
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if strings.HasPrefix(part, "*") {
|
||||||
|
part = "*" + part
|
||||||
|
} else if strings.HasSuffix(part, "*") {
|
||||||
|
part = part + "*"
|
||||||
|
} else {
|
||||||
|
part = "0" + part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize ip to 3 digits
|
||||||
|
switch len(seg[i]) {
|
||||||
|
case 1:
|
||||||
|
seg[i] = "00" + seg[i]
|
||||||
|
case 2:
|
||||||
|
seg[i] = "0" + seg[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range part {
|
||||||
|
if string(part[j]) == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if part[j] != seg[i][j] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func bundle(buf io.Writer, paths []string, flat bool) error {
|
||||||
|
gw := gzip.NewWriter(buf)
|
||||||
|
defer gw.Close()
|
||||||
|
tw := tar.NewWriter(gw)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
for _, file := range paths {
|
||||||
|
if err := func(file string) error {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
info, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use full path as name (FileInfoHeader only takes the basename)
|
||||||
|
// If we don't do this the directory strucuture would
|
||||||
|
// not be preserved
|
||||||
|
// https://golang.org/src/archive/tar/common.go?#L626
|
||||||
|
if !flat {
|
||||||
|
if !strings.HasPrefix(file, "/") {
|
||||||
|
file = "configui/" + file
|
||||||
|
} else {
|
||||||
|
file = "configui" + file
|
||||||
|
}
|
||||||
|
header.Name = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file header to the tar archive
|
||||||
|
err = tw.WriteHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file content to tar archive
|
||||||
|
_, err = io.Copy(tw, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}(file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ext2mode map[string]string = map[string]string{
|
||||||
|
"go": "golang",
|
||||||
|
"log": "sh",
|
||||||
|
"txt": "text",
|
||||||
|
"yml": "yaml",
|
||||||
|
"conf": "ini",
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,42 @@
|
||||||
|
package configui
|
||||||
|
|
||||||
|
var APP_NAME = "ConfigUI"
|
||||||
|
var UNIX_SHELL = "sh"
|
||||||
|
var WIN_SHELL = "cmd"
|
||||||
|
|
||||||
|
type ConfigUI struct {
|
||||||
|
ConfigPath string `json:"config_path"`
|
||||||
|
|
||||||
|
NoReconfig bool `json:"no_reconfig"`
|
||||||
|
LogPath string `json:"log_path"`
|
||||||
|
SilentSysOut bool `json:"silent_sys_out"`
|
||||||
|
AllowIP string `json:"allow_ip"`
|
||||||
|
|
||||||
|
Files []*File `json:"files"`
|
||||||
|
fileIndex map[string]int
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
ResultBellow bool `json:"result_bellow"`
|
||||||
|
HideConfig bool `json:"hide_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *ConfigUI {
|
||||||
|
return &ConfigUI{
|
||||||
|
fileIndex: map[string]int{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cui *ConfigUI) Get(file_name string) (*File, error) {
|
||||||
|
if file_name == APP_NAME {
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cui *ConfigUI) LoadConfig(confstr string) {
|
||||||
|
|
||||||
|
// construct fileIndex
|
||||||
|
}
|
||||||
|
func (cui *ConfigUI) GetConfig() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package configui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
RO bool `json:"ro"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
|
||||||
|
// used for parsing post data
|
||||||
|
Data string `json:"data"`
|
||||||
|
|
||||||
|
lock sync.RWMutex `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Read() ([]byte, error) {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
data, err := os.ReadFile(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Write(data []byte) error {
|
||||||
|
if f.RO {
|
||||||
|
return errors.New("this file has readonly set")
|
||||||
|
}
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
info, err := os.Stat(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(f.Path, data, info.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Do() (string, error) {
|
||||||
|
if f.Action == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
timeout := time.After(2 * time.Second)
|
||||||
|
cmd := &exec.Cmd{}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd = exec.Command(WIN_SHELL, "/c", f.Action)
|
||||||
|
} else {
|
||||||
|
exec.Command(UNIX_SHELL, "-c", f.Action)
|
||||||
|
}
|
||||||
|
var out []byte
|
||||||
|
done := make(chan struct{}, 1)
|
||||||
|
go func() {
|
||||||
|
out, _ = cmd.CombinedOutput()
|
||||||
|
// real cmd err is only pass down
|
||||||
|
// if err != nil {
|
||||||
|
// return string(out), err
|
||||||
|
// }
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
cmd.Process.Kill()
|
||||||
|
return "", errors.New("command timeout")
|
||||||
|
case <-done:
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadConfig(confstr string) ([]File, error) {
|
||||||
|
conf := []File{}
|
||||||
|
err := json.Unmarshal([]byte(confstr), &conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range conf {
|
||||||
|
if conf[i].Name == "" {
|
||||||
|
conf[i].Name = conf[i].Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileMap(files []File) map[string]*File {
|
||||||
|
fileMap := map[string]*File{}
|
||||||
|
for i := range files {
|
||||||
|
fileMap[files[i].Name] = &files[i]
|
||||||
|
}
|
||||||
|
return fileMap
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package configui
|
|
@ -11,16 +11,6 @@
|
||||||
</span></span>
|
</span></span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<p class="control">
|
|
||||||
<div class="select is-small">
|
|
||||||
<select onchange="SetTabstop(this)">
|
|
||||||
<option value="0">Tabstop</option>
|
|
||||||
<option value="4">Tabstop 4</option>
|
|
||||||
<option value="2">Tabstop 2</option>
|
|
||||||
<option value="-1">Tab</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button is-small has-tooltip-arrow" id="toolFollow"
|
<button class="button is-small has-tooltip-arrow" id="toolFollow"
|
||||||
data-tooltip="Follow file change (1s)" onclick="toolFollow()">
|
data-tooltip="Follow file change (1s)" onclick="toolFollow()">
|
||||||
|
@ -47,6 +37,16 @@
|
||||||
</span></span>
|
</span></span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<div class="select is-small">
|
||||||
|
<select onchange="SetTabstop(this)">
|
||||||
|
<option value="0">Tabstop</option>
|
||||||
|
<option value="4">Tabstop 4</option>
|
||||||
|
<option value="2">Tabstop 2</option>
|
||||||
|
<option value="-1">Tab</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
119
util.go
119
util.go
|
@ -1,118 +1 @@
|
||||||
package main
|
package configui
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"compress/gzip"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func matchIPGlob(ip, pattern string) bool {
|
|
||||||
parts := strings.Split(pattern, ".")
|
|
||||||
seg := strings.Split(ip, ".")
|
|
||||||
for i, part := range parts {
|
|
||||||
|
|
||||||
// normalize pattern to 3 digits
|
|
||||||
switch len(part) {
|
|
||||||
case 1:
|
|
||||||
if part == "*" {
|
|
||||||
part = "***"
|
|
||||||
} else {
|
|
||||||
part = "00" + part
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
if strings.HasPrefix(part, "*") {
|
|
||||||
part = "*" + part
|
|
||||||
} else if strings.HasSuffix(part, "*") {
|
|
||||||
part = part + "*"
|
|
||||||
} else {
|
|
||||||
part = "0" + part
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize ip to 3 digits
|
|
||||||
switch len(seg[i]) {
|
|
||||||
case 1:
|
|
||||||
seg[i] = "00" + seg[i]
|
|
||||||
case 2:
|
|
||||||
seg[i] = "0" + seg[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := range part {
|
|
||||||
if string(part[j]) == "*" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if part[j] != seg[i][j] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func bundle(buf io.Writer, paths []string, flat bool) error {
|
|
||||||
gw := gzip.NewWriter(buf)
|
|
||||||
defer gw.Close()
|
|
||||||
tw := tar.NewWriter(gw)
|
|
||||||
defer tw.Close()
|
|
||||||
|
|
||||||
for _, file := range paths {
|
|
||||||
if err := func(file string) error {
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
info, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := tar.FileInfoHeader(info, info.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use full path as name (FileInfoHeader only takes the basename)
|
|
||||||
// If we don't do this the directory strucuture would
|
|
||||||
// not be preserved
|
|
||||||
// https://golang.org/src/archive/tar/common.go?#L626
|
|
||||||
if !flat {
|
|
||||||
if !strings.HasPrefix(file, "/") {
|
|
||||||
file = "configui/" + file
|
|
||||||
} else {
|
|
||||||
file = "configui" + file
|
|
||||||
}
|
|
||||||
header.Name = file
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write file header to the tar archive
|
|
||||||
err = tw.WriteHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy file content to tar archive
|
|
||||||
_, err = io.Copy(tw, f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}(file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext2mode map[string]string = map[string]string{
|
|
||||||
"go": "golang",
|
|
||||||
"log": "sh",
|
|
||||||
"txt": "text",
|
|
||||||
"yml": "yaml",
|
|
||||||
"conf": "ini",
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue