diff --git a/base/mdb/hash.go b/base/mdb/hash.go index b32ce0c4..ceaacaf3 100644 --- a/base/mdb/hash.go +++ b/base/mdb/hash.go @@ -62,6 +62,10 @@ func _hash_delete(m *ice.Message, prefix, chain, field, value string) { defer Lock(m, prefix, chain)() Richs(m, prefix, chain, value, func(key string, val Map) { + if target, ok := kit.GetMeta(val)[TARGET].(io.Closer); ok { + m.Logs("close", target) + target.Close() + } m.Logs(DELETE, KEY, path.Join(prefix, chain), field, value, VALUE, kit.Format(val)) m.Conf(prefix, kit.Keys(chain, HASH, key), "") }) diff --git a/base/mdb/zone.go b/base/mdb/zone.go index 64ff89c9..46a4c58b 100644 --- a/base/mdb/zone.go +++ b/base/mdb/zone.go @@ -182,9 +182,9 @@ func ZoneCreate(m *ice.Message, arg ...Any) { m.Cmdy(INSERT, m.PrefixKey(), "", HASH, arg) } func ZoneRemove(m *ice.Message, arg ...Any) { - args := kit.Simple(arg) + args := kit.Simple(arg...) if len(args) == 0 { - args = m.OptionSimple(ZoneShort(m)) + args = m.OptionSimple(ZoneShort(m), HASH) } else if len(args) == 1 { args = []string{ZoneShort(m), args[0]} } diff --git a/base/nfs/save.go b/base/nfs/save.go index de3c0968..605eb04b 100644 --- a/base/nfs/save.go +++ b/base/nfs/save.go @@ -87,6 +87,7 @@ const ( ) const DEFS = "defs" const SAVE = "save" +const LOAD = "load" const PUSH = "push" const COPY = "copy" const LINK = "link" diff --git a/misc/ssh/connect.go b/misc/ssh/connect.go index 930a40df..a1e92b89 100644 --- a/misc/ssh/connect.go +++ b/misc/ssh/connect.go @@ -5,14 +5,15 @@ import ( "io" "net" "os" - "path" "strings" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" + ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/aaa" "shylinux.com/x/icebergs/base/cli" + "shylinux.com/x/icebergs/base/ctx" "shylinux.com/x/icebergs/base/gdb" "shylinux.com/x/icebergs/base/mdb" "shylinux.com/x/icebergs/base/nfs" @@ -44,10 +45,10 @@ func _ssh_open(m *ice.Message, arg ...string) { }, arg...) } func _ssh_dial(m *ice.Message, cb func(net.Conn), arg ...string) { - p := path.Join(kit.Env(cli.HOME), ".ssh/", fmt.Sprintf("%s@%s:%s", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT))) - if nfs.FileExists(m, p) { + p := kit.HomePath(".ssh", fmt.Sprintf("%s@%s:%s", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT))) + if nfs.ExistsFile(m, p) { if c, e := net.Dial("unix", p); e == nil { - cb(c) // 会话连接 + cb(c) // 会话复用 return } nfs.Remove(m, p) @@ -75,28 +76,21 @@ func _ssh_dial(m *ice.Message, cb func(net.Conn), arg ...string) { m.Go(func() { defer c.Close() - session, e := client.NewSession() + s, e := client.NewSession() if e != nil { return } - session.Stdin = c - session.Stdout = c - session.Stderr = c - - session.RequestPty(kit.Env("TERM"), h, w, ssh.TerminalModes{ - ssh.ECHO: 1, - ssh.TTY_OP_ISPEED: 14400, - ssh.TTY_OP_OSPEED: 14400, - }) + s.Stdin, s.Stdout, s.Stderr = c, c, c + s.RequestPty(kit.Env(cli.TERM), h, w, ssh.TerminalModes{ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400}) + defer s.Wait() gdb.SignalNotify(m, 28, func() { w, h, _ := terminal.GetSize(int(os.Stdin.Fd())) - session.WindowChange(h, w) + s.WindowChange(h, w) }) - session.Shell() - session.Wait() + s.Shell() }) }(c) } @@ -118,7 +112,6 @@ func _ssh_conn(m *ice.Message, cb func(*ssh.Client), arg ...string) { if verify := m.Option("verify"); verify == "" { fmt.Printf(q) fmt.Scanf("%s\n", &verify) - res = append(res, verify) } else { res = append(res, aaa.TOTP_GET(verify, 6, 30)) @@ -131,24 +124,21 @@ func _ssh_conn(m *ice.Message, cb func(*ssh.Client), arg ...string) { return })) methods = append(methods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { - key, err := ssh.ParsePrivateKey([]byte(m.Cmdx(nfs.CAT, path.Join(kit.Env(cli.HOME), m.Option(PRIVATE))))) + key, err := ssh.ParsePrivateKey([]byte(m.Cmdx(nfs.CAT, kit.HomePath(m.Option(PRIVATE))))) return []ssh.Signer{key}, err })) methods = append(methods, ssh.PasswordCallback(func() (string, error) { return m.Option(aaa.PASSWORD), nil })) - m.OptionCB(tcp.CLIENT, func(c net.Conn) { + m.Cmdy(tcp.CLIENT, tcp.DIAL, mdb.TYPE, SSH, mdb.NAME, m.Option(tcp.HOST), m.OptionSimple(tcp.HOST, tcp.PORT), arg, func(c net.Conn) { conn, chans, reqs, err := ssh.NewClientConn(c, m.Option(tcp.HOST)+":"+m.Option(tcp.PORT), &ssh.ClientConfig{ User: m.Option(aaa.USERNAME), Auth: methods, BannerCallback: func(message string) error { return nil }, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, }) - m.Assert(err) cb(ssh.NewClient(conn, chans, reqs)) }) - m.Cmdy(tcp.CLIENT, tcp.DIAL, mdb.TYPE, SSH, mdb.NAME, m.Option(tcp.HOST), - tcp.PORT, m.Option(tcp.PORT), tcp.HOST, m.Option(tcp.HOST), arg) } const SSH = "ssh" @@ -160,52 +150,36 @@ func init() { tcp.OPEN: {Name: "open authfile username=shy password verfiy host=shylinux.com port=22 private=.ssh/id_rsa", Help: "终端", Hand: func(m *ice.Message, arg ...string) { aaa.UserRoot(m) _ssh_open(nfs.OptionLoad(m, m.Option("authfile")), arg...) - m.Echo("exit %v@%v:%v\n", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT)) + m.Echo("exit %s@%s:%s\n", m.Option(aaa.USERNAME), m.Option(tcp.HOST), m.Option(tcp.PORT)) }}, - tcp.DIAL: {Name: "dial name=shylinux username=shy host=shylinux.com port=22 private=.ssh/id_rsa", Help: "添加", Hand: func(m *ice.Message, arg ...string) { + tcp.DIAL: {Name: "dial name=shylinux host=shylinux.com port=22 username=shy private=.ssh/id_rsa", Help: "添加", Hand: func(m *ice.Message, arg ...string) { m.Go(func() { _ssh_conn(m, func(client *ssh.Client) { - mdb.Rich(m, CONNECT, "", kit.Dict( - mdb.NAME, m.Option(mdb.NAME), - aaa.USERNAME, m.Option(aaa.USERNAME), - tcp.HOST, m.Option(tcp.HOST), tcp.PORT, m.Option(tcp.PORT), - mdb.STATUS, tcp.OPEN, CONNECT, client, - )) - m.Cmd(CONNECT, SESSION, mdb.NAME, m.Option(mdb.NAME)) + mdb.HashCreate(m.Spawn(), m.OptionSimple(mdb.NAME, tcp.HOST, tcp.PORT, aaa.USERNAME), mdb.STATUS, tcp.OPEN, kit.Dict(mdb.TARGET, client)) + m.Cmd("", SESSION, m.OptionSimple(mdb.NAME)) }, arg...) }) - m.ProcessRefresh3s() + m.Sleep300ms() }}, - SESSION: {Name: "session name", Help: "会话", Hand: func(m *ice.Message, arg ...string) { - var client *ssh.Client - mdb.Richs(m, CONNECT, "", m.Option(mdb.NAME), func(key string, value ice.Map) { - client, _ = value[CONNECT].(*ssh.Client) - }) - - h := mdb.Rich(m, SESSION, "", kit.Data(mdb.NAME, m.Option(mdb.NAME), mdb.STATUS, tcp.OPEN, CONNECT, m.Option(mdb.NAME))) - if session, e := _ssh_session(m, h, client); m.Assert(e) { - session.Shell() - session.Wait() + SESSION: {Name: "session", Help: "会话", Hand: func(m *ice.Message, arg ...string) { + if c, e := _ssh_session(m, mdb.HashTarget(m, m.Option(mdb.NAME), nil).(*ssh.Client)); m.Assert(e) { + defer c.Wait() + c.Shell() } - m.Echo(h) }}, - "command": {Name: "command cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) { - mdb.Richs(m, CONNECT, "", m.Option(mdb.NAME), func(key string, value ice.Map) { - if client, ok := value[CONNECT].(*ssh.Client); ok { - if session, e := client.NewSession(); m.Assert(e) { - defer session.Close() - if b, e := session.CombinedOutput(m.Option("cmd")); m.Assert(e) { - m.Echo(string(b)) - } - } + ctx.COMMAND: {Name: "command cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) { + client := mdb.HashTarget(m, m.Option(mdb.NAME), nil).(*ssh.Client) + if s, e := client.NewSession(); m.Assert(e) { + defer s.Close() + if b, e := s.CombinedOutput(m.Option(ice.CMD)); m.Assert(e) { + m.Echo(string(b)) } - }) + } }}, }, mdb.HashStatusAction(mdb.SHORT, "name", mdb.FIELD, "time,name,status,username,host,port")), Hand: func(m *ice.Message, arg ...string) { - mdb.HashSelect(m, arg...).Tables(func(value ice.Maps) { + if mdb.HashSelect(m, arg...).Tables(func(value ice.Maps) { m.PushButton(kit.Select("", "command,session", value[mdb.STATUS] == tcp.OPEN), mdb.REMOVE) - }) - if len(arg) == 0 { + }); len(arg) == 0 { m.Action(tcp.DIAL) } }}, diff --git a/misc/ssh/service.go b/misc/ssh/service.go index 62589b15..705b27cb 100644 --- a/misc/ssh/service.go +++ b/misc/ssh/service.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net" - "path" "strings" "golang.org/x/crypto/ssh" @@ -17,6 +16,7 @@ import ( "shylinux.com/x/icebergs/base/nfs" psh "shylinux.com/x/icebergs/base/ssh" "shylinux.com/x/icebergs/base/tcp" + "shylinux.com/x/icebergs/base/web" kit "shylinux.com/x/toolkits" ) @@ -27,11 +27,11 @@ func _ssh_config(m *ice.Message, h string) *ssh.ServerConfig { config := &ssh.ServerConfig{ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { meta, err := _ssh_meta(conn), errors.New(ice.ErrNotRight) - if tcp.IsLocalHost(m, strings.Split(conn.RemoteAddr().String(), ":")[0]) { + if tcp.IsLocalHost(m, strings.Split(conn.RemoteAddr().String(), ice.DF)[0]) { m.Logs(ice.LOG_AUTH, tcp.HOSTPORT, conn.RemoteAddr(), aaa.USERNAME, conn.User()) err = nil // 本机用户 } else { - m.Cmd(mdb.SELECT, SERVICE, kit.Keys(mdb.HASH, h), mdb.LIST, func(value ice.Maps) { + mdb.ZoneSelectCB(m, h, func(value ice.Maps) { if !strings.HasPrefix(value[mdb.NAME], conn.User()+"@") { return } @@ -57,14 +57,13 @@ func _ssh_config(m *ice.Message, h string) *ssh.ServerConfig { } return &ssh.Permissions{Extensions: meta}, err }, - BannerCallback: func(conn ssh.ConnMetadata) string { m.Logs(ice.LOG_AUTH, tcp.HOSTPORT, conn.RemoteAddr(), aaa.USERNAME, conn.User()) - return m.Conf(SERVICE, kit.Keym(WELCOME)) + return m.Config(WELCOME) }, } - if key, err := ssh.ParsePrivateKey([]byte(m.Cmdx(nfs.CAT, path.Join(kit.Env(cli.HOME), m.Option(PRIVATE))))); m.Assert(err) { + if key, err := ssh.ParsePrivateKey([]byte(m.Cmdx(nfs.CAT, kit.HomePath(m.Option(PRIVATE))))); m.Assert(err) { config.AddHostKey(key) } return config @@ -100,30 +99,30 @@ const SERVICE = "service" func init() { psh.Index.Merge(&ice.Context{Configs: ice.Configs{ SERVICE: {Name: SERVICE, Help: "服务", Value: kit.Data( - WELCOME, "\r\nwelcome to context world\r\n", GOODBYE, "\r\ngoodbye of context world\r\n", mdb.SHORT, tcp.PORT, mdb.FIELD, "time,port,status,private,authkey,count", + WELCOME, "\r\nwelcome to contexts world\r\n", GOODBYE, "\r\ngoodbye of contexts world\r\n", )}, }, Commands: ice.Commands{ SERVICE: {Name: "service port id auto listen prunes", Help: "服务", Actions: ice.MergeActions(ice.Actions{ ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) { - mdb.Richs(m, SERVICE, "", mdb.FOREACH, func(key string, value ice.Map) { - if value = kit.GetMeta(value); kit.Value(value, mdb.STATUS) == tcp.OPEN { + mdb.HashSelect(m).Tables(func(value ice.Maps) { + if value[mdb.STATUS] == tcp.OPEN { m.Cmd(SERVICE, tcp.LISTEN, tcp.PORT, value[tcp.PORT], value) } }) }}, tcp.LISTEN: {Name: "listen port=9030 private=.ssh/id_rsa authkey=.ssh/authorized_keys", Help: "添加", Hand: func(m *ice.Message, arg ...string) { - if mdb.Richs(m, SERVICE, "", m.Option(tcp.PORT), func(key string, value ice.Map) { - kit.Value(value, kit.Keym(mdb.STATUS), tcp.OPEN) - }) == nil { - m.Cmd(mdb.INSERT, SERVICE, "", mdb.HASH, tcp.PORT, m.Option(tcp.PORT), - PRIVATE, m.Option(PRIVATE), AUTHKEY, m.Option(AUTHKEY), mdb.STATUS, tcp.OPEN, arg) - m.Cmd(SERVICE, mdb.IMPORT, AUTHKEY, m.Option(AUTHKEY)) + if mdb.HashSelect(m, m.Option(tcp.PORT)).Length() > 0 { + mdb.HashModify(m, m.Option(tcp.PORT), mdb.STATUS, tcp.OPEN) + } else { + mdb.HashCreate(m, mdb.STATUS, tcp.OPEN, arg) + m.Cmd("", nfs.LOAD, m.OptionSimple(AUTHKEY)) } - m.OptionCB(tcp.SERVER, func(c net.Conn) { m.Go(func() { _ssh_accept(m, kit.Hashs(m.Option(tcp.PORT)), c) }) }) m.Go(func() { - m.Cmdy(tcp.SERVER, tcp.LISTEN, mdb.TYPE, SSH, mdb.NAME, tcp.PORT, tcp.PORT, m.Option(tcp.PORT)) + m.Cmdy(tcp.SERVER, tcp.LISTEN, mdb.TYPE, SSH, mdb.NAME, tcp.PORT, m.OptionSimple(tcp.PORT), func(c net.Conn) { + m.Go(func() { _ssh_accept(m, kit.Hashs(m.Option(tcp.PORT)), c) }) + }) }) }}, @@ -133,36 +132,29 @@ func init() { mdb.TYPE, ls[0], mdb.NAME, ls[len(ls)-1], mdb.TEXT, strings.Join(ls[1:len(ls)-1], "+")) } }}, - mdb.EXPORT: {Name: "export authkey=.ssh/authorized_keys", Help: "导出", Hand: func(m *ice.Message, arg ...string) { + nfs.LOAD: {Name: "load authkey=.ssh/authorized_keys", Help: "加载", Hand: func(m *ice.Message, arg ...string) { + m.Cmd(nfs.CAT, kit.HomePath(m.Option(AUTHKEY)), func(pub string) { + m.Cmd(SERVICE, mdb.INSERT, mdb.TEXT, pub) + }) + }}, + nfs.SAVE: {Name: "save authkey=.ssh/authorized_keys", Help: "保存", Hand: func(m *ice.Message, arg ...string) { list := []string{} - m.Cmd(mdb.SELECT, SERVICE, kit.Keys(mdb.HASH, kit.Hashs(m.Option(tcp.PORT))), mdb.LIST, func(value ice.Maps) { + mdb.ZoneSelectCB(m, m.Option(tcp.PORT), func(value ice.Maps) { list = append(list, fmt.Sprintf("%s %s %s", value[mdb.TYPE], value[mdb.TEXT], value[mdb.NAME])) }) - if len(list) > 0 { - m.Cmdy(nfs.SAVE, path.Join(kit.Env(cli.HOME), m.Option(AUTHKEY)), strings.Join(list, ice.NL)+ice.NL) + m.Cmdy(nfs.SAVE, kit.HomePath(m.Option(AUTHKEY)), strings.Join(list, ice.NL)+ice.NL) } }}, - mdb.IMPORT: {Name: "import authkey=.ssh/authorized_keys", Help: "导入", Hand: func(m *ice.Message, arg ...string) { - p := path.Join(kit.Env(cli.HOME), m.Option(AUTHKEY)) - for _, pub := range strings.Split(strings.TrimSpace(m.Cmdx(nfs.CAT, p)), ice.NL) { - m.Cmd(SERVICE, mdb.INSERT, mdb.TEXT, pub) - } - m.Echo(p) - }}, aaa.INVITE: {Name: "invite", Help: "邀请", Hand: func(m *ice.Message, arg ...string) { - u := kit.ParseURL(m.Option(ice.MSG_USERWEB)) - m.Option(cli.HOSTNAME, strings.Split(u.Host, ":")[0]) - m.ProcessInner() - + m.Option(cli.HOSTNAME, web.OptionUserWeb(m).Hostname()) if buf, err := kit.Render(`ssh -p {{.Option "port"}} {{.Option "user.name"}}@{{.Option "hostname"}}`, m); err == nil { m.EchoScript(string(buf)) } }}, }, mdb.HashStatusAction()), Hand: func(m *ice.Message, arg ...string) { if len(arg) == 0 { // 服务列表 - mdb.HashSelect(m, arg...) - m.PushAction(mdb.IMPORT, mdb.INSERT, mdb.EXPORT, aaa.INVITE) + mdb.HashSelect(m, arg...).PushAction(aaa.INVITE, mdb.INSERT, nfs.LOAD, nfs.SAVE) return } diff --git a/misc/ssh/service_darwin.go b/misc/ssh/service_darwin.go index e055000e..8e80857b 100644 --- a/misc/ssh/service_darwin.go +++ b/misc/ssh/service_darwin.go @@ -7,8 +7,9 @@ import ( "syscall" "unsafe" - pty "shylinux.com/x/creackpty" "golang.org/x/crypto/ssh" + pty "shylinux.com/x/creackpty" + ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/cli" "shylinux.com/x/icebergs/base/mdb" @@ -33,7 +34,7 @@ func _ssh_handle(m *ice.Message, meta ice.Maps, c net.Conn, channel ssh.Channel, m.Logs(CHANNEL, tcp.HOSTPORT, c.RemoteAddr(), "->", c.LocalAddr()) defer m.Logs("dischan", tcp.HOSTPORT, c.RemoteAddr(), "->", c.LocalAddr()) - shell := kit.Select("bash", kit.Env("SHELL")) + shell := kit.Select("bash", kit.Env(cli.SHELL)) list := []string{cli.PATH + "=" + kit.Env(cli.PATH)} pty, tty, err := pty.Open() diff --git a/misc/ssh/session.go b/misc/ssh/session.go index 532a62bf..ada0c167 100644 --- a/misc/ssh/session.go +++ b/misc/ssh/session.go @@ -4,6 +4,7 @@ import ( "io" "golang.org/x/crypto/ssh" + ice "shylinux.com/x/icebergs" "shylinux.com/x/icebergs/base/ctx" "shylinux.com/x/icebergs/base/mdb" @@ -12,35 +13,27 @@ import ( kit "shylinux.com/x/toolkits" ) -func _ssh_session(m *ice.Message, h string, client *ssh.Client) (*ssh.Session, error) { - session, e := client.NewSession() +func _ssh_session(m *ice.Message, client *ssh.Client) (*ssh.Session, error) { + s, e := client.NewSession() + m.Assert(e) + out, e := s.StdoutPipe() + m.Assert(e) + in, e := s.StdinPipe() m.Assert(e) - out, e := session.StdoutPipe() - m.Assert(e) - - in, e := session.StdinPipe() - m.Assert(e) + h := m.Cmdx(SESSION, mdb.CREATE, mdb.STATUS, tcp.OPEN, CONNECT, m.Option(mdb.NAME), kit.Dict(mdb.TARGET, in)) m.Go(func() { buf := make([]byte, ice.MOD_BUFS) for { - n, e := out.Read(buf) - if e != nil { + if n, e := out.Read(buf); e != nil { break + } else { + m.Cmd(SESSION, mdb.INSERT, mdb.HASH, h, mdb.TYPE, RES, mdb.TEXT, string(buf[:n])) } - - mdb.Grow(m, SESSION, kit.Keys(mdb.HASH, h), kit.Dict( - mdb.TYPE, RES, mdb.TEXT, string(buf[:n]), - )) } }) - - mdb.Richs(m, SESSION, "", h, func(key string, value ice.Map) { - kit.Value(value, kit.Keym(OUTPUT), out, kit.Keym(INPUT), in) - }) - - return session, nil + return s, nil } const ( @@ -59,20 +52,17 @@ const SESSION = "session" func init() { psh.Index.MergeCommands(ice.Commands{ - SESSION: {Name: "session name id auto", Help: "会话", Actions: ice.MergeActions(ice.Actions{ + SESSION: {Name: "session hash id auto", Help: "会话", Actions: ice.MergeActions(ice.Actions{ mdb.REPEAT: {Name: "repeat", Help: "执行", Hand: func(m *ice.Message, arg ...string) { - m.Cmdy(SESSION, ctx.ACTION, ctx.COMMAND, CMD, m.Option(mdb.TEXT)) + m.Cmdy("", ctx.COMMAND, CMD, m.Option(mdb.TEXT)) }}, ctx.COMMAND: {Name: "command cmd=pwd", Help: "命令", Hand: func(m *ice.Message, arg ...string) { - mdb.Richs(m, SESSION, "", m.Option(mdb.NAME), func(key string, value ice.Map) { - if w, ok := kit.Value(value, kit.Keym(INPUT)).(io.Writer); ok { - mdb.Grow(m, SESSION, kit.Keys(mdb.HASH, key), kit.Dict(mdb.TYPE, CMD, mdb.TEXT, m.Option(CMD))) - w.Write([]byte(m.Option(CMD) + ice.NL)) - } - }) - m.ProcessRefresh300ms() + m.Cmd("", mdb.INSERT, m.OptionSimple(mdb.HASH), mdb.TYPE, CMD, mdb.TEXT, m.Option(CMD)) + w := mdb.HashTarget(m, m.Option(mdb.HASH), nil).(io.Writer) + w.Write([]byte(m.Option(CMD) + ice.NL)) + m.Sleep300ms() }}, - }, mdb.ZoneAction(mdb.SHORT, "name", mdb.FIELD, "time,name,status,count,connect")), Hand: func(m *ice.Message, arg ...string) { + }, mdb.ZoneAction(mdb.FIELD, "time,hash,count,status,connect")), Hand: func(m *ice.Message, arg ...string) { if len(arg) == 0 { mdb.HashSelect(m, arg...).Tables(func(value ice.Maps) { m.PushButton(kit.Select("", ctx.COMMAND, value[mdb.STATUS] == tcp.OPEN), mdb.REMOVE) diff --git a/misc/ssh/ssh.shy b/misc/ssh/ssh.shy index b47cc0b9..5184efdb 100644 --- a/misc/ssh/ssh.shy +++ b/misc/ssh/ssh.shy @@ -6,7 +6,7 @@ refer ` ` field "连接" ssh.connect +field "通道" ssh.channel field "会话" ssh.session field "服务" ssh.service -field "通道" ssh.channel diff --git a/misc/webview/webview.go b/misc/webview/webview.go index 0484ba53..66fb907f 100644 --- a/misc/webview/webview.go +++ b/misc/webview/webview.go @@ -8,8 +8,8 @@ import ( ) type WebView struct { - Source string - WebView webview.WebView + webview.WebView + Source string } func (w WebView) Menu() bool { @@ -20,31 +20,29 @@ func (w WebView) Menu() bool { w.WebView.Bind(ls[0], func() { w.navigate(ls[1]) }) } }) - if len(list) == 0 { return false } - w.WebView.SetTitle("contexts") + w.WebView.SetTitle(ice.CONTEXTS) w.WebView.SetSize(200, 60*len(list), webview.HintNone) w.WebView.Navigate(kit.Format(`data:text/html, - - -
+ + + - - - %s - `, kit.Join(list, ice.NL))) + +%s +`, kit.Join(list, ice.NL))) return true } func (w WebView) Title(text string) { w.WebView.SetTitle(text) }