1
0
mirror of https://shylinux.com/x/ContextOS synced 2025-04-25 16:58:06 +08:00

add dblclick.example.js

This commit is contained in:
shaoying 2019-07-04 22:38:31 +08:00
parent ad2e298c99
commit dc711c73c7
13 changed files with 187 additions and 162 deletions

View File

@ -1,13 +1,10 @@
package cli
import (
"bufio"
"bytes"
"contexts/ctx"
"encoding/csv"
"encoding/json"
"io"
"net/http"
"os/exec"
"os/user"
"path"
@ -133,6 +130,15 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心",
"init": "etc/init.shy", "exit": "etc/exit.shy",
},
}, Help: "系统环境, shell: path, cmd, arg, dir, env, active, daemon; "},
"plugin": &ctx.Config{Name: "plugin", Value: map[string]interface{}{
"go": map[string]interface{}{
"build": []interface{}{"go", "build", "-buildmode=plugin"},
"next": []interface{}{"so", "load"},
},
"so": map[string]interface{}{
"load": []interface{}{"load"},
},
}, Help: "免密登录"},
"daemon": &ctx.Config{Name: "daemon", Value: map[string]interface{}{}, Help: "守护任务"},
"action": &ctx.Config{Name: "action", Value: map[string]interface{}{}, Help: "交互任务"},
@ -189,15 +195,6 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心",
"path": "usr/work",
}, Help: "免密登录"},
"plugin": &ctx.Config{Name: "plugin", Value: map[string]interface{}{
"go": map[string]interface{}{
"build": []interface{}{"go", "build", "-buildmode=plugin"},
"next": []interface{}{"so", "load"},
},
"so": map[string]interface{}{
"load": []interface{}{"load"},
},
}, Help: "免密登录"},
"timer": &ctx.Config{Name: "timer", Value: map[string]interface{}{}, Help: "定时器"},
"timer_next": &ctx.Config{Name: "timer_next", Value: "", Help: "定时器"},
@ -544,10 +541,91 @@ var Index = &ctx.Context{Name: "cli", Help: "管理中心",
}
return
}},
"plugin": &ctx.Command{Name: "plugin [action] file", Help: "", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
suffix, action, target := "go", "build", path.Join(m.Conf("runtime", "boot.ctx_home"), "src/examples/app/bench.go")
if len(arg) == 0 {
arg = append(arg, target)
}
if cs := strings.Split(arg[0], "."); len(cs) > 1 {
suffix = cs[len(cs)-1]
} else if cs := strings.Split(arg[1], "."); len(cs) > 1 {
suffix, action, arg = cs[len(cs)-1], arg[0], arg[1:]
}
if target = m.Cmdx("nfs.path", arg[0]); target == "" {
target = m.Cmdx("nfs.path", path.Join("src/plugin/", arg[0]))
}
for suffix != "" && action != "" {
m.Log("info", "%v %v %v", suffix, action, target)
cook := m.Confv("plugin", suffix)
next := strings.Replace(target, "."+suffix, "."+kit.Chains(cook, "next.0"), -1)
args := []string{}
if suffix == "so" {
if p, e := plugin.Open(target); m.Assert(e) {
s, e := p.Lookup("Index")
m.Assert(e)
w := *(s.(**ctx.Context))
w.Name = kit.Select(w.Name, arg, 1)
c.Register(w, nil)
m.Spawn(w).Cmd("_init", arg[1:])
}
} else {
if suffix == "go" {
args = append(args, "-o", next)
}
m.Assert(m.Cmd("cli.system", kit.Chain(cook, action), args, target))
}
suffix = kit.Chains(cook, "next.0")
action = kit.Chains(cook, "next.1")
target = next
}
return
}},
"proc": &ctx.Command{Name: "proc", Help: "", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Cmdy("cli.system", "ps", kit.Select("ax", arg, 0))
return
}},
"quit": &ctx.Command{Name: "quit code", Help: "停止服务", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Conf("runtime", "boot.count", m.Confi("runtime", "boot.count")+1)
code := kit.Select("0", arg, 0)
switch code {
case "0":
m.Cmd("cli.source", m.Conf("system", "script.exit"))
m.Echo("quit")
case "1":
if m.Option("cli.modal") != "action" {
m.Cmd("cli.source", m.Conf("system", "script.exit"))
m.Echo("restart")
}
case "2":
m.Echo("term")
}
m.Append("time", m.Time())
m.Append("code", code)
m.Echo(", wait 1s\n").Table()
m.Gos(m, func(m *ctx.Message) {
defer func() {
os.Exit(kit.Int(code))
}()
time.Sleep(time.Second * 1)
m.Cmd("cli._exit")
m.Cmd("nfs._exit")
})
return
}},
"_exit": &ctx.Command{Name: "_exit", Help: "解析脚本, script: 脚本文件, stdio: 命令终端, snippet: 代码片段", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Confm("daemon", func(key string, info map[string]interface{}) {
m.Cmd("cli.system", key, "stop")
})
return
}},
"project": &ctx.Command{Name: "project", Help: "", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
switch arg[0] {
@ -695,7 +773,7 @@ var version = struct {
m.Append("directory", "")
return
}},
"upgrade": &ctx.Command{Name: "upgrade project|bench|system|portal|script", Help: "服务升级", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
"upgrade": &ctx.Command{Name: "upgrade project|bench|system|plugin|portal|script", Help: "服务升级", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmdy("ctx.config", "upgrade")
return
@ -706,6 +784,12 @@ var version = struct {
m.Cmd("cli.publish")
return
}
if len(arg) > 0 && arg[0] == "plugin" {
m.Cmdy("web.get", "dev", fmt.Sprintf("publish/%s", arg[1]),
"upgrade", "plugin", "save", path.Join("src/plugin", arg[1]))
m.Cmdy("cli.plugin", "test.so")
return
}
if len(arg) > 1 && arg[0] == "script" {
miss := ""
if len(arg) > 2 {
@ -812,136 +896,7 @@ var version = struct {
)
return
}},
"quit": &ctx.Command{Name: "quit code", Help: "停止服务", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Conf("runtime", "boot.count", m.Confi("runtime", "boot.count")+1)
code := kit.Select("0", arg, 0)
switch code {
case "0":
m.Cmd("cli.source", m.Conf("system", "script.exit"))
m.Echo("quit")
case "1":
if m.Option("cli.modal") != "action" {
m.Cmd("cli.source", m.Conf("system", "script.exit"))
m.Echo("restart")
}
case "2":
m.Echo("term")
}
m.Append("time", m.Time())
m.Append("code", code)
m.Echo(", wait 1s\n").Table()
m.Gos(m, func(m *ctx.Message) {
defer func() {
os.Exit(kit.Int(code))
}()
time.Sleep(time.Second * 1)
m.Cmd("cli._exit")
m.Cmd("nfs._exit")
})
return
}},
"test": &ctx.Command{Name: "test file server", Help: "test", Hand: func(m *ctx.Message, c *ctx.Context, key string, args ...string) (e error) {
prefix0 := len("uri[")
prefix1 := len("request_param[")
if e = os.Mkdir("tmp", 0777); e != nil {
return
}
begin := time.Now()
f, e := os.Open(args[0])
defer f.Close()
bio := bufio.NewScanner(f)
output := map[string]*os.File{}
nreq := 0
for bio.Scan() {
word := strings.Split(bio.Text(), " ")
if len(word) != 2 {
continue
}
uri := word[0][prefix0 : len(word[0])-1]
arg := word[1][prefix1 : len(word[1])-1]
if output[uri] == nil {
output[uri], e = os.Create(path.Join("tmp", strings.Replace(uri, "/", "_", -1)+".txt"))
defer output[uri].Close()
}
nreq++
br := bytes.NewReader([]byte(arg))
res, e := http.Post(args[1]+uri, "application/json", br)
fmt.Fprintf(output[uri], uri)
fmt.Fprintf(output[uri], " ")
fmt.Fprintf(output[uri], arg)
fmt.Fprintf(output[uri], " ")
if e != nil {
fmt.Fprintf(output[uri], "%v", e)
} else if res.StatusCode != http.StatusOK {
fmt.Fprintf(output[uri], res.Status)
} else {
io.Copy(output[uri], res.Body)
}
fmt.Fprintf(output[uri], "\n")
}
m.Append("nuri", len(output))
m.Append("nreq", nreq)
m.Append("time", fmt.Sprintf("%s", time.Since(begin)))
m.Table()
return
}},
"_exit": &ctx.Command{Name: "_exit", Help: "解析脚本, script: 脚本文件, stdio: 命令终端, snippet: 代码片段", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
m.Confm("daemon", func(key string, info map[string]interface{}) {
m.Cmd("cli.system", key, "stop")
})
return
}},
"plugin": &ctx.Command{Name: "plugin [action] file", Help: "", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
suffix, action, target := "go", "build", path.Join(m.Conf("runtime", "boot.ctx_home"), "src/examples/app/bench.go")
if len(arg) == 0 {
arg = append(arg, target)
}
if cs := strings.Split(arg[0], "."); len(cs) > 1 {
suffix = cs[len(cs)-1]
} else if cs := strings.Split(arg[1], "."); len(cs) > 1 {
suffix, action, arg = cs[len(cs)-1], arg[0], arg[1:]
}
if target = m.Cmdx("nfs.path", arg[0]); target == "" {
target = m.Cmdx("nfs.path", path.Join("src/plugin/", arg[0]))
}
for suffix != "" && action != "" {
m.Log("info", "%v %v %v", suffix, action, target)
cook := m.Confv("plugin", suffix)
next := strings.Replace(target, "."+suffix, "."+kit.Chains(cook, "next.0"), -1)
args := []string{}
if suffix == "so" {
if p, e := plugin.Open(target); m.Assert(e) {
s, e := p.Lookup("Index")
m.Assert(e)
w := *(s.(**ctx.Context))
w.Name = kit.Select(w.Name, arg, 1)
c.Register(w, nil)
m.Spawn(w).Cmd("_init", arg[1:])
}
} else {
if suffix == "go" {
args = append(args, "-o", next)
}
m.Assert(m.Cmd("cli.system", kit.Chain(cook, action), args, target))
}
suffix = kit.Chains(cook, "next.0")
action = kit.Chains(cook, "next.1")
target = next
}
return
}},
"source": &ctx.Command{Name: "source [script|stdio|snippet]", Help: "解析脚本, script: 脚本文件, stdio: 命令终端, snippet: 代码片段", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
if len(arg) == 0 {
m.Cmdy("dir", "", "dir_deep", "dir_reg", ".*\\.(sh|shy|py)$")

View File

@ -4,5 +4,5 @@ var version = struct {
host string
self int
}{
"2019-07-03 22:27:00", "ZYB-20190522USI", 104,
"2019-07-04 21:17:32", "ZYB-20190522USI", 112,
}

View File

@ -367,8 +367,9 @@ func (m *Message) CallBack(sync bool, cb func(msg *Message) (sub *Message), arg
// })
select {
case <-time.After(kit.Duration(m.Conf("call_timeout"))):
case <-time.After(kit.Duration(m.Confx("call_timeout"))):
m.Log("sync", m.Format("timeout", "detail", "option"))
m.Echo("time out %v", m.Confx("call_timeout"))
case <-wait:
}
return m

View File

@ -101,7 +101,7 @@ var Index = &Context{Name: "ctx", Help: "模块中心", Server: &CTX{},
"page_limit": &Config{Name: "page_limit", Value: "10", Help: "列表大小"},
"time_format": &Config{Name: "time_format", Value: "2006-01-02 15:04:05", Help: "时间格式"},
"call_timeout": &Config{Name: "call_timeout", Value: "100s", Help: "回调超时"},
"call_timeout": &Config{Name: "call_timeout", Value: "10s", Help: "回调超时"},
},
Commands: map[string]*Command{
"_init": &Command{Name: "_init", Help: "启动", Hand: func(m *Message, c *Context, key string, arg ...string) (e error) {

View File

@ -57,6 +57,7 @@ func (mdb *MDB) Close(m *ctx.Message, arg ...string) bool {
var Index = &ctx.Context{Name: "mdb", Help: "数据中心",
Caches: map[string]*ctx.Cache{
"nsource": &ctx.Cache{Name: "nsource", Value: "0", Help: "已打开数据库的数量"},
"redis": &ctx.Cache{Name: "redis", Value: "", Help: "服务地址"},
},
Configs: map[string]*ctx.Config{
"database": &ctx.Config{Name: "database", Value: "demo", Help: "默认数据库"},
@ -130,15 +131,14 @@ var Index = &ctx.Context{Name: "mdb", Help: "数据中心",
if mdb, ok := m.Target().Server.(*MDB); m.Assert(ok) {
switch arg[0] {
case "conn":
mdb.conn, e = redis.Dial(kit.Select("tcp", arg, 2), arg[1], redis.DialKeepAlive(time.Second*10))
mdb.conn, e = redis.Dial("tcp", m.Cap("redis", arg[1]), redis.DialKeepAlive(time.Second*10))
default:
if mdb.conn == nil {
m.Echo("not open")
break
}
if mdb.conn.Err() != nil {
m.Echo("%v", mdb.conn.Err())
return
mdb.conn, e = redis.Dial("tcp", m.Cap("redis"), redis.DialKeepAlive(time.Second*10))
}
args := []interface{}{}
for _, v := range arg[1:] {
@ -156,7 +156,13 @@ var Index = &ctx.Context{Name: "mdb", Help: "数据中心",
}
m.Table()
default:
m.Echo("%v", kit.Format(res))
str := kit.Format(res)
var data interface{}
if json.Unmarshal([]byte(str), &data) == nil {
m.Echo(kit.Formats(data))
} else {
m.Echo(str)
}
}
}
}

View File

@ -1349,7 +1349,10 @@ var Index = &ctx.Context{Name: "nfs", Help: "存储中心",
m.Confm("grep", "list", func(index int, value map[string]interface{}) {
f, e := os.Open(kit.Format(value["file"]))
m.Assert(e)
if e != nil {
m.Log("warn", "%v", e)
return
}
defer f.Close()
// s, e := f.Stat()

View File

@ -65,6 +65,7 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
map[string]interface{}{"type": "text", "name": "sub", "imports": "plugin_branch", "view": "long"},
map[string]interface{}{"type": "button", "value": "执行"},
},
"options": map[string]interface{}{"call_timeout": "180s"},
},
map[string]interface{}{"componet_name": "script", "componet_help": "脚本",
"componet_tmpl": "componet", "componet_view": "Compile", "componet_init": "",
@ -145,11 +146,12 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
"componet_type": "private", "componet_ctx": "ssh", "componet_cmd": "_route",
"componet_args": []interface{}{"$$", "context", "cli", "upgrade"}, "inputs": []interface{}{
map[string]interface{}{"type": "text", "name": "pod", "imports": "plugin_pod"},
map[string]interface{}{"type": "select", "name": "action", "values": []interface{}{"script", "portal", "system", "bench"}},
map[string]interface{}{"type": "select", "name": "action", "values": []interface{}{"script", "portal", "system", "plugin", "bench"}},
map[string]interface{}{"type": "text", "name": "action"},
map[string]interface{}{"type": "button", "value": "升级"},
},
"display": map[string]interface{}{"hide_append": true, "show_result": true},
"options": map[string]interface{}{"call_timeout": "180s"},
},
map[string]interface{}{"componet_name": "missyou", "componet_help": "任务",
"componet_tmpl": "componet", "componet_view": "Compile", "componet_init": "",
@ -275,7 +277,7 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
"componet_tmpl": "componet", "componet_view": "Context", "componet_init": "",
"componet_type": "private", "componet_ctx": "nfs", "componet_cmd": "git",
"componet_args": []interface{}{}, "inputs": []interface{}{
map[string]interface{}{"type": "text", "name": "dir", "imports": "plugin_dir", "view": "long"},
map[string]interface{}{"type": "text", "name": "dir", "view": "long"},
map[string]interface{}{"type": "select", "name": "cmd", "values": []interface{}{
"add", "commit", "checkout", "merge", "init",
}},
@ -287,7 +289,7 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
"componet_tmpl": "componet", "componet_view": "Context", "componet_init": "",
"componet_type": "private", "componet_ctx": "nfs", "componet_cmd": "git",
"componet_args": []interface{}{}, "inputs": []interface{}{
map[string]interface{}{"type": "text", "name": "dir", "view": "long", "imports": "plugin_dir"},
map[string]interface{}{"type": "text", "name": "dir", "view": "long"},
map[string]interface{}{"type": "select", "name": "cmd", "values": []interface{}{
"branch", "status", "diff", "log", "push", "update",
}},
@ -509,6 +511,11 @@ var Index = &ctx.Context{Name: "ssh", Help: "集群中心",
}
msg := m.Find(kit.Format(tool["componet_ctx"]))
if option, ok := tool["options"].(map[string]interface{}); ok {
for k, v := range option {
msg.Option(k, v)
}
}
arg = arg[4:]
args := []string{}

View File

@ -1125,6 +1125,12 @@ var Index = &ctx.Context{Name: "web", Help: "应用中心",
key = key + "." + m.Option("GOOS") + "." + m.Option("GOARCH")
}
p := ""
if m.Option("upgrade") == "plugin" {
if !strings.HasSuffix(key, ".so") {
key += ".so"
}
p = m.Cmdx("nfs.path", path.Join("src/plugin", key))
}
if m.Option("upgrade") == "script" {
if m.Options("missyou") {
p = m.Cmdx("nfs.path", path.Join(m.Conf("missyou", "path"), m.Option("missyou"), "usr/script", key))

View File

@ -1,5 +1,6 @@
package main
import (
"encoding/json"
"bufio"
"bytes"
"sync/atomic"
@ -28,9 +29,12 @@ var Index = &ctx.Context{Name: "test", Help: "接口测试工具",
"prefix0": {Name: "prefix0", Help: "请求前缀", Value: "uri["},
"prefix1": {Name: "prefix1", Help: "参数前缀", Value: "request_param["},
"timeout": {Name: "timeout", Help: "请求超时", Value: "10s"},
"nsleep": {Name: "nsleep", Help: "阻塞时长", Value: "10000"},
},
Commands: map[string]*ctx.Command{
"diff": {Name: "diff file server1 server2", Help:"接口对比工具", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
"diff": {Name: "diff file server1 server2",
Form: map[string]int{"nsleep": 1},
Help:"接口对比工具", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) (e error) {
os.MkdirAll(m.Conf("outdir"), 0777)
f, e := os.Open(arg[0])
@ -71,6 +75,10 @@ var Index = &ctx.Context{Name: "test", Help: "接口测试工具",
nline++
input <- []string{uri, arg, fmt.Sprintf("%d", nline)}
api[uri]++
if nline % kit.Int(m.Confx("nsleep")) == 0 {
time.Sleep(time.Second)
fmt.Printf("sleep 1s...\n")
}
}
close(input)
wg.Wait()
@ -118,8 +126,14 @@ var Index = &ctx.Context{Name: "test", Help: "接口测试工具",
t3 = time.Since(begin)
begin = time.Now()
var d1, d2 interface{}
json.Unmarshal(b1, &d1)
json.Unmarshal(b2, &d2)
s1, _ := json.Marshal(d1)
s2, _ := json.Marshal(d2)
var num int32
if bytes.Compare(b1, b2) == 0 {
if bytes.Compare(s1, s2) == 0 {
atomic.AddInt32(&same, 1)
num = same
result = "same"

View File

@ -19,6 +19,11 @@ fieldset.Target>div.output>div.item>div.text {
padding:6px;
float:left;
}
fieldset.Target>div.output>div.item>div.time {
padding-left:5px;
font-size:10px;
color:gray;
}
fieldset.Target>div.output>div.item>div.user {
border-right:solid 1px green;
border-bottom:solid 1px green;

View File

@ -182,6 +182,7 @@ page = Page({
var river = ""
var which = {}
output.DisplayUser = true
output.DisplayTime = true
return {
Listen: {
river: function(value, old) {
@ -265,9 +266,10 @@ page = Page({
}
this.Update([river, storm], "plugin", ["node", "name"], "index", false, function(line, index, event, args, cbs) {
var plugin = event.Plugin
var plugin = event.Plugin || {}
var meta = plugin && plugin.Field && plugin.Field.Meta || {}
event.shiftKey? page.target.Pane.Send("field", plugin.Format()):
field.Pane.Run([river, storm, index].concat(args), function(msg) {
field.Pane.Run([meta.river||river, meta.storm||storm, meta.action||index].concat(args), function(msg) {
var text = plugin? plugin.Reveal(msg): ""
text && event.ctrlKey && page.target.Pane.Send(text[0], text[1])
typeof cbs == "function" && cbs(msg)

View File

@ -68,7 +68,7 @@ function Page(page) {
break
case "code":
list.push({view: ["code", key.length>1? line[key[0]]+"("+line[key[1]]+")":
list.push({view: ["code", "div", key.length>1? line[key[0]]+"("+line[key[1]]+")":
(key.length>0? line[key[0]]: "null")], click: cb})
break
@ -94,8 +94,12 @@ function Page(page) {
break
}
parent.DisplayUser && (list = [{view: ["user", "div", line.create_nick||line.create_user]}, {view: ["text"], list:list}])
!parent.DisplayRaw && (list = [{view: ["item"], list:list}])
var item = []
parent.DisplayUser && item.push({view: ["user", "div", line.create_nick||line.create_user]})
parent.DisplayTime && (item.push({text: [line.create_time, "div", "time"]}))
item.push({view: ["text"], list:list})
!parent.DisplayRaw && (list = [{view: ["item"], list:item}])
ui = kit.AppendChild(parent, list)
ui.field && (ui.field.Meta = text)
return ui
@ -555,9 +559,24 @@ function Plugin(page, pane, field) {
item.onfocus = function(event) {
page.pane = pane.Field, page.plugin = field, page.input = event.target
}
item.onkeyup = function(event) {
page.oninput(event, function(event) {
switch (event.key) {
case "w":
break
default:
return false
}
event.stopPropagation()
event.preventDefault()
return true
})
}
item.onkeydown = function(event) {
page.oninput(event, function(event) {
switch (event.key) {
case "w":
break
case "p":
action.Back()
break
@ -622,9 +641,12 @@ function Plugin(page, pane, field) {
if (item.type == "text") {
item.onclick = function(event) {
if (event.ctrlKey) {
action.value = kit.History.get("txt", -1).data
action.value = kit.History.get("txt", -1).data.trim()
}
}
item.ondblclick = function(event) {
action.value = kit.History.get("txt", -1).data.trim()
}
}
args && count < args.length && (item.value = args[count++]||item.value||"")
item.className = "args"
@ -723,7 +745,7 @@ function Plugin(page, pane, field) {
// }
page.Sync("plugin_"+exports[0]).set(plugin.onexport[exports[2]||""](value, name, line))
});
(display.show_result || !msg.append) && msg.result && kit.AppendChild(output, [{view: ["code", "div", msg.Results()]}])
(display.show_result || !msg.append) && msg.result && kit.OrderCode(kit.AppendChild(output, [{view: ["code", "div", msg.Results()]}]).first)
},
},
onexport: {

View File

@ -303,6 +303,10 @@ kit = toolkit = {
tr.Meta = row
fields.forEach(function(key, j) {
var td = kit.AppendChild(tr, "td", kit.Color(row[key]))
if (row[key].startsWith("http")) {
td.innerHTML = "<a href='"+row[key]+"' target='_blank'>"+row[key]+"</a>"
}
if (typeof cb == "function") {
td.onclick = function(event) {
cb(row[key], key, row, i, tr, event)