├── feature.png ├── svnadmin.json ├── gconfig ├── gconfig_test.go └── gconfig.go ├── svn-conf ├── svnconf_test.go └── svnconf.go ├── svn-hook ├── svnhook_test.go └── svnhook.go ├── svn-passwd ├── svnpasswd_test.go └── svnpasswd.go ├── svn-server ├── assets │ ├── index.html │ ├── css │ │ ├── highlight.css │ │ ├── bootstrap-switch.min.css │ │ └── bootstrap-switch.css │ ├── js │ │ ├── switchevent.js │ │ ├── newpasswd.js │ │ ├── newgroup.js │ │ ├── newauth.js │ │ ├── bootstrap-switch.min.js │ │ ├── bootstrap-switch.js │ │ └── bootstrap.min.js │ └── config.html └── svn-server.go ├── README.md ├── svn-auth ├── svnauth_test.go └── svnauth.go ├── svn-manager ├── svn-manager_test.go └── svn-manager.go └── LICENSE /feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scbizu/svnpanel/HEAD/feature.png -------------------------------------------------------------------------------- /svnadmin.json: -------------------------------------------------------------------------------- 1 | { 2 | "svnroot": "/home/svn/", 3 | "username": "admin", 4 | "password": "ee466a4359e3cc07e142e668deff1849", 5 | "salt":"ndasec" 6 | } 7 | -------------------------------------------------------------------------------- /gconfig/gconfig_test.go: -------------------------------------------------------------------------------- 1 | package gconfig 2 | 3 | import "testing" 4 | 5 | func TestNewGconfig(t *testing.T) { 6 | conf := NewGconfig() 7 | t.Log(conf.Username) 8 | } 9 | -------------------------------------------------------------------------------- /svn-conf/svnconf_test.go: -------------------------------------------------------------------------------- 1 | package svnconf 2 | 3 | import "testing" 4 | 5 | func TestExportGeneral(t *testing.T) { 6 | conf := NewSVNconf("/home/svn/repo") 7 | general, err := conf.ExportGeneral() 8 | if err != nil { 9 | t.Error(err) 10 | } 11 | t.Log(string(general)) 12 | } 13 | 14 | func TestExporRemarkGeneral(t *testing.T) { 15 | conf := NewSVNconf("/home/svn/repo") 16 | general, err := conf.ExportRemarkGeneral("authz-db") 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | t.Log(string(general)) 21 | } 22 | 23 | func TestExporRmremarkGeneral(t *testing.T) { 24 | conf := NewSVNconf("/home/svn/repo") 25 | general, err := conf.ExportRmremarkGeneral("authz-db") 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | t.Log(string(general)) 30 | } 31 | -------------------------------------------------------------------------------- /svn-hook/svnhook_test.go: -------------------------------------------------------------------------------- 1 | package svnhook 2 | 3 | import "testing" 4 | 5 | func TestGetAuthor(t *testing.T) { 6 | hook := NewHook("/home/svn", "repo") 7 | author, err := hook.GetAuthor() 8 | if err != nil { 9 | t.Error(err) 10 | } 11 | t.Log(author) 12 | } 13 | 14 | func TestGetChanged(t *testing.T) { 15 | hook := NewHook("/home/svn", "repo") 16 | changed, err := hook.GetChanged() 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | t.Log(changed) 21 | } 22 | 23 | func TestGetdate(t *testing.T) { 24 | hook := NewHook("/home/svn", "repo") 25 | date, err := hook.Getdate() 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | t.Log(date) 30 | } 31 | 32 | func TestGetinfo(t *testing.T) { 33 | hook := NewHook("/home/svn", "repo") 34 | info, err := hook.Getinfo() 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | for k, v := range info { 39 | t.Log(k, ":", v) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /gconfig/gconfig.go: -------------------------------------------------------------------------------- 1 | package gconfig 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | //GlobalConf ... 10 | type GlobalConf struct { 11 | //SVNPATH ... 12 | SVNPATH string `json:"svnroot"` 13 | //Username ... 14 | Username string `json:"username"` 15 | //Password ... 16 | Password string `json:"password"` 17 | //Salt ... 18 | Salt string `json:"salt"` 19 | } 20 | 21 | //NewGconfig init a configuration (read json from svnadmin.json) 22 | func NewGconfig() *GlobalConf { 23 | file, err := os.Open("../svnadmin.json") 24 | if err != nil { 25 | panic(err) 26 | } 27 | defer file.Close() 28 | content, err := ioutil.ReadAll(file) 29 | if err != nil { 30 | panic(err) 31 | } 32 | //content is a json file 33 | conf := new(GlobalConf) 34 | scontent := string(content) 35 | err = json.Unmarshal([]byte(scontent), &conf) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return conf 40 | } 41 | -------------------------------------------------------------------------------- /svn-passwd/svnpasswd_test.go: -------------------------------------------------------------------------------- 1 | package svnpasswd 2 | 3 | import "testing" 4 | 5 | func TestExportUser(t *testing.T) { 6 | user := NewUser("/home/svn/repo") 7 | res, err := user.ExportUser() 8 | if err != nil { 9 | t.Error(err) 10 | } 11 | t.Log(string(res)) 12 | } 13 | 14 | func TestExportEditedUser(t *testing.T) { 15 | user := NewUser("/home/svn/repo") 16 | olddata := make(map[string]string) 17 | olddata["username"] = "scnace" 18 | olddata["pwd"] = "scnace" 19 | newdata := make(map[string]string) 20 | newdata["username"] = "scnace" 21 | newdata["pwd"] = "123456" 22 | editUser, err := user.ExportEditedUser("users", olddata, newdata) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | t.Log(string(editUser)) 27 | } 28 | 29 | func TestExportAfterDelUser(t *testing.T) { 30 | user := NewUser("/home/svn/repo") 31 | olddata := make(map[string]string) 32 | olddata["username"] = "scnace" 33 | olddata["pwd"] = "scnace" 34 | afterDel, err := user.ExportAfterDelUser("users", olddata) 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | t.Log(string(afterDel)) 39 | } 40 | 41 | func TestExportAddUser(t *testing.T) { 42 | user := NewUser("/home/svn/repo") 43 | newdata := make(map[string]string) 44 | newdata["username"] = "heiheihei" 45 | newdata["pwd"] = "123456" 46 | afterAdd, err := user.ExportAddUser("users", newdata) 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | t.Log(string(afterAdd)) 51 | } 52 | -------------------------------------------------------------------------------- /svn-server/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SVN admin 7 | 8 | 9 |
10 | 14 |
15 |
16 |
17 | 18 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svnpanel 2 | 3 | A light auth management tool for svn implement by Golang. 4 | 5 | ## Arch 6 | 7 | ### list 8 | 9 | > use `svnlook` command ,and implement `svnlook` wrapper functions . 10 | 11 | 0x00 列出所有svn repo 12 | 13 | > Read conf directory and show some useful configurations . 14 | 15 | 0x01 列出当前repo的一些设置 16 | 17 | * SVNROOT/conf/svnserve.conf 18 | 19 | * **是否允许匿名登录** : 读取`anon-access`的值,如果`anon-access`被注释掉(即`#anon-access=xxx`,svn创建repo时的默认设置。),则被认为开启匿名登录(值不为`none`都被认为是匿名登录); 20 | 21 | * ~~**匿名用户文件读写权限** : 读取`auth-access`的值,如果值为`write`,那么通过验证(已登录)的用户都可以读和写;如果值为`read`,那么匿名的用户则不可以写(不能update到svn服务器)~~(feature deleted because of `anon-access` is always set `none`) 22 | 23 | * **开启用户设置(password)** : 开启即可进行用户增删改查(即设置`password-db=passwd`)。 24 | 25 | * **开启权限设置(authz)** : 开启即可进行用户组的权限配置(即设置`authz-db=authz`) 26 | 27 | * SVNROOT/conf/passwd 28 | 29 | 当开启`passwd`后,获取`[users]`字段下内容,`json化`用户名 和 密码 . 30 | 31 | * SVNROOT/conf/authz 32 | 33 | 当开启`authz`后,读取 ~~`[aliases]`~~,`[groups]`,和 一些repo 目录的权限。 34 | 35 | ### config 36 | 37 | > edit files under `conf` directory and save to the conf . 38 | 39 | 0x02 异步修改conf设置 40 | 41 | * 修改passwd文件 42 | 43 | 处理前端发过来的json(`{"old":["username":"","password":""],"new":["username":"","password":""]}`),old用于查询要修改的帐号,new用来替换修改的帐号。 44 | 45 | * 修改authz文件(暂不支持aliases) 46 | 47 | ## APIs 48 | 49 | |Router|Method|DataType| Service| 50 | |---|---|---|---| 51 | |/config:repo|GET|JSON|查询选中版本库的详细信息| 52 | |/repos|GET|JSON|查询全部版本库| 53 | |/edit|PUT|JSON|修改svnserve.conf的字段| 54 | |/passwd|POST|JSON|修改passwd下的用户名和密码| 55 | |/newpasswd|POST|JSON|passwd下添加新的用户| 56 | |/delpasswd|POST|JSON|删除passwd下的某个用户信息| 57 | |/groups|POST|JSON|修改authz下group字段下的用户组设置| 58 | |/addgroup|POST|JSON|增加一个用户组| 59 | |/delgroup|POST|JSON|删除一个用户组| 60 | |/editauth|POST|JSON|修改一个路径的权限| 61 | |/delauth|POST|JSON|删除一条权限| 62 | |/addauth|POST|JSON|增加一条权限| 63 | 64 | ## How To 65 | 66 | 配置文件位于根目录下 文件名是 svnadmin.json 67 | 68 | key的生成规则是 `密码?盐?` 69 | 70 | 盐可以自定义 71 | 72 | ## preview 73 | 74 | ![screenshots](./feature.png) 75 | -------------------------------------------------------------------------------- /svn-auth/svnauth_test.go: -------------------------------------------------------------------------------- 1 | package svnauth 2 | 3 | import "testing" 4 | 5 | func Test_NewSVNAuth(t *testing.T) {} 6 | 7 | func Test_ExportGroups(t *testing.T) { 8 | auth := NewSVNAuth("/home/svn/repo") 9 | data, err := auth.ExportGroups() 10 | if err != nil { 11 | t.Error(err) 12 | } 13 | t.Log(string(data)) 14 | } 15 | 16 | func TestExportDirectory(t *testing.T) { 17 | auth := NewSVNAuth("/home/svn/repo") 18 | data, err := auth.ExportDirectory() 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | t.Log(string(data)) 23 | } 24 | 25 | func TestExportEditedGroup(t *testing.T) { 26 | auth := NewSVNAuth("/home/svn/repo") 27 | olddata := make(map[string]string) 28 | olddata["groupname"] = "harry_and_sally" 29 | olddata["users"] = "harry,sally" 30 | newdata := make(map[string]string) 31 | newdata["groupname"] = "hahaha" 32 | newdata["users"] = "scnace,scbizu" 33 | data, err := auth.ExportEditedGroup("groups", olddata, newdata) 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | t.Log(string(data)) 38 | } 39 | 40 | func TestExportDelGroup(t *testing.T) { 41 | auth := NewSVNAuth("/home/svn/repo") 42 | olddata := make(map[string]string) 43 | olddata["groupname"] = "nace" 44 | olddata["users"] = "scnace" 45 | data, err := auth.ExportDelGroup("groups", olddata) 46 | if err != nil { 47 | t.Error(err) 48 | } 49 | t.Log(string(data)) 50 | } 51 | 52 | func TestExportAddGroup(t *testing.T) { 53 | auth := NewSVNAuth("/home/svn/repo") 54 | newdata := make(map[string]string) 55 | newdata["groupname"] = "nace" 56 | newdata["users"] = "scnace,scbizu" 57 | data, err := auth.ExportAddGroup("groups", newdata) 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | t.Log(string(data)) 62 | } 63 | 64 | func TestExportEditedDirectory(t *testing.T) { 65 | auth := NewSVNAuth("/home/svn/repo") 66 | olddata := make(map[string]string) 67 | olddata["users"] = "@harry_and_sally" 68 | olddata["auth"] = "rw" 69 | newdata := make(map[string]string) 70 | newdata["users"] = "@nace" 71 | newdata["auth"] = "rw" 72 | data, err := auth.ExportEditedDirectory("repository:/baz/fuz", olddata, newdata) 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | t.Log(string(data)) 77 | } 78 | 79 | func TestExportAddDirectory(t *testing.T) { 80 | auth := NewSVNAuth("/home/svn/repo") 81 | newdata := make(map[string]string) 82 | newdata["users"] = "scbizu" 83 | newdata["auth"] = "r" 84 | data, err := auth.ExportAddDirectory("/foo", newdata) 85 | if err != nil { 86 | t.Error(err) 87 | } 88 | t.Log(string(data)) 89 | } 90 | 91 | func TestExportDelDirectory(t *testing.T) { 92 | auth := NewSVNAuth("/home/svn/repo") 93 | olddata := make(map[string]string) 94 | olddata["users"] = "scbizu" 95 | olddata["auth"] = "r" 96 | data, err := auth.ExportDelDirectory("/foo/bar", olddata) 97 | if err != nil { 98 | t.Error(err) 99 | } 100 | t.Log(string(data)) 101 | } 102 | 103 | func TestWrapReplaceDtag(t *testing.T) { 104 | auth := NewSVNAuth("/home/svn/repo") 105 | err := auth.WrapReplaceDtag("repo") 106 | if err != nil { 107 | t.Error(err) 108 | } 109 | t.Log("replaced Dtag.") 110 | } 111 | -------------------------------------------------------------------------------- /svn-server/assets/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; padding: 0.5em; 9 | background: #F0F0F0; 10 | } 11 | 12 | .hljs, 13 | .hljs-subst, 14 | .hljs-tag .hljs-title, 15 | .lisp .hljs-title, 16 | .clojure .hljs-built_in, 17 | .nginx .hljs-title { 18 | color: black; 19 | } 20 | 21 | .hljs-string, 22 | .hljs-title, 23 | .hljs-constant, 24 | .hljs-parent, 25 | .hljs-tag .hljs-value, 26 | .hljs-rules .hljs-value, 27 | .hljs-rules .hljs-value .hljs-number, 28 | .hljs-preprocessor, 29 | .hljs-pragma, 30 | .haml .hljs-symbol, 31 | .ruby .hljs-symbol, 32 | .ruby .hljs-symbol .hljs-string, 33 | .hljs-aggregate, 34 | .hljs-template_tag, 35 | .django .hljs-variable, 36 | .smalltalk .hljs-class, 37 | .hljs-addition, 38 | .hljs-flow, 39 | .hljs-stream, 40 | .bash .hljs-variable, 41 | .apache .hljs-tag, 42 | .apache .hljs-cbracket, 43 | .tex .hljs-command, 44 | .tex .hljs-special, 45 | .erlang_repl .hljs-function_or_atom, 46 | .asciidoc .hljs-header, 47 | .markdown .hljs-header, 48 | .coffeescript .hljs-attribute { 49 | color: #800; 50 | } 51 | 52 | .smartquote, 53 | .hljs-comment, 54 | .hljs-annotation, 55 | .hljs-template_comment, 56 | .diff .hljs-header, 57 | .hljs-chunk, 58 | .asciidoc .hljs-blockquote, 59 | .markdown .hljs-blockquote { 60 | color: #888; 61 | } 62 | 63 | .hljs-number, 64 | .hljs-date, 65 | .hljs-regexp, 66 | .hljs-literal, 67 | .hljs-hexcolor, 68 | .smalltalk .hljs-symbol, 69 | .smalltalk .hljs-char, 70 | .go .hljs-constant, 71 | .hljs-change, 72 | .lasso .hljs-variable, 73 | .makefile .hljs-variable, 74 | .asciidoc .hljs-bullet, 75 | .markdown .hljs-bullet, 76 | .asciidoc .hljs-link_url, 77 | .markdown .hljs-link_url { 78 | color: #080; 79 | } 80 | 81 | .hljs-label, 82 | .hljs-javadoc, 83 | .ruby .hljs-string, 84 | .hljs-decorator, 85 | .hljs-filter .hljs-argument, 86 | .hljs-localvars, 87 | .hljs-array, 88 | .hljs-attr_selector, 89 | .hljs-important, 90 | .hljs-pseudo, 91 | .hljs-pi, 92 | .haml .hljs-bullet, 93 | .hljs-doctype, 94 | .hljs-deletion, 95 | .hljs-envvar, 96 | .hljs-shebang, 97 | .apache .hljs-sqbracket, 98 | .nginx .hljs-built_in, 99 | .tex .hljs-formula, 100 | .erlang_repl .hljs-reserved, 101 | .hljs-prompt, 102 | .asciidoc .hljs-link_label, 103 | .markdown .hljs-link_label, 104 | .vhdl .hljs-attribute, 105 | .clojure .hljs-attribute, 106 | .asciidoc .hljs-attribute, 107 | .lasso .hljs-attribute, 108 | .coffeescript .hljs-property, 109 | .hljs-phony { 110 | color: #88F 111 | } 112 | 113 | .hljs-keyword, 114 | .hljs-id, 115 | .hljs-title, 116 | .hljs-built_in, 117 | .hljs-aggregate, 118 | .css .hljs-tag, 119 | .hljs-javadoctag, 120 | .hljs-phpdoc, 121 | .hljs-yardoctag, 122 | .smalltalk .hljs-class, 123 | .hljs-winutils, 124 | .bash .hljs-variable, 125 | .apache .hljs-tag, 126 | .go .hljs-typename, 127 | .tex .hljs-command, 128 | .asciidoc .hljs-strong, 129 | .markdown .hljs-strong, 130 | .hljs-request, 131 | .hljs-status { 132 | font-weight: bold; 133 | } 134 | 135 | .asciidoc .hljs-emphasis, 136 | .markdown .hljs-emphasis { 137 | font-style: italic; 138 | } 139 | 140 | .nginx .hljs-built_in { 141 | font-weight: normal; 142 | } 143 | 144 | .coffeescript .javascript, 145 | .javascript .xml, 146 | .lasso .markup, 147 | .tex .hljs-formula, 148 | .xml .javascript, 149 | .xml .vbscript, 150 | .xml .css, 151 | .xml .hljs-cdata { 152 | opacity: 0.5; 153 | } 154 | -------------------------------------------------------------------------------- /svn-hook/svnhook.go: -------------------------------------------------------------------------------- 1 | package svnhook 2 | 3 | import ( 4 | "io/ioutil" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | //Hook ,,, 10 | type Hook struct { 11 | svnpath string 12 | repo string 13 | repospath string 14 | } 15 | 16 | //NewHook ... 17 | func NewHook(svnpath string, repo string) *Hook { 18 | hook := new(Hook) 19 | hook.svnpath = svnpath 20 | hook.repo = repo 21 | hook.repospath = hook.svnpath + "/" + hook.repo 22 | return hook 23 | } 24 | 25 | //GetAuthor hook the svn ,and get its repo author . 26 | func (hook *Hook) GetAuthor() (string, error) { 27 | cmd := exec.Command("svnlook", "author", hook.repospath) 28 | stdout, err := cmd.StdoutPipe() 29 | if err != nil { 30 | return "", err 31 | } 32 | if err = cmd.Start(); err != nil { 33 | return "", err 34 | } 35 | author, err := ioutil.ReadAll(stdout) 36 | if err != nil { 37 | return "", err 38 | } 39 | trimauthor := strings.TrimSuffix(string(author), "\n") 40 | return string(trimauthor), nil 41 | } 42 | 43 | //GetChanged : 打印修改的路径 44 | /** 45 | * 'A':条目添加到版本库 46 | * 'D':条目从版本库删除 47 | * ‘U’:条目内容改变了 48 | * ' U':条目属性改变了 49 | * 'UU':文件内容和属性改变了 50 | **/ 51 | func (hook *Hook) GetChanged() (string, error) { 52 | cmd := exec.Command("svnlook", "changed", hook.repospath) 53 | stdout, err := cmd.StdoutPipe() 54 | if err != nil { 55 | return "", err 56 | } 57 | if err = cmd.Start(); err != nil { 58 | return "", err 59 | } 60 | changed, err := ioutil.ReadAll(stdout) 61 | if err != nil { 62 | return "", err 63 | } 64 | return string(changed), nil 65 | } 66 | 67 | //Getdate get time stamp 68 | func (hook *Hook) Getdate() (string, error) { 69 | cmd := exec.Command("svnlook", "date", hook.repospath) 70 | stdout, err := cmd.StdoutPipe() 71 | if err != nil { 72 | return "", err 73 | } 74 | if err = cmd.Start(); err != nil { 75 | return "", err 76 | } 77 | date, err := ioutil.ReadAll(stdout) 78 | if err != nil { 79 | return "", err 80 | } 81 | return string(date), nil 82 | } 83 | 84 | //Getdiff print file diff in GNU format 85 | func (hook *Hook) Getdiff() (string, error) { 86 | cmd := exec.Command("svnlook", "diff", hook.repospath) 87 | stdout, err := cmd.StdoutPipe() 88 | if err != nil { 89 | return "", err 90 | } 91 | if err = cmd.Start(); err != nil { 92 | return "", err 93 | } 94 | diff, err := ioutil.ReadAll(stdout) 95 | if err != nil { 96 | return "", err 97 | } 98 | return string(diff), nil 99 | } 100 | 101 | //Getlog print file log 102 | func (hook *Hook) Getlog() (string, error) { 103 | cmd := exec.Command("svnlook", "log", hook.repospath) 104 | stdout, err := cmd.StdoutPipe() 105 | if err != nil { 106 | return "", err 107 | } 108 | if err = cmd.Start(); err != nil { 109 | return "", err 110 | } 111 | log, err := ioutil.ReadAll(stdout) 112 | if err != nil { 113 | return "", err 114 | } 115 | return string(log), nil 116 | } 117 | 118 | //Getinfo return a specific map 119 | func (hook *Hook) Getinfo() (map[string]string, error) { 120 | cmd := exec.Command("svnlook", "info", hook.repospath) 121 | stdout, err := cmd.StdoutPipe() 122 | if err != nil { 123 | return nil, err 124 | } 125 | if err = cmd.Start(); err != nil { 126 | return nil, err 127 | } 128 | info, err := ioutil.ReadAll(stdout) 129 | if err != nil { 130 | return nil, err 131 | } 132 | splitinfo := strings.SplitN(string(info), "\n", -1) 133 | infomap := make(map[string]string) 134 | infomap["author"] = splitinfo[0] 135 | infomap["timestamp"] = splitinfo[1] 136 | infomap["logsize"] = splitinfo[2] 137 | infomap["log"] = splitinfo[3] 138 | return infomap, nil 139 | } 140 | -------------------------------------------------------------------------------- /svn-server/assets/js/switchevent.js: -------------------------------------------------------------------------------- 1 | //匿名登录事件 2 | $('input[name="anycheckbox"]').on('switchChange.bootstrapSwitch', function(event, state) { 3 | var repo = $('#reponame').text(); 4 | if (state){ 5 | $.ajax({ 6 | url:"/edit", 7 | dataType:"json", 8 | data:{reponame:repo,tag:"anon-access",action:"remark"}, 9 | type:"PUT", 10 | }).done(function(data,textStatus,jqXHR){ 11 | if (jqXHR.status==200){ 12 | console.log("remarked anon-access"); 13 | } 14 | }); 15 | }else{ 16 | $.ajax({ 17 | url:"/edit", 18 | dataType:"json", 19 | data:{reponame:repo,tag:"anon-access",action:"rmremark"}, 20 | type:"PUT", 21 | }).done(function(data,textStatus,jqXHR){ 22 | if (jqXHR.status==200){ 23 | console.log("rm remark of anon-access"); 24 | } 25 | }); 26 | } 27 | }); 28 | //设置权限事件 29 | $('input[name="authzcheckbox"]').on('switchChange.bootstrapSwitch', function(event, state) { 30 | var repo = $('#reponame').text(); 31 | if(state){ 32 | $.ajax({ 33 | url:"/edit" , 34 | dataType:"json", 35 | data:{reponame:repo,tag:"authz-db",action:"rmremark"}, 36 | type:"PUT", 37 | }).done(function(data,textStatus,jqXHR){ 38 | if (jqXHR.status==200){ 39 | var dataObj=jQuery.parseJSON(data) 40 | // console.log(data) 41 | $('.authz').attr("style","display:block"); 42 | $('.authz-name').attr("style","display:inline"); 43 | $('.adb').text(dataObj.Adb); 44 | console.log("remarked authz-db"); 45 | } 46 | }); 47 | }else{ 48 | $.ajax({ 49 | url:"/edit", 50 | dataType:"json", 51 | data:{reponame:repo,tag:"authz-db",action:"remark"}, 52 | type:"PUT", 53 | }).done(function(data,textStatus,jqXHR){ 54 | if (textStatus === "success"){ 55 | $('.authz').attr("style","display:none"); 56 | $('.authz-name').attr("style","display:none"); 57 | console.log("rmed the remark of authz-db..."); 58 | } 59 | }); 60 | } 61 | }); 62 | //用户事件 63 | $('input[name="passwdcheckbox"]').on('switchChange.bootstrapSwitch', function(event, state) { 64 | var repo = $('#reponame').text(); 65 | if(state){ 66 | $.ajax({ 67 | url:"/edit", 68 | dataType:"json", 69 | data:{reponame:repo,tag:"password-db",action:"rmremark"}, 70 | type:"PUT", 71 | }).done(function(data,textStatus,jqXHR){ 72 | if (textStatus === "success"){ 73 | var dataObj=jQuery.parseJSON(data) 74 | $('.passwd').attr("style","display:block"); 75 | $('.passwd-name').attr("style","display:inline"); 76 | $('.pdb').text(dataObj.Pdb) 77 | } 78 | }); 79 | }else{ 80 | $.ajax({ 81 | url:"/edit", 82 | dataType:"json", 83 | data:{reponame:repo,tag:"password-db",action:"remark"}, 84 | type:"PUT", 85 | }).done(function(data,textStatus,jqXHR){ 86 | if (textStatus === "success"){ 87 | console.log("rmed the remark of password-db..."); 88 | $('.passwd').attr("style","display:none"); 89 | $('.passwd-name').attr("style","display:none"); 90 | } 91 | }); 92 | } 93 | }); 94 | 95 | $(function() { 96 | $('[name="anycheckbox"]').bootstrapSwitch(); 97 | }); 98 | $(function(){ 99 | $('[name="passwdcheckbox"]').bootstrapSwitch(); 100 | }); 101 | $(function(){ 102 | $('[name="authzcheckbox"]').bootstrapSwitch(); 103 | }); 104 | -------------------------------------------------------------------------------- /svn-server/assets/js/newpasswd.js: -------------------------------------------------------------------------------- 1 | $("#new_passwd_btn").on('click',function(){ 2 | $("#new_passwd").css("display","none") 3 | $("#new_passwd").before(` 4 | 5 | 6 | 7 | 8 |     9 | 10 | 11 | `) 12 | 13 | $("#passwd_new_del").on('click',function(){ 14 | console.log("quit edit") 15 | $("#new_passwd_tr").remove(); 16 | $("#new_passwd").css("display","block"); 17 | }); 18 | 19 | $("#passwd_new_ok").on('click',function(){ 20 | console.log("adding user ....") 21 | var repo = $('#reponame').text(); 22 | var user = $("#new_passwd_username").val(); 23 | var passwd = $("#new_passwd_pwd").val(); 24 | $.ajax({ 25 | url:"/newpasswd", 26 | data:{reponame:repo,new_username:user,new_pwd:passwd}, 27 | type:"POST", 28 | dataType:"json", 29 | }).done(function(data,statusText,jqXHR){ 30 | if (jqXHR.status ===200){ 31 | $("#new_passwd_tr").remove(); 32 | $("#new_passwd").before(` 33 | `+user+` 34 | `+passwd+` 35 | 36 | 37 | 38 |     39 | 40 | `); 41 | 42 | $('#edit_'+user).on('click',function(e){ 43 | var username = $('#username_'+user).text() 44 | var password = $('#password_'+user).text() 45 | //remove td 46 | $('#username_'+user).remove(); 47 | $('#password_'+user).remove(); 48 | //add new td 49 | $('#op_'+user).before("") 50 | $('#op_'+user).before("") 51 | //change button 52 | $('#edit_'+user).css("display","none"); 53 | $('#ok_'+user).css("display","inline"); 54 | }); 55 | // confirm edit .. 56 | $('#ok_'+user).on('click',function(e){ 57 | //handle ajax edit post 58 | var o_username= user 59 | var o_pwd= passwd 60 | var username = $('#edited_username_'+user).val() 61 | var password = $('#edited_password_'+user).val() 62 | var repo = $('#reponame').text(); 63 | $.ajax({ 64 | url:"/passwd" , 65 | data:{reponame:repo,old_username:o_username,old_pwd:o_pwd,new_username:username,new_pwd:password}, 66 | dataType:"json", 67 | type:"POST", 68 | }).done(function(data,statusText,jqXHR){ 69 | 70 | if (jqXHR.status===200){ 71 | 72 | $("#username_"+user).remove(); 73 | $("#password_"+user).remove(); 74 | $("#op_"+user).before(""+username+""); 75 | $("#op_"+user).before(""+password+""); 76 | $('#edit_'+user).css("display","inline"); 77 | $('#ok_'+user).css("display","none"); 78 | } 79 | }); 80 | console.log(username) 81 | console.log(password) 82 | }); 83 | 84 | $('#del_'+user).on('click',function(e){ 85 | //handle ajax delete 86 | var repo = $('#reponame').text(); 87 | $.ajax({ 88 | url:"/delpasswd", 89 | data:{reponame:repo,old_username:user,old_pwd:passwd}, 90 | type:"POST", 91 | dataType:"json", 92 | }).done(function(data,statusText,jqXHR){ 93 | if (jqXHR.status ===200){ 94 | console.log("delete a passwd key-pair..") 95 | $("#username_"+user).remove(); 96 | $("#password_"+user).remove(); 97 | $("#op_"+user).remove(); 98 | } 99 | }); 100 | }); 101 | } 102 | }); 103 | $("#new_passwd").css("display","block"); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /svn-server/assets/js/newgroup.js: -------------------------------------------------------------------------------- 1 | $("#new_group_btn").on('click',function(){ 2 | $("#new_group").css("display","none") 3 | $("#new_group").before(` 4 | 5 | 6 | 7 | 8 |     9 | 10 | 11 | `) 12 | 13 | $("#group_new_del").on('click',function(){ 14 | console.log("quit edit") 15 | $("#new_group_tr").remove(); 16 | $("#new_group").css("display","block"); 17 | }); 18 | 19 | $("#group_new_ok").on('click',function(){ 20 | console.log("adding user ....") 21 | var repo = $('#reponame').text(); 22 | var gname = $("#new_group_gname").val(); 23 | var gmember = $("#new_group_gmember").val(); 24 | $.ajax({ 25 | url:"/addgroup", 26 | data:{reponame:repo,new_groupname:gname,new_users:gmember}, 27 | type:"POST", 28 | dataType:"json", 29 | }).done(function(data,statusText,jqXHR){ 30 | if (jqXHR.status ===200){ 31 | $("#new_group_tr").remove(); 32 | $("#new_group").before(` 33 | `+gname+` 34 | 35 | `+gmember+` 36 | 37 | 38 | 39 |     40 | 41 | `); 42 | 43 | $('#group_edit_'+gname).on('click',function(e){ 44 | console.log('edited') 45 | var groupname = $('#gname_'+gname).text() 46 | var groupmember = $('#gmember_'+gname).text() 47 | //remove td 48 | $('#gname_'+gname).remove(); 49 | $('#gmember_'+gname).remove(); 50 | //add new td 51 | $('#group_op_'+gname).before("") 52 | $('#group_op_'+gname).before("") 53 | //change button 54 | $('#group_edit_'+gname).css("display","none"); 55 | $('#group_ok_'+gname).css("display","inline"); 56 | }); 57 | // confirm edit .. 58 | $('#group_ok_'+gname).on('click',function(e){ 59 | //handle ajax edit post 60 | var o_gname= gname 61 | var o_gmember= gmember 62 | var new_gname = $('#edited_gname_'+gname).val() 63 | var new_gmember = $('#edited_gmember_'+gname).val() 64 | var repo = $('#reponame').text(); 65 | $.ajax({ 66 | url:"/groups" , 67 | data:{reponame:repo,old_groupname:o_gname,old_users:o_gmember,new_groupname:new_gname,new_users:new_gmember}, 68 | dataType:"json", 69 | type:"POST", 70 | }).done(function(data,statusText,jqXHR){ 71 | 72 | if (jqXHR.status===200){ 73 | 74 | $("#gname_"+gname).remove(); 75 | $("#gmember_"+gname).remove(); 76 | $("#group_op_"+gname).before(""+new_gname+""); 77 | $("#group_op_"+gname).before(""+new_gmember+""); 78 | $('#group_edit_'+gname).css("display","inline"); 79 | $('#group_ok_'+gname).css("display","none"); 80 | } 81 | }); 82 | }); 83 | 84 | $('#group_del_'+gname).on('click',function(e){ 85 | //handle ajax delete 86 | var repo = $('#reponame').text(); 87 | $.ajax({ 88 | url:"/delgroup", 89 | data:{reponame:repo,old_groupname:gname,old_users:gmember}, 90 | type:"POST", 91 | dataType:"json", 92 | }).done(function(data,statusText,jqXHR){ 93 | if (jqXHR.status ===200){ 94 | console.log("delete a passwd key-pair..") 95 | $("#gname_"+gname).remove(); 96 | $("#gmember_"+gname).remove(); 97 | $("#group_op_"+gname).remove(); 98 | } 99 | }); 100 | }); 101 | } 102 | }); 103 | $("#new_group").css("display","block"); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /svn-conf/svnconf.go: -------------------------------------------------------------------------------- 1 | package svnconf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | //SVNconf ... 14 | type SVNconf struct { 15 | //SVNPATH ... 16 | SVNPATH string `json:"svnpath"` 17 | anonAC string 18 | authAC string 19 | pdb string 20 | adb string 21 | gdb string 22 | } 23 | 24 | //NewSVNconf ... 25 | func NewSVNconf(path string) *SVNconf { 26 | conf := &SVNconf{} 27 | conf.SVNPATH = path 28 | return conf 29 | } 30 | 31 | //Readfill will read the authz to the buffer 32 | func (conf *SVNconf) readfile() ([]byte, error) { 33 | confs := filepath.Join(conf.SVNPATH, "conf", "svnserve.conf") 34 | filehandle, err := os.Open(confs) 35 | defer filehandle.Close() 36 | if err != nil { 37 | return nil, errors.New(`Please check your ./conf/svnserve.conf file,if you change it's name,rename it as "svnserve.conf".`) 38 | } 39 | content, err := ioutil.ReadAll(filehandle) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return content, nil 44 | } 45 | 46 | //remarktag把tag重新改到注释状态 47 | func (conf *SVNconf) remarktag(srcContent []byte, tag string) error { 48 | confs := filepath.Join(conf.SVNPATH, "conf", "svnserve.conf") 49 | var lines []string 50 | var dstContent bytes.Buffer 51 | lines = strings.Split(string(srcContent), "\n") 52 | for k, v := range lines { 53 | if strings.HasPrefix(v, tag) { 54 | v = strings.Replace(lines[k], tag, "#"+tag, -1) 55 | } 56 | dstContent.WriteString(v + "\n") 57 | } 58 | //write to file 59 | err := ioutil.WriteFile(confs, dstContent.Bytes(), 0666) 60 | if err != nil { 61 | return err 62 | } 63 | return nil 64 | } 65 | 66 | //Rmremarktag 删除注释状态 67 | func (conf *SVNconf) rmremarktag(srcContent []byte, tag string) error { 68 | confs := filepath.Join(conf.SVNPATH, "conf", "svnserve.conf") 69 | var lines []string 70 | var dstContent bytes.Buffer 71 | lines = strings.Split(string(srcContent), "\n") 72 | for k, v := range lines { 73 | if strings.HasPrefix(strings.TrimSpace(v), "#"+tag) { 74 | v = strings.Replace(lines[k], "#"+tag, tag, -1) 75 | } 76 | dstContent.WriteString(v + "\n") 77 | } 78 | //write to file 79 | err := ioutil.WriteFile(confs, dstContent.Bytes(), 0644) 80 | if err != nil { 81 | return err 82 | } 83 | return nil 84 | } 85 | 86 | //file2json parse authz content to readable json . 87 | //[general]=>["anon-access = read","auth-access = write",...] 88 | func file2json(filecontent []byte) ([]byte, error) { 89 | var lines []string 90 | lines = strings.Split(string(filecontent), "\n") 91 | jsonm := make(map[string][]string) 92 | for k, v := range lines { 93 | if strings.HasPrefix(v, "[") { 94 | key := strings.TrimPrefix(v, "[") 95 | key = strings.TrimSuffix(key, "]") 96 | var value []string 97 | for i := k + 1; i < len(lines); i++ { 98 | if strings.HasPrefix(lines[i], "[") { 99 | break 100 | } 101 | if lines[i] != "" && !strings.HasPrefix(lines[i], "#") { 102 | 103 | value = append(value, lines[i]) 104 | 105 | } 106 | } 107 | jsonm[key] = value 108 | } 109 | } 110 | jsondata, err := json.Marshal(jsonm) 111 | if err != nil { 112 | return []byte(""), err 113 | } 114 | return jsondata, nil 115 | } 116 | 117 | func parseGeneral(rawjson []byte) ([]byte, error) { 118 | conf := make(map[string][]string) 119 | err := json.Unmarshal(rawjson, &conf) 120 | if err != nil { 121 | return []byte(""), err 122 | } 123 | generals := make(map[string]string) 124 | general := conf["general"] 125 | for _, v := range general { 126 | trimv := strings.TrimSpace(string(v)) 127 | splitv := strings.SplitN(trimv, "=", 2) 128 | generals[strings.TrimSpace(splitv[0])] = strings.TrimSpace(splitv[1]) 129 | } 130 | generalsjson, err := json.Marshal(generals) 131 | if err != nil { 132 | return []byte(""), err 133 | } 134 | return generalsjson, nil 135 | } 136 | 137 | //ExportGeneral export the content . 138 | func (conf *SVNconf) ExportGeneral() ([]byte, error) { 139 | content, err := conf.readfile() 140 | if err != nil { 141 | return []byte(""), err 142 | } 143 | rawjson, err := file2json(content) 144 | if err != nil { 145 | return []byte(""), err 146 | } 147 | general, err := parseGeneral(rawjson) 148 | if err != nil { 149 | return []byte(""), err 150 | } 151 | return general, nil 152 | } 153 | 154 | //ExportRemarkGeneral export the content after edited .. 155 | func (conf *SVNconf) ExportRemarkGeneral(tag string) ([]byte, error) { 156 | content, err := conf.readfile() 157 | if err != nil { 158 | return []byte(""), err 159 | } 160 | err = conf.remarktag(content, tag) 161 | if err != nil { 162 | return []byte(""), err 163 | } 164 | content, err = conf.readfile() 165 | if err != nil { 166 | return []byte(""), err 167 | } 168 | rawjson, err := file2json(content) 169 | if err != nil { 170 | return []byte(""), err 171 | } 172 | general, err := parseGeneral(rawjson) 173 | if err != nil { 174 | return []byte(""), err 175 | } 176 | return general, nil 177 | } 178 | 179 | //ExportRmremarkGeneral export the content after edited .. 180 | func (conf *SVNconf) ExportRmremarkGeneral(tag string) ([]byte, error) { 181 | content, err := conf.readfile() 182 | if err != nil { 183 | return []byte(""), err 184 | } 185 | err = conf.rmremarktag(content, tag) 186 | if err != nil { 187 | return []byte(""), err 188 | } 189 | content, err = conf.readfile() 190 | if err != nil { 191 | return []byte(""), err 192 | } 193 | rawjson, err := file2json(content) 194 | if err != nil { 195 | return []byte(""), err 196 | } 197 | general, err := parseGeneral(rawjson) 198 | if err != nil { 199 | return []byte(""), err 200 | } 201 | return general, nil 202 | } 203 | -------------------------------------------------------------------------------- /svn-server/assets/css/bootstrap-switch.min.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;margin:0;z-index:-1;opacity:0;filter:alpha(opacity=0)}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px} -------------------------------------------------------------------------------- /svn-manager/svn-manager_test.go: -------------------------------------------------------------------------------- 1 | package svnmanager 2 | 3 | import "testing" 4 | 5 | func TestLsrepo(t *testing.T) { 6 | data := LsRepo("/home/svn/") 7 | // t.Log(len(data)) 8 | t.Log(data[0]) 9 | } 10 | 11 | func TestFetchRepoinfo(t *testing.T) { 12 | repo := InitRepo("repo") 13 | repoinfo, err := repo.FetchRepoInfo("/home/svn") 14 | // repoinfo, err := json.Marshal(repo) 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | for k, v := range repoinfo { 19 | t.Log(k, ":", v) 20 | } 21 | } 22 | 23 | func TestFetchGroup(t *testing.T) { 24 | repo := InitRepo("repo") 25 | groups, err := repo.FetchRepoGroup("/home/svn") 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | for k, v := range groups { 30 | t.Log(k, ":", v) 31 | } 32 | } 33 | 34 | func TestFetchEditedGroup(t *testing.T) { 35 | repo := InitRepo("repo") 36 | olddata := make(map[string]string) 37 | olddata["groupname"] = "harry_and_sally" 38 | olddata["users"] = "harry,sally" 39 | newdata := make(map[string]string) 40 | newdata["groupname"] = "hahaha" 41 | newdata["users"] = "scnace,scbizu" 42 | groups, err := repo.FetchRepoEditedGroup("/home/svn", olddata, newdata) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | for k, v := range groups { 47 | t.Log(k, ":", v) 48 | } 49 | } 50 | 51 | func TestFetchAddGroup(t *testing.T) { 52 | repo := InitRepo("repo") 53 | newdata := make(map[string]string) 54 | newdata["groupname"] = "hahaha" 55 | newdata["users"] = "scnace,scbizu" 56 | groups, err := repo.FetchRepoAddGroup("/home/svn", newdata) 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | for k, v := range groups { 61 | t.Log(k, ":", v) 62 | } 63 | } 64 | 65 | func TestFetchDelGroup(t *testing.T) { 66 | repo := InitRepo("repo") 67 | olddata := make(map[string]string) 68 | olddata["groupname"] = "hahaha" 69 | olddata["users"] = "scnace,scbizu" 70 | groups, err := repo.FetchRepoDelGroup("/home/svn", olddata) 71 | if err != nil { 72 | t.Error(err) 73 | } 74 | for k, v := range groups { 75 | t.Log(k, ":", v) 76 | } 77 | } 78 | 79 | func TestFetchRepoDirectory(t *testing.T) { 80 | repo := InitRepo("repo") 81 | directory, err := repo.FetchRepoDirectory("/home/svn") 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | for k, v := range directory { 86 | for kk, vv := range v { 87 | t.Log(k, kk, vv) 88 | } 89 | 90 | } 91 | } 92 | 93 | func TestFetchRepoUsers(t *testing.T) { 94 | repo := InitRepo("repo") 95 | users, err := repo.FetchRepoUsers("/home/svn") 96 | if err != nil { 97 | t.Error(err) 98 | } 99 | for k, v := range users { 100 | t.Log(k, v) 101 | } 102 | } 103 | 104 | func TestFetchRepoGeneral(t *testing.T) { 105 | repo := InitRepo("repo") 106 | generals, err := repo.FetchRepoGeneral("/home/svn") 107 | if err != nil { 108 | t.Error(err) 109 | } 110 | for k, v := range generals { 111 | t.Log(k, v) 112 | } 113 | } 114 | 115 | func TestFetchRepoRemarkGeneral(t *testing.T) { 116 | repo := InitRepo("repo") 117 | generals, err := repo.FetchRepoRemarkGeneral("/home/svn", "authz-db") 118 | if err != nil { 119 | t.Error(err) 120 | } 121 | for k, v := range generals { 122 | t.Log(k, v) 123 | } 124 | } 125 | 126 | func TestFetchRepoRmremarkGeneral(t *testing.T) { 127 | repo := InitRepo("repo") 128 | generals, err := repo.FetchRepoRmremarkGeneral("/home/svn", "authz-db") 129 | if err != nil { 130 | t.Error(err) 131 | } 132 | for k, v := range generals { 133 | t.Log(k, v) 134 | } 135 | } 136 | 137 | func TestFetchRepoEditPasswd(t *testing.T) { 138 | repo := InitRepo("repo") 139 | olddata := make(map[string]string) 140 | olddata["username"] = "scnace" 141 | olddata["pwd"] = "scnace" 142 | newdata := make(map[string]string) 143 | newdata["username"] = "scnace" 144 | newdata["pwd"] = "123456" 145 | editUser, err := repo.FetchRepoEditPasswd("/home/svn", olddata, newdata) 146 | if err != nil { 147 | t.Error(err) 148 | } 149 | for k, v := range editUser { 150 | t.Log(k, v) 151 | } 152 | } 153 | 154 | func TestFetchRepoDelPasswd(t *testing.T) { 155 | repo := InitRepo("repo") 156 | olddata := make(map[string]string) 157 | olddata["username"] = "scnace" 158 | olddata["pwd"] = "scnace" 159 | delUser, err := repo.FetchRepoDelPasswd("/home/svn", olddata) 160 | if err != nil { 161 | t.Error(err) 162 | } 163 | for k, v := range delUser { 164 | t.Log(k, v) 165 | } 166 | } 167 | 168 | func TestFetchRepoAddPasswd(t *testing.T) { 169 | repo := InitRepo("repo") 170 | newdata := make(map[string]string) 171 | newdata["username"] = "scnace" 172 | newdata["pwd"] = "scnace" 173 | addUser, err := repo.FetchRepoAddPasswd("/home/svn", newdata) 174 | if err != nil { 175 | t.Error(err) 176 | } 177 | for k, v := range addUser { 178 | t.Log(k, v) 179 | } 180 | } 181 | 182 | func TestFetchRepoEditedDirectory(t *testing.T) { 183 | repo := InitRepo("repo") 184 | olddata := make(map[string]string) 185 | olddata["users"] = "@nace" 186 | olddata["auth"] = "rw" 187 | newdata := make(map[string]string) 188 | newdata["users"] = "scnace" 189 | newdata["auth"] = "rw" 190 | editDirectory, err := repo.FetchRepoEditedDirectory("/home/svn", "repository:/baz/fuz", olddata, newdata) 191 | if err != nil { 192 | t.Error(err) 193 | } 194 | for k, v := range editDirectory { 195 | t.Log(k, v) 196 | } 197 | } 198 | 199 | func TestFetchRepoAddDirectory(t *testing.T) { 200 | repo := InitRepo("repo") 201 | newdata := make(map[string]string) 202 | newdata["users"] = "scbizu" 203 | newdata["auth"] = "r" 204 | addDirectory, err := repo.FetchRepoAddDirectory("/home/svn", "/foo/bar", newdata) 205 | if err != nil { 206 | t.Error(err) 207 | } 208 | for k, v := range addDirectory { 209 | t.Log(k, v) 210 | } 211 | } 212 | 213 | func TestFetchRepoDelDirectory(t *testing.T) { 214 | repo := InitRepo("repo") 215 | olddata := make(map[string]string) 216 | olddata["users"] = "scbizu" 217 | olddata["auth"] = "r" 218 | delDirectory, err := repo.FetchRepoDelDirectory("/home/svn", "/foo/bar", olddata) 219 | if err != nil { 220 | t.Error(err) 221 | } 222 | for k, v := range delDirectory { 223 | t.Log(k, v) 224 | } 225 | } 226 | 227 | func TestFetchReplaceDtag(t *testing.T) { 228 | repo := InitRepo("repo") 229 | err := repo.FetchReplaceDtag("/home/svn", "repo") 230 | if err != nil { 231 | t.Error(err) 232 | } 233 | t.Log("dtag repalced.") 234 | } 235 | -------------------------------------------------------------------------------- /svn-server/assets/css/bootstrap-switch.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .bootstrap-switch { 23 | display: inline-block; 24 | direction: ltr; 25 | cursor: pointer; 26 | border-radius: 4px; 27 | border: 1px solid; 28 | border-color: #cccccc; 29 | position: relative; 30 | text-align: left; 31 | overflow: hidden; 32 | line-height: 8px; 33 | z-index: 0; 34 | -webkit-user-select: none; 35 | -moz-user-select: none; 36 | -ms-user-select: none; 37 | user-select: none; 38 | vertical-align: middle; 39 | -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 40 | -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 41 | transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 42 | } 43 | .bootstrap-switch .bootstrap-switch-container { 44 | display: inline-block; 45 | top: 0; 46 | border-radius: 4px; 47 | -webkit-transform: translate3d(0, 0, 0); 48 | transform: translate3d(0, 0, 0); 49 | } 50 | .bootstrap-switch .bootstrap-switch-handle-on, 51 | .bootstrap-switch .bootstrap-switch-handle-off, 52 | .bootstrap-switch .bootstrap-switch-label { 53 | -webkit-box-sizing: border-box; 54 | -moz-box-sizing: border-box; 55 | box-sizing: border-box; 56 | cursor: pointer; 57 | display: inline-block !important; 58 | height: 100%; 59 | padding: 6px 12px; 60 | font-size: 14px; 61 | line-height: 20px; 62 | } 63 | .bootstrap-switch .bootstrap-switch-handle-on, 64 | .bootstrap-switch .bootstrap-switch-handle-off { 65 | text-align: center; 66 | z-index: 1; 67 | } 68 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, 69 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary { 70 | color: #fff; 71 | background: #337ab7; 72 | } 73 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info, 74 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info { 75 | color: #fff; 76 | background: #5bc0de; 77 | } 78 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, 79 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { 80 | color: #fff; 81 | background: #5cb85c; 82 | } 83 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning, 84 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning { 85 | background: #f0ad4e; 86 | color: #fff; 87 | } 88 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger, 89 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger { 90 | color: #fff; 91 | background: #d9534f; 92 | } 93 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default, 94 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { 95 | color: #000; 96 | background: #eeeeee; 97 | } 98 | .bootstrap-switch .bootstrap-switch-label { 99 | text-align: center; 100 | margin-top: -1px; 101 | margin-bottom: -1px; 102 | z-index: 100; 103 | color: #333333; 104 | background: #ffffff; 105 | } 106 | .bootstrap-switch .bootstrap-switch-handle-on { 107 | border-bottom-left-radius: 3px; 108 | border-top-left-radius: 3px; 109 | } 110 | .bootstrap-switch .bootstrap-switch-handle-off { 111 | border-bottom-right-radius: 3px; 112 | border-top-right-radius: 3px; 113 | } 114 | .bootstrap-switch input[type='radio'], 115 | .bootstrap-switch input[type='checkbox'] { 116 | position: absolute !important; 117 | top: 0; 118 | left: 0; 119 | margin: 0; 120 | z-index: -1; 121 | opacity: 0; 122 | filter: alpha(opacity=0); 123 | } 124 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on, 125 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, 126 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label { 127 | padding: 1px 5px; 128 | font-size: 12px; 129 | line-height: 1.5; 130 | } 131 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on, 132 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off, 133 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label { 134 | padding: 5px 10px; 135 | font-size: 12px; 136 | line-height: 1.5; 137 | } 138 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on, 139 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off, 140 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label { 141 | padding: 6px 16px; 142 | font-size: 18px; 143 | line-height: 1.3333333; 144 | } 145 | .bootstrap-switch.bootstrap-switch-disabled, 146 | .bootstrap-switch.bootstrap-switch-readonly, 147 | .bootstrap-switch.bootstrap-switch-indeterminate { 148 | cursor: default !important; 149 | } 150 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on, 151 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on, 152 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on, 153 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off, 154 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off, 155 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off, 156 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label, 157 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label, 158 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label { 159 | opacity: 0.5; 160 | filter: alpha(opacity=50); 161 | cursor: default !important; 162 | } 163 | .bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container { 164 | -webkit-transition: margin-left 0.5s; 165 | -o-transition: margin-left 0.5s; 166 | transition: margin-left 0.5s; 167 | } 168 | .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { 169 | border-bottom-left-radius: 0; 170 | border-top-left-radius: 0; 171 | border-bottom-right-radius: 3px; 172 | border-top-right-radius: 3px; 173 | } 174 | .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { 175 | border-bottom-right-radius: 0; 176 | border-top-right-radius: 0; 177 | border-bottom-left-radius: 3px; 178 | border-top-left-radius: 3px; 179 | } 180 | .bootstrap-switch.bootstrap-switch-focused { 181 | border-color: #66afe9; 182 | outline: 0; 183 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); 184 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); 185 | } 186 | .bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label, 187 | .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label { 188 | border-bottom-right-radius: 3px; 189 | border-top-right-radius: 3px; 190 | } 191 | .bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label, 192 | .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label { 193 | border-bottom-left-radius: 3px; 194 | border-top-left-radius: 3px; 195 | } 196 | -------------------------------------------------------------------------------- /svn-server/assets/js/newauth.js: -------------------------------------------------------------------------------- 1 | $("#new_auth_btn").on('click',function(){ 2 | var repo = $('#reponame').text(); 3 | $("#new_auth").css("display","none") 4 | $("#new_auth").before(` 5 | 6 |
7 | `+repo+`: 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |   16 | 17 | 18 | 19 | `) 20 | // load data from users column 21 | $.ajax({ 22 | url:"/users", 23 | data:{repo:repo}, 24 | }).done(function(data,textStatus,jqXHR){ 25 | var obj = jQuery.parseJSON(data) 26 | for (key in obj) { 27 | // console.log(key) 28 | $("#new_cuser").append("") 29 | } 30 | 31 | }) 32 | // load data from groups column 33 | $.ajax({ 34 | url:"/groups", 35 | data:{repo:repo}, 36 | }).done(function(data,textStatus,jqXHR){ 37 | var obj = jQuery.parseJSON(data) 38 | for (key in obj){ 39 | $("#new_cuser").append(``) 40 | } 41 | }) 42 | 43 | $("#auth_new_del").on('click',function(){ 44 | $("#new_auth_tr").remove(); 45 | $("#new_auth").css("display","block"); 46 | }); 47 | 48 | $("#auth_new_ok").on('click',function(){ 49 | var index=parseInt($("#addindex").attr("index"),10)+1 50 | var newpath = repo+":"+$("#new_cpath").val(); 51 | var userarray = $("#new_cuser").val(); 52 | var newuser=""; 53 | userarray.forEach(function(user,index){ 54 | if(index == userarray.length-1){ 55 | newuser+=user 56 | }else{ 57 | newuser+=user+"," 58 | } 59 | }); 60 | var newauth = $("#new_cauth").val(); 61 | $.ajax({ 62 | url:"/addauth" , 63 | data:{tag:newpath,new_users:newuser,new_auth:newauth,reponame:repo}, 64 | dataType:"json", 65 | type:"post", 66 | }).done(function(data,statusText,jqXHR){ 67 | if(jqXHR.status===200){ 68 | $("#new_auth_tr").remove(); 69 | $("#new_auth").before(` 70 | `+newpath+` 71 | `+newuser+` 72 | `+newauth+` 73 | 74 | 75 | 76 |   77 | 78 | `); 79 | 80 | $("#config_edit_"+index).on('click',function(){ 81 | var users = $("#cuser_"+index).text() 82 | var auth = $("#cauth_"+index).text() 83 | $("#cuser_"+index).remove(); 84 | $("#cauth_"+index).remove(); 85 | // $("#config_op_"+index).before(``) 86 | $("#config_op_"+index).before(``) 87 | $("#config_op_"+index).before(``) 88 | 89 | // load data from users column 90 | $.ajax({ 91 | url:"/users", 92 | data:{repo:repo}, 93 | }).done(function(data,textStatus,jqXHR){ 94 | var obj = jQuery.parseJSON(data) 95 | for (key in obj) { 96 | // console.log(key) 97 | $("#edited_cuser_"+index).append("") 98 | } 99 | 100 | }) 101 | // load data from groups column 102 | $.ajax({ 103 | url:"/groups", 104 | data:{repo:repo}, 105 | }).done(function(data,textStatus,jqXHR){ 106 | var obj = jQuery.parseJSON(data) 107 | for (key in obj){ 108 | $("#edited_cuser_"+index).append(``) 109 | } 110 | }) 111 | 112 | 113 | $("#config_edit_"+index).css('display',"none") 114 | $("#config_ok_"+index).css('display',"inline") 115 | }); 116 | 117 | $('#config_ok_'+index).on('click',function(e){ 118 | //handle ajax edit post 119 | var o_user= newuser 120 | var o_auth= newauth 121 | var userarray = $('#edited_cuser_'+index).val() 122 | var new_user=""; 123 | userarray.forEach(function(user,index){ 124 | if(index == userarray.length-1){ 125 | new_user+=user 126 | }else{ 127 | new_user+=user+"," 128 | } 129 | }); 130 | var new_auth = $('#edited_cauth_'+index).val() 131 | var repo = $('#reponame').text(); 132 | var tag = newpath 133 | $.ajax({ 134 | url:"/editauth" , 135 | data:{reponame:repo,old_users:o_user,old_auth:o_auth,new_users:new_user,new_auth:new_auth,tag:tag}, 136 | dataType:"json", 137 | type:"POST", 138 | }).done(function(data,statusText,jqXHR){ 139 | 140 | if (jqXHR.status===200){ 141 | $("#cuser_"+index).remove(); 142 | $("#cauth_"+index).remove(); 143 | $("#config_op_"+index).before(""+new_user+""); 144 | $("#config_op_"+index).before(""+new_auth+""); 145 | $('#config_edit_'+index).css("display","inline"); 146 | $('#config_ok_'+index).css("display","none"); 147 | } 148 | }); 149 | }); 150 | 151 | $('#config_del_'+index).on('click',function(e){ 152 | //handle ajax delete 153 | var repo = $('#reponame').text(); 154 | 155 | $.ajax({ 156 | url:"/delauth", 157 | data:{reponame:repo,old_users:newuser,old_auth:newauth,tag:newpath}, 158 | type:"POST", 159 | dataType:"json", 160 | }).done(function(data,statusText,jqXHR){ 161 | if (jqXHR.status ===200){ 162 | $("#cuser_"+index).remove(); 163 | $("#cpath_"+index).remove(); 164 | $("#cauth_"+index).remove(); 165 | $("#config_op_"+index).remove(); 166 | } 167 | }); 168 | }); 169 | 170 | $("#new_auth").css("display","block"); 171 | } 172 | }); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /svn-passwd/svnpasswd.go: -------------------------------------------------------------------------------- 1 | package svnpasswd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | //User ... 14 | type User struct { 15 | //Username ... 16 | Username string `json:"username"` 17 | //Password ... 18 | Password string `json:"password"` 19 | //SVNPATH ... 20 | SVNPATH string `json:"svnpath"` 21 | } 22 | 23 | //NewUser ... 24 | func NewUser(path string) *User { 25 | user := new(User) 26 | user.SVNPATH = path 27 | return user 28 | } 29 | 30 | //Readfill will read the passwd to the buffer 31 | func (user *User) readfile() ([]byte, error) { 32 | authz := filepath.Join(user.SVNPATH, "conf", "passwd") 33 | filehandle, err := os.Open(authz) 34 | defer filehandle.Close() 35 | if err != nil { 36 | return nil, errors.New(`Please check your ./conf/passwd file,if you change it's name,rename it as "passwd".`) 37 | } 38 | 39 | content, err := ioutil.ReadAll(filehandle) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return content, nil 44 | } 45 | 46 | //file2json parse passwd content to readable json . 47 | func file2json(filecontent []byte) ([]byte, error) { 48 | var lines []string 49 | lines = strings.Split(string(filecontent), "\n") 50 | jsonm := make(map[string][]string) 51 | for k, v := range lines { 52 | if strings.HasPrefix(v, "[") { 53 | key := strings.TrimPrefix(v, "[") 54 | key = strings.TrimSuffix(key, "]") 55 | var value []string 56 | for i := k + 1; i < len(lines); i++ { 57 | if strings.HasPrefix(lines[i], "[") { 58 | break 59 | } 60 | if lines[i] != "" && !strings.HasPrefix(lines[i], "#") { 61 | 62 | value = append(value, lines[i]) 63 | 64 | } 65 | } 66 | jsonm[key] = value 67 | } 68 | } 69 | jsondata, err := json.Marshal(jsonm) 70 | if err != nil { 71 | return []byte(""), err 72 | } 73 | return jsondata, nil 74 | } 75 | 76 | //Attention: keep the user as a primary key . 77 | func (user *User) changeUser(tagname string, olddata map[string]string, newdata map[string]string, srcContent []byte) error { 78 | passwdPath := filepath.Join(user.SVNPATH, "conf", "passwd") 79 | var lines []string 80 | var dstContent bytes.Buffer 81 | lines = strings.Split(string(srcContent), "\n") 82 | // O(n) loop 83 | for k, v := range lines { 84 | v = strings.TrimPrefix(v, "[") 85 | v = strings.TrimSuffix(v, "]") 86 | if strings.TrimSpace(v) == tagname { 87 | dstContent.WriteString(lines[k] + "\n") 88 | for i := k + 1; i < len(lines); i++ { 89 | if strings.HasPrefix(lines[i], "[") { 90 | break 91 | } 92 | tsplit := strings.SplitN(lines[i], "=", 2) 93 | //find the username ; not change username 94 | if strings.TrimSpace(tsplit[0]) == olddata["username"] { 95 | // replace rudely 96 | lines[i] = newdata["username"] + "=" + newdata["pwd"] 97 | } 98 | } 99 | } else { 100 | dstContent.WriteString(v + "\n") 101 | } 102 | err := ioutil.WriteFile(passwdPath, dstContent.Bytes(), 0666) 103 | if err != nil { 104 | return err 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | //Attention: keep the user as a primary key . 111 | func (user *User) delUser(tagname string, olddata map[string]string, srcContent []byte) error { 112 | passwdPath := filepath.Join(user.SVNPATH, "conf", "passwd") 113 | var lines []string 114 | var dstContent bytes.Buffer 115 | lines = strings.Split(string(srcContent), "\n") 116 | // O(n) loop 117 | for k, v := range lines { 118 | v = strings.TrimPrefix(v, "[") 119 | v = strings.TrimSuffix(v, "]") 120 | if strings.TrimSpace(v) == tagname { 121 | dstContent.WriteString(lines[k] + "\n") 122 | for i := k + 1; i < len(lines); i++ { 123 | if strings.HasPrefix(lines[i], "[") { 124 | break 125 | } 126 | tsplit := strings.SplitN(lines[i], "=", 2) 127 | //find the username ; not change username 128 | if strings.TrimSpace(tsplit[0]) == olddata["username"] { 129 | // not really delete ,avoid the risk that lose a user key-pair permanently 130 | lines[i] = "#" + olddata["username"] + "=" + olddata["pwd"] 131 | } 132 | } 133 | } else { 134 | dstContent.WriteString(v + "\n") 135 | } 136 | err := ioutil.WriteFile(passwdPath, dstContent.Bytes(), 0666) 137 | if err != nil { 138 | return err 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | func (user *User) addUser(tagname string, newdata map[string]string, srcContent []byte) error { 145 | passwdPath := filepath.Join(user.SVNPATH, "conf", "passwd") 146 | var lines []string 147 | var dstContent bytes.Buffer 148 | lines = strings.Split(string(srcContent), "\n") 149 | // O(n) loop 150 | for k, v := range lines { 151 | v = strings.TrimPrefix(v, "[") 152 | v = strings.TrimSuffix(v, "]") 153 | if strings.TrimSpace(v) == tagname { 154 | dstContent.WriteString(lines[k] + "\n") 155 | for i := k + 1; i < len(lines); i++ { 156 | if strings.HasPrefix(lines[i], "[") { 157 | break 158 | } 159 | tsplit := strings.SplitN(lines[i], "=", 2) 160 | //find the username ; not change username 161 | if strings.TrimSpace(tsplit[0]) == newdata["username"] { 162 | //return a existed error 163 | return errors.New("user existed") 164 | } 165 | } 166 | dstContent.WriteString(newdata["username"] + "=" + newdata["pwd"] + "\n") 167 | } else { 168 | dstContent.WriteString(v + "\n") 169 | } 170 | err := ioutil.WriteFile(passwdPath, dstContent.Bytes(), 0666) 171 | if err != nil { 172 | return err 173 | } 174 | } 175 | return nil 176 | } 177 | 178 | //parse users json 179 | func parseUsers(rawjson []byte) ([]byte, error) { 180 | passwd := make(map[string][]string) 181 | err := json.Unmarshal(rawjson, &passwd) 182 | if err != nil { 183 | return []byte(""), err 184 | } 185 | userspasswd := make(map[string]string) 186 | users := passwd["users"] 187 | for _, v := range users { 188 | trimv := strings.TrimSpace(string(v)) 189 | splitv := strings.SplitN(trimv, "=", 2) 190 | userspasswd[strings.TrimSpace(splitv[0])] = strings.TrimSpace(splitv[1]) 191 | } 192 | passwdjson, err := json.Marshal(userspasswd) 193 | if err != nil { 194 | return []byte(""), err 195 | } 196 | return passwdjson, nil 197 | } 198 | 199 | //ExportUser ... 200 | func (user *User) ExportUser() ([]byte, error) { 201 | content, err := user.readfile() 202 | if err != nil { 203 | return []byte(""), err 204 | } 205 | rawjson, err := file2json(content) 206 | if err != nil { 207 | return []byte(""), err 208 | } 209 | users, err := parseUsers(rawjson) 210 | if err != nil { 211 | return []byte(""), err 212 | } 213 | return users, nil 214 | } 215 | 216 | //ExportEditedUser export edited user data 217 | func (user *User) ExportEditedUser(tag string, olddata map[string]string, newdata map[string]string) ([]byte, error) { 218 | content, err := user.readfile() 219 | if err != nil { 220 | return []byte(""), err 221 | } 222 | err = user.changeUser(tag, olddata, newdata, content) 223 | if err != nil { 224 | return []byte(""), err 225 | } 226 | content, err = user.readfile() 227 | if err != nil { 228 | return []byte(""), err 229 | } 230 | rawjson, err := file2json(content) 231 | if err != nil { 232 | return []byte(""), err 233 | } 234 | users, err := parseUsers(rawjson) 235 | if err != nil { 236 | return []byte(""), err 237 | } 238 | return users, nil 239 | } 240 | 241 | //ExportAfterDelUser export all the user key-pair after a "delete " operation 242 | func (user *User) ExportAfterDelUser(tag string, olddata map[string]string) ([]byte, error) { 243 | content, err := user.readfile() 244 | if err != nil { 245 | return []byte(""), err 246 | } 247 | err = user.delUser(tag, olddata, content) 248 | if err != nil { 249 | return []byte(""), err 250 | } 251 | content, err = user.readfile() 252 | if err != nil { 253 | return []byte(""), err 254 | } 255 | rawjson, err := file2json(content) 256 | if err != nil { 257 | return []byte(""), err 258 | } 259 | users, err := parseUsers(rawjson) 260 | if err != nil { 261 | return []byte(""), err 262 | } 263 | return users, nil 264 | } 265 | 266 | //ExportAddUser export user key-pair after add operation 267 | func (user *User) ExportAddUser(tag string, newdata map[string]string) ([]byte, error) { 268 | content, err := user.readfile() 269 | if err != nil { 270 | return []byte(""), err 271 | } 272 | err = user.addUser(tag, newdata, content) 273 | if err != nil { 274 | return []byte(""), err 275 | } 276 | content, err = user.readfile() 277 | if err != nil { 278 | return []byte(""), err 279 | } 280 | rawjson, err := file2json(content) 281 | if err != nil { 282 | return []byte(""), err 283 | } 284 | users, err := parseUsers(rawjson) 285 | if err != nil { 286 | return []byte(""), err 287 | } 288 | return users, nil 289 | } 290 | -------------------------------------------------------------------------------- /svn-manager/svn-manager.go: -------------------------------------------------------------------------------- 1 | package svnmanager 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/scbizu/svnpanel/svn-auth" 10 | "github.com/scbizu/svnpanel/svn-conf" 11 | "github.com/scbizu/svnpanel/svn-hook" 12 | "github.com/scbizu/svnpanel/svn-passwd" 13 | ) 14 | 15 | //Repo .... 16 | type Repo struct { 17 | //Reponame ... 18 | Reponame string `json:"name"` 19 | //RepoAuthor 20 | RepoAuthor string `json:"author"` 21 | //RepoStatus 22 | RepoStatus string `json:"status"` 23 | } 24 | 25 | //InitRepo ... 26 | func InitRepo(repo string) *Repo { 27 | Newrepo := new(Repo) 28 | Newrepo.Reponame = repo 29 | return Newrepo 30 | } 31 | 32 | //LsRepo return a slice of repos name 33 | func LsRepo(svnpath string) []string { 34 | cmd := exec.Command("ls", svnpath) 35 | stdout, err := cmd.StdoutPipe() 36 | defer stdout.Close() 37 | if err != nil { 38 | panic(err) 39 | } 40 | cmd.Start() 41 | data, err := ioutil.ReadAll(stdout) 42 | if err != nil { 43 | panic(err) 44 | } 45 | trimdata := strings.TrimSuffix(string(data), "\n") 46 | // fmt.Printf("%q", string(data)) 47 | splitrepos := strings.SplitN(string(trimdata), "\n", -1) 48 | return splitrepos 49 | } 50 | 51 | //FetchRepoInfo return repo json format info 52 | func (repo *Repo) FetchRepoInfo(svnpath string) (map[string]string, error) { 53 | hook := svnhook.NewHook(svnpath, repo.Reponame) 54 | author, err := hook.GetAuthor() 55 | if err != nil { 56 | return nil, err 57 | } 58 | info, err := hook.Getinfo() 59 | if err != nil { 60 | return nil, err 61 | } 62 | info["owner"] = author 63 | info["reponame"] = repo.Reponame 64 | return info, nil 65 | } 66 | 67 | //FetchRepoGroup ... 68 | func (repo *Repo) FetchRepoGroup(svnpath string) (map[string]string, error) { 69 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 70 | groups, err := auth.ExportGroups() 71 | if err != nil { 72 | return nil, err 73 | } 74 | var groupsmap map[string]string 75 | err = json.Unmarshal(groups, &groupsmap) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return groupsmap, nil 80 | } 81 | 82 | //FetchRepoEditedGroup fetch edited group info . 83 | func (repo *Repo) FetchRepoEditedGroup(svnpath string, olddata map[string]string, newdata map[string]string) (map[string]string, error) { 84 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 85 | groups, err := auth.ExportEditedGroup("groups", olddata, newdata) 86 | if err != nil { 87 | return nil, err 88 | } 89 | var groupsmap map[string]string 90 | err = json.Unmarshal(groups, &groupsmap) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return groupsmap, nil 95 | } 96 | 97 | //FetchRepoDelGroup fetch group info after deleted. 98 | func (repo *Repo) FetchRepoDelGroup(svnpath string, olddata map[string]string) (map[string]string, error) { 99 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 100 | groups, err := auth.ExportDelGroup("groups", olddata) 101 | if err != nil { 102 | return nil, err 103 | } 104 | var groupsmap map[string]string 105 | err = json.Unmarshal(groups, &groupsmap) 106 | if err != nil { 107 | return nil, err 108 | } 109 | return groupsmap, nil 110 | } 111 | 112 | //FetchRepoAddGroup fetch group info after inserted. 113 | func (repo *Repo) FetchRepoAddGroup(svnpath string, newdata map[string]string) (map[string]string, error) { 114 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 115 | groups, err := auth.ExportAddGroup("groups", newdata) 116 | if err != nil { 117 | return nil, err 118 | } 119 | var groupsmap map[string]string 120 | err = json.Unmarshal(groups, &groupsmap) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return groupsmap, nil 125 | } 126 | 127 | //FetchRepoDirectory .. 128 | func (repo *Repo) FetchRepoDirectory(svnpath string) (map[string]map[string]string, error) { 129 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 130 | directory, err := auth.ExportDirectory() 131 | if err != nil { 132 | return nil, err 133 | } 134 | var directorymap map[string]map[string]string 135 | err = json.Unmarshal(directory, &directorymap) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return directorymap, nil 140 | } 141 | 142 | //FetchRepoEditedDirectory fetch auth key-pair after edited. 143 | func (repo *Repo) FetchRepoEditedDirectory(svnpath string, tag string, olddata map[string]string, newdata map[string]string) (map[string]map[string]string, error) { 144 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 145 | directory, err := auth.ExportEditedDirectory(tag, olddata, newdata) 146 | if err != nil { 147 | return nil, err 148 | } 149 | var directorymap map[string]map[string]string 150 | err = json.Unmarshal(directory, &directorymap) 151 | if err != nil { 152 | return nil, err 153 | } 154 | return directorymap, nil 155 | } 156 | 157 | //FetchRepoDelDirectory fetch auth key-pair after deleted. 158 | func (repo *Repo) FetchRepoDelDirectory(svnpath string, tag string, olddata map[string]string) (map[string]map[string]string, error) { 159 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 160 | directory, err := auth.ExportDelDirectory(tag, olddata) 161 | if err != nil { 162 | return nil, err 163 | } 164 | var directorymap map[string]map[string]string 165 | err = json.Unmarshal(directory, &directorymap) 166 | if err != nil { 167 | return nil, err 168 | } 169 | return directorymap, nil 170 | } 171 | 172 | //FetchRepoAddDirectory fetch auth key-par after inserted. 173 | func (repo *Repo) FetchRepoAddDirectory(svnpath string, tag string, newdata map[string]string) (map[string]map[string]string, error) { 174 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 175 | directory, err := auth.ExportAddDirectory(tag, newdata) 176 | if err != nil { 177 | return nil, err 178 | } 179 | var directorymap map[string]map[string]string 180 | err = json.Unmarshal(directory, &directorymap) 181 | if err != nil { 182 | return nil, err 183 | } 184 | return directorymap, nil 185 | } 186 | 187 | //FetchRepoUsers ... 188 | func (repo *Repo) FetchRepoUsers(svnpath string) (map[string]string, error) { 189 | user := svnpasswd.NewUser(svnpath + "/" + repo.Reponame) 190 | users, err := user.ExportUser() 191 | if err != nil { 192 | return nil, err 193 | } 194 | var usermap map[string]string 195 | err = json.Unmarshal(users, &usermap) 196 | if err != nil { 197 | return nil, err 198 | } 199 | return usermap, nil 200 | } 201 | 202 | //FetchRepoGeneral ... 203 | func (repo *Repo) FetchRepoGeneral(svnpath string) (map[string]string, error) { 204 | conf := svnconf.NewSVNconf(svnpath + "/" + repo.Reponame) 205 | general, err := conf.ExportGeneral() 206 | if err != nil { 207 | return nil, err 208 | } 209 | var generalmap map[string]string 210 | err = json.Unmarshal(general, &generalmap) 211 | if err != nil { 212 | return nil, err 213 | } 214 | return generalmap, nil 215 | } 216 | 217 | //FetchRepoRemarkGeneral remark section . 218 | func (repo *Repo) FetchRepoRemarkGeneral(svnpath string, tag string) (map[string]string, error) { 219 | conf := svnconf.NewSVNconf(svnpath + "/" + repo.Reponame) 220 | general, err := conf.ExportRemarkGeneral(tag) 221 | if err != nil { 222 | return nil, err 223 | } 224 | var generalmap map[string]string 225 | err = json.Unmarshal(general, &generalmap) 226 | if err != nil { 227 | return nil, err 228 | } 229 | return generalmap, nil 230 | } 231 | 232 | //FetchRepoRmremarkGeneral rm remark section . 233 | func (repo *Repo) FetchRepoRmremarkGeneral(svnpath string, tag string) (map[string]string, error) { 234 | conf := svnconf.NewSVNconf(svnpath + "/" + repo.Reponame) 235 | general, err := conf.ExportRmremarkGeneral(tag) 236 | if err != nil { 237 | return nil, err 238 | } 239 | var generalmap map[string]string 240 | err = json.Unmarshal(general, &generalmap) 241 | if err != nil { 242 | return nil, err 243 | } 244 | return generalmap, nil 245 | } 246 | 247 | //FetchRepoEditPasswd fetch edited passwd info 248 | func (repo *Repo) FetchRepoEditPasswd(svnpath string, olddata map[string]string, newdata map[string]string) (map[string]string, error) { 249 | user := svnpasswd.NewUser(svnpath + "/" + repo.Reponame) 250 | editedUser, err := user.ExportEditedUser("users", olddata, newdata) 251 | if err != nil { 252 | return nil, err 253 | } 254 | var usermap map[string]string 255 | err = json.Unmarshal(editedUser, &usermap) 256 | if err != nil { 257 | return nil, err 258 | } 259 | return usermap, nil 260 | } 261 | 262 | //FetchRepoDelPasswd fetch user key-pair after a delete operation . 263 | func (repo *Repo) FetchRepoDelPasswd(svnpath string, olddata map[string]string) (map[string]string, error) { 264 | user := svnpasswd.NewUser(svnpath + "/" + repo.Reponame) 265 | delUser, err := user.ExportAfterDelUser("users", olddata) 266 | if err != nil { 267 | return nil, err 268 | } 269 | var usermap map[string]string 270 | err = json.Unmarshal(delUser, &usermap) 271 | if err != nil { 272 | return nil, err 273 | } 274 | return usermap, nil 275 | } 276 | 277 | //FetchRepoAddPasswd fetch all key-pair after a adding operation 278 | func (repo *Repo) FetchRepoAddPasswd(svnpath string, newdata map[string]string) (map[string]string, error) { 279 | user := svnpasswd.NewUser(svnpath + "/" + repo.Reponame) 280 | addUser, err := user.ExportAddUser("users", newdata) 281 | if err != nil { 282 | return nil, err 283 | } 284 | var usermap map[string]string 285 | err = json.Unmarshal(addUser, &usermap) 286 | if err != nil { 287 | return nil, err 288 | } 289 | return usermap, nil 290 | } 291 | 292 | //FetchReplaceDtag ... 293 | func (repo Repo) FetchReplaceDtag(svnpath string, newtag string) error { 294 | auth := svnauth.NewSVNAuth(svnpath + "/" + repo.Reponame) 295 | err := auth.WrapReplaceDtag(newtag) 296 | if err != nil { 297 | return err 298 | } 299 | return nil 300 | } 301 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright ndasec 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /svn-server/svn-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "html/template" 8 | "io" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/labstack/echo" 13 | "github.com/labstack/echo/engine/standard" 14 | "github.com/labstack/echo/middleware" 15 | "github.com/scbizu/svnpanel/gconfig" 16 | "github.com/scbizu/svnpanel/svn-hook" 17 | "github.com/scbizu/svnpanel/svn-manager" 18 | ) 19 | 20 | //Conf ... 21 | type Conf struct { 22 | Reponame string 23 | Author string 24 | Aliases string 25 | Group map[string]string 26 | Directories []map[string]string 27 | Users map[string]string 28 | Anon string 29 | Pdb string 30 | Adb string 31 | } 32 | 33 | //Template ... 34 | type Template struct { 35 | templates *template.Template 36 | } 37 | 38 | //Render ... 39 | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 40 | return t.templates.ExecuteTemplate(w, name, data) 41 | } 42 | 43 | func main() { 44 | server := echo.New() 45 | gconf := gconfig.NewGconfig() 46 | SVNPATH := gconf.SVNPATH 47 | 48 | //BasicAuth 49 | server.Use(middleware.BasicAuth(func(username string, pwd string) bool { 50 | //solve salt 51 | salt := gconf.Salt 52 | rawcode := pwd + "?" + salt + "?" 53 | hasher := md5.New() 54 | hasher.Write([]byte(rawcode)) 55 | if username == gconf.Username && hex.EncodeToString(hasher.Sum(nil)) == gconf.Password { 56 | return true 57 | } 58 | return false 59 | })) 60 | server.Static("/", "assets") 61 | t := &Template{ 62 | templates: template.Must(template.ParseGlob("assets/config.html")), 63 | } 64 | server.SetRenderer(t) 65 | 66 | server.File("/", "assets/index.html") 67 | 68 | server.GET("/users", func(c echo.Context) error { 69 | repo := c.QueryParam("repo") 70 | repopath := svnmanager.InitRepo(repo) 71 | user, err := repopath.FetchRepoUsers(SVNPATH) 72 | if err != nil { 73 | return c.JSON(403, "invaild reponame") 74 | } 75 | 76 | usersjson, _ := json.Marshal(user) 77 | return c.JSON(200, string(usersjson)) 78 | }) 79 | 80 | server.GET("/groups", func(c echo.Context) error { 81 | repo := c.QueryParam("repo") 82 | repopath := svnmanager.InitRepo(repo) 83 | group, err := repopath.FetchRepoGroup(SVNPATH) 84 | if err != nil { 85 | return c.JSON(403, "invaild reponame") 86 | } 87 | gjson, _ := json.Marshal(group) 88 | return c.JSON(200, string(gjson)) 89 | }) 90 | 91 | server.GET("/config/:repo", func(c echo.Context) error { 92 | repo := c.Param("repo") 93 | hook := svnhook.NewHook(SVNPATH, repo) 94 | author, err := hook.GetAuthor() 95 | if err != nil { 96 | return err 97 | } 98 | 99 | repopath := svnmanager.InitRepo(repo) 100 | err = repopath.FetchReplaceDtag(SVNPATH, repo) 101 | if err != nil { 102 | return c.JSON(500, []byte("replace dtag error!")) 103 | } 104 | groups, err := repopath.FetchRepoGroup(SVNPATH) 105 | if err != nil { 106 | return err 107 | } 108 | directory, err := repopath.FetchRepoDirectory(SVNPATH) 109 | if err != nil { 110 | return err 111 | } 112 | // directories := make([]map[string]string, 6) 113 | var directories []map[string]string 114 | 115 | for k, v := range directory { 116 | for kk, vv := range v { 117 | tempDirectories := make(map[string]string) 118 | tempDirectories["path"] = k 119 | tempDirectories["user"] = kk 120 | tempDirectories["auth"] = vv 121 | directories = append(directories, tempDirectories) 122 | 123 | } 124 | } 125 | 126 | users, err := repopath.FetchRepoUsers(SVNPATH) 127 | if err != nil { 128 | return err 129 | } 130 | generals, err := repopath.FetchRepoGeneral(SVNPATH) 131 | if err != nil { 132 | return err 133 | } 134 | conf := Conf{ 135 | Reponame: repo, 136 | Author: author, 137 | Group: groups, 138 | Directories: directories, 139 | Users: users, 140 | Anon: generals["anon-access"], 141 | Pdb: generals["password-db"], 142 | Adb: generals["authz-db"], 143 | } 144 | return c.Render(http.StatusOK, "conf", conf) 145 | }) 146 | 147 | server.GET("/repos", func(c echo.Context) error { 148 | data := svnmanager.LsRepo(SVNPATH) 149 | // var datamap map[string]string 150 | var datamaps []map[string]string 151 | for _, v := range data { 152 | repo := svnmanager.InitRepo(v) 153 | infomap, err := repo.FetchRepoInfo(SVNPATH) 154 | if err != nil { 155 | return err 156 | } 157 | datamaps = append(datamaps, infomap) 158 | } 159 | 160 | datajson, err := json.Marshal(datamaps) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | return c.JSON(http.StatusOK, string(datajson)) 166 | }) 167 | 168 | //edit svnserve.conf file 169 | server.Put("/edit", func(c echo.Context) error { 170 | tag := strings.TrimSpace(c.FormValue("tag")) 171 | action := strings.TrimSpace(c.FormValue("action")) 172 | reponame := strings.TrimSpace(c.FormValue("reponame")) 173 | repo := svnmanager.InitRepo(reponame) 174 | var generals map[string]string 175 | switch action { 176 | case "remark": 177 | var err error 178 | generals, err = repo.FetchRepoRemarkGeneral(SVNPATH, tag) 179 | if err != nil { 180 | return err 181 | } 182 | case "rmremark": 183 | var err error 184 | generals, err = repo.FetchRepoRmremarkGeneral(SVNPATH, tag) 185 | if err != nil { 186 | return err 187 | } 188 | } 189 | conf := new(Conf) 190 | conf.Adb = generals["authz-db"] 191 | conf.Pdb = generals["password-db"] 192 | gjson, err := json.Marshal(conf) 193 | if err != nil { 194 | return err 195 | } 196 | return c.JSON(200, string(gjson)) 197 | }) 198 | //edit passwd file 199 | server.Post("/passwd", func(c echo.Context) error { 200 | 201 | olddataUsername := c.FormValue("old_username") 202 | olddataPwd := c.FormValue("old_pwd") 203 | newdataUsername := c.FormValue("new_username") 204 | newdataPwd := c.FormValue("new_pwd") 205 | reponame := c.FormValue("reponame") 206 | olddata := make(map[string]string) 207 | olddata["username"] = olddataUsername 208 | olddata["pwd"] = olddataPwd 209 | newdata := make(map[string]string) 210 | newdata["username"] = newdataUsername 211 | newdata["pwd"] = newdataPwd 212 | repo := svnmanager.InitRepo(reponame) 213 | passwdmap, err := repo.FetchRepoEditPasswd(SVNPATH, olddata, newdata) 214 | if err != nil { 215 | return c.JSON(500, err.Error()) 216 | } 217 | passwdjson, err := json.Marshal(passwdmap) 218 | if err != nil { 219 | return c.JSON(500, err.Error()) 220 | } 221 | return c.JSON(200, string(passwdjson)) 222 | }) 223 | //delete user key-par 224 | server.Post("/delpasswd", func(c echo.Context) error { 225 | olddataUsername := c.FormValue("old_username") 226 | olddataPwd := c.FormValue("old_pwd") 227 | reponame := c.FormValue("reponame") 228 | olddata := make(map[string]string) 229 | olddata["username"] = olddataUsername 230 | olddata["pwd"] = olddataPwd 231 | repo := svnmanager.InitRepo(reponame) 232 | dmap, err := repo.FetchRepoDelPasswd(SVNPATH, olddata) 233 | if err != nil { 234 | return c.JSON(500, err.Error()) 235 | } 236 | djson, err := json.Marshal(dmap) 237 | if err != nil { 238 | return c.JSON(500, err.Error()) 239 | } 240 | return c.JSON(200, string(djson)) 241 | }) 242 | // add a user key-pair 243 | server.Post("/newpasswd", func(c echo.Context) error { 244 | newdataUsername := c.FormValue("new_username") 245 | newdataPwd := c.FormValue("new_pwd") 246 | reponame := c.FormValue("reponame") 247 | newdata := make(map[string]string) 248 | newdata["username"] = newdataUsername 249 | newdata["pwd"] = newdataPwd 250 | repo := svnmanager.InitRepo(reponame) 251 | amap, err := repo.FetchRepoAddPasswd(SVNPATH, newdata) 252 | if err != nil { 253 | return c.JSON(500, err.Error()) 254 | } 255 | ajson, err := json.Marshal(amap) 256 | if err != nil { 257 | return c.JSON(500, err.Error()) 258 | } 259 | return c.JSON(200, string(ajson)) 260 | }) 261 | //edit authz file` groups ` section 262 | server.Post("/groups", func(c echo.Context) error { 263 | olddataGroups := c.FormValue("old_groupname") 264 | olddataUsers := c.FormValue("old_users") 265 | newdataGroups := c.FormValue("new_groupname") 266 | newdataUsers := c.FormValue("new_users") 267 | reponame := c.FormValue("reponame") 268 | olddata := make(map[string]string) 269 | olddata["groupname"] = olddataGroups 270 | olddata["users"] = olddataUsers 271 | newdata := make(map[string]string) 272 | newdata["groupname"] = newdataGroups 273 | newdata["users"] = newdataUsers 274 | repo := svnmanager.InitRepo(reponame) 275 | gmap, err := repo.FetchRepoEditedGroup(SVNPATH, olddata, newdata) 276 | if err != nil { 277 | return c.JSON(500, err.Error()) 278 | } 279 | gjson, err := json.Marshal(gmap) 280 | if err != nil { 281 | return c.JSON(500, err.Error()) 282 | } 283 | return c.JSON(200, string(gjson)) 284 | }) 285 | //add a new group 286 | server.Post("/addgroup", func(c echo.Context) error { 287 | newdataGroups := c.FormValue("new_groupname") 288 | newdataUsers := c.FormValue("new_users") 289 | reponame := c.FormValue("reponame") 290 | newdata := make(map[string]string) 291 | newdata["groupname"] = newdataGroups 292 | newdata["users"] = newdataUsers 293 | repo := svnmanager.InitRepo(reponame) 294 | gmap, err := repo.FetchRepoAddGroup(SVNPATH, newdata) 295 | if err != nil { 296 | return c.JSON(500, err.Error()) 297 | } 298 | gjson, err := json.Marshal(gmap) 299 | if err != nil { 300 | return c.JSON(500, err.Error()) 301 | } 302 | return c.JSON(200, gjson) 303 | }) 304 | //delete a group 305 | server.Post("/delgroup", func(c echo.Context) error { 306 | olddataGroups := c.FormValue("old_groupname") 307 | olddataUsers := c.FormValue("old_users") 308 | reponame := c.FormValue("reponame") 309 | olddata := make(map[string]string) 310 | olddata["groupname"] = olddataGroups 311 | olddata["users"] = olddataUsers 312 | repo := svnmanager.InitRepo(reponame) 313 | gmap, err := repo.FetchRepoDelGroup(SVNPATH, olddata) 314 | if err != nil { 315 | return c.JSON(500, err.Error()) 316 | } 317 | gjson, err := json.Marshal(gmap) 318 | if err != nil { 319 | return c.JSON(500, err.Error()) 320 | } 321 | return c.JSON(200, string(gjson)) 322 | }) 323 | //edit the key-pair of a certain repo directory 324 | server.Post("/editauth", func(c echo.Context) error { 325 | olddataUsers := c.FormValue("old_users") 326 | olddataAuth := c.FormValue("old_auth") 327 | newdataUsers := c.FormValue("new_users") 328 | newdataAuth := c.FormValue("new_auth") 329 | 330 | tag := c.FormValue("tag") 331 | reponame := c.FormValue("reponame") 332 | 333 | olddata := make(map[string]string) 334 | olddata["users"] = strings.TrimSpace(olddataUsers) 335 | olddata["auth"] = strings.TrimSpace(olddataAuth) 336 | newdata := make(map[string]string) 337 | newdata["users"] = strings.TrimSpace(newdataUsers) 338 | newdata["auth"] = strings.TrimSpace(newdataAuth) 339 | repo := svnmanager.InitRepo(reponame) 340 | amap, err := repo.FetchRepoEditedDirectory(SVNPATH, tag, olddata, newdata) 341 | if err != nil { 342 | return c.JSON(500, err.Error()) 343 | } 344 | ajson, err := json.Marshal(amap) 345 | if err != nil { 346 | return c.JSON(500, err.Error()) 347 | } 348 | return c.JSON(200, string(ajson)) 349 | }) 350 | 351 | //delete one auth key-pair under a certain repo directory 352 | server.Post("/delauth", func(c echo.Context) error { 353 | olddataUsers := c.FormValue("old_users") 354 | olddataAuth := c.FormValue("old_auth") 355 | tag := c.FormValue("tag") 356 | reponame := c.FormValue("reponame") 357 | olddata := make(map[string]string) 358 | olddata["users"] = strings.TrimSpace(olddataUsers) 359 | olddata["auth"] = strings.TrimSpace(olddataAuth) 360 | repo := svnmanager.InitRepo(reponame) 361 | amap, err := repo.FetchRepoDelDirectory(SVNPATH, tag, olddata) 362 | if err != nil { 363 | return c.JSON(500, err.Error()) 364 | } 365 | ajson, err := json.Marshal(amap) 366 | if err != nil { 367 | return c.JSON(500, err.Error()) 368 | } 369 | return c.JSON(200, string(ajson)) 370 | }) 371 | 372 | //add one auth key-pair under a certain repo directory 373 | server.Post("/addauth", func(c echo.Context) error { 374 | newdataUsers := c.FormValue("new_users") 375 | newdataAuth := c.FormValue("new_auth") 376 | tag := c.FormValue("tag") 377 | reponame := c.FormValue("reponame") 378 | 379 | newdata := make(map[string]string) 380 | newdata["users"] = strings.TrimSpace(newdataUsers) 381 | newdata["auth"] = strings.TrimSpace(newdataAuth) 382 | repo := svnmanager.InitRepo(reponame) 383 | amap, err := repo.FetchRepoAddDirectory(SVNPATH, tag, newdata) 384 | if err != nil { 385 | return c.JSON(500, err.Error()) 386 | } 387 | ajson, err := json.Marshal(amap) 388 | if err != nil { 389 | return c.JSON(500, err.Error()) 390 | } 391 | return c.JSON(200, string(ajson)) 392 | }) 393 | 394 | server.Run(standard.New(":1312")) 395 | } 396 | -------------------------------------------------------------------------------- /svn-server/assets/js/bootstrap-switch.min.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | (function(){var t=[].slice;!function(e,i){"use strict";var n;return n=function(){function t(t,i){null==i&&(i={}),this.$element=e(t),this.options=e.extend({},e.fn.bootstrapSwitch.defaults,{state:this.$element.is(":checked"),size:this.$element.data("size"),animate:this.$element.data("animate"),disabled:this.$element.is(":disabled"),readonly:this.$element.is("[readonly]"),indeterminate:this.$element.data("indeterminate"),inverse:this.$element.data("inverse"),radioAllOff:this.$element.data("radio-all-off"),onColor:this.$element.data("on-color"),offColor:this.$element.data("off-color"),onText:this.$element.data("on-text"),offText:this.$element.data("off-text"),labelText:this.$element.data("label-text"),handleWidth:this.$element.data("handle-width"),labelWidth:this.$element.data("label-width"),baseClass:this.$element.data("base-class"),wrapperClass:this.$element.data("wrapper-class")},i),this.prevOptions={},this.$wrapper=e("
",{"class":function(t){return function(){var e;return e=[""+t.options.baseClass].concat(t._getClasses(t.options.wrapperClass)),e.push(t.options.state?t.options.baseClass+"-on":t.options.baseClass+"-off"),null!=t.options.size&&e.push(t.options.baseClass+"-"+t.options.size),t.options.disabled&&e.push(t.options.baseClass+"-disabled"),t.options.readonly&&e.push(t.options.baseClass+"-readonly"),t.options.indeterminate&&e.push(t.options.baseClass+"-indeterminate"),t.options.inverse&&e.push(t.options.baseClass+"-inverse"),t.$element.attr("id")&&e.push(t.options.baseClass+"-id-"+t.$element.attr("id")),e.join(" ")}}(this)()}),this.$container=e("
",{"class":this.options.baseClass+"-container"}),this.$on=e("",{html:this.options.onText,"class":this.options.baseClass+"-handle-on "+this.options.baseClass+"-"+this.options.onColor}),this.$off=e("",{html:this.options.offText,"class":this.options.baseClass+"-handle-off "+this.options.baseClass+"-"+this.options.offColor}),this.$label=e("",{html:this.options.labelText,"class":this.options.baseClass+"-label"}),this.$element.on("init.bootstrapSwitch",function(e){return function(){return e.options.onInit.apply(t,arguments)}}(this)),this.$element.on("switchChange.bootstrapSwitch",function(i){return function(n){return!1===i.options.onSwitchChange.apply(t,arguments)?i.$element.is(":radio")?e("[name='"+i.$element.attr("name")+"']").trigger("previousState.bootstrapSwitch",!0):i.$element.trigger("previousState.bootstrapSwitch",!0):void 0}}(this)),this.$container=this.$element.wrap(this.$container).parent(),this.$wrapper=this.$container.wrap(this.$wrapper).parent(),this.$element.before(this.options.inverse?this.$off:this.$on).before(this.$label).before(this.options.inverse?this.$on:this.$off),this.options.indeterminate&&this.$element.prop("indeterminate",!0),this._init(),this._elementHandlers(),this._handleHandlers(),this._labelHandlers(),this._formHandler(),this._externalLabelHandler(),this.$element.trigger("init.bootstrapSwitch",this.options.state)}return t.prototype._constructor=t,t.prototype.setPrevOptions=function(){return this.prevOptions=e.extend(!0,{},this.options)},t.prototype.state=function(t,i){return"undefined"==typeof t?this.options.state:this.options.disabled||this.options.readonly?this.$element:this.options.state&&!this.options.radioAllOff&&this.$element.is(":radio")?this.$element:(this.$element.is(":radio")?e("[name='"+this.$element.attr("name")+"']").trigger("setPreviousOptions.bootstrapSwitch"):this.$element.trigger("setPreviousOptions.bootstrapSwitch"),this.options.indeterminate&&this.indeterminate(!1),t=!!t,this.$element.prop("checked",t).trigger("change.bootstrapSwitch",i),this.$element)},t.prototype.toggleState=function(t){return this.options.disabled||this.options.readonly?this.$element:this.options.indeterminate?(this.indeterminate(!1),this.state(!0)):this.$element.prop("checked",!this.options.state).trigger("change.bootstrapSwitch",t)},t.prototype.size=function(t){return"undefined"==typeof t?this.options.size:(null!=this.options.size&&this.$wrapper.removeClass(this.options.baseClass+"-"+this.options.size),t&&this.$wrapper.addClass(this.options.baseClass+"-"+t),this._width(),this._containerPosition(),this.options.size=t,this.$element)},t.prototype.animate=function(t){return"undefined"==typeof t?this.options.animate:(t=!!t,t===this.options.animate?this.$element:this.toggleAnimate())},t.prototype.toggleAnimate=function(){return this.options.animate=!this.options.animate,this.$wrapper.toggleClass(this.options.baseClass+"-animate"),this.$element},t.prototype.disabled=function(t){return"undefined"==typeof t?this.options.disabled:(t=!!t,t===this.options.disabled?this.$element:this.toggleDisabled())},t.prototype.toggleDisabled=function(){return this.options.disabled=!this.options.disabled,this.$element.prop("disabled",this.options.disabled),this.$wrapper.toggleClass(this.options.baseClass+"-disabled"),this.$element},t.prototype.readonly=function(t){return"undefined"==typeof t?this.options.readonly:(t=!!t,t===this.options.readonly?this.$element:this.toggleReadonly())},t.prototype.toggleReadonly=function(){return this.options.readonly=!this.options.readonly,this.$element.prop("readonly",this.options.readonly),this.$wrapper.toggleClass(this.options.baseClass+"-readonly"),this.$element},t.prototype.indeterminate=function(t){return"undefined"==typeof t?this.options.indeterminate:(t=!!t,t===this.options.indeterminate?this.$element:this.toggleIndeterminate())},t.prototype.toggleIndeterminate=function(){return this.options.indeterminate=!this.options.indeterminate,this.$element.prop("indeterminate",this.options.indeterminate),this.$wrapper.toggleClass(this.options.baseClass+"-indeterminate"),this._containerPosition(),this.$element},t.prototype.inverse=function(t){return"undefined"==typeof t?this.options.inverse:(t=!!t,t===this.options.inverse?this.$element:this.toggleInverse())},t.prototype.toggleInverse=function(){var t,e;return this.$wrapper.toggleClass(this.options.baseClass+"-inverse"),e=this.$on.clone(!0),t=this.$off.clone(!0),this.$on.replaceWith(t),this.$off.replaceWith(e),this.$on=t,this.$off=e,this.options.inverse=!this.options.inverse,this.$element},t.prototype.onColor=function(t){var e;return e=this.options.onColor,"undefined"==typeof t?e:(null!=e&&this.$on.removeClass(this.options.baseClass+"-"+e),this.$on.addClass(this.options.baseClass+"-"+t),this.options.onColor=t,this.$element)},t.prototype.offColor=function(t){var e;return e=this.options.offColor,"undefined"==typeof t?e:(null!=e&&this.$off.removeClass(this.options.baseClass+"-"+e),this.$off.addClass(this.options.baseClass+"-"+t),this.options.offColor=t,this.$element)},t.prototype.onText=function(t){return"undefined"==typeof t?this.options.onText:(this.$on.html(t),this._width(),this._containerPosition(),this.options.onText=t,this.$element)},t.prototype.offText=function(t){return"undefined"==typeof t?this.options.offText:(this.$off.html(t),this._width(),this._containerPosition(),this.options.offText=t,this.$element)},t.prototype.labelText=function(t){return"undefined"==typeof t?this.options.labelText:(this.$label.html(t),this._width(),this.options.labelText=t,this.$element)},t.prototype.handleWidth=function(t){return"undefined"==typeof t?this.options.handleWidth:(this.options.handleWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.labelWidth=function(t){return"undefined"==typeof t?this.options.labelWidth:(this.options.labelWidth=t,this._width(),this._containerPosition(),this.$element)},t.prototype.baseClass=function(t){return this.options.baseClass},t.prototype.wrapperClass=function(t){return"undefined"==typeof t?this.options.wrapperClass:(t||(t=e.fn.bootstrapSwitch.defaults.wrapperClass),this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")),this.$wrapper.addClass(this._getClasses(t).join(" ")),this.options.wrapperClass=t,this.$element)},t.prototype.radioAllOff=function(t){return"undefined"==typeof t?this.options.radioAllOff:(t=!!t,t===this.options.radioAllOff?this.$element:(this.options.radioAllOff=t,this.$element))},t.prototype.onInit=function(t){return"undefined"==typeof t?this.options.onInit:(t||(t=e.fn.bootstrapSwitch.defaults.onInit),this.options.onInit=t,this.$element)},t.prototype.onSwitchChange=function(t){return"undefined"==typeof t?this.options.onSwitchChange:(t||(t=e.fn.bootstrapSwitch.defaults.onSwitchChange),this.options.onSwitchChange=t,this.$element)},t.prototype.destroy=function(){var t;return t=this.$element.closest("form"),t.length&&t.off("reset.bootstrapSwitch").removeData("bootstrap-switch"),this.$container.children().not(this.$element).remove(),this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"),this.$element},t.prototype._width=function(){var t,e;return t=this.$on.add(this.$off),t.add(this.$label).css("width",""),e="auto"===this.options.handleWidth?Math.max(this.$on.width(),this.$off.width()):this.options.handleWidth,t.width(e),this.$label.width(function(t){return function(i,n){return"auto"!==t.options.labelWidth?t.options.labelWidth:e>n?e:n}}(this)),this._handleWidth=this.$on.outerWidth(),this._labelWidth=this.$label.outerWidth(),this.$container.width(2*this._handleWidth+this._labelWidth),this.$wrapper.width(this._handleWidth+this._labelWidth)},t.prototype._containerPosition=function(t,e){return null==t&&(t=this.options.state),this.$container.css("margin-left",function(e){return function(){var i;return i=[0,"-"+e._handleWidth+"px"],e.options.indeterminate?"-"+e._handleWidth/2+"px":t?e.options.inverse?i[1]:i[0]:e.options.inverse?i[0]:i[1]}}(this)),e?setTimeout(function(){return e()},50):void 0},t.prototype._init=function(){var t,e;return t=function(t){return function(){return t.setPrevOptions(),t._width(),t._containerPosition(null,function(){return t.options.animate?t.$wrapper.addClass(t.options.baseClass+"-animate"):void 0})}}(this),this.$wrapper.is(":visible")?t():e=i.setInterval(function(n){return function(){return n.$wrapper.is(":visible")?(t(),i.clearInterval(e)):void 0}}(this),50)},t.prototype._elementHandlers=function(){return this.$element.on({"setPreviousOptions.bootstrapSwitch":function(t){return function(e){return t.setPrevOptions()}}(this),"previousState.bootstrapSwitch":function(t){return function(e){return t.options=t.prevOptions,t.options.indeterminate&&t.$wrapper.addClass(t.options.baseClass+"-indeterminate"),t.$element.prop("checked",t.options.state).trigger("change.bootstrapSwitch",!0)}}(this),"change.bootstrapSwitch":function(t){return function(i,n){var o;return i.preventDefault(),i.stopImmediatePropagation(),o=t.$element.is(":checked"),t._containerPosition(o),o!==t.options.state?(t.options.state=o,t.$wrapper.toggleClass(t.options.baseClass+"-off").toggleClass(t.options.baseClass+"-on"),n?void 0:(t.$element.is(":radio")&&e("[name='"+t.$element.attr("name")+"']").not(t.$element).prop("checked",!1).trigger("change.bootstrapSwitch",!0),t.$element.trigger("switchChange.bootstrapSwitch",[o]))):void 0}}(this),"focus.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.addClass(t.options.baseClass+"-focused")}}(this),"blur.bootstrapSwitch":function(t){return function(e){return e.preventDefault(),t.$wrapper.removeClass(t.options.baseClass+"-focused")}}(this),"keydown.bootstrapSwitch":function(t){return function(e){if(e.which&&!t.options.disabled&&!t.options.readonly)switch(e.which){case 37:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!1);case 39:return e.preventDefault(),e.stopImmediatePropagation(),t.state(!0)}}}(this)})},t.prototype._handleHandlers=function(){return this.$on.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!1),t.$element.trigger("focus.bootstrapSwitch")}}(this)),this.$off.on("click.bootstrapSwitch",function(t){return function(e){return e.preventDefault(),e.stopPropagation(),t.state(!0),t.$element.trigger("focus.bootstrapSwitch")}}(this))},t.prototype._labelHandlers=function(){return this.$label.on({click:function(t){return t.stopPropagation()},"mousedown.bootstrapSwitch touchstart.bootstrapSwitch":function(t){return function(e){return t._dragStart||t.options.disabled||t.options.readonly?void 0:(e.preventDefault(),e.stopPropagation(),t._dragStart=(e.pageX||e.originalEvent.touches[0].pageX)-parseInt(t.$container.css("margin-left"),10),t.options.animate&&t.$wrapper.removeClass(t.options.baseClass+"-animate"),t.$element.trigger("focus.bootstrapSwitch"))}}(this),"mousemove.bootstrapSwitch touchmove.bootstrapSwitch":function(t){return function(e){var i;if(null!=t._dragStart&&(e.preventDefault(),i=(e.pageX||e.originalEvent.touches[0].pageX)-t._dragStart,!(i<-t._handleWidth||i>0)))return t._dragEnd=i,t.$container.css("margin-left",t._dragEnd+"px")}}(this),"mouseup.bootstrapSwitch touchend.bootstrapSwitch":function(t){return function(e){var i;if(t._dragStart)return e.preventDefault(),t.options.animate&&t.$wrapper.addClass(t.options.baseClass+"-animate"),t._dragEnd?(i=t._dragEnd>-(t._handleWidth/2),t._dragEnd=!1,t.state(t.options.inverse?!i:i)):t.state(!t.options.state),t._dragStart=!1}}(this),"mouseleave.bootstrapSwitch":function(t){return function(e){return t.$label.trigger("mouseup.bootstrapSwitch")}}(this)})},t.prototype._externalLabelHandler=function(){var t;return t=this.$element.closest("label"),t.on("click",function(e){return function(i){return i.preventDefault(),i.stopImmediatePropagation(),i.target===t[0]?e.toggleState():void 0}}(this))},t.prototype._formHandler=function(){var t;return t=this.$element.closest("form"),t.data("bootstrap-switch")?void 0:t.on("reset.bootstrapSwitch",function(){return i.setTimeout(function(){return t.find("input").filter(function(){return e(this).data("bootstrap-switch")}).each(function(){return e(this).bootstrapSwitch("state",this.checked)})},1)}).data("bootstrap-switch",!0)},t.prototype._getClasses=function(t){var i,n,o,s;if(!e.isArray(t))return[this.options.baseClass+"-"+t];for(n=[],o=0,s=t.length;s>o;o++)i=t[o],n.push(this.options.baseClass+"-"+i);return n},t}(),e.fn.bootstrapSwitch=function(){var i,o,s;return o=arguments[0],i=2<=arguments.length?t.call(arguments,1):[],s=this,this.each(function(){var t,a;return t=e(this),a=t.data("bootstrap-switch"),a||t.data("bootstrap-switch",a=new n(this,o)),"string"==typeof o?s=a[o].apply(a,i):void 0}),s},e.fn.bootstrapSwitch.Constructor=n,e.fn.bootstrapSwitch.defaults={state:!0,size:null,animate:!0,disabled:!1,readonly:!1,indeterminate:!1,inverse:!1,radioAllOff:!1,onColor:"primary",offColor:"default",onText:"ON",offText:"OFF",labelText:" ",handleWidth:"auto",labelWidth:"auto",baseClass:"bootstrap-switch",wrapperClass:"wrapper",onInit:function(){},onSwitchChange:function(){}}}(window.jQuery,window)}).call(this); -------------------------------------------------------------------------------- /svn-auth/svnauth.go: -------------------------------------------------------------------------------- 1 | package svnauth 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | //SVNAuth the auth structure 14 | type SVNAuth struct { 15 | //SVNPATH the repo path 16 | SVNPATH string `json:"svnpath"` 17 | Aliases string `json:"aliases"` 18 | Groups string `json:"groups"` 19 | } 20 | 21 | //NewSVNAuth init a svn structure 22 | func NewSVNAuth(path string) *SVNAuth { 23 | auth := &SVNAuth{} 24 | auth.SVNPATH = path 25 | return auth 26 | } 27 | 28 | //Readfill will read the authz to the buffer 29 | func (auth *SVNAuth) readfile() ([]byte, error) { 30 | authz := filepath.Join(auth.SVNPATH, "conf", "authz") 31 | filehandle, err := os.Open(authz) 32 | defer filehandle.Close() 33 | if err != nil { 34 | return nil, errors.New(`Please check your ./conf/authz file,if you change it's name,rename it as "authz".`) 35 | } 36 | 37 | content, err := ioutil.ReadAll(filehandle) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return content, nil 42 | } 43 | 44 | //file2json parse authz content to readable json . 45 | func file2json(filecontent []byte) ([]byte, error) { 46 | var lines []string 47 | lines = strings.Split(string(filecontent), "\n") 48 | jsonm := make(map[string][]string) 49 | for k, v := range lines { 50 | if strings.HasPrefix(v, "[") { 51 | key := strings.TrimPrefix(v, "[") 52 | key = strings.TrimSuffix(key, "]") 53 | var value []string 54 | for i := k + 1; i < len(lines); i++ { 55 | if strings.HasPrefix(lines[i], "[") { 56 | break 57 | } 58 | if lines[i] != "" && !strings.HasPrefix(lines[i], "#") { 59 | 60 | value = append(value, lines[i]) 61 | 62 | } 63 | } 64 | jsonm[key] = value 65 | } 66 | } 67 | jsondata, err := json.Marshal(jsonm) 68 | if err != nil { 69 | return []byte(""), err 70 | } 71 | return jsondata, nil 72 | } 73 | 74 | //edit 75 | //Attention: keep the groupname as a primary key . 76 | func (auth *SVNAuth) changeGroup(tagname string, olddata map[string]string, newdata map[string]string, srcContent []byte) error { 77 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 78 | var lines []string 79 | var dstContent bytes.Buffer 80 | lines = strings.Split(string(srcContent), "\n") 81 | // O(n) loop 82 | for k, v := range lines { 83 | t := strings.TrimPrefix(v, "[") 84 | t = strings.TrimSuffix(t, "]") 85 | if strings.TrimSpace(t) == tagname { 86 | dstContent.WriteString(lines[k] + "\n") 87 | for i := k + 1; i < len(lines); i++ { 88 | if strings.HasPrefix(lines[i], "[") { 89 | break 90 | } 91 | tsplit := strings.SplitN(lines[i], "=", 2) 92 | //find the username ; not change username 93 | if strings.TrimSpace(tsplit[0]) == olddata["groupname"] { 94 | // replace rudely 95 | lines[i] = newdata["groupname"] + "=" + newdata["users"] 96 | } 97 | } 98 | } else { 99 | dstContent.WriteString(v + "\n") 100 | } 101 | } 102 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 103 | if err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | //delete 110 | //Attention: keep the groupname as a primary key . 111 | func (auth *SVNAuth) delGroup(tagname string, olddata map[string]string, srcContent []byte) error { 112 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 113 | var lines []string 114 | var dstContent bytes.Buffer 115 | lines = strings.Split(string(srcContent), "\n") 116 | // O(n) loop 117 | for k, v := range lines { 118 | t := strings.TrimPrefix(v, "[") 119 | t = strings.TrimSuffix(t, "]") 120 | if strings.TrimSpace(t) == tagname { 121 | dstContent.WriteString(lines[k] + "\n") 122 | for i := k + 1; i < len(lines); i++ { 123 | if strings.HasPrefix(lines[i], "[") { 124 | break 125 | } 126 | tsplit := strings.SplitN(lines[i], "=", 2) 127 | //find the username ; not change username 128 | if strings.TrimSpace(tsplit[0]) == olddata["groupname"] { 129 | // not really delete ,avoid the risk that lose a user key-pair permanently 130 | lines[i] = "#" + olddata["groupname"] + "=" + olddata["users"] 131 | } 132 | } 133 | } else { 134 | dstContent.WriteString(v + "\n") 135 | } 136 | } 137 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 138 | if err != nil { 139 | return err 140 | } 141 | return nil 142 | } 143 | 144 | //delete 145 | //Attention: keep the groupname as a primary key . 146 | func (auth *SVNAuth) addGroup(tagname string, newdata map[string]string, srcContent []byte) error { 147 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 148 | var lines []string 149 | var dstContent bytes.Buffer 150 | lines = strings.Split(string(srcContent), "\n") 151 | // O(n) loop 152 | for k, v := range lines { 153 | t := strings.TrimPrefix(v, "[") 154 | t = strings.TrimSuffix(t, "]") 155 | if strings.TrimSpace(t) == tagname { 156 | dstContent.WriteString(lines[k] + "\n") 157 | for i := k + 1; i < len(lines); i++ { 158 | if strings.HasPrefix(lines[i], "[") { 159 | break 160 | } 161 | tsplit := strings.SplitN(lines[i], "=", 2) 162 | //find the username ; not change username 163 | if strings.TrimSpace(tsplit[0]) == newdata["groupname"] { 164 | //return a existed error 165 | return errors.New("group existed") 166 | } 167 | } 168 | dstContent.WriteString(newdata["groupname"] + "=" + newdata["users"] + "\n") 169 | } else { 170 | dstContent.WriteString(v + "\n") 171 | } 172 | } 173 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 174 | if err != nil { 175 | return err 176 | } 177 | return nil 178 | } 179 | 180 | func (auth *SVNAuth) changeDirectory(tagname string, olddata map[string]string, newdata map[string]string, srcContent []byte) error { 181 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 182 | var lines []string 183 | var dstContent bytes.Buffer 184 | lines = strings.Split(string(srcContent), "\n") 185 | // O(n) loop 186 | for k, v := range lines { 187 | t := strings.TrimPrefix(v, "[") 188 | t = strings.TrimSuffix(t, "]") 189 | if strings.TrimSpace(t) == tagname { 190 | dstContent.WriteString(lines[k] + "\n") 191 | for i := k + 1; i < len(lines); i++ { 192 | if strings.HasPrefix(lines[i], "[") { 193 | break 194 | } 195 | tsplit := strings.SplitN(lines[i], "=", 2) 196 | //find the username ; not change username 197 | if strings.TrimSpace(tsplit[0]) == olddata["users"] { 198 | // replace rudely 199 | lines[i] = newdata["users"] + "=" + newdata["auth"] 200 | } 201 | } 202 | } else { 203 | dstContent.WriteString(v + "\n") 204 | } 205 | } 206 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 207 | if err != nil { 208 | return err 209 | } 210 | return nil 211 | } 212 | 213 | //replaceDtagPrefix replace initial reponame or some bad reponames 214 | func (auth SVNAuth) replaceDtagPrefix(newprefix string, srcContent []byte) error { 215 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 216 | var lines []string 217 | var dstContent bytes.Buffer 218 | lines = strings.Split(string(srcContent), "\n") 219 | // O(n) loop 220 | for _, v := range lines { 221 | if strings.Contains(v, "[") && strings.Contains(v, "]") && !strings.HasPrefix(v, "#") { 222 | t := strings.TrimPrefix(v, "[") 223 | t = strings.TrimSuffix(t, "]") 224 | if strings.Contains(t, ":") { 225 | tsplit := strings.SplitN(t, ":", 2) 226 | tstring := "[" + newprefix + ":" + tsplit[1] + "]" 227 | dstContent.WriteString(tstring + "\n") 228 | } else { 229 | dstContent.WriteString(v + "\n") 230 | } 231 | } else { 232 | dstContent.WriteString(v + "\n") 233 | } 234 | } 235 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 236 | if err != nil { 237 | return err 238 | } 239 | return nil 240 | } 241 | 242 | //Attention: keep the users as a primary key . 243 | func (auth *SVNAuth) addDirectory(tagname string, newdata map[string]string, srcContent []byte) error { 244 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 245 | var lines []string 246 | var dstContent bytes.Buffer 247 | tagExisted := false 248 | lines = strings.Split(string(srcContent), "\n") 249 | // O(n) loop 250 | for k, v := range lines { 251 | t := strings.TrimPrefix(v, "[") 252 | t = strings.TrimSuffix(t, "]") 253 | if strings.TrimSpace(t) == tagname { 254 | tagExisted = true 255 | dstContent.WriteString(lines[k] + "\n") 256 | for i := k + 1; i < len(lines); i++ { 257 | if strings.HasPrefix(lines[i], "[") { 258 | break 259 | } 260 | tsplit := strings.SplitN(lines[i], "=", 2) 261 | //find the username ; not change username 262 | if strings.TrimSpace(tsplit[0]) == newdata["users"] { 263 | //return a existed error 264 | return errors.New("Directory existed") 265 | } 266 | } 267 | dstContent.WriteString(newdata["users"] + "=" + newdata["auth"] + "\n") 268 | } else { 269 | if strings.TrimSpace(v) != "" { 270 | dstContent.WriteString(v + "\n") 271 | } 272 | } 273 | } 274 | //tag was not existed yet 275 | if !tagExisted { 276 | dstContent.WriteString("[" + tagname + "]" + "\n") 277 | dstContent.WriteString(newdata["users"] + "=" + newdata["auth"] + "\n") 278 | } 279 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 280 | if err != nil { 281 | return err 282 | } 283 | return nil 284 | } 285 | 286 | //Attention: keep the users as a primary key . 287 | func (auth *SVNAuth) delDirectory(tagname string, olddata map[string]string, srcContent []byte) error { 288 | authzPath := filepath.Join(auth.SVNPATH, "conf", "authz") 289 | var lines []string 290 | var dstContent bytes.Buffer 291 | lines = strings.Split(string(srcContent), "\n") 292 | // O(n) loop 293 | for k, v := range lines { 294 | t := strings.TrimPrefix(v, "[") 295 | t = strings.TrimSuffix(t, "]") 296 | if strings.TrimSpace(t) == tagname { 297 | dstContent.WriteString(lines[k] + "\n") 298 | for i := k + 1; i < len(lines); i++ { 299 | if strings.HasPrefix(lines[i], "[") { 300 | break 301 | } 302 | tsplit := strings.SplitN(lines[i], "=", 2) 303 | //find the username ; not change username 304 | if strings.TrimSpace(tsplit[0]) == olddata["users"] { 305 | // not really delete ,avoid the risk that lose a user key-pair permanently 306 | lines[i] = "#" + olddata["users"] + "=" + olddata["auth"] 307 | } 308 | } 309 | } else { 310 | dstContent.WriteString(v + "\n") 311 | } 312 | err := ioutil.WriteFile(authzPath, dstContent.Bytes(), 0666) 313 | if err != nil { 314 | return err 315 | } 316 | } 317 | return nil 318 | } 319 | 320 | /////////////////////////parse function ///////////// 321 | 322 | //parse groups json 323 | func parseGroups(rawjson []byte) ([]byte, error) { 324 | authz := make(map[string][]string) 325 | err := json.Unmarshal(rawjson, &authz) 326 | if err != nil { 327 | return []byte(""), err 328 | } 329 | usergroups := make(map[string]string) 330 | groups := authz["groups"] 331 | for _, v := range groups { 332 | trimv := strings.TrimSpace(string(v)) 333 | splitv := strings.SplitN(trimv, "=", 2) 334 | usergroups[strings.TrimSpace(splitv[0])] = strings.TrimSpace(splitv[1]) 335 | } 336 | groupsjson, err := json.Marshal(usergroups) 337 | if err != nil { 338 | return []byte(""), err 339 | } 340 | return groupsjson, nil 341 | } 342 | 343 | //pasreDirectory parse other section except [aliases] [groups] 344 | func parseDirectory(rawjson []byte) ([]byte, error) { 345 | authz := make(map[string][]string) 346 | err := json.Unmarshal(rawjson, &authz) 347 | if err != nil { 348 | return []byte(""), err 349 | } 350 | var userauth map[string]string 351 | directory := make(map[string]map[string]string) 352 | for k, v := range authz { 353 | userauth = make(map[string]string) 354 | if k != "aliases" && k != "groups" { 355 | for _, authrow := range v { 356 | trim := strings.TrimSpace(string(authrow)) 357 | split := strings.SplitN(trim, "=", 2) 358 | userauth[split[0]] = split[1] 359 | } 360 | directory[k] = userauth 361 | } 362 | } 363 | directoryjson, err := json.Marshal(directory) 364 | if err != nil { 365 | return []byte(""), err 366 | } 367 | return directoryjson, nil 368 | } 369 | 370 | //Unsupport aliases 371 | // func parseAliases(rawjson []byte) ([]byte, error) { 372 | // authz := make(map[string]string) 373 | // err := json.Unmarshal(rawjson, &authz) 374 | // if err != nil { 375 | // return []byte(""), err 376 | // } 377 | // useraliases := make(map[string]string) 378 | // aliases := authz["aliases"] 379 | // for _, v := range aliases { 380 | // trimv := strings.TrimSpace(string(v)) 381 | // splitv := strings.SplitN(trimv, "=", 2) 382 | // useraliases[splitv[0]] = splitv[1] 383 | // } 384 | // aliasesjson, err := json.Marshal(useraliases) 385 | // if err != nil { 386 | // return []byte(""), err 387 | // } 388 | // return aliasesjson, nil 389 | // } 390 | 391 | //ExportGroups ... 392 | func (auth *SVNAuth) ExportGroups() ([]byte, error) { 393 | content, err := auth.readfile() 394 | if err != nil { 395 | return []byte(""), err 396 | } 397 | rawjson, err := file2json(content) 398 | if err != nil { 399 | return []byte(""), err 400 | } 401 | groups, err := parseGroups(rawjson) 402 | if err != nil { 403 | return []byte(""), err 404 | } 405 | return groups, nil 406 | } 407 | 408 | //ExportDirectory export directory json data 409 | func (auth *SVNAuth) ExportDirectory() ([]byte, error) { 410 | content, err := auth.readfile() 411 | if err != nil { 412 | return []byte(""), err 413 | } 414 | rawjson, err := file2json(content) 415 | if err != nil { 416 | return []byte(""), err 417 | } 418 | directory, err := parseDirectory(rawjson) 419 | if err != nil { 420 | return []byte(""), err 421 | } 422 | return directory, nil 423 | } 424 | 425 | //ExportEditedGroup export edited group data 426 | func (auth *SVNAuth) ExportEditedGroup(tag string, olddata map[string]string, newdata map[string]string) ([]byte, error) { 427 | content, err := auth.readfile() 428 | if err != nil { 429 | return []byte(""), err 430 | } 431 | err = auth.changeGroup(tag, olddata, newdata, content) 432 | if err != nil { 433 | return []byte(""), err 434 | } 435 | content, err = auth.readfile() 436 | if err != nil { 437 | return []byte(""), err 438 | } 439 | rawjson, err := file2json(content) 440 | if err != nil { 441 | return []byte(""), err 442 | } 443 | groups, err := parseGroups(rawjson) 444 | if err != nil { 445 | return []byte(""), err 446 | } 447 | return groups, nil 448 | } 449 | 450 | //ExportDelGroup export group info after a delete . 451 | func (auth *SVNAuth) ExportDelGroup(tag string, olddata map[string]string) ([]byte, error) { 452 | content, err := auth.readfile() 453 | if err != nil { 454 | return []byte(""), err 455 | } 456 | err = auth.delGroup(tag, olddata, content) 457 | if err != nil { 458 | return []byte(""), err 459 | } 460 | content, err = auth.readfile() 461 | if err != nil { 462 | return []byte(""), err 463 | } 464 | rawjson, err := file2json(content) 465 | if err != nil { 466 | return []byte(""), err 467 | } 468 | groups, err := parseGroups(rawjson) 469 | if err != nil { 470 | return []byte(""), err 471 | } 472 | return groups, nil 473 | } 474 | 475 | //ExportAddGroup export group info after an add operation . 476 | func (auth *SVNAuth) ExportAddGroup(tag string, newdata map[string]string) ([]byte, error) { 477 | content, err := auth.readfile() 478 | if err != nil { 479 | return []byte(""), err 480 | } 481 | err = auth.addGroup(tag, newdata, content) 482 | if err != nil { 483 | return []byte(""), err 484 | } 485 | content, err = auth.readfile() 486 | if err != nil { 487 | return []byte(""), err 488 | } 489 | rawjson, err := file2json(content) 490 | if err != nil { 491 | return []byte(""), err 492 | } 493 | groups, err := parseGroups(rawjson) 494 | if err != nil { 495 | return []byte(""), err 496 | } 497 | return groups, nil 498 | } 499 | 500 | //ExportEditedDirectory export directory info after edited 501 | func (auth *SVNAuth) ExportEditedDirectory(tag string, olddata map[string]string, newdata map[string]string) ([]byte, error) { 502 | content, err := auth.readfile() 503 | if err != nil { 504 | return []byte(""), err 505 | } 506 | err = auth.changeDirectory(tag, olddata, newdata, content) 507 | if err != nil { 508 | return []byte(""), err 509 | } 510 | content, err = auth.readfile() 511 | if err != nil { 512 | return []byte(""), err 513 | } 514 | rawjson, err := file2json(content) 515 | if err != nil { 516 | return []byte(""), err 517 | } 518 | directory, err := parseDirectory(rawjson) 519 | if err != nil { 520 | return []byte(""), err 521 | } 522 | return directory, nil 523 | } 524 | 525 | //ExportAddDirectory export group info after an add operation . 526 | func (auth *SVNAuth) ExportAddDirectory(tag string, newdata map[string]string) ([]byte, error) { 527 | content, err := auth.readfile() 528 | if err != nil { 529 | return []byte(""), err 530 | } 531 | err = auth.addDirectory(tag, newdata, content) 532 | if err != nil { 533 | return []byte(""), err 534 | } 535 | content, err = auth.readfile() 536 | if err != nil { 537 | return []byte(""), err 538 | } 539 | rawjson, err := file2json(content) 540 | if err != nil { 541 | return []byte(""), err 542 | } 543 | directory, err := parseDirectory(rawjson) 544 | if err != nil { 545 | return []byte(""), err 546 | } 547 | return directory, nil 548 | } 549 | 550 | //ExportDelDirectory export auth key-pair after a delete . 551 | func (auth *SVNAuth) ExportDelDirectory(tag string, olddata map[string]string) ([]byte, error) { 552 | content, err := auth.readfile() 553 | if err != nil { 554 | return []byte(""), err 555 | } 556 | err = auth.delDirectory(tag, olddata, content) 557 | if err != nil { 558 | return []byte(""), err 559 | } 560 | content, err = auth.readfile() 561 | if err != nil { 562 | return []byte(""), err 563 | } 564 | rawjson, err := file2json(content) 565 | if err != nil { 566 | return []byte(""), err 567 | } 568 | directory, err := parseDirectory(rawjson) 569 | if err != nil { 570 | return []byte(""), err 571 | } 572 | return directory, nil 573 | } 574 | 575 | //WrapReplaceDtag wrap replaceDtagPrefix function 576 | func (auth *SVNAuth) WrapReplaceDtag(newtag string) error { 577 | content, err := auth.readfile() 578 | if err != nil { 579 | return err 580 | } 581 | err = auth.replaceDtagPrefix(newtag, content) 582 | if err != nil { 583 | return err 584 | } 585 | return nil 586 | } 587 | -------------------------------------------------------------------------------- /svn-server/assets/config.html: -------------------------------------------------------------------------------- 1 | {{define "conf"}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | SVN admin 9 | 10 | 11 | 12 |
13 | 18 |
19 |
20 |
    21 |
  • 仓库名:    :    {{.Reponame}}
  • 22 |
  • 创建者:    :     {{.Author}}
  • 23 | 24 | {{if (eq .Anon "none")}} 25 |
  • 允许匿名:    :    
  • 26 | {{else}} 27 |
  • 允许匿名:    :    
  • 28 | {{end}} 29 | {{if ne .Pdb ""}} 30 |
  • 开启用户设置:    :     31 | 32 | 33 |           34 | 用户设置文件名: {{.Pdb}} 35 |
  • 36 |
    37 |
  • 38 | 39 | 40 | 41 | 42 | 43 | 44 | {{range $k,$v := $.Users}} 45 | 46 | 47 | 48 | 53 | 54 | {{end}} 55 | 56 | 57 | 58 |
    用户名密码操作
    {{$k}}{{$v}} 49 | 50 | 51 |     52 |
    59 |
  • 60 |
    61 | {{else}} 62 |
  • 开启用户设置:    :     63 |           64 | 65 |
  • 66 | 91 | {{end}} 92 | {{if ne .Adb ""}} 93 |
  • 开启权限设置:    :     94 |           95 | 权限配置文件名:{{.Adb}} 96 |
  • 97 |
    98 |
  • 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | {{range $key,$value := .Group}} 107 | 108 | 109 | 110 | 116 | 117 | {{end}} 118 | 119 | 120 | 121 |
    用户组名用户(填写已存在用户,用,分隔)操作
    {{$key}}{{$value}} 111 | 112 | 113 |     114 | 115 |
    122 | 123 |
  • 124 |
  • 125 |
    126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | {{range $index,$userrow := .Directories}} 134 | 135 | 136 | 137 | 138 | 143 | 144 | {{end}} 145 | 146 | 147 | 148 |
    项目路径用户/用户组权限操作
    {{index $userrow "path"}}{{index $userrow "user"}}{{index $userrow "auth"}} 139 | 140 | 141 | 142 |
    149 |
    150 |
  • 151 |
    152 | {{else}} 153 |
  • 开启权限设置:    :     154 |           155 | 156 |
  • 157 | 212 | {{end}} 213 |
