├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bin ├── README.md ├── command │ └── command.go ├── conf │ ├── conf.go │ ├── conf.ini │ ├── conf_test.go │ ├── group.conf │ └── host.conf └── gosshtool.go ├── conf ├── app.conf └── hosts.conf ├── controllers ├── base.go ├── command.go ├── front.go ├── group.go ├── host.go ├── login.go ├── main.go └── user.go ├── gosshtool ├── gosshtool.sql ├── main.go ├── models ├── base.go ├── groups.go ├── hostinfo.go ├── rights.go └── user.go ├── routers └── router.go ├── static ├── css │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── custom.css │ ├── dataTables.bootstrap.min.css │ └── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── bg-header.jpg │ ├── common │ │ ├── help.png │ │ ├── ico_list.gif │ │ ├── search.png │ │ └── tit_bg.gif │ ├── touxiang.jpg │ ├── xixi.jpg │ └── xixi2.jpg ├── img │ ├── Screenshot-command.png │ ├── Screenshot-host.png │ ├── ansible.png │ ├── gosshtool.png │ ├── user2-160x160.jpg │ ├── user3-128x128.jpg │ └── user4-128x128.jpg ├── js │ ├── bootstrap.min.js │ ├── dataTables.bootstrap.min.js │ ├── jquery-3.0.0.min.js │ └── jquery.dataTables.min.js └── layer │ ├── layer.js │ ├── mobile │ ├── layer.js │ └── need │ │ └── layer.css │ ├── skin │ └── layui-layer-red │ │ └── style.css │ └── theme │ └── default │ ├── icon-ext.png │ ├── icon.png │ ├── layer.css │ ├── loading-0.gif │ ├── loading-1.gif │ └── loading-2.gif ├── tests └── default_test.go ├── utils ├── handler │ └── handler.go ├── msgcrypt │ ├── msgcrypt.go │ └── msgcrypt_test.go ├── sshclient │ ├── sftp.go │ └── sshclient.go └── validate │ └── validation.go └── views ├── common ├── datatable-css.tpl └── datatable-js.tpl ├── group ├── add.html ├── command.html ├── edit.html ├── index.html ├── index.html.bak └── modal.html ├── host ├── add.html ├── changepass.html ├── command.html ├── edit.html ├── index.html └── index.html.layui ├── index.tpl ├── layout ├── css.tpl ├── footer_js.tpl ├── header.tpl ├── layer.tpl ├── layui.tpl ├── leftbar.tpl └── main.tpl ├── login └── index.html └── user ├── add.html ├── changepass.html ├── edit.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | bin/conf/conf.ini 2 | conf/* 3 | web 4 | gosshtool 5 | session/* 6 | static/layui 7 | models/base.go 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gosshtool 2 | similar to salt-ssh tools, 类似于salt-ssh的一个GO实现远程服务器管理的工具, 具有web页面操作,在多远程主机操作时,使用goroutines多协程后,ssh响应速度比ansible,salt-ssh快几倍
3 | gosshtool:
4 | ![image](https://github.com/kggg/gosshtool/blob/master/static/img/gosshtool.png)
5 | ansible:
6 | ![image](https://github.com/kggg/gosshtool/blob/master/static/img/ansible.png) 7 |
8 | ## 数据存储方式 9 | mysql 10 | 11 | ## 命令行模式 12 | Usage: gossh host [host|group] options [cmd|copyfile]
13 | -h : specified a remote host, use , split one or more host
14 | -g : specified a remote hostgroup
15 | -e : Regrex match a remote host name default
16 | -m : select a module, -m [cmd|copy]>
17 | copy : [src, dest,mode,force,backup,user,owner]

18 | 19 | e.g.: gossh -h steven -m cmd 'uptime'
20 | gossh -h dbserver -m copy "src=/etc/nginx/nginx.conf dest=/etc/nginx/nginx.conf mode=0644"
21 | 22 | ## 主机列表 23 | ![image](https://github.com/kggg/gosshtool/blob/master/static/img/Screenshot-host.png) 24 | 25 | ## ssh远程操作 26 | ![image](https://github.com/kggg/gosshtool/blob/master/static/img/Screenshot-command.png) 27 | 28 | ## 群组ssh远程操作 29 | 30 | ## 配置文件管理 31 | 32 | 33 | ## 用户管理 34 | 35 | 36 | ## 用户权限 37 | 38 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | ## 命令行模式 #### 2 | Usage: gosshtool -host [host|group] -m [module] [action] 3 | -host : 主机名, 多个主机可以使用,分开 4 | -m : 功能模块, 包含: 5 | cmd, shell命令 6 | sendfile, 发送文件 7 | getfiel, 从远程主机复制文件 8 | action : -m 的参数 9 | 10 | -------------------------------------------------------------------------------- /bin/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "gosshtool/bin/conf" 7 | "gosshtool/models" 8 | "log" 9 | "os" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/fatih/color" 14 | ) 15 | 16 | type Command struct { 17 | Host []models.Hostinfo 18 | Module string 19 | Action string 20 | Model string 21 | } 22 | 23 | func (this *Command) AddHost(h string) { 24 | hosts := strings.Split(h, ",") 25 | for _, host := range hosts { 26 | host = strings.TrimSpace(host) 27 | if this.Model == "mysql" { 28 | hostinfo, err := models.FindHostByName(host) 29 | if err != nil { 30 | fmt.Printf("get host by name error from db: %v\n", err) 31 | os.Exit(1) 32 | } 33 | if hostinfo.Name == "" { 34 | continue 35 | } 36 | this.Host = append(this.Host, hostinfo) 37 | } 38 | if this.Model == "file" { 39 | 40 | } 41 | 42 | } 43 | if len(this.Host) < 1 { 44 | fmt.Printf("No hostname match your input [%s]\n", h) 45 | os.Exit(1) 46 | } 47 | } 48 | 49 | func (this *Command) AddGroup(g string) { 50 | groups, err := models.FindHostByGroupname(g) 51 | if err != nil { 52 | fmt.Errorf("get host info error from db: %v\n", err) 53 | } 54 | for _, v := range groups { 55 | this.Host = append(this.Host, v.Hostinfo) 56 | } 57 | if len(this.Host) < 1 { 58 | fmt.Printf("No groupname match your input [%s]\n", g) 59 | os.Exit(1) 60 | } 61 | } 62 | 63 | func (this *Command) AddModule(c string) { 64 | this.Module = c 65 | } 66 | 67 | func (this *Command) AddRegular(str string) { 68 | allhost, err := models.FindAllHostinfo() 69 | if err != nil { 70 | fmt.Println("database error") 71 | os.Exit(1) 72 | } 73 | m, _ := regexp.Compile(str) 74 | 75 | for _, v := range allhost { 76 | if str == "*" || str == "all" { 77 | this.Host = append(this.Host, v.Hostinfo) 78 | } else { 79 | if m.MatchString(v.Name) { 80 | this.Host = append(this.Host, v.Hostinfo) 81 | } 82 | } 83 | } 84 | if len(this.Host) < 1 { 85 | fmt.Printf("No hostname match your input [%s]\n", str) 86 | os.Exit(1) 87 | } 88 | 89 | } 90 | 91 | func (c *Command) Listhostinfo() { 92 | allhost, err := models.FindAllHostinfo() 93 | if err != nil { 94 | fmt.Println("get all host info error") 95 | os.Exit(1) 96 | } 97 | //fmt.Printf("## IP:port\tHostname\tUser ##\n") 98 | color.Cyan("## IP:port\tHostname\tUser ##\n") 99 | for _, v := range allhost { 100 | fmt.Printf("%s:%d\t%s\t%s\n", v.Hostinfo.Ip, v.Hostinfo.Port, v.Hostinfo.Name, v.Hostinfo.User) 101 | } 102 | os.Exit(0) 103 | } 104 | 105 | var ( 106 | host string 107 | group string 108 | reg string 109 | list bool 110 | module string 111 | ) 112 | 113 | func init() { 114 | flag.StringVar(&host, "h", "", "remote hostname") 115 | flag.StringVar(&group, "g", "", "group name") 116 | flag.StringVar(®, "e", "", "regrex match host name") 117 | flag.BoolVar(&list, "list", false, "list host info") 118 | flag.StringVar(&module, "m", "", "module name") 119 | } 120 | 121 | func Getworkdir() (string, error) { 122 | pwd, err := os.Getwd() 123 | if err != nil { 124 | return "", err 125 | } 126 | return pwd, nil 127 | } 128 | 129 | func ParseCommand() *Command { 130 | /* 131 | flag.Usage = func() { 132 | fmt.Printf("Usage: %s host [host|group] options [cmd|copyfile]\n", os.Args[0]) 133 | fmt.Printf("\t -h : specified a remote host, use , split one or more host\n") 134 | fmt.Printf("\t -g : specified a remote hostgroup\n") 135 | fmt.Printf("\t -e : Regrex match a remote host name default\n") 136 | fmt.Printf("\t -list : list host info\n") 137 | fmt.Printf("\t -m : select a module, -m [cmd|copy]\n") 138 | fmt.Printf("\t\t copy : [src, dest,mode,force,backup,user,owner]\n\n") 139 | fmt.Printf("e.g.: gosshtoll -h steven -m cmd 'uptime'\n") 140 | os.Exit(0) 141 | } 142 | */ 143 | flag.Parse() 144 | workdir, err := Getworkdir() 145 | if err != nil { 146 | log.Fatal(err) 147 | } 148 | filename := workdir + "/conf/conf.ini" 149 | cfg, err := conf.Newconfig(filename) 150 | if err != nil { 151 | log.Fatal(err) 152 | } 153 | mode := cfg.Getmode() 154 | cmdname := &Command{} 155 | cmdname.Model = mode 156 | if host != "" { 157 | cmdname.AddHost(host) 158 | } 159 | if group != "" { 160 | cmdname.AddGroup(group) 161 | } 162 | if reg != "" { 163 | cmdname.AddRegular(reg) 164 | } 165 | 166 | if module != "" { 167 | cmdname.AddModule(module) 168 | if flag.NArg() > 0 { 169 | cmdname.Action = strings.TrimSpace(flag.Args()[0]) 170 | } else { 171 | fmt.Println("The NArg() empty") 172 | flag.Usage() 173 | } 174 | } else if module == "" && list { 175 | cmdname.Listhostinfo() 176 | } else { 177 | fmt.Println("Error: must to specified a module, like as: cmd, sendfile, getfile") 178 | flag.Usage() 179 | } 180 | return cmdname 181 | } 182 | -------------------------------------------------------------------------------- /bin/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/BurntSushi/toml" 7 | ) 8 | 9 | type Mysql struct { 10 | Username string 11 | Password string 12 | Host string 13 | Dbname string 14 | Ipaddr string 15 | Port int64 16 | } 17 | 18 | type File struct { 19 | Hostpath string 20 | Grouppath string 21 | } 22 | 23 | type Config struct { 24 | Mode string 25 | Mysqldb Mysql `toml:"mysql"` 26 | Fileinfo File `toml:"file"` 27 | Filepath string 28 | } 29 | 30 | func Newconfig(filename string) (*Config, error) { 31 | config := &Config{} 32 | config.Filepath = filename 33 | _, err := toml.DecodeFile(config.Filepath, config) 34 | if err != nil { 35 | return &Config{}, err 36 | } 37 | return config, nil 38 | } 39 | 40 | func (c *Config) Getmode() string { 41 | return c.Mode 42 | } 43 | 44 | func (c *Config) GetDBparams() Mysql { 45 | return c.Mysqldb 46 | } 47 | 48 | func (c *Config) GetFileinfo() File { 49 | return c.Fileinfo 50 | } 51 | 52 | func (c *Config) GetHostfile(filepath string) (*File, error) { 53 | var fileinfo File 54 | /* 55 | filepath := c.Fileinfo.Hostpath 56 | workdir, err := Getworkdir() 57 | if err != nil { 58 | return File{}, err 59 | } 60 | filepath = workdir + filepath 61 | */ 62 | _, err := toml.DecodeFile(filepath, fileinfo) 63 | if err != nil { 64 | return &File{}, err 65 | } 66 | return &fileinfo, nil 67 | 68 | } 69 | 70 | func Getworkdir() (string, error) { 71 | pwd, err := os.Getwd() 72 | if err != nil { 73 | return "", err 74 | } 75 | return pwd, nil 76 | } 77 | -------------------------------------------------------------------------------- /bin/conf/conf.ini: -------------------------------------------------------------------------------- 1 | mode = "mysql" 2 | 3 | [mysql] 4 | ipaddr = "127.0.0.1" 5 | username = "root" 6 | password = "" 7 | dbname = "gosshtool" 8 | port = 3306 9 | 10 | [file] 11 | hostpath= "/conf/host.conf" 12 | grouppath= "/conf/group.conf" 13 | -------------------------------------------------------------------------------- /bin/conf/conf_test.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetmode(t *testing.T) { 9 | filedir, err := Getworkdir() 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | filename := filedir + "/conf.ini" 14 | cfg, err := Newconfig(filename) 15 | if err != nil { 16 | //fmt.Println(err) 17 | t.Error(err) 18 | } 19 | //fmt.Println(cfg) 20 | m := cfg.Getmode() 21 | fmt.Println(m) 22 | } 23 | 24 | func TestGetDBparams(t *testing.T) { 25 | filedir, err := Getworkdir() 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | filename := filedir + "/conf.ini" 30 | cfg, err := Newconfig(filename) 31 | if err != nil { 32 | //fmt.Println(err) 33 | t.Error(err) 34 | } 35 | m := cfg.GetDBparams() 36 | fmt.Println(m) 37 | } 38 | 39 | func TestGetFileinfo(t *testing.T) { 40 | filedir, err := Getworkdir() 41 | if err != nil { 42 | t.Error(err) 43 | } 44 | filename := filedir + "/conf.ini" 45 | cfg, err := Newconfig(filename) 46 | if err != nil { 47 | t.Error(err) 48 | } 49 | m := cfg.GetFileinfo() 50 | fmt.Println(m) 51 | } 52 | 53 | func TestGetHostfile(t *testing.T) { 54 | filedir, err := Getworkdir() 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | filename := filedir + "/conf.ini" 59 | cfg, err := Newconfig(filename) 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | filehost, err := cfg.GetHostfile("/home/steven/go/src/gosshtool/bin/conf/host.conf") 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | fmt.Println(filehost) 68 | } 69 | -------------------------------------------------------------------------------- /bin/conf/group.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/bin/conf/group.conf -------------------------------------------------------------------------------- /bin/conf/host.conf: -------------------------------------------------------------------------------- 1 | [steven] 2 | username=root 3 | password= 4 | ipaddr=10.68.2.30 5 | port=22 6 | 7 | [monitor] 8 | username=root 9 | password= 10 | ipaddr=10.68.2.31 11 | port=22 12 | 13 | 14 | -------------------------------------------------------------------------------- /bin/gosshtool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "gosshtool/bin/command" 5 | "gosshtool/utils/handler" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | cmd := command.ParseCommand() 11 | err := handler.HandleFunc(cmd) 12 | if err != nil { 13 | log.Fatal("Error: ", err) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = web 2 | httpport = 8080 3 | runmode = dev 4 | 5 | sessionon = true 6 | SessionProvider = file 7 | SessionProviderConfig = ./session 8 | SessionGCMaxLifetime = 28800 9 | SessionAutoSetCookie = true 10 | -------------------------------------------------------------------------------- /conf/hosts.conf: -------------------------------------------------------------------------------- 1 | title = "the remote host info" 2 | [hosts] 3 | 4 | # Indentation (tabs and/or spaces) is allowed but not required 5 | [hosts.webserver1] 6 | ip = "192.168.1.30" 7 | user = "root" 8 | pass = "lsdddd" 9 | port = 22 10 | 11 | [hosts.webserver2] 12 | ip = "192.168.1.31" 13 | user = "root" 14 | pass = "webdddddles" 15 | port = 22 16 | 17 | [hosts.webserver3] 18 | ip = "192.168.1.32" 19 | user = "root" 20 | pass = "boxnewtonjoules" 21 | port = 22 22 | -------------------------------------------------------------------------------- /controllers/base.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/astaxie/beego" 7 | ) 8 | 9 | type BaseController struct { 10 | beego.Controller 11 | } 12 | 13 | func (this *BaseController) Response(status bool, str string, data interface{}) { 14 | this.Data["json"] = &map[string]interface{}{"status": status, "info": str, "data": data} 15 | this.ServeJSON() 16 | this.StopRun() 17 | } 18 | 19 | func (this *BaseController) checkerr(err error, str string) { 20 | if err != nil { 21 | this.Response(false, str, err.Error()) 22 | } 23 | } 24 | 25 | func (this *BaseController) isPost() bool { 26 | return this.Ctx.Request.Method == "POST" 27 | } 28 | 29 | func (this *BaseController) getClientIp() string { 30 | s := strings.Split(this.Ctx.Request.RemoteAddr, ":") 31 | return s[0] 32 | } 33 | 34 | // 重定向 35 | func (self *BaseController) redirect(url string) { 36 | self.Redirect(url, 302) 37 | self.StopRun() 38 | } 39 | 40 | //ajax返回 列表 41 | func (self *BaseController) ajaxList(msg interface{}, msgno int, count int64, data interface{}) { 42 | out := make(map[string]interface{}) 43 | out["code"] = msgno 44 | out["msg"] = msg 45 | out["count"] = count 46 | out["data"] = data 47 | self.Data["json"] = out 48 | self.ServeJSON() 49 | self.StopRun() 50 | } 51 | -------------------------------------------------------------------------------- /controllers/command.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "gosshtool/models" 5 | "gosshtool/utils/msgcrypt" 6 | "gosshtool/utils/sshclient" 7 | "strings" 8 | ) 9 | 10 | type CommandController struct { 11 | FrontController 12 | } 13 | 14 | func (c *CommandController) Execute() { 15 | host := c.Ctx.Input.Param(":hostname") 16 | hostname, err := models.FindHostByName(host) 17 | c.checkerr(err, "get host by name error") 18 | 19 | if c.isPost() { 20 | cc := c.GetString("command") 21 | if cc == "" { 22 | c.Response(false, "the command empty", "") 23 | } 24 | if strings.Contains(cc, ">") { 25 | c.Response(false, "命令行中不能包含重写向符号", "") 26 | } 27 | decryptPass, err := msgcrypt.AesDecrypt(hostname.Pass) 28 | c.checkerr(err, "decrypt pass error") 29 | var sskey bool 30 | if hostname.Skey == 1 { 31 | sskey = true 32 | } else { 33 | sskey = false 34 | } 35 | client, err := sshclient.NewClient(hostname.Ip, hostname.User, decryptPass, hostname.Port, sskey, hostname.Name) 36 | c.checkerr(err, "ssh client connect error") 37 | result, err := client.Run(cc) 38 | c.checkerr(err, "execute remote cmd error") 39 | c.Response(true, "success", string(result)) 40 | /* 41 | var cmd Cmd 42 | cmd.Result = string(result) 43 | cmd.Err = err 44 | c.Data["json"] = &cmd 45 | c.ServeJSON() 46 | */ 47 | } 48 | c.Data["Title"] = "主机页面" 49 | c.Data["Hosts"] = hostname 50 | c.TplName = "host/command.html" 51 | 52 | } 53 | 54 | type Cmd struct { 55 | Result string 56 | Err error 57 | } 58 | -------------------------------------------------------------------------------- /controllers/front.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | type FrontController struct { 4 | BaseController 5 | } 6 | 7 | func (c *FrontController) Prepare() { 8 | username := c.GetSession("username") 9 | if username == nil { 10 | c.redirect("/login") 11 | } 12 | userid := c.GetSession("userid") 13 | c.Data["Userid"] = userid 14 | c.Data["Username"] = username 15 | //c.Layout = "layout/layui.tpl" 16 | c.Layout = "layout/main.tpl" 17 | 18 | } 19 | -------------------------------------------------------------------------------- /controllers/group.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "gosshtool/models" 5 | "gosshtool/utils/msgcrypt" 6 | "gosshtool/utils/sshclient" 7 | "gosshtool/utils/validate" 8 | 9 | "github.com/astaxie/beego/validation" 10 | 11 | //"log" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | ) 16 | 17 | type GroupController struct { 18 | FrontController 19 | } 20 | 21 | func (c *GroupController) Get() { 22 | groups, err := models.FindAllGroups() 23 | c.checkerr(err, "get groups info error") 24 | if c.isPost() { 25 | group := strings.TrimSpace(c.GetString("gname")) 26 | info := c.GetString("info") 27 | gid, _ := c.GetInt("id") 28 | 29 | valid := validation.Validation{} 30 | var groupv = validate.Groups{group, info} 31 | b, err := valid.Valid(&groupv) 32 | if err != nil { 33 | c.Response(false, err.Error()+"valid groups info error", "") 34 | } 35 | if !b { 36 | for _, err := range valid.Errors { 37 | c.Response(false, err.Key+" validation error: "+err.Message, "") 38 | } 39 | } 40 | _, err = models.EditGroups(group, info, gid) 41 | if err == nil { 42 | c.Response(true, "编辑组名成功", "") 43 | } else { 44 | c.Response(false, "编辑组名失败", "") 45 | } 46 | } 47 | c.Data["Title"] = "组别列表" 48 | c.Data["Groups"] = groups 49 | c.TplName = "group/index.html" 50 | } 51 | 52 | func (c *GroupController) AddGroups() { 53 | 54 | if c.isPost() { 55 | group := strings.TrimSpace(c.GetString("gname")) 56 | info := c.GetString("info") 57 | 58 | valid := validation.Validation{} 59 | var groupv = validate.Groups{group, info} 60 | b, err := valid.Valid(&groupv) 61 | if err != nil { 62 | c.Response(false, err.Error()+"valid groups info error", "") 63 | } 64 | if !b { 65 | for _, err := range valid.Errors { 66 | c.Response(false, err.Key+" validation error: "+err.Message, "") 67 | } 68 | } 69 | if models.GroupsExistCheck(group) { 70 | c.Response(false, "the groupname has been already existing", "") 71 | } 72 | _, err = models.AddGroups(group, info) 73 | if err == nil { 74 | c.Response(true, "添加组名成功", "") 75 | } else { 76 | c.Response(false, "添加组名失败", "") 77 | } 78 | } 79 | 80 | c.Data["Title"] = "新增组别" 81 | c.TplName = "group/add.html" 82 | } 83 | 84 | /* 85 | func (c *GroupController) EditGroups() { 86 | id := c.Ctx.Input.Param(":id") 87 | bid, err := strconv.Atoi(id) 88 | c.checkerr(err, "delete id error with a to i") 89 | groupinfo, err := models.FindGroupsById(bid) 90 | c.checkerr(err, "get groupid info error") 91 | if c.isPost() { 92 | group := strings.TrimSpace(c.GetString("gname")) 93 | info := c.GetString("info") 94 | 95 | valid := validation.Validation{} 96 | var groupv = validate.Groups{group, info} 97 | b, err := valid.Valid(&groupv) 98 | if err != nil { 99 | c.Response(false, err.Error()+"valid groups info error") 100 | } 101 | if !b { 102 | for _, err := range valid.Errors { 103 | c.Response(false, err.Key+" validation error: "+err.Message) 104 | } 105 | } 106 | _, err = models.EditGroups(group, info, bid) 107 | if err == nil { 108 | c.Response(true, "编辑组名成功") 109 | } else { 110 | c.Response(false, "编辑组名失败") 111 | } 112 | } 113 | c.Data["Title"] = "编辑组别" 114 | c.Data["Groups"] = groupinfo 115 | c.TplName = "group/edit.html" 116 | } 117 | */ 118 | 119 | func (c *GroupController) DeleteGroups() { 120 | id := c.Ctx.Input.Param(":id") 121 | bid, err := strconv.Atoi(id) 122 | c.checkerr(err, "delete id error with a to i") 123 | _, err = models.DeleteGroups(bid) 124 | if err == nil { 125 | c.Redirect("/group", 302) 126 | } 127 | } 128 | 129 | func (c *GroupController) Execute() { 130 | groupname := c.Ctx.Input.Param(":group") 131 | groups, err := models.FindHostByGroupname(groupname) 132 | c.checkerr(err, "get group by name error") 133 | 134 | if c.isPost() { 135 | cc := c.GetString("command") 136 | if cc == "" { 137 | c.Response(false, "the command empty", "") 138 | } 139 | var result = make([]map[string]interface{}, 0, len(groups)) 140 | var wg sync.WaitGroup 141 | for _, hostname := range groups { 142 | wg.Add(1) 143 | go func(ip, user, pass string, port int, skey int, name string) { 144 | out := make(map[string]interface{}) 145 | decryptPass, err := msgcrypt.AesDecrypt(pass) 146 | c.checkerr(err, "decrypt pass error") 147 | var sskey bool 148 | if skey == 1 { 149 | sskey = true 150 | } else { 151 | sskey = false 152 | } 153 | client, err := sshclient.NewClient(ip, user, decryptPass, port, sskey, name) 154 | c.checkerr(err, "ssh client connect error") 155 | res, err := client.Run(cc) 156 | c.checkerr(err, "ssh session exec command error") 157 | out["ip"] = ip 158 | out["hostname"] = name 159 | out["res"] = string(res) 160 | result = append(result, out) 161 | wg.Done() 162 | }(hostname.Ip, hostname.User, hostname.Pass, hostname.Port, hostname.Skey, hostname.Name) 163 | } 164 | wg.Wait() 165 | c.Data["json"] = result 166 | c.ServeJSON() 167 | } 168 | c.Data["Groups"] = groups 169 | c.Data["Gname"] = groupname 170 | c.Data["Title"] = "远程命令" 171 | c.TplName = "group/command.html" 172 | } 173 | -------------------------------------------------------------------------------- /controllers/host.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "gosshtool/models" 5 | "gosshtool/utils/msgcrypt" 6 | "gosshtool/utils/validate" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/astaxie/beego/validation" 11 | ) 12 | 13 | type HostController struct { 14 | FrontController 15 | } 16 | 17 | func (c *HostController) Get() { 18 | hostinfo, err := models.FindAllHostinfo() 19 | c.checkerr(err, "get hostinfo error") 20 | groups, err := models.FindAllGroups() 21 | c.checkerr(err, "find groups error") 22 | if c.isPost() { 23 | hid, _ := c.GetInt("id") 24 | hostname := strings.TrimSpace(c.GetString("hostname")) 25 | user := strings.TrimSpace(c.GetString("user")) 26 | ipaddr := strings.TrimSpace(c.GetString("ipaddr")) 27 | port := strings.TrimSpace(c.GetString("port")) 28 | group := strings.TrimSpace(c.GetString("group")) 29 | 30 | valid := validation.Validation{} 31 | var hostinfo = validate.HostEdit{hostname, ipaddr, user, port, group} 32 | b, err := valid.Valid(&hostinfo) 33 | if err != nil { 34 | c.Response(false, err.Error()+"valid hostinfo error", "") 35 | } 36 | if !b { 37 | for _, err := range valid.Errors { 38 | c.Response(false, err.Key+" validation error: "+err.Message, "") 39 | } 40 | } 41 | iport, err := strconv.Atoi(port) 42 | c.checkerr(err, "port to int error") 43 | igroup, err := strconv.Atoi(group) 44 | c.checkerr(err, "groupid to int error") 45 | _, err = models.EditHost(hostname, ipaddr, user, iport, igroup, hid) 46 | if err == nil { 47 | c.Response(true, "编辑主机成功", "") 48 | } else { 49 | c.Response(false, "修改失败", "") 50 | } 51 | } 52 | c.Data["Title"] = "主机列表" 53 | c.Data["Groups"] = groups 54 | c.Data["Hosts"] = hostinfo 55 | c.TplName = "host/index.html" 56 | } 57 | 58 | func (c *HostController) AddHost() { 59 | groups, err := models.FindAllGroups() 60 | c.checkerr(err, "find groups error") 61 | if c.isPost() { 62 | hostname := strings.TrimSpace(c.GetString("hostname")) 63 | user := strings.TrimSpace(c.GetString("user")) 64 | ipaddr := strings.TrimSpace(c.GetString("ipaddr")) 65 | pass := strings.TrimSpace(c.GetString("pass")) 66 | pass2 := strings.TrimSpace(c.GetString("pass2")) 67 | port := strings.TrimSpace(c.GetString("port")) 68 | group := strings.TrimSpace(c.GetString("group")) 69 | 70 | valid := validation.Validation{} 71 | var hostinfo = validate.HostInfo{hostname, ipaddr, user, pass, pass2, port, group} 72 | b, err := valid.Valid(&hostinfo) 73 | if err != nil { 74 | c.Response(false, err.Error()+"valid hostinfo error", "") 75 | } 76 | if !b { 77 | for _, err := range valid.Errors { 78 | c.Response(false, err.Key+" validation error: "+err.Message, "") 79 | } 80 | } 81 | iport, err := strconv.Atoi(port) 82 | c.checkerr(err, "port to int error") 83 | igroup, err := strconv.Atoi(group) 84 | c.checkerr(err, "groupid to int error") 85 | if models.NameExistCheck(hostname) { 86 | c.Response(false, "the hostname has been already existing", "") 87 | } 88 | encryptPass, err := msgcrypt.AesEncrypt(pass) 89 | c.checkerr(err, "encrypt pass error") 90 | _, err = models.AddHost(hostname, ipaddr, user, encryptPass, iport, igroup) 91 | if err == nil { 92 | c.Response(true, "添加主机成功", "") 93 | } else { 94 | c.Response(false, "添加主机失败", "") 95 | } 96 | 97 | } 98 | c.Data["Title"] = "新增主机" 99 | c.Data["Groups"] = groups 100 | c.TplName = "host/add.html" 101 | } 102 | 103 | /* 104 | func (c *HostController) EditHost() { 105 | id := c.Ctx.Input.Param(":id") 106 | bid, err := strconv.Atoi(id) 107 | c.checkerr(err, "delete id error with a to i") 108 | hostinfo, err := models.FindHostById(bid) 109 | c.checkerr(err, "find hostinfo by id error") 110 | groups, err := models.FindAllGroups() 111 | c.checkerr(err, "find groups error") 112 | if c.isPost() { 113 | hostname := strings.TrimSpace(c.GetString("hostname")) 114 | user := strings.TrimSpace(c.GetString("user")) 115 | ipaddr := strings.TrimSpace(c.GetString("ipaddr")) 116 | port := strings.TrimSpace(c.GetString("port")) 117 | group := strings.TrimSpace(c.GetString("group")) 118 | 119 | valid := validation.Validation{} 120 | var hostinfo = validate.HostEdit{hostname, ipaddr, user, port, group} 121 | b, err := valid.Valid(&hostinfo) 122 | if err != nil { 123 | c.Response(false, err.Error()+"valid hostinfo error") 124 | } 125 | if !b { 126 | for _, err := range valid.Errors { 127 | c.Response(false, err.Key+" validation error: "+err.Message) 128 | } 129 | } 130 | iport, err := strconv.Atoi(port) 131 | c.checkerr(err, "port to int error") 132 | igroup, err := strconv.Atoi(group) 133 | c.checkerr(err, "groupid to int error") 134 | _, err = models.EditHost(hostname, ipaddr, user, iport, igroup, bid) 135 | if err == nil { 136 | c.Response(true, "编辑主机成功") 137 | } else { 138 | c.Response(false, "修改失败") 139 | } 140 | } 141 | c.Data["Title"] = "新增主机" 142 | c.Data["Groups"] = groups 143 | c.Data["Hosts"] = hostinfo 144 | c.TplName = "host/edit.html" 145 | } 146 | */ 147 | 148 | func (c *HostController) ChangePass() { 149 | id := c.Ctx.Input.Param(":id") 150 | bid, err := strconv.Atoi(id) 151 | c.checkerr(err, "get change host id error with a to i") 152 | if c.isPost() { 153 | oldpass := strings.TrimSpace(c.GetString("oldpass")) 154 | newpass := strings.TrimSpace(c.GetString("newpass")) 155 | newpass2 := strings.TrimSpace(c.GetString("newpass2")) 156 | if oldpass == "" || newpass == "" || newpass2 == "" { 157 | c.Response(false, "密码不能为空", "") 158 | } 159 | if len(oldpass) > 50 || len(newpass) > 50 || len(newpass2) > 50 { 160 | c.Response(false, "密码长度不能超过50字符", "") 161 | } 162 | if newpass != newpass2 { 163 | c.Response(false, "新密码与确认密码不一致", "") 164 | } 165 | hostinfo, err := models.FindHostById(bid) 166 | c.checkerr(err, "get host info by id error") 167 | encryptoldpass, cerr := msgcrypt.AesEncrypt(oldpass) 168 | c.checkerr(cerr, "encrypt oldpass error") 169 | if encryptoldpass != hostinfo.Pass { 170 | c.Response(false, "旧密码不正确", "") 171 | } 172 | encryptPass, err := msgcrypt.AesEncrypt(newpass) 173 | c.checkerr(err, "encrypt newpass error") 174 | _, err = models.ChangeHostPass(encryptPass, bid) 175 | if err != nil { 176 | c.Response(false, "修改密码入库失败", "") 177 | } 178 | c.Response(true, "修改密码成功", "") 179 | } 180 | c.Data["Id"] = bid 181 | c.TplName = "host/changepass.html" 182 | c.Layout = "layout/layer.tpl" 183 | } 184 | 185 | func (c *HostController) DelHost() { 186 | id := c.Ctx.Input.Param(":id") 187 | bid, err := strconv.Atoi(id) 188 | c.checkerr(err, "delete id error with a to i") 189 | _, err = models.DeleteHost(bid) 190 | if err == nil { 191 | c.Redirect("/host", 302) 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /controllers/login.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "gosshtool/models" 5 | 6 | "github.com/gogather/com" 7 | ) 8 | 9 | type LoginController struct { 10 | BaseController 11 | } 12 | 13 | type Login struct { 14 | name string 15 | password string 16 | } 17 | 18 | func (c *LoginController) Prepare() { 19 | c.EnableXSRF = false 20 | } 21 | 22 | func (c *LoginController) Get() { 23 | c.TplName = "login/index.html" 24 | } 25 | 26 | func (c *LoginController) Post() { 27 | username := c.GetString("username") 28 | password := c.GetString("password") 29 | if username == "" || password == "" { 30 | c.Response(false, "用户和密码不能为空", "") 31 | } 32 | //need to verify with mysql user table 33 | user, err := models.FindUserByName(username) 34 | if err != nil { 35 | c.Response(false, "用户不存在", "") 36 | 37 | } else { 38 | pass := com.Md5(password) 39 | if pass == user.Pass { 40 | 41 | c.SetSession("username", username) 42 | c.SetSession("userid", user.Id) 43 | c.Response(true, "success", "") 44 | } else { 45 | c.Response(false, "passwd invalid", "") 46 | } 47 | } 48 | } 49 | 50 | func (this *LoginController) Logout() { 51 | this.SetSession("username", nil) 52 | this.DelSession("username") 53 | this.Ctx.Redirect(302, "/login") 54 | } 55 | -------------------------------------------------------------------------------- /controllers/main.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | type MainController struct { 4 | FrontController 5 | } 6 | 7 | func (c *MainController) Get() { 8 | c.Data["Title"] = "主页" 9 | c.TplName = "index.tpl" 10 | } 11 | -------------------------------------------------------------------------------- /controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "gosshtool/models" 5 | "gosshtool/utils/validate" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/astaxie/beego/validation" 10 | "github.com/gogather/com" 11 | ) 12 | 13 | type UserController struct { 14 | FrontController 15 | } 16 | 17 | func (c *UserController) Get() { 18 | users, err := models.FindAllUser() 19 | c.checkerr(err, "find all user info error") 20 | 21 | c.Data["Title"] = "用户列表" 22 | c.Data["Users"] = users 23 | c.TplName = "user/index.html" 24 | } 25 | 26 | func (c *UserController) Add() { 27 | if c.isPost() { 28 | username := strings.TrimSpace(c.GetString("username")) 29 | password := strings.TrimSpace(c.GetString("password")) 30 | password2 := strings.TrimSpace(c.GetString("password2")) 31 | email := strings.TrimSpace(c.GetString("email")) 32 | valid := validation.Validation{} 33 | var userinfo = validate.UserAdd{username, password, password2, email} 34 | b, err := valid.Valid(&userinfo) 35 | if err != nil { 36 | c.Response(false, err.Error()+"valid userinfo input error", "") 37 | } 38 | if !b { 39 | for _, err := range valid.Errors { 40 | c.Response(false, err.Key+"; validation error: "+err.Message, "") 41 | } 42 | } 43 | check := models.UserExistCheck(username) 44 | if check { 45 | c.Response(false, "用户已经存在", "") 46 | } 47 | pass := com.Md5(password) 48 | _, err = models.AddUser(username, pass, email) 49 | if err != nil { 50 | c.Response(false, "添加用户信息入库失败", "") 51 | } 52 | c.Response(true, "添加用户入库成功", "") 53 | 54 | } 55 | c.Data["Title"] = "新增用户" 56 | c.TplName = "user/add.html" 57 | } 58 | 59 | func (c *UserController) Edit() { 60 | id := c.Ctx.Input.Param(":id") 61 | bid, err := strconv.Atoi(id) 62 | rights, err := models.FindAllRights() 63 | c.checkerr(err, "Find all rights info error") 64 | userinfo, err := models.FindUserById(bid) 65 | c.checkerr(err, "edit userid error with a to i") 66 | if c.isPost() { 67 | email := strings.TrimSpace(c.GetString("email")) 68 | right := strings.TrimSpace(c.GetString("rights")) 69 | valid := validation.Validation{} 70 | var userinfo = validate.UserEdit{email, right} 71 | b, err := valid.Valid(&userinfo) 72 | if err != nil { 73 | c.Response(false, err.Error()+"valid userinfo input error", "") 74 | } 75 | if !b { 76 | for _, err := range valid.Errors { 77 | c.Response(false, err.Key+"; validation error: "+err.Message, "") 78 | } 79 | } 80 | iright, err := strconv.Atoi(right) 81 | c.checkerr(err, "right atoi error") 82 | _, err = models.EditUser(email, iright, bid) 83 | if err != nil { 84 | c.Response(false, "添加用户信息入库失败", "") 85 | } 86 | c.Response(true, "添加用户入库成功", "") 87 | } 88 | c.Data["Userinfo"] = userinfo 89 | c.Data["Rights"] = rights 90 | c.TplName = "user/edit.html" 91 | c.Layout = "layout/layer.tpl" 92 | } 93 | 94 | func (c *UserController) Delete() { 95 | id := c.Ctx.Input.Param(":id") 96 | bid, err := strconv.Atoi(id) 97 | c.checkerr(err, "delete id error with a to i") 98 | _, err = models.DeleteUser(bid) 99 | if err == nil { 100 | c.Response(true, "success", "") 101 | } 102 | } 103 | 104 | func (c *UserController) ChangePass() { 105 | id := c.Ctx.Input.Param(":id") 106 | bid, err := strconv.Atoi(id) 107 | c.checkerr(err, "get change user id error with a to i") 108 | if c.isPost() { 109 | oldpass := strings.TrimSpace(c.GetString("oldpass")) 110 | newpass := strings.TrimSpace(c.GetString("newpass")) 111 | newpass2 := strings.TrimSpace(c.GetString("newpass2")) 112 | if oldpass == "" || newpass == "" || newpass2 == "" { 113 | c.Response(false, "密码不能为空", "") 114 | } 115 | if len(oldpass) > 50 || len(newpass) > 50 || len(newpass2) > 50 { 116 | c.Response(false, "密码长度不能超过50字符", "") 117 | } 118 | if newpass != newpass2 { 119 | c.Response(false, "新密码与确认密码不一致", "") 120 | } 121 | userinfo, err := models.FindUserById(bid) 122 | c.checkerr(err, "get user info by id error") 123 | encryptoldpass := com.Md5(oldpass) 124 | if encryptoldpass != userinfo.Pass { 125 | c.Response(false, "旧密码不正确", "") 126 | } 127 | encryptPass := com.Md5(newpass) 128 | _, err = models.ChangeUserPass(encryptPass, bid) 129 | if err != nil { 130 | c.Response(false, "修改密码入库失败", "") 131 | } 132 | c.Response(true, "修改密码成功", "") 133 | } 134 | c.Data["Id"] = bid 135 | c.TplName = "user/changepass.html" 136 | c.Layout = "layout/layer.tpl" 137 | } 138 | -------------------------------------------------------------------------------- /gosshtool: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/gosshtool -------------------------------------------------------------------------------- /gosshtool.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.7.21, for Linux (x86_64) 2 | -- 3 | -- Host: localhost Database: gosshtool 4 | -- ------------------------------------------------------ 5 | -- Server version 5.7.21 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `groups` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `groups`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `groups` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `gname` varchar(100) NOT NULL, 28 | `info` varchar(400) NOT NULL, 29 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 30 | PRIMARY KEY (`id`) 31 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; 32 | /*!40101 SET character_set_client = @saved_cs_client */; 33 | 34 | -- 35 | -- Table structure for table `hostinfo` 36 | -- 37 | 38 | DROP TABLE IF EXISTS `hostinfo`; 39 | /*!40101 SET @saved_cs_client = @@character_set_client */; 40 | /*!40101 SET character_set_client = utf8 */; 41 | CREATE TABLE `hostinfo` ( 42 | `id` int(11) NOT NULL AUTO_INCREMENT, 43 | `groups_id` int(11) NOT NULL, 44 | `name` varchar(100) NOT NULL, 45 | `ip` varchar(50) NOT NULL, 46 | `user` varchar(100) NOT NULL, 47 | `pass` varchar(200) NOT NULL, 48 | `port` int(11) NOT NULL DEFAULT '22', 49 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 50 | PRIMARY KEY (`id`) 51 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1; 52 | /*!40101 SET character_set_client = @saved_cs_client */; 53 | 54 | -- 55 | -- Table structure for table `rights` 56 | -- 57 | 58 | DROP TABLE IF EXISTS `rights`; 59 | /*!40101 SET @saved_cs_client = @@character_set_client */; 60 | /*!40101 SET character_set_client = utf8 */; 61 | CREATE TABLE `rights` ( 62 | `id` int(11) NOT NULL AUTO_INCREMENT, 63 | `rname` varchar(60) NOT NULL, 64 | PRIMARY KEY (`id`) 65 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 66 | /*!40101 SET character_set_client = @saved_cs_client */; 67 | 68 | -- 69 | -- Table structure for table `user` 70 | -- 71 | 72 | DROP TABLE IF EXISTS `user`; 73 | /*!40101 SET @saved_cs_client = @@character_set_client */; 74 | /*!40101 SET character_set_client = utf8 */; 75 | CREATE TABLE `user` ( 76 | `id` int(11) NOT NULL AUTO_INCREMENT, 77 | `name` varchar(50) NOT NULL, 78 | `pass` varchar(50) NOT NULL, 79 | `email` varchar(60) NOT NULL, 80 | `rights_id` int(11) NOT NULL DEFAULT '3', 81 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 82 | PRIMARY KEY (`id`) 83 | ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1; 84 | /*!40101 SET character_set_client = @saved_cs_client */; 85 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 86 | 87 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 88 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 89 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 90 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 91 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 92 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 93 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 94 | 95 | -- Dump completed on 2018-04-04 18:26:13 96 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | _ "gosshtool/routers" 6 | ) 7 | 8 | func main() { 9 | beego.Run() 10 | } 11 | -------------------------------------------------------------------------------- /models/base.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | _ "github.com/go-sql-driver/mysql" 6 | ) 7 | 8 | func init() { 9 | orm.RegisterDriver("mysql", orm.DRMySQL) 10 | orm.RegisterDataBase("default", "mysql", "root:@tcp/gosshtool?charset=utf8") 11 | orm.Debug = true 12 | } 13 | -------------------------------------------------------------------------------- /models/groups.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | func init() { 8 | orm.RegisterModel(new(Hostgroups)) 9 | } 10 | 11 | type Hostgroups struct { 12 | Id int 13 | Gname string 14 | Info string 15 | } 16 | 17 | func FindAllGroups() ([]Hostgroups, error) { 18 | o := orm.NewOrm() 19 | var groups []Hostgroups 20 | _, err := o.QueryTable("hostgroups").All(&groups) 21 | return groups, err 22 | } 23 | 24 | func FindGroupsById(id int) (Hostgroups, error) { 25 | o := orm.NewOrm() 26 | var groups Hostgroups 27 | err := o.QueryTable("hostgroups").Filter("id", id).One(&groups) 28 | return groups, err 29 | } 30 | 31 | func FindGroupsByName(gname string) ([]Hostgroups, error) { 32 | o := orm.NewOrm() 33 | var groups []Hostgroups 34 | _, err := o.QueryTable("hostgroups").Filter("gname", gname).All(&groups) 35 | return groups, err 36 | } 37 | 38 | func AddGroups(name, info string) (int64, error) { 39 | o := orm.NewOrm() 40 | sql := "insert into hostgroups (gname, info) values( ?, ?)" 41 | res, err := o.Raw(sql, name, info).Exec() 42 | if nil != err { 43 | return 0, err 44 | } else { 45 | return res.LastInsertId() 46 | } 47 | 48 | } 49 | 50 | func EditGroups(name, info string, id int) (int64, error) { 51 | o := orm.NewOrm() 52 | sql := "update hostgroups set gname=?,info=? where id=?" 53 | res, err := o.Raw(sql, name, info, id).Exec() 54 | if nil != err { 55 | return 0, err 56 | } else { 57 | return res.LastInsertId() 58 | } 59 | 60 | } 61 | 62 | func DeleteGroups(id int) (int64, error) { 63 | o := orm.NewOrm() 64 | if num, err := o.Delete(&Hostgroups{Id: id}); err == nil { 65 | return num, err 66 | } else { 67 | return 0, err 68 | } 69 | } 70 | 71 | func GroupsExistCheck(name string) bool { 72 | o := orm.NewOrm() 73 | exist := o.QueryTable("hostgroups").Filter("gname", name).Exist() 74 | return exist 75 | } 76 | -------------------------------------------------------------------------------- /models/hostinfo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | type Hostinfo struct { 8 | Id int 9 | Name string 10 | Ip string 11 | User string 12 | Pass string 13 | Port int 14 | Skey int 15 | Groups *Hostgroups `orm:"rel(fk)"` 16 | Created_at string 17 | } 18 | 19 | type HostAll struct { 20 | Hostinfo 21 | Gname string 22 | } 23 | 24 | func init() { 25 | orm.RegisterModel(new(Hostinfo)) 26 | } 27 | 28 | func FindAllHostinfo() ([]HostAll, error) { 29 | var hostinfo []HostAll 30 | o := orm.NewOrm() 31 | //_, err := o.QueryTable("hostinfo").RelatedSel().OrderBy("-id").Limit(5, start).All(&hostinfo) 32 | sql := "select hostinfo.id,hostinfo.ip,hostinfo.user,hostinfo.pass,hostinfo.port,hostinfo.name, hostinfo.skey,hostgroups.gname from hostinfo left join hostgroups on hostgroups.id=hostinfo.groups_id" 33 | _, err := o.Raw(sql).QueryRows(&hostinfo) 34 | return hostinfo, err 35 | } 36 | 37 | func FindHostByName(str string) (Hostinfo, error) { 38 | o := orm.NewOrm() 39 | var hostinfo Hostinfo 40 | err := o.QueryTable("hostinfo").Filter("name", str).One(&hostinfo) 41 | //err := o.QueryTable("hostinfo").Filter("name", str).RelatedSel("hostgroups").One(&hostinfo) 42 | return hostinfo, err 43 | } 44 | 45 | func FindHostById(id int) (Hostinfo, error) { 46 | o := orm.NewOrm() 47 | var hostinfo Hostinfo 48 | err := o.QueryTable("hostinfo").Filter("Id", id).RelatedSel("hostgroups").One(&hostinfo) 49 | return hostinfo, err 50 | } 51 | 52 | func FindHostByGroupname(gname string) ([]HostAll, error) { 53 | var hostinfo []HostAll 54 | o := orm.NewOrm() 55 | sql := "select hostinfo.id,hostinfo.ip,hostinfo.user,hostinfo.pass,hostinfo.port,hostinfo.name, hostgroups.gname from hostinfo inner join hostgroups on hostgroups.id=hostinfo.groups_id where hostgroups.gname=?" 56 | _, err := o.Raw(sql, gname).QueryRows(&hostinfo) 57 | return hostinfo, err 58 | } 59 | 60 | func FindHostByIp(str string) (Hostinfo, error) { 61 | o := orm.NewOrm() 62 | var hostinfo Hostinfo 63 | err := o.QueryTable("hostinfo").Filter("Ip", str).One(&hostinfo) 64 | return hostinfo, err 65 | } 66 | 67 | func AddHost(name, ip, user, pass string, port int, group int) (int64, error) { 68 | o := orm.NewOrm() 69 | sql := "insert into hostinfo (name, ip, user, pass,port,groups_id) values( ?, ?, ?, ?,?,?)" 70 | res, err := o.Raw(sql, name, ip, user, pass, port, group).Exec() 71 | if nil != err { 72 | return 0, err 73 | } else { 74 | return res.LastInsertId() 75 | } 76 | 77 | } 78 | 79 | func EditHost(name, ip, user string, port int, group int, id int) (int64, error) { 80 | o := orm.NewOrm() 81 | sql := "update hostinfo set name=?,ip=?, user=?,port=?, groups_id=? where id=?" 82 | res, err := o.Raw(sql, name, ip, user, port, group, id).Exec() 83 | if nil != err { 84 | return 0, err 85 | } else { 86 | return res.LastInsertId() 87 | } 88 | 89 | } 90 | 91 | func ChangeHostPass(pass string, id int) (int64, error) { 92 | o := orm.NewOrm() 93 | sql := "update hostinfo set pass=? where id=?" 94 | res, err := o.Raw(sql, pass, id).Exec() 95 | if nil != err { 96 | return 0, err 97 | } else { 98 | return res.LastInsertId() 99 | } 100 | 101 | } 102 | 103 | func DeleteHost(id int) (int64, error) { 104 | o := orm.NewOrm() 105 | if num, err := o.Delete(&Hostinfo{Id: id}); err == nil { 106 | return num, err 107 | } else { 108 | return 0, err 109 | } 110 | } 111 | 112 | func NameExistCheck(name string) bool { 113 | o := orm.NewOrm() 114 | exist := o.QueryTable("hostinfo").Filter("name", name).Exist() 115 | return exist 116 | } 117 | -------------------------------------------------------------------------------- /models/rights.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | type Rights struct { 8 | Id int 9 | Rname string 10 | } 11 | 12 | func init() { 13 | orm.RegisterModel(new(Rights)) 14 | } 15 | 16 | func FindAllRights() ([]Rights, error) { 17 | o := orm.NewOrm() 18 | var right []Rights 19 | _, err := o.QueryTable("rights").All(&right) 20 | return right, err 21 | } 22 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | ) 6 | 7 | type User struct { 8 | Id int 9 | Name string 10 | Pass string 11 | Email string 12 | Rights *Rights `orm:"rel(fk)"` 13 | Created_at string 14 | } 15 | 16 | func init() { 17 | orm.RegisterModel(new(User)) 18 | } 19 | 20 | func FindAllUser() ([]User, error) { 21 | o := orm.NewOrm() 22 | var user []User 23 | _, err := o.QueryTable("user").RelatedSel("rights").All(&user) 24 | return user, err 25 | } 26 | 27 | func FindUserByName(name string) (User, error) { 28 | o := orm.NewOrm() 29 | var user User 30 | err := o.QueryTable("user").Filter("name", name).One(&user) 31 | return user, err 32 | } 33 | 34 | func FindUserById(id int) (User, error) { 35 | o := orm.NewOrm() 36 | var user User 37 | err := o.QueryTable("user").Filter("id", id).RelatedSel("rights").One(&user) 38 | return user, err 39 | } 40 | 41 | func AddUser(name, pass, email string) (int64, error) { 42 | o := orm.NewOrm() 43 | sql := "insert into user (name, pass,email) values( ?, ?, ?)" 44 | res, err := o.Raw(sql, name, pass, email).Exec() 45 | if nil != err { 46 | return 0, err 47 | } else { 48 | return res.LastInsertId() 49 | } 50 | 51 | } 52 | 53 | func EditUser(email string, right, id int) (int64, error) { 54 | o := orm.NewOrm() 55 | sql := "update user set email=?,rights_id=? where id=?" 56 | res, err := o.Raw(sql, email, right, id).Exec() 57 | if nil != err { 58 | return 0, err 59 | } else { 60 | return res.LastInsertId() 61 | } 62 | 63 | } 64 | 65 | func ChangeUserPass(pass string, id int) (int64, error) { 66 | o := orm.NewOrm() 67 | sql := "update user set pass=? where id=?" 68 | res, err := o.Raw(sql, pass, id).Exec() 69 | if nil != err { 70 | return 0, err 71 | } else { 72 | return res.LastInsertId() 73 | } 74 | 75 | } 76 | 77 | func DeleteUser(id int) (int64, error) { 78 | o := orm.NewOrm() 79 | if num, err := o.Delete(&User{Id: id}); err == nil { 80 | return num, err 81 | } else { 82 | return 0, err 83 | } 84 | } 85 | 86 | func UserExistCheck(name string) bool { 87 | o := orm.NewOrm() 88 | exist := o.QueryTable("user").Filter("name", name).Exist() 89 | return exist 90 | } 91 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "gosshtool/controllers" 6 | ) 7 | 8 | func init() { 9 | beego.Router("/login", &controllers.LoginController{}) 10 | beego.Router("/logout", &controllers.LoginController{}, "*:Logout") 11 | beego.Router("/", &controllers.MainController{}) 12 | 13 | beego.Router("/user", &controllers.UserController{}) 14 | beego.Router("/user/add", &controllers.UserController{}, "*:Add") 15 | beego.Router("/user/edit/:id([0-9]+)", &controllers.UserController{}, "get,post:Edit") 16 | beego.Router("/user/delete/:id([0-9]+)", &controllers.UserController{}, "post:Delete") 17 | beego.Router("/user/changepass/:id([0-9]+)", &controllers.UserController{}, "get,post:ChangePass") 18 | 19 | beego.Router("/host", &controllers.HostController{}, "get,post:Get") 20 | beego.Router("/host/add", &controllers.HostController{}, "get,post:AddHost") 21 | beego.Router("/host/delete/:id([0-9]+)", &controllers.HostController{}, "post:DelHost") 22 | beego.Router("/host/changepass/:id([0-9]+)", &controllers.HostController{}, "get,post:ChangePass") 23 | beego.Router("/host/command/:hostname", &controllers.CommandController{}, "get,post:Execute") 24 | 25 | beego.Router("/group", &controllers.GroupController{}, "get,post:Get") 26 | beego.Router("/group/add", &controllers.GroupController{}, "get,post:AddGroups") 27 | beego.Router("/group/delete/:id([0-9]+)", &controllers.GroupController{}, "post:DeleteGroups") 28 | beego.Router("/group/command/:group", &controllers.GroupController{}, "get,post:Execute") 29 | } 30 | -------------------------------------------------------------------------------- /static/css/custom.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background: #ffffff; 3 | /* padding-top: 55px; */ 4 | } 5 | 6 | /***** background color ***********/ 7 | .bg-color-grey{ 8 | background: #dddddd; 9 | } 10 | 11 | .bg-color-white{ 12 | background: white; 13 | } 14 | 15 | .bg-color-red{ 16 | background: rgba(240,100,80, 0.3); 17 | } 18 | 19 | .bg-color-primary{ 20 | background: #337ab7; 21 | border-color: #337ab7; 22 | border-top-color: rgb(51, 122, 183); 23 | border-right-color: rgb(51, 122, 183); 24 | border-bottom-color: rgb(51, 122, 183); 25 | border-left-color: rgb(51, 122, 183); 26 | } 27 | 28 | /******* text color *****************/ 29 | .red{ 30 | color: rgba(250, 80, 50, 0.4); 31 | } 32 | 33 | 34 | /******************/ 35 | .margin-tb-10{ 36 | margin-top:10px; 37 | margin-bottom: 10px; 38 | } 39 | 40 | .margin-auto{ 41 | margin: 0 auto; 42 | } 43 | 44 | .margin-top-5{ 45 | margin-top: 5px; 46 | } 47 | 48 | .margin-top-20{ 49 | margin-top: 20px; 50 | } 51 | 52 | .margin-bottom-5{ 53 | margin-bottom: 5px; 54 | } 55 | 56 | .margin-bottom-20{ 57 | margin-bottom: 20px; 58 | } 59 | 60 | .padding-auto-0{ 61 | padding: 0; 62 | } 63 | 64 | .padding-auto-5{ 65 | padding-left: 5px; 66 | padding-right: 5px; 67 | } 68 | 69 | .padding-auto-15{ 70 | padding: 15px, 15px, 15px, 15px; 71 | } 72 | 73 | .padding-tb-15{ 74 | padding-top: 15px; 75 | padding-bottom: 10px; 76 | } 77 | .padding-tb-100{ 78 | padding-top: 100px; 79 | padding-bottom: 250px; 80 | } 81 | 82 | .padding-top-15{ 83 | padding-top: 15px; 84 | padding-bottom: 15px; 85 | } 86 | 87 | .padding-left-15{ 88 | padding-left: 15px; 89 | } 90 | 91 | .padding-bottom-5{ 92 | padding-bottom:5px; 93 | } 94 | 95 | 96 | .border-radius-3{ 97 | border-radius: 3px; 98 | } 99 | 100 | .border-grey-1{ 101 | border: 1px solid #cccccc; 102 | } 103 | 104 | .border-left-3{ 105 | border-left: 3px solid #cccccc; 106 | border-top: 1px solid #cccccc; 107 | border-right: 1px solid #cccccc; 108 | border-bottom: 1px solid #cccccc; 109 | } 110 | 111 | .border-top-5{ 112 | border-top: 5px solid #1b809e; 113 | } 114 | 115 | .bs-callout-info { 116 | border-left-color: #1b809e; 117 | } 118 | .bs-callout { 119 | margin: 20px 0; 120 | border: 1px solid #ddd; 121 | border-left-width: 5px; 122 | border-left-color: #337ab7; 123 | border-radius: 3px; 124 | height: 40px; 125 | padding-top: 10px; 126 | background-color: rgba(100,100,100, 0.3); 127 | } 128 | 129 | 130 | .height-60{ 131 | height: 60px; 132 | } 133 | 134 | .font-size-18{ 135 | font-size: 18px; 136 | font-weight: bold; 137 | } 138 | -------------------------------------------------------------------------------- /static/css/dataTables.bootstrap.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} 2 | -------------------------------------------------------------------------------- /static/css/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/css/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/css/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/css/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/css/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/css/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/css/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/css/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/images/bg-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/bg-header.jpg -------------------------------------------------------------------------------- /static/images/common/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/common/help.png -------------------------------------------------------------------------------- /static/images/common/ico_list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/common/ico_list.gif -------------------------------------------------------------------------------- /static/images/common/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/common/search.png -------------------------------------------------------------------------------- /static/images/common/tit_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/common/tit_bg.gif -------------------------------------------------------------------------------- /static/images/touxiang.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/touxiang.jpg -------------------------------------------------------------------------------- /static/images/xixi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/xixi.jpg -------------------------------------------------------------------------------- /static/images/xixi2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/images/xixi2.jpg -------------------------------------------------------------------------------- /static/img/Screenshot-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/Screenshot-command.png -------------------------------------------------------------------------------- /static/img/Screenshot-host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/Screenshot-host.png -------------------------------------------------------------------------------- /static/img/ansible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/ansible.png -------------------------------------------------------------------------------- /static/img/gosshtool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/gosshtool.png -------------------------------------------------------------------------------- /static/img/user2-160x160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/user2-160x160.jpg -------------------------------------------------------------------------------- /static/img/user3-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/user3-128x128.jpg -------------------------------------------------------------------------------- /static/img/user4-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kggg/gosshtool/fcc202bd0ff4265148bcd19627cd5655c1720fcd/static/img/user4-128x128.jpg -------------------------------------------------------------------------------- /static/js/dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 3 integration 3 | ©2011-2015 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d,m){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(f.ext.classes, 6 | {sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,s,j,n){var o=new f.Api(a),t=a.oClasses,k=a.oLanguage.oPaginate,u=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault();!b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")}; 7 | l=0;for(h=f.length;l",{"class":t.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("",{href:"#", 8 | "aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('