1
0
forked from x/ContextOS

add plugin.js

This commit is contained in:
shaoying 2019-05-24 00:21:06 +08:00
parent 281d6cff52
commit c049690d83
8 changed files with 401 additions and 63 deletions

View File

@ -172,7 +172,12 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心",
m.Cmdy("ctx.config", "runtime", arg[0])
return
}
m.Conf("runtime", arg[0], arg[1])
if arg[0] == "node.route" && m.Confs("runtime", "work.serve") {
m.Conf("runtime", "work.route", arg[1])
return
}
m.Echo(arg[1])
}
return
@ -664,6 +669,10 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心",
}
return
}},
"notice": &ctx.Command{Name: "notice", Help: "睡眠, time(ns/us/ms/s/m/h): 时间值(纳秒/微秒/毫秒/秒/分钟/小时)", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Cmd("cli.system", "osascript", "-e", fmt.Sprintf("display notification \"%s\"", kit.Select("", arg, 0)))
return
}},
"init": &ctx.Command{Name: "init", Help: "停止服务", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Conf("runtime", "host.GOARCH", runtime.GOARCH)

View File

@ -2,6 +2,7 @@ package nfs
import (
"contexts/ctx"
"crypto/md5"
"toolkit"
"crypto/sha1"
@ -954,6 +955,15 @@ func (nfs *NFS) Start(m *ctx.Message, arg ...string) bool {
nfs.Send(meta, v)
}
for _, k := range msg.Meta[body] {
if k == "binary" {
switch d := msg.Data[k].(type) {
case []byte:
nfs.Send("size", len(d))
n, e := nfs.io.Write(d)
m.Log("info", "send %v %v", n, e)
}
continue
}
for _, v := range msg.Meta[k] {
nfs.Send(k, v)
}
@ -1279,6 +1289,43 @@ var Index = &ctx.Context{Name: "nfs", Help: "存储中心",
return
}},
"hash": &ctx.Command{Name: "hash filename", Help: "查找文件路径", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
dir, name := path.Split(arg[0])
m.Append("dir", dir)
m.Append("name", name)
m.Append("type", strings.TrimPrefix(path.Ext(arg[0]), "."))
if s, e := os.Stat(arg[0]); e == nil && !s.IsDir() {
m.Append("size", s.Size())
if f, e := os.Open(arg[0]); e == nil {
defer f.Close()
md := md5.New()
io.Copy(md, f)
h := md.Sum(nil)
m.Echo(hex.EncodeToString(h[:]))
}
}
return
}},
"copy": &ctx.Command{Name: "copy to from", Help: "查找文件路径", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
dir, _ := path.Split(arg[0])
m.Assert(os.MkdirAll(dir, 0777))
to, e := os.Create(arg[0])
m.Assert(e)
defer to.Close()
for _, from := range arg[1:] {
f, e := os.Open(from)
m.Assert(e)
defer f.Close()
n, e := io.Copy(to, f)
m.Assert(e)
m.Log("info", "copy %d from %s to %s", n, from, arg[0])
}
m.Echo(arg[0])
return
}},
"path": &ctx.Command{Name: "path filename", Help: "查找文件路径", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
return
@ -1294,12 +1341,18 @@ var Index = &ctx.Context{Name: "nfs", Help: "存储中心",
})
return
}},
"load": &ctx.Command{Name: "load file [buf_size [pos]]", Help: "加载文件, buf_size: 加载大小, pos: 加载位置", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if p, f, e := open(m, arg[0]); e == nil {
defer f.Close()
pos := kit.Int(kit.Select("0", arg, 2))
size := kit.Int(m.Confx("buf_size", arg, 1))
if size == -1 {
s, e := f.Stat()
m.Assert(e)
size = int(s.Size())
}
buf := make([]byte, size)
if l, e := f.ReadAt(buf, int64(pos)); e == io.EOF || m.Assert(e) {

View File

@ -2,6 +2,10 @@ package ssh
import (
"contexts/ctx"
"encoding/hex"
"io"
"os"
"path"
"strings"
"toolkit"
)
@ -35,18 +39,25 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
Configs: map[string]*ctx.Config{
"node": &ctx.Config{Name: "node", Value: map[string]interface{}{}, Help: "节点信息"},
"cert": &ctx.Config{Name: "cert", Value: map[string]interface{}{}, Help: "用户信息"},
"file": &ctx.Config{Name: "file", Value: map[string]interface{}{}, Help: "用户信息"},
"trust": &ctx.Config{Name: "trust", Value: map[string]interface{}{"fresh": false, "user": true, "up": true}, Help: "可信节点"},
"timer": &ctx.Config{Name: "timer", Value: map[string]interface{}{"interval": "10s", "timer": ""}, Help: "断线重连"},
"componet": &ctx.Config{Name: "componet", Value: map[string]interface{}{
"index": []interface{}{
map[string]interface{}{"componet_name": "pwd", "componet_help": "pwd", "componet_tmpl": "componet",
"componet_view": "FlashList", "componet_init": "initFlashList",
"componet_ctx": "nfs", "componet_cmd": "pwd",
"componet_view": "FlashList", "componet_init": "initFlashList.js",
"componet_ctx": "nfs", "componet_cmd": "pwd", "componet_args": []interface{}{"@text"}, "inputs": []interface{}{
map[string]interface{}{"type": "button", "value": "当前", "click": "show"},
map[string]interface{}{"type": "button", "value": "所有", "click": "show"},
map[string]interface{}{"type": "text", "name": "text"},
},
"display_result": "", "display_append": "",
},
map[string]interface{}{"componet_name": "dir", "componet_help": "dir", "componet_tmpl": "componet",
"componet_view": "FlashList", "componet_init": "initFlashList",
"componet_ctx": "nfs", "componet_cmd": "dir",
"componet_view": "FlashList", "componet_init": "initFlashList.js",
"componet_ctx": "nfs", "componet_cmd": "dir", "componet_args": []interface{}{"@text"}, "inputs": []interface{}{
map[string]interface{}{"type": "text", "name": "text"},
},
"display_result": "", "display_append": "",
},
},
@ -89,6 +100,8 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
"node [create|check text|trust node]",
"user [create|check text|share role node...|proxy node|trust node]",
"work [create node name|check node name]",
"file [import]",
"tool ",
}, Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Add("append", "key", "node.cert")
@ -204,6 +217,10 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
}
switch arg[1] {
case "serve": // 注册节点
m.Conf("runtime", "work.serve", true)
m.Conf("runtime", "work.route", m.Conf("runtime", "node.route"))
case "create": // 创建证书
user := m.Conf("runtime", "user.route")
if user == "" {
@ -234,10 +251,75 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
m.Echo("true")
}
}
case "tool":
switch arg[1] {
case "check": // 数字验签
m.Cmdy("ssh.remote", arg[2], "check", arg[0])
m.Cmdy("ssh.remote", arg[2], "check", arg[0], arg[3:])
case "run":
m.Cmdy("ssh.remote", arg[2], "check", arg[0], "run", arg[3:])
}
case "file":
switch arg[1] {
case "import":
if msg := m.Cmd("nfs.hash", arg[2]); msg.Results(0) {
h := msg.Result(0)
m.Conf("file", kit.Hashs(h, msg.Append("name")), map[string]interface{}{
"create_time": m.Time(),
"create_user": m.Option("username"),
"name": msg.Append("name"),
"type": msg.Append("type"),
"size": msg.Append("size"),
"hash": h,
})
m.Cmdy("nfs.copy", path.Join("var/file/hash", h[:2], h), arg[2])
}
case "fetch":
if m.Confs("file", arg[2]) {
m.Echo(arg[2])
break
}
msg := m.Cmd("ssh.remote", arg[3], "check", "file", arg[2])
h := msg.Append("hash")
m.Conf("file", arg[2], map[string]interface{}{
"create_time": m.Time(),
"create_user": m.Option("username"),
"name": msg.Append("name"),
"type": msg.Append("type"),
"size": msg.Append("size"),
"hash": h,
})
p := path.Join("var/file/hash", h[:2], h)
if m.Cmds("nfs.path", p) {
m.Echo(arg[2])
break
}
m.Cmdy("nfs.copy", p)
f, e := os.Create(p)
m.Assert(e)
for i := 0; int64(i) < msg.Appendi("size"); i += 1024 {
msg := m.Cmd("ssh.remote", arg[3], "check", "file", arg[2], 1, 1024, i)
for _, d := range msg.Meta["data"] {
b, e := hex.DecodeString(d)
m.Assert(e)
_, e = f.Write(b)
m.Assert(e)
}
}
default:
m.Confm("file", arg[1], func(file map[string]interface{}) {
m.Append("hash", file["hash"])
m.Append("size", file["size"])
m.Append("type", file["type"])
m.Append("name", file["name"])
})
m.Table()
}
}
return
@ -259,6 +341,7 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
m.Echo(m.Cmdx("aaa.rsa", "sign", m.Conf("runtime", "user.key"), arg[2]))
}
}
case "work": // 工作验签
switch arg[1] {
case "search":
@ -283,14 +366,69 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
m.Echo(arg[1])
}
}
case "tool":
m.Confm("componet", func(key string, index int, value map[string]interface{}) {
m.Add("append", "key", key)
m.Add("append", "index", index)
m.Add("append", "name", value["componet_name"])
m.Add("append", "help", value["componet_help"])
})
m.Table()
if len(arg) == 1 {
m.Confm("componet", func(key string, index int, value map[string]interface{}) {
m.Add("append", "key", key)
m.Add("append", "index", index)
m.Add("append", "name", value["componet_name"])
m.Add("append", "help", value["componet_help"])
})
m.Table()
break
}
switch arg[1] {
case "run":
tool := m.Confm("componet", []string{arg[2], arg[3]})
msg := m.Find(kit.Format(tool["componet_ctx"]))
msg.Cmd(tool["componet_cmd"], arg[4:]).CopyTo(m)
default:
m.Confm("componet", arg[1:], func(value map[string]interface{}) {
m.Add("append", "name", value["componet_name"])
m.Add("append", "help", value["componet_help"])
m.Add("append", "view", value["componet_view"])
m.Add("append", "init", m.Cmdx("nfs.load", path.Join("usr/librarys/plugin", kit.Format(value["componet_init"])), -1))
m.Add("append", "inputs", kit.Format(value["inputs"]))
})
m.Table()
}
break
case "file":
if len(arg) == 2 {
m.Confm("file", arg[1], func(file map[string]interface{}) {
m.Append("hash", file["hash"])
m.Append("size", file["size"])
m.Append("type", file["type"])
m.Append("name", file["name"])
})
m.Table()
break
}
h := m.Conf("file", []string{arg[1], "hash"})
if f, e := os.Open(path.Join("var/file/hash", h[:2], h)); e == nil {
defer f.Close()
pos := kit.Int(kit.Select("0", arg, 4))
size := kit.Int(kit.Select("1024", arg, 3))
count := kit.Int(kit.Select("3", arg, 2))
buf := make([]byte, count*size)
if l, e := f.ReadAt(buf, int64(pos)); e == io.EOF || m.Assert(e) {
for i := 0; i < count; i++ {
if l < (i+1)*size {
m.Add("append", "data", hex.EncodeToString(buf[i*size:l]))
break
}
m.Add("append", "data", hex.EncodeToString(buf[i*size:(i+1)*size]))
}
}
}
}
return
}},
@ -365,12 +503,6 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
m.Cmdm(arg)
return
}},
"componet": &ctx.Command{Name: "componet", Help: "组件", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
tool := m.Confm("componet", []string{arg[0], arg[1]})
msg := m.Find(kit.Format(tool["componet_ctx"]))
msg.Cmd(tool["componet_cmd"]).CopyTo(m)
return
}},
"remote": &ctx.Command{Name: "remote auto|dial|listen args...", Help: "连接", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
// 设备证书
if !m.Confs("runtime", "node.cert") || !m.Confs("runtime", "node.key") {

View File

@ -284,12 +284,25 @@ var Index = &ctx.Context{Name: "chat", Help: "会议中心",
m.Table()
break
}
if len(arg) == 3 {
m.Confm("flow", []string{arg[1], "tool", arg[2], "list"}, func(index int, value map[string]interface{}) {
m.Add("append", "node", value["node"])
m.Add("append", "group", value["group"])
m.Add("append", "index", value["index"])
m.Add("append", "name", value["name"])
m.Confm("flow", []string{arg[1], "tool", arg[2], "list"}, func(index int, tool map[string]interface{}) {
m.Add("append", "river", arg[1])
m.Add("append", "storm", arg[2])
m.Add("append", "action", index)
m.Add("append", "node", tool["node"])
m.Add("append", "group", tool["group"])
m.Add("append", "index", tool["index"])
m.Option("username", "shy")
msg := m.Cmd("ssh.cert", "tool", "check", tool["node"], tool["group"], tool["index"])
m.Add("append", "name", msg.Append("name"))
m.Add("append", "help", msg.Append("help"))
m.Add("append", "view", msg.Append("view"))
m.Add("append", "init", msg.Append("init"))
m.Add("append", "inputs", msg.Append("inputs"))
})
m.Table()
break
@ -297,7 +310,7 @@ var Index = &ctx.Context{Name: "chat", Help: "会议中心",
if tool := m.Confm("flow", []string{arg[1], "tool", arg[2], "list", arg[3]}); tool != nil {
m.Option("username", "shy")
m.Cmdy("ssh.remote", tool["node"], "componet", tool["group"], tool["index"])
m.Cmdy("ssh.cert", "tool", "run", tool["node"], tool["group"], tool["index"], arg[4:])
break
}
}

View File

@ -1,9 +1,9 @@
var page = Page({
conf: {border: 4, banner: 105},
size: function(event, sizes) {
sizes = sizes || {}
conf: {border: 4, banner: 105, layout: {river:160, source:60, action:60, storm:160}},
onlayout: function(event, sizes) {
var width = document.body.offsetWidth
var height = document.body.offsetHeight-page.conf.banner
sizes = sizes || {}
sizes.river == undefined && (sizes.river = page.river.offsetWidth-page.conf.border)
sizes.storm == undefined && (sizes.storm = page.storm.offsetWidth-page.conf.border)
@ -14,11 +14,16 @@ var page = Page({
sizes.action == undefined && (sizes.action = page.action.offsetHeight-page.conf.border)
sizes.source == undefined && (sizes.source = page.source.offsetHeight-page.conf.border)
sizes.target = height - sizes.action - sizes.source - 2*page.conf.border
if (sizes.action == -1) {
sizes.action = height
sizes.target = 0
sizes.source = 0
}
page.target.Size(sizes.width, sizes.target)
page.source.Size(sizes.width, sizes.source)
page.action.Size(sizes.width, sizes.action)
kit.History.add("lay", sizes)
},
oncontrol: function(event, target, action) {
switch (action) {
case "control":
@ -36,6 +41,7 @@ var page = Page({
break
}
},
initOcean: function(page, pane, form, output) {
var table = kit.AppendChild(output, "table")
var ui = kit.AppendChild(pane, [{view: ["create ocean"], list: [
@ -152,7 +158,7 @@ var page = Page({
}}}])
pane.Size = function(width, height) {
pane.style.display = width==0? "none": "block"
pane.style.display = (width==0 || height==0)? "none": "block"
pane.style.width = width+"px"
pane.style.height = height+"px"
ui.first.style.width = (width-7)+"px"
@ -178,20 +184,18 @@ var page = Page({
},
}
pane.Show = function() {
output.Update(["storm", river, water], "text", ["node", "name"], "index", false, function(line, index) {
if (event.shiftKey) {
page.target.Send("field", JSON.stringify({
componet_group: "index",
componet_name: "river",
cmds: ["storm", river, water, index],
input: [{type: "input", data: {name: "hi", value: line.cmd}}]
}))
return
}
form.Run(["storm", river, water, index], function(msg) {
msg.append && msg.append[0]?
output.Update(["storm", river, water], "plugin", ["node", "name"], "index", false, function(line, index, event, args, cbs) {
var cmds = ["storm", river, water, index].concat(args)
event.shiftKey? page.target.Send("field", JSON.stringify({
componet_group: "index", componet_name: "river",
cmds: cmds, input: [{type: "input", data: {name: "hi", value: line.cmd}}]
})): form.Run(cmds, function(msg) {
event.ctrlKey && (msg.append && msg.append[0]?
page.target.Send("table", JSON.stringify(ctx.Table(msg))):
page.target.Send("text", msg.result.join(""))
page.target.Send("text", msg.result.join("")))
cbs(msg)
})
})
}
@ -200,8 +204,20 @@ var page = Page({
var name = prompt("name")
name && form.Run(["river", "tool", river, water, "add", name], pane.Show)
},
"恢复": function(event) {
page.onlayout(event, page.conf.layout)
},
"放大": function(event) {
page.onlayout(event, {action:300})
},
"最宽": function(event) {
page.onlayout(event, {river:0, storm:0})
},
"最大": function(event) {
page.onlayout(event, {river:0, action: -1, storm:0})
},
}
return {"button": ["添加"], "action": pane.Action}
return {"button": ["添加", "恢复", "放大", "最宽", "最大"], "action": pane.Action}
},
initStorm: function(page, pane, form, output) {
var river = ""
@ -291,10 +307,10 @@ var page = Page({
output.Append = function(type, line, key, which, cb) {
var index = list.length
type = line.type || type
var ui = kit.AppendChild(output, page.View(type, line, key, function(event) {
var ui = page.View(output, type, line, key, function(event, cmds, cbs) {
output.Select(index), pane.which.set(line[which])
typeof cb == "function" && cb(line, index, event)
}))
typeof cb == "function" && cb(line, index, event, cmds, cbs)
})
if (type == "table") {
kit.OrderTable(ui.last)
}
@ -334,6 +350,6 @@ var page = Page({
return conf
})
page.size(null, {river:160, source:60, action:60, storm:160})
page.onlayout(null, page.conf.layout)
},
})

View File

@ -1,9 +1,10 @@
function Page(page) {
page.__proto__ = {
_id: 1, ID: function() {
return this._id++
},
Sync: function(m) {
var meta = m
var data = ""
var list = []
var meta = m, data = "", list = []
return {
change: function(cb) {
list.push(cb)
@ -31,19 +32,24 @@ function Page(page) {
},
}
},
View: function(type, line, key, cb) {
View: function(parent, type, line, key, cb) {
var ui = {}
var result = []
switch (type) {
case "icon":
return [{view: ["item", "div"], list: [{type: "img", data: {src: line[key[0]]}}, {}]}]
result = [{view: ["item", "div"], list: [{type: "img", data: {src: line[key[0]]}}, {}]}]
break
case "text":
switch (key.length) {
case 0:
return [{view: ["item", "div", "null"], click: cb}]
result = [{view: ["item", "div", "null"], click: cb}]
break
case 1:
return [{view: ["item", "div", line[key[0]]], click: cb}]
result = [{view: ["item", "div", line[key[0]]], click: cb}]
break
default:
return [{view: ["item", "div", line[key[0]]+"("+line[key[1]]+")"], click: cb}]
result = [{view: ["item", "div", line[key[0]]+"("+line[key[1]]+")"], click: cb}]
}
break
@ -63,7 +69,7 @@ function Page(page) {
list.push({view: ["", "tr"], list: line})
}
var result = [{view: [""], list: [{view: ["", "table"], list: list}]}]
return result
break
case "field":
var data = JSON.parse(line.text)
@ -73,6 +79,7 @@ function Page(page) {
}
var result = [{view: ["", "fieldset"], list: [
{text: ["", "legend"]},
{name: "form", view: ["", "form"], dataset: {
componet_group: data.componet_group,
componet_name: data.componet_name,
@ -81,12 +88,97 @@ function Page(page) {
{name: "table", view: ["", "table"]},
{view: ["", "code"], list: [{name: "code", view: ["", "pre"]}]},
]}]
return result
break
case "plugin":
var id = "plugin"+page.ID()
var input = [{type: "input", style: {"display": "none"}}]
JSON.parse(line.inputs || "[]").forEach(function(item, index, inputs) {
function run(event) {
var args = []
ui.option.querySelectorAll("input").forEach(function(item, index){
if (index==0) {
return
}
if (item.type == "text") {
args.push(item.value)
}
})
ui.option.Run(event, args, function(msg) {
ui.option.ondaemon(msg)
})
}
item.type == "button"? item.onclick = function(event) {
var plugin = page[id];
(plugin[item.click] || function() {
run(event)
})(item, index, inputs, event, ui.option)
}: item.onkeyup = function(event) {
event.key == "Enter" && (index == inputs.length-1? run(event): event.target.nextSibling.focus())
if (event.ctrlKey) {
switch (event.key) {
case "k":
kit.DelText(target, target.selectionStart)
break
case "u":
kit.DelText(target, 0, target.selectionEnd)
break
case "w":
var start = target.selectionStart-2
var end = target.selectionEnd-1
for (var i = start; i >= 0; i--) {
if (target.value[end] == " " && target.value[i] != " ") {
break
}
if (target.value[end] != " " && target.value[i] == " ") {
break
}
}
kit.DelText(target, i+1, end-i)
break
case "c":
ui.output.innerHTML = ""
break
case "r":
ui.output.innerHTML = ""
case "j":
run(event)
break
case "l":
page.action.scrollTo(0, ui.option.parentNode.offsetTop)
break
case "m":
event.stopPropagation()
var uis = page.View(parent, type, line, key, cb)
page.action.scrollTo(0, uis.option.parentNode.offsetTop)
ui.option.querySelectorAll("input")[1].focus()
break
}
}
}
input.push({type: "input", data: item})
})
var result = [{view: [line.view, "fieldset"], data: {id: id}, list: [
{script: "Plugin("+id+","+line.init+")"},
{text: [line.name, "legend"]},
{name: "option", view: ["option", "form"], data: {Run: cb}, list: input},
{name: "output", view: ["output", "div"]},
]}]
break
}
ui = kit.AppendChild(parent, result)
return ui
},
reload: function() {
location.reload()
},
showToast: function(text) {
},
onscroll: function(event, target, action) {
switch (action) {
@ -97,9 +189,6 @@ function Page(page) {
break
}
},
showToast: function(text) {
},
initHeader: function(page, field, option, output) {
return [{"text": ["shycontext", "div", "title"]}]
@ -138,7 +227,7 @@ function Page(page) {
return false
}
pane.Size = function(width, height) {
pane.style.display = width==0? "none": "block"
pane.style.display = (width==0 || height==0)? "none": "block"
pane.style.width = width+"px"
pane.style.height = height+"px"
}
@ -168,7 +257,9 @@ function Page(page) {
window.onload = function() {
page.init(page)
window.onresize = page.size
window.onresize = function(event) {
page.onlayout && page.onlayout(event)
}
document.body.onkeydown = function(event) {
page.onscroll && page.onscroll(event, document.body, "scroll")
}
@ -178,3 +269,13 @@ function Page(page) {
}
return page
}
function Plugin(field, plugin) {
var option = field.querySelector("form.option")
var output = field.querySelector("div.output")
plugin.__proto__ = {
field: field,
}
plugin.init(page, page.action, field, option, output)
page[field.id] = plugin
}

View File

@ -3,7 +3,7 @@ kit = toolkit = {
isSpace: function(c) {
return c == " " || c == "Enter"
},
History: {dir: [], pod: [], ctx: [], cmd: [], txt: [], key: [],
History: {dir: [], pod: [], ctx: [], cmd: [], txt: [], key: [], lay: [],
add: function(type, data) {
var list = this[type] || []
data && list.push({time: Date.now(), data: data})
@ -240,6 +240,11 @@ kit = toolkit = {
child.type = "code"
child.list = [{"type": "pre" ,"data": {"innerText": child.code[0]}, "name": child.code[1]}]
child.code.length > 2 && (child.data["className"] = child.code[2])
} else if (child.script) {
child.data.innerHTML = child.script
child.type = "script"
} else if (child.include) {
child.data["src"] = child.include[0]
child.data["type"] = "text/javascript"

View File

@ -268,6 +268,15 @@ $ cat etc/local.shy
chat模块提供了信息管理。
访问http://localhost:9094/chat
启动服务节点
boot.sh
```
~cli
runtime work.route mac
```
### 所有目录
#### 一级目录