mirror of
https://shylinux.com/x/icebergs
synced 2025-04-26 01:24:05 +08:00
action opt text web.spide
This commit is contained in:
parent
e7fd69d55d
commit
8ee9f90031
@ -61,28 +61,34 @@ const DAEMON = "daemon"
|
||||
func init() {
|
||||
Index.Merge(&ice.Context{
|
||||
Configs: map[string]*ice.Config{
|
||||
DAEMON: {Name: "daemon", Help: "守护进程", Value: kit.Data()},
|
||||
DAEMON: {Name: DAEMON, Help: "守护进程", Value: kit.Data()},
|
||||
},
|
||||
Commands: map[string]*ice.Command{
|
||||
DAEMON: {Name: "daemon hash 查看:button=auto 清理:button", Help: "守护进程", Action: map[string]*ice.Action{
|
||||
"delete": {Name: "delete", Help: "删除", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Cmdy("mdb.delete", DAEMON, "", "hash", "hash", m.Option("hash"))
|
||||
}},
|
||||
|
||||
"prune": {Name: "prune", Help: "清理", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Richs(DAEMON, "", kit.MDB_FOREACH, func(key string, value map[string]interface{}) {
|
||||
if strings.Count(m.Cmdx(SYSTEM, "ps", value["pid"]), "\n") > 1 {
|
||||
value["status"] = "start"
|
||||
if strings.Count(m.Cmdx(SYSTEM, "ps", value[kit.MDB_PID]), "\n") > 1 {
|
||||
value[kit.MDB_STATUS] = StatusStart
|
||||
return
|
||||
}
|
||||
m.Conf(DAEMON, kit.Keys(kit.MDB_HASH, key), "")
|
||||
})
|
||||
}},
|
||||
"stop": {Name: "stop", Help: "停止", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Richs(DAEMON, "", m.Option(kit.MDB_HASH), func(key string, value map[string]interface{}) {
|
||||
m.Cmdy(SYSTEM, "kill", value[kit.MDB_PID])
|
||||
if strings.Count(m.Cmdx(SYSTEM, "ps", value[kit.MDB_PID]), "\n") == 1 {
|
||||
value[kit.MDB_STATUS] = StatusClose
|
||||
return
|
||||
}
|
||||
m.Conf(DAEMON, kit.Keys("hash", key), "")
|
||||
})
|
||||
}},
|
||||
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if len(arg) == 0 {
|
||||
m.Option("fields", "time,hash,status,pid,name,dir")
|
||||
m.Cmdy("mdb.select", DAEMON, "", "hash")
|
||||
m.Cmdy("mdb.select", DAEMON, "", kit.MDB_HASH)
|
||||
m.Sort("time", "time_r")
|
||||
m.PushAction("停止")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,37 @@ func _spide_render(m *ice.Message, kind, name, text string, arg ...string) {
|
||||
}
|
||||
|
||||
const SPIDE = "spide"
|
||||
const (
|
||||
SPIDE_SHY = "shy"
|
||||
SPIDE_DEV = "dev"
|
||||
SPIDE_SELF = "self"
|
||||
|
||||
SPIDE_MSG = "msg"
|
||||
SPIDE_RAW = "raw"
|
||||
SPIDE_CACHE = "cache"
|
||||
|
||||
SPIDE_GET = "GET"
|
||||
SPIDE_PUT = "PUT"
|
||||
SPIDE_POST = "POST"
|
||||
SPIDE_DELETE = "DELETE"
|
||||
|
||||
SPIDE_FILE = "file"
|
||||
SPIDE_DATA = "data"
|
||||
SPIDE_PART = "part"
|
||||
SPIDE_FORM = "form"
|
||||
SPIDE_JSON = "json"
|
||||
|
||||
SPIDE_CLIENT = "client"
|
||||
SPIDE_HEADER = "header"
|
||||
SPIDE_COOKIE = "cookie"
|
||||
SPIDE_METHOD = "method"
|
||||
|
||||
ContentType = "Content-Type"
|
||||
ContentLength = "Content-Length"
|
||||
ContentFORM = "application/x-www-form-urlencoded"
|
||||
ContentJSON = "application/json"
|
||||
ContentHTML = "text/html"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Index.Merge(&ice.Context{
|
||||
@ -130,29 +161,29 @@ func init() {
|
||||
}
|
||||
|
||||
m.Richs(SPIDE, nil, arg[0], func(key string, value map[string]interface{}) {
|
||||
client := value["client"].(map[string]interface{})
|
||||
client := value[SPIDE_CLIENT].(map[string]interface{})
|
||||
// 缓存数据
|
||||
cache := ""
|
||||
switch arg[1] {
|
||||
case "raw":
|
||||
case SPIDE_MSG:
|
||||
cache, arg = arg[1], arg[1:]
|
||||
case "msg":
|
||||
case SPIDE_RAW:
|
||||
cache, arg = arg[1], arg[1:]
|
||||
case "cache":
|
||||
case SPIDE_CACHE:
|
||||
cache, arg = arg[1], arg[1:]
|
||||
}
|
||||
|
||||
// 请求方法
|
||||
method := kit.Select("POST", client["method"])
|
||||
method := kit.Select(SPIDE_POST, client[SPIDE_METHOD])
|
||||
switch arg = arg[1:]; arg[0] {
|
||||
case "GET":
|
||||
method, arg = "GET", arg[1:]
|
||||
case "PUT":
|
||||
method, arg = "PUT", arg[1:]
|
||||
case "POST":
|
||||
method, arg = "POST", arg[1:]
|
||||
case "DELETE":
|
||||
method, arg = "DELETE", arg[1:]
|
||||
case SPIDE_GET:
|
||||
method, arg = SPIDE_GET, arg[1:]
|
||||
case SPIDE_PUT:
|
||||
method, arg = SPIDE_PUT, arg[1:]
|
||||
case SPIDE_POST:
|
||||
method, arg = SPIDE_POST, arg[1:]
|
||||
case SPIDE_DELETE:
|
||||
method, arg = SPIDE_DELETE, arg[1:]
|
||||
}
|
||||
|
||||
// 请求地址
|
||||
@ -161,18 +192,18 @@ func init() {
|
||||
// 渲染引擎
|
||||
head := map[string]string{}
|
||||
body, ok := m.Optionv("body").(io.Reader)
|
||||
if !ok && len(arg) > 0 && method != "GET" {
|
||||
if !ok && len(arg) > 0 && method != SPIDE_GET {
|
||||
switch arg[0] {
|
||||
case "file":
|
||||
case SPIDE_FILE:
|
||||
if f, e := os.Open(arg[1]); m.Warn(e != nil, "%s", e) {
|
||||
return
|
||||
} else {
|
||||
defer f.Close()
|
||||
body, arg = f, arg[2:]
|
||||
}
|
||||
case "data":
|
||||
case SPIDE_DATA:
|
||||
body, arg = bytes.NewBufferString(arg[1]), arg[2:]
|
||||
case "part":
|
||||
case SPIDE_PART:
|
||||
buf := &bytes.Buffer{}
|
||||
mp := multipart.NewWriter(buf)
|
||||
for i := 1; i < len(arg)-1; i += 2 {
|
||||
@ -188,16 +219,16 @@ func init() {
|
||||
}
|
||||
}
|
||||
mp.Close()
|
||||
head["Content-Type"] = mp.FormDataContentType()
|
||||
head[ContentType] = mp.FormDataContentType()
|
||||
body = buf
|
||||
case "form":
|
||||
case SPIDE_FORM:
|
||||
data := []string{}
|
||||
for i := 1; i < len(arg)-1; i += 2 {
|
||||
data = append(data, url.QueryEscape(arg[i])+"="+url.QueryEscape(arg[i+1]))
|
||||
}
|
||||
body = bytes.NewBufferString(strings.Join(data, "&"))
|
||||
head["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
case "json":
|
||||
head[ContentType] = ContentFORM
|
||||
case SPIDE_JSON:
|
||||
arg = arg[1:]
|
||||
fallthrough
|
||||
default:
|
||||
@ -206,7 +237,7 @@ func init() {
|
||||
kit.Value(data, arg[i], arg[i+1])
|
||||
}
|
||||
if b, e := json.Marshal(data); m.Assert(e) {
|
||||
head["Content-Type"] = "application/json"
|
||||
head[ContentType] = ContentJSON
|
||||
body = bytes.NewBuffer(b)
|
||||
}
|
||||
m.Log(ice.LOG_EXPORT, "json: %s", kit.Format(data))
|
||||
@ -223,14 +254,14 @@ func init() {
|
||||
m.Assert(e)
|
||||
|
||||
// 请求变量
|
||||
kit.Fetch(value["cookie"], func(key string, value string) {
|
||||
kit.Fetch(value[SPIDE_COOKIE], func(key string, value string) {
|
||||
req.AddCookie(&http.Cookie{Name: key, Value: value})
|
||||
m.Info("%s: %s", key, value)
|
||||
})
|
||||
kit.Fetch(value["header"], func(key string, value string) {
|
||||
kit.Fetch(value[SPIDE_HEADER], func(key string, value string) {
|
||||
req.Header.Set(key, value)
|
||||
})
|
||||
list := kit.Simple(m.Optionv("header"))
|
||||
list := kit.Simple(m.Optionv(SPIDE_HEADER))
|
||||
for i := 0; i < len(list)-1; i += 2 {
|
||||
req.Header.Set(list[i], list[i+1])
|
||||
m.Info("%s: %s", list[i], list[i+1])
|
||||
@ -244,7 +275,7 @@ func init() {
|
||||
if web.Client == nil {
|
||||
web.Client = &http.Client{Timeout: kit.Duration(kit.Format(client["timeout"]))}
|
||||
}
|
||||
m.Info("%s: %s", req.Header.Get("Content-Length"), req.Header.Get("Content-Type"))
|
||||
m.Info("%s: %s", req.Header.Get(ContentLength), req.Header.Get(ContentType))
|
||||
|
||||
// 发送请求
|
||||
res, e := web.Client.Do(req)
|
||||
@ -253,7 +284,7 @@ func init() {
|
||||
}
|
||||
|
||||
// 检查结果
|
||||
m.Cost("%s %s: %s", res.Status, res.Header.Get("Content-Length"), res.Header.Get("Content-Type"))
|
||||
m.Cost("%s %s: %s", res.Status, res.Header.Get(ContentLength), res.Header.Get(ContentType))
|
||||
if m.Warn(res.StatusCode != http.StatusOK, "%s", res.Status) {
|
||||
m.Set(ice.MSG_RESULT)
|
||||
// return
|
||||
@ -267,15 +298,15 @@ func init() {
|
||||
|
||||
// 解析引擎
|
||||
switch cache {
|
||||
case "cache":
|
||||
case SPIDE_CACHE:
|
||||
m.Optionv("response", res)
|
||||
m.Cmdy(CACHE, DOWNLOAD, res.Header.Get("Content-Type"), uri)
|
||||
m.Cmdy(CACHE, DOWNLOAD, res.Header.Get(ContentType), uri)
|
||||
m.Echo(m.Append(DATA))
|
||||
case "raw":
|
||||
case SPIDE_RAW:
|
||||
if b, e := ioutil.ReadAll(res.Body); m.Assert(e) {
|
||||
m.Echo(string(b))
|
||||
}
|
||||
case "msg":
|
||||
case SPIDE_MSG:
|
||||
var data map[string][]string
|
||||
m.Assert(json.NewDecoder(res.Body).Decode(&data))
|
||||
m.Info("res: %s", kit.Formats(data))
|
||||
@ -286,7 +317,7 @@ func init() {
|
||||
}
|
||||
m.Resultv(data[ice.MSG_RESULT])
|
||||
default:
|
||||
if strings.HasPrefix(res.Header.Get("Content-Type"), "text/html") {
|
||||
if strings.HasPrefix(res.Header.Get(ContentType), ContentHTML) {
|
||||
b, _ := ioutil.ReadAll(res.Body)
|
||||
m.Echo(string(b))
|
||||
break
|
||||
|
@ -2,12 +2,14 @@ package code
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
ice "github.com/shylinux/icebergs"
|
||||
"github.com/shylinux/icebergs/base/cli"
|
||||
"github.com/shylinux/icebergs/base/mdb"
|
||||
"github.com/shylinux/icebergs/base/nfs"
|
||||
"github.com/shylinux/icebergs/base/tcp"
|
||||
"github.com/shylinux/icebergs/base/web"
|
||||
kit "github.com/shylinux/toolkits"
|
||||
)
|
||||
@ -46,6 +48,18 @@ func init() {
|
||||
m.Cmd(cli.SYSTEM, "tar", "xvf", name)
|
||||
m.Echo(p)
|
||||
}},
|
||||
"start": {Name: "start source", Help: "启动", Hand: func(m *ice.Message, arg ...string) {
|
||||
port := m.Cmdx(tcp.PORT, "get")
|
||||
p := "var/daemon/" + port
|
||||
os.MkdirAll(p, ice.MOD_DIR)
|
||||
|
||||
m.Cmd(nfs.DIR, "usr/install/"+arg[0]).Table(func(index int, value map[string]string, head []string) {
|
||||
m.Cmd(cli.SYSTEM, "cp", "-r", value["path"], p)
|
||||
})
|
||||
|
||||
m.Option(cli.CMD_DIR, p)
|
||||
m.Cmdy(cli.DAEMON, arg[1])
|
||||
}},
|
||||
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
m.Option("fields", "time,progress,size,total,name,link")
|
||||
if len(arg) > 0 {
|
||||
|
@ -3,13 +3,10 @@ package es
|
||||
import (
|
||||
ice "github.com/shylinux/icebergs"
|
||||
"github.com/shylinux/icebergs/base/cli"
|
||||
"github.com/shylinux/icebergs/base/mdb"
|
||||
"github.com/shylinux/icebergs/base/tcp"
|
||||
"github.com/shylinux/icebergs/base/web"
|
||||
"github.com/shylinux/icebergs/core/code"
|
||||
kit "github.com/shylinux/toolkits"
|
||||
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -20,6 +17,7 @@ const ES = "es"
|
||||
var Index = &ice.Context{Name: ES, Help: "搜索",
|
||||
Configs: map[string]*ice.Config{
|
||||
ES: {Name: ES, Help: "搜索", Value: kit.Data(
|
||||
"address", "http://localhost:9200",
|
||||
"windows", "https://elasticsearch.thans.cn/downloads/elasticsearch/elasticsearch-7.3.2-windows-x86_64.zip",
|
||||
"darwin", "https://elasticsearch.thans.cn/downloads/elasticsearch/elasticsearch-7.3.2-darwin-x86_64.tar.gz",
|
||||
"linux", "https://elasticsearch.thans.cn/downloads/elasticsearch/elasticsearch-7.3.2-linux-x86_64.tar.gz",
|
||||
@ -30,23 +28,14 @@ var Index = &ice.Context{Name: ES, Help: "搜索",
|
||||
ice.CTX_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {}},
|
||||
|
||||
ES: {Name: "es hash=auto auto 启动:button 安装:button", Help: "搜索", Action: map[string]*ice.Action{
|
||||
"install": {Name: "install", Help: "安装", Hand: func(m *ice.Message, arg ...string) {
|
||||
"download": {Name: "download", Help: "安装", Hand: func(m *ice.Message, arg ...string) {
|
||||
m.Cmdy("web.code.install", "download", m.Conf(ES, kit.Keys(kit.MDB_META, runtime.GOOS)))
|
||||
}},
|
||||
|
||||
"start": {Name: "start", Help: "启动", Hand: func(m *ice.Message, arg ...string) {
|
||||
name := path.Base(m.Conf(ES, kit.Keys(kit.MDB_META, runtime.GOOS)))
|
||||
name = strings.Join(strings.Split(name, "-")[:2], "-")
|
||||
|
||||
port := m.Cmdx(tcp.PORT, "get")
|
||||
p := "var/daemon/" + port
|
||||
os.MkdirAll(p, ice.MOD_DIR)
|
||||
for _, dir := range []string{"bin", "jdk", "lib", "logs", "config", "modules", "plugins"} {
|
||||
m.Cmd(cli.SYSTEM, "cp", "-r", "usr/install/"+name+"/"+dir, p)
|
||||
}
|
||||
|
||||
m.Option(cli.CMD_DIR, p)
|
||||
m.Cmdy(cli.DAEMON, "bin/elasticsearch")
|
||||
m.Cmdy("web.code.install", "start", name, "bin/elasticsearch")
|
||||
}},
|
||||
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if len(arg) == 0 {
|
||||
@ -55,66 +44,43 @@ var Index = &ice.Context{Name: ES, Help: "搜索",
|
||||
}
|
||||
|
||||
m.Richs(cli.DAEMON, "", arg[0], func(key string, value map[string]interface{}) {
|
||||
m.Cmdy("web.spide", "dev", "raw", "GET", "http://localhost:9200")
|
||||
m.Cmdy(web.SPIDE, web.SPIDE_DEV, web.SPIDE_RAW, web.SPIDE_GET, m.Conf(ES, "meta.address"))
|
||||
})
|
||||
}},
|
||||
|
||||
"GET": {Name: "GET 查看:button cmd=/", Help: "命令", Action: map[string]*ice.Action{}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if pod := m.Option("_pod"); pod != "" {
|
||||
m.Option("_pod", "")
|
||||
m.Cmdy(web.SPACE, pod, "web.code.es.GET", arg)
|
||||
return
|
||||
m.Cmdy(web.SPACE, pod, m.Prefix(cmd), arg)
|
||||
|
||||
if m.Result(0) != ice.ErrWarn || m.Result(1) != ice.ErrNotFound {
|
||||
return
|
||||
}
|
||||
m.Set(ice.MSG_RESULT)
|
||||
}
|
||||
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", "GET", kit.MergeURL2("http://localhost:9200", kit.Select("/", arg, 0))))))
|
||||
m.Option(web.SPIDE_HEADER, web.ContentType, web.ContentJSON)
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx(web.SPIDE, web.SPIDE_DEV, web.SPIDE_RAW,
|
||||
web.SPIDE_GET, kit.MergeURL2(m.Conf(ES, "meta.address"), kit.Select("/", arg, 0))))))
|
||||
}},
|
||||
"CMD": {Name: "CMD 执行:button method:select=GET|PUT|POST|DELETE cmd=/ data:textarea", Help: "命令", Action: map[string]*ice.Action{}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if pod := m.Option("_pod"); pod != "" {
|
||||
m.Option("_pod", "")
|
||||
m.Cmdy(web.SPACE, pod, "web.code.es.CMD", arg)
|
||||
return
|
||||
}
|
||||
m.Cmdy(web.SPACE, pod, m.Prefix(cmd), arg)
|
||||
|
||||
if arg[0] == "GET" {
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", arg[0], kit.MergeURL2("http://localhost:9200", arg[1])))))
|
||||
return
|
||||
}
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", arg[0], kit.MergeURL2("http://localhost:9200", arg[1]), "data", arg[2]))))
|
||||
}},
|
||||
|
||||
"index": {Name: "table index 创建:button", Help: "索引", Action: map[string]*ice.Action{}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if len(arg) == 0 {
|
||||
m.Cmdy(cli.DAEMON)
|
||||
return
|
||||
}
|
||||
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", "PUT", "http://localhost:9200/"+arg[0]))))
|
||||
}},
|
||||
|
||||
"mapping": {Name: "mapping index mapping 创建:button text:textarea", Help: "映射", Action: map[string]*ice.Action{}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
if len(arg) == 0 {
|
||||
m.Cmdy(cli.DAEMON)
|
||||
return
|
||||
}
|
||||
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", "PUT", "http://localhost:9200/"+arg[0]+"/_mapping/"+arg[1], "data", arg[2]))))
|
||||
}},
|
||||
|
||||
"document": {Name: "table index=index_test mapping=mapping_test id=1 查看:button 添加:button data:textarea", Help: "文档", Action: map[string]*ice.Action{
|
||||
mdb.INSERT: {Name: "insert", Help: "添加", Hand: func(m *ice.Message, arg ...string) {
|
||||
if len(arg) > 3 {
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", "PUT", "http://localhost:9200/"+arg[0]+"/"+arg[1]+"/"+arg[2], "data", arg[3]))))
|
||||
if m.Result(0) != ice.ErrWarn || m.Result(1) != ice.ErrNotFound {
|
||||
return
|
||||
}
|
||||
}},
|
||||
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
||||
m.Option("header", "Content-Type", "application/json")
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx("web.spide", "dev", "raw", "GET", "http://localhost:9200/"+arg[0]+"/"+arg[1]+"/"+arg[2]))))
|
||||
m.Set(ice.MSG_RESULT)
|
||||
}
|
||||
|
||||
m.Option(web.SPIDE_HEADER, web.ContentType, web.ContentJSON)
|
||||
prefix := []string{web.SPIDE, web.SPIDE_DEV, web.SPIDE_RAW, arg[0], kit.MergeURL2(m.Conf(ES, "meta.address"), arg[1])}
|
||||
|
||||
if len(arg) > 2 {
|
||||
prefix = append(prefix, web.SPIDE_DATA, arg[2])
|
||||
}
|
||||
m.Echo(kit.Formats(kit.UnMarshal(m.Cmdx(prefix))))
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
@ -27,16 +27,14 @@ curl http://localhost:9200
|
||||
`
|
||||
|
||||
# field command web.code.es.command option `{ _pod centos.remote }`
|
||||
field GET web.code.es.GET style command args `[ /index_test/test_type/1 ]` option `{ _pod centos.remote }`
|
||||
field "查询数据" web.code.es.GET args `[ /index_test/test_type/1 ]` style command option `{ _pod centos.remote }`
|
||||
|
||||
field CMD web.code.es.CMD style command args `[ POST /index_test/test_type/1 ] ` content `{
|
||||
field "提交数据" web.code.es.CMD args `[ POST /index_test/test_type/1 ] ` content `{
|
||||
"name": "lisi",
|
||||
"age" : "12"
|
||||
}` option `{ _pod centos.remote }`
|
||||
|
||||
field es web.code.es.es
|
||||
field install web.code.install
|
||||
return
|
||||
|
||||
field document web.code.es.document
|
||||
}` style command option `{ _pod centos.remote }`
|
||||
|
||||
section 管理
|
||||
field "服务管理" web.code.es.es
|
||||
field "进程管理" cli.daemon
|
||||
field "下载管理" web.code.install
|
||||
|
Loading…
x
Reference in New Issue
Block a user