214 |
215 |
216 | 217 | 218 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | {{end}} 431 | -------------------------------------------------------------------------------- /svn-server/assets/js/bootstrap-switch.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | (function() { 23 | var slice = [].slice; 24 | 25 | (function($, window) { 26 | "use strict"; 27 | var BootstrapSwitch; 28 | BootstrapSwitch = (function() { 29 | function BootstrapSwitch(element, options) { 30 | if (options == null) { 31 | options = {}; 32 | } 33 | this.$element = $(element); 34 | this.options = $.extend({}, $.fn.bootstrapSwitch.defaults, { 35 | state: this.$element.is(":checked"), 36 | size: this.$element.data("size"), 37 | animate: this.$element.data("animate"), 38 | disabled: this.$element.is(":disabled"), 39 | readonly: this.$element.is("[readonly]"), 40 | indeterminate: this.$element.data("indeterminate"), 41 | inverse: this.$element.data("inverse"), 42 | radioAllOff: this.$element.data("radio-all-off"), 43 | onColor: this.$element.data("on-color"), 44 | offColor: this.$element.data("off-color"), 45 | onText: this.$element.data("on-text"), 46 | offText: this.$element.data("off-text"), 47 | labelText: this.$element.data("label-text"), 48 | handleWidth: this.$element.data("handle-width"), 49 | labelWidth: this.$element.data("label-width"), 50 | baseClass: this.$element.data("base-class"), 51 | wrapperClass: this.$element.data("wrapper-class") 52 | }, options); 53 | this.prevOptions = {}; 54 | this.$wrapper = $("
", { 55 | "class": (function(_this) { 56 | return function() { 57 | var classes; 58 | classes = ["" + _this.options.baseClass].concat(_this._getClasses(_this.options.wrapperClass)); 59 | classes.push(_this.options.state ? _this.options.baseClass + "-on" : _this.options.baseClass + "-off"); 60 | if (_this.options.size != null) { 61 | classes.push(_this.options.baseClass + "-" + _this.options.size); 62 | } 63 | if (_this.options.disabled) { 64 | classes.push(_this.options.baseClass + "-disabled"); 65 | } 66 | if (_this.options.readonly) { 67 | classes.push(_this.options.baseClass + "-readonly"); 68 | } 69 | if (_this.options.indeterminate) { 70 | classes.push(_this.options.baseClass + "-indeterminate"); 71 | } 72 | if (_this.options.inverse) { 73 | classes.push(_this.options.baseClass + "-inverse"); 74 | } 75 | if (_this.$element.attr("id")) { 76 | classes.push(_this.options.baseClass + "-id-" + (_this.$element.attr("id"))); 77 | } 78 | return classes.join(" "); 79 | }; 80 | })(this)() 81 | }); 82 | this.$container = $("
", { 83 | "class": this.options.baseClass + "-container" 84 | }); 85 | this.$on = $("", { 86 | html: this.options.onText, 87 | "class": this.options.baseClass + "-handle-on " + this.options.baseClass + "-" + this.options.onColor 88 | }); 89 | this.$off = $("", { 90 | html: this.options.offText, 91 | "class": this.options.baseClass + "-handle-off " + this.options.baseClass + "-" + this.options.offColor 92 | }); 93 | this.$label = $("", { 94 | html: this.options.labelText, 95 | "class": this.options.baseClass + "-label" 96 | }); 97 | this.$element.on("init.bootstrapSwitch", (function(_this) { 98 | return function() { 99 | return _this.options.onInit.apply(element, arguments); 100 | }; 101 | })(this)); 102 | this.$element.on("switchChange.bootstrapSwitch", (function(_this) { 103 | return function(e) { 104 | if (false === _this.options.onSwitchChange.apply(element, arguments)) { 105 | if (_this.$element.is(":radio")) { 106 | return $("[name='" + (_this.$element.attr('name')) + "']").trigger("previousState.bootstrapSwitch", true); 107 | } else { 108 | return _this.$element.trigger("previousState.bootstrapSwitch", true); 109 | } 110 | } 111 | }; 112 | })(this)); 113 | this.$container = this.$element.wrap(this.$container).parent(); 114 | this.$wrapper = this.$container.wrap(this.$wrapper).parent(); 115 | this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off); 116 | if (this.options.indeterminate) { 117 | this.$element.prop("indeterminate", true); 118 | } 119 | this._init(); 120 | this._elementHandlers(); 121 | this._handleHandlers(); 122 | this._labelHandlers(); 123 | this._formHandler(); 124 | this._externalLabelHandler(); 125 | this.$element.trigger("init.bootstrapSwitch", this.options.state); 126 | } 127 | 128 | BootstrapSwitch.prototype._constructor = BootstrapSwitch; 129 | 130 | BootstrapSwitch.prototype.setPrevOptions = function() { 131 | return this.prevOptions = $.extend(true, {}, this.options); 132 | }; 133 | 134 | BootstrapSwitch.prototype.state = function(value, skip) { 135 | if (typeof value === "undefined") { 136 | return this.options.state; 137 | } 138 | if (this.options.disabled || this.options.readonly) { 139 | return this.$element; 140 | } 141 | if (this.options.state && !this.options.radioAllOff && this.$element.is(":radio")) { 142 | return this.$element; 143 | } 144 | if (this.$element.is(":radio")) { 145 | $("[name='" + (this.$element.attr('name')) + "']").trigger("setPreviousOptions.bootstrapSwitch"); 146 | } else { 147 | this.$element.trigger("setPreviousOptions.bootstrapSwitch"); 148 | } 149 | if (this.options.indeterminate) { 150 | this.indeterminate(false); 151 | } 152 | value = !!value; 153 | this.$element.prop("checked", value).trigger("change.bootstrapSwitch", skip); 154 | return this.$element; 155 | }; 156 | 157 | BootstrapSwitch.prototype.toggleState = function(skip) { 158 | if (this.options.disabled || this.options.readonly) { 159 | return this.$element; 160 | } 161 | if (this.options.indeterminate) { 162 | this.indeterminate(false); 163 | return this.state(true); 164 | } else { 165 | return this.$element.prop("checked", !this.options.state).trigger("change.bootstrapSwitch", skip); 166 | } 167 | }; 168 | 169 | BootstrapSwitch.prototype.size = function(value) { 170 | if (typeof value === "undefined") { 171 | return this.options.size; 172 | } 173 | if (this.options.size != null) { 174 | this.$wrapper.removeClass(this.options.baseClass + "-" + this.options.size); 175 | } 176 | if (value) { 177 | this.$wrapper.addClass(this.options.baseClass + "-" + value); 178 | } 179 | this._width(); 180 | this._containerPosition(); 181 | this.options.size = value; 182 | return this.$element; 183 | }; 184 | 185 | BootstrapSwitch.prototype.animate = function(value) { 186 | if (typeof value === "undefined") { 187 | return this.options.animate; 188 | } 189 | value = !!value; 190 | if (value === this.options.animate) { 191 | return this.$element; 192 | } 193 | return this.toggleAnimate(); 194 | }; 195 | 196 | BootstrapSwitch.prototype.toggleAnimate = function() { 197 | this.options.animate = !this.options.animate; 198 | this.$wrapper.toggleClass(this.options.baseClass + "-animate"); 199 | return this.$element; 200 | }; 201 | 202 | BootstrapSwitch.prototype.disabled = function(value) { 203 | if (typeof value === "undefined") { 204 | return this.options.disabled; 205 | } 206 | value = !!value; 207 | if (value === this.options.disabled) { 208 | return this.$element; 209 | } 210 | return this.toggleDisabled(); 211 | }; 212 | 213 | BootstrapSwitch.prototype.toggleDisabled = function() { 214 | this.options.disabled = !this.options.disabled; 215 | this.$element.prop("disabled", this.options.disabled); 216 | this.$wrapper.toggleClass(this.options.baseClass + "-disabled"); 217 | return this.$element; 218 | }; 219 | 220 | BootstrapSwitch.prototype.readonly = function(value) { 221 | if (typeof value === "undefined") { 222 | return this.options.readonly; 223 | } 224 | value = !!value; 225 | if (value === this.options.readonly) { 226 | return this.$element; 227 | } 228 | return this.toggleReadonly(); 229 | }; 230 | 231 | BootstrapSwitch.prototype.toggleReadonly = function() { 232 | this.options.readonly = !this.options.readonly; 233 | this.$element.prop("readonly", this.options.readonly); 234 | this.$wrapper.toggleClass(this.options.baseClass + "-readonly"); 235 | return this.$element; 236 | }; 237 | 238 | BootstrapSwitch.prototype.indeterminate = function(value) { 239 | if (typeof value === "undefined") { 240 | return this.options.indeterminate; 241 | } 242 | value = !!value; 243 | if (value === this.options.indeterminate) { 244 | return this.$element; 245 | } 246 | return this.toggleIndeterminate(); 247 | }; 248 | 249 | BootstrapSwitch.prototype.toggleIndeterminate = function() { 250 | this.options.indeterminate = !this.options.indeterminate; 251 | this.$element.prop("indeterminate", this.options.indeterminate); 252 | this.$wrapper.toggleClass(this.options.baseClass + "-indeterminate"); 253 | this._containerPosition(); 254 | return this.$element; 255 | }; 256 | 257 | BootstrapSwitch.prototype.inverse = function(value) { 258 | if (typeof value === "undefined") { 259 | return this.options.inverse; 260 | } 261 | value = !!value; 262 | if (value === this.options.inverse) { 263 | return this.$element; 264 | } 265 | return this.toggleInverse(); 266 | }; 267 | 268 | BootstrapSwitch.prototype.toggleInverse = function() { 269 | var $off, $on; 270 | this.$wrapper.toggleClass(this.options.baseClass + "-inverse"); 271 | $on = this.$on.clone(true); 272 | $off = this.$off.clone(true); 273 | this.$on.replaceWith($off); 274 | this.$off.replaceWith($on); 275 | this.$on = $off; 276 | this.$off = $on; 277 | this.options.inverse = !this.options.inverse; 278 | return this.$element; 279 | }; 280 | 281 | BootstrapSwitch.prototype.onColor = function(value) { 282 | var color; 283 | color = this.options.onColor; 284 | if (typeof value === "undefined") { 285 | return color; 286 | } 287 | if (color != null) { 288 | this.$on.removeClass(this.options.baseClass + "-" + color); 289 | } 290 | this.$on.addClass(this.options.baseClass + "-" + value); 291 | this.options.onColor = value; 292 | return this.$element; 293 | }; 294 | 295 | BootstrapSwitch.prototype.offColor = function(value) { 296 | var color; 297 | color = this.options.offColor; 298 | if (typeof value === "undefined") { 299 | return color; 300 | } 301 | if (color != null) { 302 | this.$off.removeClass(this.options.baseClass + "-" + color); 303 | } 304 | this.$off.addClass(this.options.baseClass + "-" + value); 305 | this.options.offColor = value; 306 | return this.$element; 307 | }; 308 | 309 | BootstrapSwitch.prototype.onText = function(value) { 310 | if (typeof value === "undefined") { 311 | return this.options.onText; 312 | } 313 | this.$on.html(value); 314 | this._width(); 315 | this._containerPosition(); 316 | this.options.onText = value; 317 | return this.$element; 318 | }; 319 | 320 | BootstrapSwitch.prototype.offText = function(value) { 321 | if (typeof value === "undefined") { 322 | return this.options.offText; 323 | } 324 | this.$off.html(value); 325 | this._width(); 326 | this._containerPosition(); 327 | this.options.offText = value; 328 | return this.$element; 329 | }; 330 | 331 | BootstrapSwitch.prototype.labelText = function(value) { 332 | if (typeof value === "undefined") { 333 | return this.options.labelText; 334 | } 335 | this.$label.html(value); 336 | this._width(); 337 | this.options.labelText = value; 338 | return this.$element; 339 | }; 340 | 341 | BootstrapSwitch.prototype.handleWidth = function(value) { 342 | if (typeof value === "undefined") { 343 | return this.options.handleWidth; 344 | } 345 | this.options.handleWidth = value; 346 | this._width(); 347 | this._containerPosition(); 348 | return this.$element; 349 | }; 350 | 351 | BootstrapSwitch.prototype.labelWidth = function(value) { 352 | if (typeof value === "undefined") { 353 | return this.options.labelWidth; 354 | } 355 | this.options.labelWidth = value; 356 | this._width(); 357 | this._containerPosition(); 358 | return this.$element; 359 | }; 360 | 361 | BootstrapSwitch.prototype.baseClass = function(value) { 362 | return this.options.baseClass; 363 | }; 364 | 365 | BootstrapSwitch.prototype.wrapperClass = function(value) { 366 | if (typeof value === "undefined") { 367 | return this.options.wrapperClass; 368 | } 369 | if (!value) { 370 | value = $.fn.bootstrapSwitch.defaults.wrapperClass; 371 | } 372 | this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")); 373 | this.$wrapper.addClass(this._getClasses(value).join(" ")); 374 | this.options.wrapperClass = value; 375 | return this.$element; 376 | }; 377 | 378 | BootstrapSwitch.prototype.radioAllOff = function(value) { 379 | if (typeof value === "undefined") { 380 | return this.options.radioAllOff; 381 | } 382 | value = !!value; 383 | if (value === this.options.radioAllOff) { 384 | return this.$element; 385 | } 386 | this.options.radioAllOff = value; 387 | return this.$element; 388 | }; 389 | 390 | BootstrapSwitch.prototype.onInit = function(value) { 391 | if (typeof value === "undefined") { 392 | return this.options.onInit; 393 | } 394 | if (!value) { 395 | value = $.fn.bootstrapSwitch.defaults.onInit; 396 | } 397 | this.options.onInit = value; 398 | return this.$element; 399 | }; 400 | 401 | BootstrapSwitch.prototype.onSwitchChange = function(value) { 402 | if (typeof value === "undefined") { 403 | return this.options.onSwitchChange; 404 | } 405 | if (!value) { 406 | value = $.fn.bootstrapSwitch.defaults.onSwitchChange; 407 | } 408 | this.options.onSwitchChange = value; 409 | return this.$element; 410 | }; 411 | 412 | BootstrapSwitch.prototype.destroy = function() { 413 | var $form; 414 | $form = this.$element.closest("form"); 415 | if ($form.length) { 416 | $form.off("reset.bootstrapSwitch").removeData("bootstrap-switch"); 417 | } 418 | this.$container.children().not(this.$element).remove(); 419 | this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"); 420 | return this.$element; 421 | }; 422 | 423 | BootstrapSwitch.prototype._width = function() { 424 | var $handles, handleWidth; 425 | $handles = this.$on.add(this.$off); 426 | $handles.add(this.$label).css("width", ""); 427 | handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth; 428 | $handles.width(handleWidth); 429 | this.$label.width((function(_this) { 430 | return function(index, width) { 431 | if (_this.options.labelWidth !== "auto") { 432 | return _this.options.labelWidth; 433 | } 434 | if (width < handleWidth) { 435 | return handleWidth; 436 | } else { 437 | return width; 438 | } 439 | }; 440 | })(this)); 441 | this._handleWidth = this.$on.outerWidth(); 442 | this._labelWidth = this.$label.outerWidth(); 443 | this.$container.width((this._handleWidth * 2) + this._labelWidth); 444 | return this.$wrapper.width(this._handleWidth + this._labelWidth); 445 | }; 446 | 447 | BootstrapSwitch.prototype._containerPosition = function(state, callback) { 448 | if (state == null) { 449 | state = this.options.state; 450 | } 451 | this.$container.css("margin-left", (function(_this) { 452 | return function() { 453 | var values; 454 | values = [0, "-" + _this._handleWidth + "px"]; 455 | if (_this.options.indeterminate) { 456 | return "-" + (_this._handleWidth / 2) + "px"; 457 | } 458 | if (state) { 459 | if (_this.options.inverse) { 460 | return values[1]; 461 | } else { 462 | return values[0]; 463 | } 464 | } else { 465 | if (_this.options.inverse) { 466 | return values[0]; 467 | } else { 468 | return values[1]; 469 | } 470 | } 471 | }; 472 | })(this)); 473 | if (!callback) { 474 | return; 475 | } 476 | return setTimeout(function() { 477 | return callback(); 478 | }, 50); 479 | }; 480 | 481 | BootstrapSwitch.prototype._init = function() { 482 | var init, initInterval; 483 | init = (function(_this) { 484 | return function() { 485 | _this.setPrevOptions(); 486 | _this._width(); 487 | return _this._containerPosition(null, function() { 488 | if (_this.options.animate) { 489 | return _this.$wrapper.addClass(_this.options.baseClass + "-animate"); 490 | } 491 | }); 492 | }; 493 | })(this); 494 | if (this.$wrapper.is(":visible")) { 495 | return init(); 496 | } 497 | return initInterval = window.setInterval((function(_this) { 498 | return function() { 499 | if (_this.$wrapper.is(":visible")) { 500 | init(); 501 | return window.clearInterval(initInterval); 502 | } 503 | }; 504 | })(this), 50); 505 | }; 506 | 507 | BootstrapSwitch.prototype._elementHandlers = function() { 508 | return this.$element.on({ 509 | "setPreviousOptions.bootstrapSwitch": (function(_this) { 510 | return function(e) { 511 | return _this.setPrevOptions(); 512 | }; 513 | })(this), 514 | "previousState.bootstrapSwitch": (function(_this) { 515 | return function(e) { 516 | _this.options = _this.prevOptions; 517 | if (_this.options.indeterminate) { 518 | _this.$wrapper.addClass(_this.options.baseClass + "-indeterminate"); 519 | } 520 | return _this.$element.prop("checked", _this.options.state).trigger("change.bootstrapSwitch", true); 521 | }; 522 | })(this), 523 | "change.bootstrapSwitch": (function(_this) { 524 | return function(e, skip) { 525 | var state; 526 | e.preventDefault(); 527 | e.stopImmediatePropagation(); 528 | state = _this.$element.is(":checked"); 529 | _this._containerPosition(state); 530 | if (state === _this.options.state) { 531 | return; 532 | } 533 | _this.options.state = state; 534 | _this.$wrapper.toggleClass(_this.options.baseClass + "-off").toggleClass(_this.options.baseClass + "-on"); 535 | if (!skip) { 536 | if (_this.$element.is(":radio")) { 537 | $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true); 538 | } 539 | return _this.$element.trigger("switchChange.bootstrapSwitch", [state]); 540 | } 541 | }; 542 | })(this), 543 | "focus.bootstrapSwitch": (function(_this) { 544 | return function(e) { 545 | e.preventDefault(); 546 | return _this.$wrapper.addClass(_this.options.baseClass + "-focused"); 547 | }; 548 | })(this), 549 | "blur.bootstrapSwitch": (function(_this) { 550 | return function(e) { 551 | e.preventDefault(); 552 | return _this.$wrapper.removeClass(_this.options.baseClass + "-focused"); 553 | }; 554 | })(this), 555 | "keydown.bootstrapSwitch": (function(_this) { 556 | return function(e) { 557 | if (!e.which || _this.options.disabled || _this.options.readonly) { 558 | return; 559 | } 560 | switch (e.which) { 561 | case 37: 562 | e.preventDefault(); 563 | e.stopImmediatePropagation(); 564 | return _this.state(false); 565 | case 39: 566 | e.preventDefault(); 567 | e.stopImmediatePropagation(); 568 | return _this.state(true); 569 | } 570 | }; 571 | })(this) 572 | }); 573 | }; 574 | 575 | BootstrapSwitch.prototype._handleHandlers = function() { 576 | this.$on.on("click.bootstrapSwitch", (function(_this) { 577 | return function(event) { 578 | event.preventDefault(); 579 | event.stopPropagation(); 580 | _this.state(false); 581 | return _this.$element.trigger("focus.bootstrapSwitch"); 582 | }; 583 | })(this)); 584 | return this.$off.on("click.bootstrapSwitch", (function(_this) { 585 | return function(event) { 586 | event.preventDefault(); 587 | event.stopPropagation(); 588 | _this.state(true); 589 | return _this.$element.trigger("focus.bootstrapSwitch"); 590 | }; 591 | })(this)); 592 | }; 593 | 594 | BootstrapSwitch.prototype._labelHandlers = function() { 595 | return this.$label.on({ 596 | "click": function(e) { 597 | return e.stopPropagation(); 598 | }, 599 | "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) { 600 | return function(e) { 601 | if (_this._dragStart || _this.options.disabled || _this.options.readonly) { 602 | return; 603 | } 604 | e.preventDefault(); 605 | e.stopPropagation(); 606 | _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10); 607 | if (_this.options.animate) { 608 | _this.$wrapper.removeClass(_this.options.baseClass + "-animate"); 609 | } 610 | return _this.$element.trigger("focus.bootstrapSwitch"); 611 | }; 612 | })(this), 613 | "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) { 614 | return function(e) { 615 | var difference; 616 | if (_this._dragStart == null) { 617 | return; 618 | } 619 | e.preventDefault(); 620 | difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart; 621 | if (difference < -_this._handleWidth || difference > 0) { 622 | return; 623 | } 624 | _this._dragEnd = difference; 625 | return _this.$container.css("margin-left", _this._dragEnd + "px"); 626 | }; 627 | })(this), 628 | "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) { 629 | return function(e) { 630 | var state; 631 | if (!_this._dragStart) { 632 | return; 633 | } 634 | e.preventDefault(); 635 | if (_this.options.animate) { 636 | _this.$wrapper.addClass(_this.options.baseClass + "-animate"); 637 | } 638 | if (_this._dragEnd) { 639 | state = _this._dragEnd > -(_this._handleWidth / 2); 640 | _this._dragEnd = false; 641 | _this.state(_this.options.inverse ? !state : state); 642 | } else { 643 | _this.state(!_this.options.state); 644 | } 645 | return _this._dragStart = false; 646 | }; 647 | })(this), 648 | "mouseleave.bootstrapSwitch": (function(_this) { 649 | return function(e) { 650 | return _this.$label.trigger("mouseup.bootstrapSwitch"); 651 | }; 652 | })(this) 653 | }); 654 | }; 655 | 656 | BootstrapSwitch.prototype._externalLabelHandler = function() { 657 | var $externalLabel; 658 | $externalLabel = this.$element.closest("label"); 659 | return $externalLabel.on("click", (function(_this) { 660 | return function(event) { 661 | event.preventDefault(); 662 | event.stopImmediatePropagation(); 663 | if (event.target === $externalLabel[0]) { 664 | return _this.toggleState(); 665 | } 666 | }; 667 | })(this)); 668 | }; 669 | 670 | BootstrapSwitch.prototype._formHandler = function() { 671 | var $form; 672 | $form = this.$element.closest("form"); 673 | if ($form.data("bootstrap-switch")) { 674 | return; 675 | } 676 | return $form.on("reset.bootstrapSwitch", function() { 677 | return window.setTimeout(function() { 678 | return $form.find("input").filter(function() { 679 | return $(this).data("bootstrap-switch"); 680 | }).each(function() { 681 | return $(this).bootstrapSwitch("state", this.checked); 682 | }); 683 | }, 1); 684 | }).data("bootstrap-switch", true); 685 | }; 686 | 687 | BootstrapSwitch.prototype._getClasses = function(classes) { 688 | var c, cls, i, len; 689 | if (!$.isArray(classes)) { 690 | return [this.options.baseClass + "-" + classes]; 691 | } 692 | cls = []; 693 | for (i = 0, len = classes.length; i < len; i++) { 694 | c = classes[i]; 695 | cls.push(this.options.baseClass + "-" + c); 696 | } 697 | return cls; 698 | }; 699 | 700 | return BootstrapSwitch; 701 | 702 | })(); 703 | $.fn.bootstrapSwitch = function() { 704 | var args, option, ret; 705 | option = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 706 | ret = this; 707 | this.each(function() { 708 | var $this, data; 709 | $this = $(this); 710 | data = $this.data("bootstrap-switch"); 711 | if (!data) { 712 | $this.data("bootstrap-switch", data = new BootstrapSwitch(this, option)); 713 | } 714 | if (typeof option === "string") { 715 | return ret = data[option].apply(data, args); 716 | } 717 | }); 718 | return ret; 719 | }; 720 | $.fn.bootstrapSwitch.Constructor = BootstrapSwitch; 721 | return $.fn.bootstrapSwitch.defaults = { 722 | state: true, 723 | size: null, 724 | animate: true, 725 | disabled: false, 726 | readonly: false, 727 | indeterminate: false, 728 | inverse: false, 729 | radioAllOff: false, 730 | onColor: "primary", 731 | offColor: "default", 732 | onText: "ON", 733 | offText: "OFF", 734 | labelText: " ", 735 | handleWidth: "auto", 736 | labelWidth: "auto", 737 | baseClass: "bootstrap-switch", 738 | wrapperClass: "wrapper", 739 | onInit: function() {}, 740 | onSwitchChange: function() {} 741 | }; 742 | })(window.jQuery, window); 743 | 744 | }).call(this); 745 | -------------------------------------------------------------------------------- /svn-server/assets/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------