├── .gitignore ├── README.md ├── cache └── cache.go ├── cfg.example.json ├── conf └── app.conf ├── control ├── g ├── cfg.go └── g.go ├── http ├── base │ ├── controller.go │ └── filter.go ├── home │ ├── home_controller.go │ └── home_routes.go ├── http.go └── uic │ ├── auth_controller.go │ ├── sso_controller.go │ ├── team_controller.go │ ├── uic_routes.go │ └── user_controller.go ├── main.go ├── model ├── database.go └── uic │ ├── models.go │ ├── session.go │ ├── team.go │ └── user.go ├── scripts └── schema.sql ├── static ├── css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── g.css │ └── select2-bootstrap.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── img │ └── logo.png ├── js │ ├── bootstrap.min.js │ ├── g.js │ ├── jquery.min.js │ └── jquery.min.map ├── layer │ ├── extend │ │ └── layer.ext.js │ ├── layer.min.js │ └── skin │ │ ├── default │ │ ├── icon_ext.png │ │ ├── textbg.png │ │ ├── xubox_ico0.png │ │ ├── xubox_loading0.gif │ │ ├── xubox_loading1.gif │ │ ├── xubox_loading2.gif │ │ ├── xubox_loading3.gif │ │ └── xubox_title0.png │ │ ├── layer.css │ │ └── layer.ext.css └── select2 │ ├── select2-spinner.gif │ ├── select2.css │ ├── select2.min.js │ ├── select2.png │ └── select2x2.png ├── utils ├── ldap.go ├── ldap_test.go ├── regexp.go └── uuid.go └── views ├── auth ├── index.html ├── login.html └── register.html ├── home └── index.html ├── layout ├── foot.html ├── head.html └── paginator.html ├── team ├── create.html ├── edit.html └── list.html └── user ├── about.html ├── create.html ├── edit.html ├── info.html ├── list.html └── profile.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.sw[op] 25 | /cfg.json 26 | /var 27 | /log 28 | /tmp 29 | /.idea 30 | *.iml 31 | *.log 32 | /falcon-fe* 33 | /fe* 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | falcon-fe 2 | === 3 | 4 | 鉴于很多用户反馈UIC太难安装了(虽然我觉得其实很容易……),用Go语言重新实现了一个,也就是这个falcon-fe了。 5 | 6 | 另外,监控系统组件比较多,有不少web组件,比如uic、portal、alarm、dashboard,没有一个统一的地方汇总查看,falcon-fe也做了一些快捷配置,类似监控系统的hao123导航了 7 | 8 | # 安装Go语言环境 9 | 10 | ``` 11 | cd ~ 12 | wget http://dinp.qiniudn.com/go1.4.1.linux-amd64.tar.gz 13 | tar zxvf go1.4.1.linux-amd64.tar.gz 14 | mkdir -p workspace/src 15 | echo "" >> .bashrc 16 | echo 'export GOROOT=$HOME/go' >> .bashrc 17 | echo 'export GOPATH=$HOME/workspace' >> .bashrc 18 | echo 'export PATH=$GOROOT/bin:$GOPATH/bin:$PATH' >> .bashrc 19 | echo "" >> .bashrc 20 | source .bashrc 21 | ``` 22 | 23 | # 编译安装fe模块 24 | 25 | ``` 26 | cd $GOPATH/src/github.com/open-falcon 27 | git clone https://github.com/open-falcon/fe.git 28 | cd fe 29 | go get ./... 30 | ./control build 31 | ./control start 32 | ``` 33 | 34 | # 配置介绍 35 | 36 | ``` 37 | { 38 | "log": "debug", 39 | "company": "MI", # 填写自己公司的名称,用于生成联系人二维码 40 | "http": { 41 | "enabled": true, 42 | "listen": "0.0.0.0:1234" # 自己随便搞个端口,别跟现有的重复了,可以使用8080,与老版本保持一致 43 | }, 44 | "cache": { 45 | "enabled": true, 46 | "redis": "127.0.0.1:6379", # 这个redis跟judge、alarm用的redis不同,这个只是作为缓存来用 47 | "idle": 10, 48 | "max": 1000, 49 | "timeout": { 50 | "conn": 10000, 51 | "read": 5000, 52 | "write": 5000 53 | } 54 | }, 55 | "salt": "0i923fejfd3", # 搞一个随机字符串 56 | "canRegister": true, 57 | "ldap": { 58 | "enabled": false, 59 | "addr": "ldap.example.com:389", 60 | "baseDN": "dc=example,dc=com", 61 | "bindDN": "cn=mananger,dc=example,dc=com",#允许匿名查询的话,填""值即可 62 | "bindPasswd": "12345678", 63 | "userField": "uid", #用于认证的属性,通常为 uid 或 sAMAccountName(AD)。也可以使用诸如mail的属性,这样认证的用户名就是邮箱(前提ldap里有) 64 | "attributes": ["sn","mail","telephoneNumber"] #数组顺序重要,依次为姓名,邮箱,电话在ldap中的属性名。fe将按这些属性名去ldap中查询新用户的属性,并插入到fe的数据库内。 65 | }, 66 | "uic": { 67 | "addr": "root:password@tcp(127.0.0.1:3306)/fe?charset=utf8&loc=Asia%2FChongqing", 68 | "idle": 10, 69 | "max": 100 70 | }, 71 | "shortcut": { 72 | "falconPortal": "http://11.11.11.11:5050/", 浏览器可访问的portal地址 73 | "falconDashboard": "http://11.11.11.11:7070/", 浏览器可访问的dashboard地址 74 | "falconAlarm": "http://11.11.11.11:6060/" 浏览器可访问的alarm的http地址 75 | } 76 | } 77 | ``` 78 | 79 | # 设置root账号的密码 80 | 81 | 该项目中的注册用户是有不同角色的,目前分三种角色:普通用户、管理员、root账号。系统启动之后第一件事情应该是设置root的密码,浏览器访问:http://fe.example.com/root?password=abc (此处假设你的项目访问地址是fe.example.com,也可以使用ip),这样就设置了root账号的密码为abc。普通用户可以支持注册。 82 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/open-falcon/fe/g" 5 | "github.com/toolkits/cache" 6 | "time" 7 | ) 8 | 9 | func InitCache() { 10 | cfg := g.Config() 11 | if !cfg.Cache.Enabled { 12 | return 13 | } 14 | 15 | cache.InitCache( 16 | cfg.Cache.Redis, 17 | cfg.Cache.Idle, 18 | cfg.Cache.Max, 19 | time.Duration(cfg.Cache.Timeout.Conn)*time.Millisecond, 20 | time.Duration(cfg.Cache.Timeout.Read)*time.Millisecond, 21 | time.Duration(cfg.Cache.Timeout.Write)*time.Millisecond, 22 | time.Hour, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /cfg.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": "debug", 3 | "company": "MI", 4 | "http": { 5 | "enabled": true, 6 | "listen": "0.0.0.0:1234" 7 | }, 8 | "cache": { 9 | "enabled": true, 10 | "redis": "127.0.0.1:6379", 11 | "idle": 10, 12 | "max": 1000, 13 | "timeout": { 14 | "conn": 10000, 15 | "read": 5000, 16 | "write": 5000 17 | } 18 | }, 19 | "salt": "", 20 | "canRegister": true, 21 | "ldap": { 22 | "enabled": false, 23 | "addr": "ldap.example.com:389", 24 | "baseDN": "dc=example,dc=com", 25 | "bindDN": "cn=mananger,dc=example,dc=com", 26 | "bindPasswd": "12345678", 27 | "userField": "uid", 28 | "attributes": ["sn","mail","telephoneNumber"] 29 | }, 30 | "uic": { 31 | "addr": "root:@tcp(127.0.0.1:3306)/uic?charset=utf8&loc=Asia%2FChongqing", 32 | "idle": 10, 33 | "max": 100 34 | }, 35 | "shortcut": { 36 | "falconPortal": "http://11.11.11.11:5050/", 37 | "falconDashboard": "http://11.11.11.11:7070/", 38 | "falconAlarm": "http://11.11.11.11:9912/" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | config = "cfg.json" 2 | AppName = falcon-fe 3 | RunMode = dev -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORKSPACE=$(cd $(dirname $0)/; pwd) 4 | cd $WORKSPACE 5 | 6 | mkdir -p var 7 | 8 | module=fe 9 | app=falcon-$module 10 | conf=cfg.json 11 | pidfile=var/app.pid 12 | logfile=var/app.log 13 | 14 | function check_pid() { 15 | if [ -f $pidfile ];then 16 | pid=`cat $pidfile` 17 | if [ -n $pid ]; then 18 | running=`ps -p $pid|grep -v "PID TTY" |wc -l` 19 | return $running 20 | fi 21 | fi 22 | return 0 23 | } 24 | 25 | function start() { 26 | check_pid 27 | running=$? 28 | if [ $running -gt 0 ];then 29 | echo -n "$app now is running already, pid=" 30 | cat $pidfile 31 | return 1 32 | fi 33 | 34 | nohup ./$app -c $conf &> $logfile & 35 | echo $! > $pidfile 36 | echo "$app started..., pid=$!" 37 | } 38 | 39 | function stop() { 40 | pid=`cat $pidfile` 41 | kill $pid 42 | echo "$app stoped..." 43 | } 44 | 45 | function restart() { 46 | stop 47 | sleep 1 48 | start 49 | } 50 | 51 | function status() { 52 | check_pid 53 | running=$? 54 | if [ $running -gt 0 ];then 55 | echo "started" 56 | else 57 | echo "stoped" 58 | fi 59 | } 60 | 61 | function tailf() { 62 | tail -f $logfile 63 | } 64 | 65 | function build() { 66 | go build 67 | if [ $? -ne 0 ]; then 68 | exit $? 69 | fi 70 | mv $module $app 71 | ./$app -v 72 | } 73 | 74 | function pack() { 75 | build 76 | version=`./$app -v` 77 | tar zcvf $app-$version.tar.gz control cfg.example.json $app conf static views scripts 78 | } 79 | 80 | function packbin() { 81 | build 82 | version=`./$app -v` 83 | tar zcvf $app-bin-$version.tar.gz $app 84 | } 85 | 86 | function help() { 87 | echo "$0 pid|reload|build|pack|packbin|start|stop|restart|status|tail" 88 | } 89 | 90 | function pid() { 91 | cat $pidfile 92 | } 93 | 94 | function reload() { 95 | curl -s 127.0.0.1:2001/config/reload | python -m json.tool 96 | } 97 | 98 | if [ "$1" == "" ]; then 99 | help 100 | elif [ "$1" == "stop" ];then 101 | stop 102 | elif [ "$1" == "start" ];then 103 | start 104 | elif [ "$1" == "restart" ];then 105 | restart 106 | elif [ "$1" == "status" ];then 107 | status 108 | elif [ "$1" == "tail" ];then 109 | tailf 110 | elif [ "$1" == "build" ];then 111 | build 112 | elif [ "$1" == "pack" ];then 113 | pack 114 | elif [ "$1" == "packbin" ];then 115 | packbin 116 | elif [ "$1" == "pid" ];then 117 | pid 118 | elif [ "$1" == "reload" ];then 119 | reload 120 | else 121 | help 122 | fi 123 | -------------------------------------------------------------------------------- /g/cfg.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/toolkits/file" 7 | "log" 8 | "sync" 9 | ) 10 | 11 | type HttpConfig struct { 12 | Enabled bool `json:"enabled"` 13 | Listen string `json:"listen"` 14 | } 15 | 16 | type TimeoutConfig struct { 17 | Conn int64 `json:"conn"` 18 | Read int64 `json:"read"` 19 | Write int64 `json:"write"` 20 | } 21 | 22 | type CacheConfig struct { 23 | Enabled bool `json:"enabled"` 24 | Redis string `json:"redis"` 25 | Idle int `json:"idle"` 26 | Max int `json:"max"` 27 | Timeout *TimeoutConfig `json:"timeout"` 28 | } 29 | 30 | type UicConfig struct { 31 | Addr string `json:"addr"` 32 | Idle int `json:"idle"` 33 | Max int `json:"max"` 34 | } 35 | 36 | type ShortcutConfig struct { 37 | FalconPortal string `json:"falconPortal"` 38 | FalconDashboard string `json:"falconDashboard"` 39 | FalconAlarm string `json:"falconAlarm"` 40 | } 41 | 42 | type LdapConfig struct { 43 | Enabled bool `json:"enabled"` 44 | Addr string `json:"addr"` 45 | BindDN string `json:"bindDN"` 46 | BaseDN string `json:"baseDN` 47 | BindPasswd string `json:"bindPasswd"` 48 | UserField string `json:"userField"` 49 | Attributes []string `json:attributes` 50 | } 51 | 52 | type GlobalConfig struct { 53 | Log string `json:"log"` 54 | Company string `json:"company"` 55 | Cache *CacheConfig `json:"cache"` 56 | Http *HttpConfig `json:"http"` 57 | Salt string `json:"salt"` 58 | CanRegister bool `json:"canRegister"` 59 | Ldap *LdapConfig `json:"ldap"` 60 | Uic *UicConfig `json:"uic"` 61 | Shortcut *ShortcutConfig `json:"shortcut"` 62 | } 63 | 64 | var ( 65 | ConfigFile string 66 | config *GlobalConfig 67 | configLock = new(sync.RWMutex) 68 | ) 69 | 70 | func Config() *GlobalConfig { 71 | configLock.RLock() 72 | defer configLock.RUnlock() 73 | return config 74 | } 75 | 76 | func ParseConfig(cfg string) error { 77 | if cfg == "" { 78 | return fmt.Errorf("use -c to specify configuration file") 79 | } 80 | 81 | if !file.IsExist(cfg) { 82 | return fmt.Errorf("config file %s is nonexistent", cfg) 83 | } 84 | 85 | ConfigFile = cfg 86 | 87 | configContent, err := file.ToTrimString(cfg) 88 | if err != nil { 89 | return fmt.Errorf("read config file %s fail %s", cfg, err) 90 | } 91 | 92 | var c GlobalConfig 93 | err = json.Unmarshal([]byte(configContent), &c) 94 | if err != nil { 95 | return fmt.Errorf("parse config file %s fail %s", cfg, err) 96 | } 97 | 98 | configLock.Lock() 99 | defer configLock.Unlock() 100 | 101 | config = &c 102 | 103 | log.Println("read config file:", cfg, "successfully") 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /g/g.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | const ( 8 | VERSION = "0.0.5" 9 | ) 10 | 11 | func init() { 12 | runtime.GOMAXPROCS(runtime.NumCPU()) 13 | } 14 | -------------------------------------------------------------------------------- /http/base/controller.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/toolkits/web" 6 | "strings" 7 | ) 8 | 9 | type BaseController struct { 10 | beego.Controller 11 | } 12 | 13 | func (this *BaseController) ServeErrJson(msg string) { 14 | this.Data["json"] = map[string]interface{}{ 15 | "msg": msg, 16 | } 17 | this.ServeJSON() 18 | } 19 | 20 | func (this *BaseController) SetPaginator(per int, nums int64) *web.Paginator { 21 | p := web.NewPaginator(this.Ctx.Request, per, nums) 22 | this.Data["paginator"] = p 23 | return p 24 | } 25 | 26 | func (this *BaseController) ServeOKJson() { 27 | this.Data["json"] = map[string]interface{}{ 28 | "msg": "", 29 | } 30 | this.ServeJSON() 31 | } 32 | 33 | func (this *BaseController) AutoServeError(err error) { 34 | if err != nil { 35 | this.ServeErrJson(err.Error()) 36 | } else { 37 | this.ServeOKJson() 38 | } 39 | } 40 | 41 | func (this *BaseController) ServeDataJson(data interface{}) { 42 | this.Data["json"] = map[string]interface{}{ 43 | "msg": "", 44 | "data": data, 45 | } 46 | this.ServeJSON() 47 | } 48 | 49 | func (this *BaseController) NotFound(body string) { 50 | this.Ctx.ResponseWriter.WriteHeader(404) 51 | this.Ctx.ResponseWriter.Write([]byte(body)) 52 | } 53 | 54 | func (this *BaseController) MustGetInt(key string, def int) int { 55 | val, err := this.GetInt(key, def) 56 | if err != nil { 57 | return def 58 | } 59 | 60 | return val 61 | } 62 | 63 | func (this *BaseController) MustGetInt64(key string, def int64) int64 { 64 | val, err := this.GetInt64(key, def) 65 | if err != nil { 66 | return def 67 | } 68 | 69 | return val 70 | } 71 | 72 | func (this *BaseController) MustGetString(key string, def string) string { 73 | return strings.TrimSpace(this.GetString(key, def)) 74 | } 75 | 76 | func (this *BaseController) MustGetBool(key string, def bool) bool { 77 | raw := strings.TrimSpace(this.GetString(key, "0")) 78 | if raw == "true" || raw == "1" || raw == "on" || raw == "checked" || raw == "yes" { 79 | return true 80 | } else if raw == "false" || raw == "0" || raw == "off" || raw == "" || raw == "no" { 81 | return false 82 | } else { 83 | return def 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /http/base/filter.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/astaxie/beego/context" 5 | "github.com/open-falcon/fe/model/uic" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | var FilterLoginUser = func(ctx *context.Context) { 11 | cookieSig := ctx.GetCookie("sig") 12 | if cookieSig == "" { 13 | ctx.Redirect(302, "/auth/login?callback="+ctx.Request.URL.String()) 14 | return 15 | } 16 | 17 | sessionObj := uic.ReadSessionBySig(cookieSig) 18 | if sessionObj == nil || int64(sessionObj.Expired) < time.Now().Unix() { 19 | ctx.Redirect(302, "/auth/login?callback="+ctx.Request.URL.String()) 20 | return 21 | } 22 | 23 | u := uic.ReadUserById(sessionObj.Uid) 24 | if u == nil { 25 | ctx.Redirect(302, "/auth/login?callback="+ctx.Request.URL.String()) 26 | return 27 | } 28 | 29 | ctx.Input.SetData("CurrentUser", u) 30 | } 31 | 32 | var FilterTargetUser = func(ctx *context.Context) { 33 | userId := ctx.Input.Query("id") 34 | if userId == "" { 35 | ctx.ResponseWriter.WriteHeader(403) 36 | ctx.ResponseWriter.Write([]byte("id is necessary")) 37 | return 38 | } 39 | 40 | id, err := strconv.ParseInt(userId, 10, 64) 41 | if err != nil { 42 | ctx.ResponseWriter.WriteHeader(403) 43 | ctx.ResponseWriter.Write([]byte("id is invalid")) 44 | return 45 | } 46 | 47 | u := uic.ReadUserById(id) 48 | if u == nil { 49 | ctx.ResponseWriter.WriteHeader(403) 50 | ctx.ResponseWriter.Write([]byte("no such user")) 51 | return 52 | } 53 | 54 | ctx.Input.SetData("TargetUser", u) 55 | } 56 | 57 | var FilterTargetTeam = func(ctx *context.Context) { 58 | tid := ctx.Input.Query("id") 59 | if tid == "" { 60 | ctx.ResponseWriter.WriteHeader(403) 61 | ctx.ResponseWriter.Write([]byte("id is necessary")) 62 | return 63 | } 64 | 65 | id, err := strconv.ParseInt(tid, 10, 64) 66 | if err != nil { 67 | ctx.ResponseWriter.WriteHeader(403) 68 | ctx.ResponseWriter.Write([]byte("id is invalid")) 69 | return 70 | } 71 | 72 | t := uic.ReadTeamById(id) 73 | if t == nil { 74 | ctx.ResponseWriter.WriteHeader(403) 75 | ctx.ResponseWriter.Write([]byte("no such team")) 76 | return 77 | } 78 | 79 | ctx.Input.SetData("TargetTeam", t) 80 | } 81 | -------------------------------------------------------------------------------- /http/home/home_controller.go: -------------------------------------------------------------------------------- 1 | package home 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/open-falcon/fe/g" 6 | ) 7 | 8 | type HomeController struct { 9 | beego.Controller 10 | } 11 | 12 | func (this *HomeController) Get() { 13 | this.Data["Shortcut"] = g.Config().Shortcut 14 | this.TplName = "home/index.html" 15 | } 16 | -------------------------------------------------------------------------------- /http/home/home_routes.go: -------------------------------------------------------------------------------- 1 | package home 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/context" 6 | "github.com/open-falcon/fe/g" 7 | ) 8 | 9 | func ConfigRoutes() { 10 | beego.Router("/", &HomeController{}) 11 | 12 | beego.Get("/health", func(ctx *context.Context) { 13 | ctx.Output.Body([]byte("ok")) 14 | }) 15 | 16 | beego.Get("/version", func(ctx *context.Context) { 17 | ctx.Output.Body([]byte(g.VERSION)) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/open-falcon/fe/g" 6 | "github.com/open-falcon/fe/http/home" 7 | "github.com/open-falcon/fe/http/uic" 8 | uic_model "github.com/open-falcon/fe/model/uic" 9 | ) 10 | 11 | func Start() { 12 | if !g.Config().Http.Enabled { 13 | return 14 | } 15 | 16 | addr := g.Config().Http.Listen 17 | if addr == "" { 18 | return 19 | } 20 | 21 | home.ConfigRoutes() 22 | uic.ConfigRoutes() 23 | 24 | beego.AddFuncMap("member", uic_model.MembersByTeamId) 25 | beego.Run(addr) 26 | } 27 | -------------------------------------------------------------------------------- /http/uic/auth_controller.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "github.com/open-falcon/fe/g" 5 | "github.com/open-falcon/fe/http/base" 6 | . "github.com/open-falcon/fe/model/uic" 7 | "github.com/open-falcon/fe/utils" 8 | "github.com/toolkits/str" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type AuthController struct { 14 | base.BaseController 15 | } 16 | 17 | func (this *AuthController) Logout() { 18 | u := this.Ctx.Input.GetData("CurrentUser").(*User) 19 | RemoveSessionByUid(u.Id) 20 | this.Ctx.SetCookie("sig", "", 0, "/") 21 | this.Redirect("/auth/login", 302) 22 | } 23 | 24 | func (this *AuthController) LoginGet() { 25 | appSig := this.GetString("sig", "") 26 | callback := this.GetString("callback", "") 27 | 28 | cookieSig := this.Ctx.GetCookie("sig") 29 | if cookieSig == "" { 30 | this.renderLoginPage(appSig, callback) 31 | return 32 | } 33 | 34 | sessionObj := ReadSessionBySig(cookieSig) 35 | if sessionObj == nil || int64(sessionObj.Expired) < time.Now().Unix() { 36 | this.renderLoginPage(appSig, callback) 37 | return 38 | } 39 | 40 | if appSig != "" && callback != "" { 41 | SaveSessionAttrs(sessionObj.Uid, appSig, sessionObj.Expired) 42 | this.Redirect(callback, 302) 43 | } else { 44 | this.Redirect("/me/info", 302) 45 | } 46 | } 47 | 48 | func (this *AuthController) LoginPost() { 49 | name := this.GetString("name", "") 50 | password := this.GetString("password", "") 51 | 52 | if name == "" || password == "" { 53 | this.ServeErrJson("name or password is blank") 54 | return 55 | } 56 | 57 | var u *User 58 | 59 | ldapEnabled := this.MustGetBool("ldap", false) 60 | 61 | if ldapEnabled { 62 | sucess, err := utils.LdapBind(g.Config().Ldap.Addr, 63 | g.Config().Ldap.BaseDN, 64 | g.Config().Ldap.BindDN, 65 | g.Config().Ldap.BindPasswd, 66 | g.Config().Ldap.UserField, 67 | name, 68 | password) 69 | if err != nil { 70 | this.ServeErrJson(err.Error()) 71 | return 72 | } 73 | 74 | if !sucess { 75 | this.ServeErrJson("name or password error") 76 | return 77 | } 78 | 79 | user_attributes, err := utils.Ldapsearch(g.Config().Ldap.Addr, 80 | g.Config().Ldap.BaseDN, 81 | g.Config().Ldap.BindDN, 82 | g.Config().Ldap.BindPasswd, 83 | g.Config().Ldap.UserField, 84 | name, 85 | g.Config().Ldap.Attributes) 86 | userSn := "" 87 | userMail := "" 88 | userTel := "" 89 | if err == nil { 90 | userSn = user_attributes["sn"] 91 | userMail = user_attributes["mail"] 92 | userTel = user_attributes["telephoneNumber"] 93 | } 94 | 95 | arr := strings.Split(name, "@") 96 | var userName, userEmail string 97 | if len(arr) == 2 { 98 | userName = arr[0] 99 | userEmail = name 100 | } else { 101 | userName = name 102 | userEmail = userMail 103 | } 104 | 105 | u = ReadUserByName(userName) 106 | if u == nil { 107 | // 说明用户不存在 108 | u = &User{ 109 | Name: userName, 110 | Passwd: "", 111 | Cnname: userSn, 112 | Phone: userTel, 113 | Email: userEmail, 114 | } 115 | _, err = u.Save() 116 | if err != nil { 117 | this.ServeErrJson("insert user fail " + err.Error()) 118 | return 119 | } 120 | } 121 | } else { 122 | u = ReadUserByName(name) 123 | if u == nil { 124 | this.ServeErrJson("no such user") 125 | return 126 | } 127 | 128 | if u.Passwd != str.Md5Encode(g.Config().Salt+password) { 129 | this.ServeErrJson("password error") 130 | return 131 | } 132 | } 133 | 134 | expired := this.CreateSession(u.Id, 3600*24*30) 135 | 136 | appSig := this.GetString("sig", "") 137 | callback := this.GetString("callback", "") 138 | if appSig != "" && callback != "" { 139 | SaveSessionAttrs(u.Id, appSig, expired) 140 | } 141 | 142 | this.ServeDataJson(callback) 143 | } 144 | 145 | func (this *AuthController) renderLoginPage(sig, callback string) { 146 | this.Data["CanRegister"] = g.Config().CanRegister 147 | this.Data["LdapEnabled"] = g.Config().Ldap.Enabled 148 | this.Data["Sig"] = sig 149 | this.Data["Callback"] = callback 150 | this.TplName = "auth/login.html" 151 | } 152 | 153 | func (this *AuthController) RegisterGet() { 154 | this.Data["CanRegister"] = g.Config().CanRegister 155 | this.TplName = "auth/register.html" 156 | } 157 | 158 | func (this *AuthController) RegisterPost() { 159 | if !g.Config().CanRegister { 160 | this.ServeErrJson("registration system is not open") 161 | return 162 | } 163 | 164 | name := strings.TrimSpace(this.GetString("name", "")) 165 | password := strings.TrimSpace(this.GetString("password", "")) 166 | repeatPassword := strings.TrimSpace(this.GetString("repeat_password", "")) 167 | 168 | if password != repeatPassword { 169 | this.ServeErrJson("password not equal the repeart one") 170 | return 171 | } 172 | 173 | if !utils.IsUsernameValid(name) { 174 | this.ServeErrJson("name pattern is invalid") 175 | return 176 | } 177 | 178 | if ReadUserIdByName(name) > 0 { 179 | this.ServeErrJson("name is already existent") 180 | return 181 | } 182 | 183 | lastId, err := InsertRegisterUser(name, str.Md5Encode(g.Config().Salt+password)) 184 | if err != nil { 185 | this.ServeErrJson("insert user fail " + err.Error()) 186 | return 187 | } 188 | 189 | this.CreateSession(lastId, 3600*24*30) 190 | 191 | this.ServeOKJson() 192 | } 193 | 194 | func (this *AuthController) CreateSession(uid int64, maxAge int) int { 195 | sig := utils.GenerateUUID() 196 | expired := int(time.Now().Unix()) + maxAge 197 | SaveSessionAttrs(uid, sig, expired) 198 | this.Ctx.SetCookie("sig", sig, maxAge, "/") 199 | return expired 200 | } 201 | -------------------------------------------------------------------------------- /http/uic/sso_controller.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "github.com/open-falcon/fe/http/base" 5 | "github.com/open-falcon/fe/model/uic" 6 | "github.com/open-falcon/fe/utils" 7 | ) 8 | 9 | type SsoController struct { 10 | base.BaseController 11 | } 12 | 13 | func (this *SsoController) Sig() { 14 | this.Ctx.Output.Body([]byte(utils.GenerateUUID())) 15 | } 16 | 17 | func (this *SsoController) User() { 18 | sig := this.Ctx.Input.Param(":sig") 19 | if sig == "" { 20 | this.NotFound("sig is blank") 21 | return 22 | } 23 | 24 | s := uic.ReadSessionBySig(sig) 25 | if s == nil { 26 | this.NotFound("no such sig") 27 | return 28 | } 29 | 30 | u := uic.ReadUserById(s.Uid) 31 | if u == nil { 32 | this.NotFound("no such user") 33 | return 34 | } 35 | 36 | this.Data["json"] = map[string]interface{}{ 37 | "user": u, 38 | } 39 | this.ServeJSON() 40 | } 41 | 42 | func (this *SsoController) Logout() { 43 | sig := this.Ctx.Input.Param(":sig") 44 | if sig == "" { 45 | this.ServeErrJson("sig is blank") 46 | return 47 | } 48 | 49 | s := uic.ReadSessionBySig(sig) 50 | if s != nil { 51 | uic.RemoveSessionByUid(s.Uid) 52 | } 53 | 54 | this.ServeOKJson() 55 | } 56 | -------------------------------------------------------------------------------- /http/uic/team_controller.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "github.com/open-falcon/fe/http/base" 5 | . "github.com/open-falcon/fe/model/uic" 6 | "github.com/open-falcon/fe/utils" 7 | "strings" 8 | ) 9 | 10 | type TeamController struct { 11 | base.BaseController 12 | } 13 | 14 | func (this *TeamController) Teams() { 15 | query := strings.TrimSpace(this.GetString("query", "")) 16 | if utils.HasDangerousCharacters(query) { 17 | this.ServeErrJson("query is invalid") 18 | return 19 | } 20 | 21 | per := this.MustGetInt("per", 10) 22 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 23 | 24 | teams, err := QueryMineTeams(query, me.Id) 25 | if err != nil { 26 | this.ServeErrJson("occur error " + err.Error()) 27 | return 28 | } 29 | 30 | total, err := teams.Count() 31 | if err != nil { 32 | this.ServeErrJson("occur error " + err.Error()) 33 | return 34 | } 35 | 36 | pager := this.SetPaginator(per, total) 37 | teams = teams.Limit(per, pager.Offset()) 38 | 39 | var ts []Team 40 | _, err = teams.All(&ts) 41 | if err != nil { 42 | this.ServeErrJson("occur error " + err.Error()) 43 | return 44 | } 45 | 46 | this.Data["Teams"] = ts 47 | this.Data["Query"] = query 48 | this.Data["Me"] = me 49 | this.Data["IamRoot"] = me.Name == "root" 50 | this.TplName = "team/list.html" 51 | } 52 | 53 | func (this *TeamController) CreateTeamGet() { 54 | this.TplName = "team/create.html" 55 | } 56 | 57 | func (this *TeamController) CreateTeamPost() { 58 | name := strings.TrimSpace(this.GetString("name", "")) 59 | if name == "" { 60 | this.ServeErrJson("name is blank") 61 | return 62 | } 63 | 64 | if utils.HasDangerousCharacters(name) { 65 | this.ServeErrJson("name is invalid") 66 | return 67 | } 68 | 69 | resume := strings.TrimSpace(this.GetString("resume", "")) 70 | if utils.HasDangerousCharacters(resume) { 71 | this.ServeErrJson("resume is invalid") 72 | return 73 | } 74 | 75 | t := ReadTeamByName(name) 76 | if t != nil { 77 | this.ServeErrJson("name is already existent") 78 | return 79 | } 80 | 81 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 82 | lastId, err := SaveTeamAttrs(name, resume, me.Id) 83 | if err != nil { 84 | this.ServeErrJson("occur error " + err.Error()) 85 | return 86 | } 87 | 88 | uids := strings.TrimSpace(this.GetString("users", "")) 89 | if utils.HasDangerousCharacters(uids) { 90 | this.ServeErrJson("uids is invalid") 91 | return 92 | } 93 | 94 | err = PutUsersInTeam(lastId, uids) 95 | if err != nil { 96 | this.ServeErrJson("occur error " + err.Error()) 97 | } else { 98 | this.ServeOKJson() 99 | } 100 | } 101 | 102 | func (this *TeamController) Users() { 103 | teamName := strings.TrimSpace(this.GetString("name", "")) 104 | if teamName == "" { 105 | this.ServeErrJson("name is blank") 106 | return 107 | } 108 | 109 | this.Data["json"] = map[string]interface{}{ 110 | "users": MembersByTeamName(teamName), 111 | "msg": "", 112 | } 113 | this.ServeJSON() 114 | } 115 | 116 | func (this *TeamController) DeleteTeam() { 117 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 118 | targetTeam := this.Ctx.Input.GetData("TargetTeam").(*Team) 119 | if !me.CanWrite(targetTeam) { 120 | this.ServeErrJson("no privilege") 121 | return 122 | } 123 | 124 | err := targetTeam.Remove() 125 | if err != nil { 126 | this.ServeErrJson(err.Error()) 127 | return 128 | } 129 | 130 | this.ServeOKJson() 131 | } 132 | 133 | func (this *TeamController) EditGet() { 134 | targetTeam := this.Ctx.Input.GetData("TargetTeam").(*Team) 135 | this.Data["TargetTeam"] = targetTeam 136 | this.TplName = "team/edit.html" 137 | } 138 | 139 | func (this *TeamController) EditPost() { 140 | targetTeam := this.Ctx.Input.GetData("TargetTeam").(*Team) 141 | resume := this.MustGetString("resume", "") 142 | userIdstr := this.MustGetString("users", "") 143 | 144 | if utils.HasDangerousCharacters(resume) || utils.HasDangerousCharacters(userIdstr) { 145 | this.ServeErrJson("parameter resume or users is invalid") 146 | return 147 | } 148 | 149 | if targetTeam.Resume != resume { 150 | targetTeam.Resume = resume 151 | targetTeam.Update() 152 | } 153 | 154 | this.AutoServeError(targetTeam.UpdateUsers(userIdstr)) 155 | } 156 | 157 | // for portal api: query team 158 | func (this *TeamController) Query() { 159 | query := this.MustGetString("query", "") 160 | limit := this.MustGetInt("limit", 10) 161 | 162 | qs := QueryAllTeams(query) 163 | var ts []Team 164 | qs.Limit(limit).All(&ts) 165 | this.Data["json"] = map[string]interface{}{ 166 | "msg": "", 167 | "teams": ts, 168 | } 169 | this.ServeJSON() 170 | } 171 | 172 | func (this *TeamController) All() { 173 | this.Redirect("/me/teams", 301) 174 | } 175 | -------------------------------------------------------------------------------- /http/uic/uic_routes.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/context" 6 | "github.com/open-falcon/fe/http/base" 7 | ) 8 | 9 | func ConfigRoutes() { 10 | 11 | beego.Router("/root", &UserController{}, "get:CreateRoot") 12 | 13 | beego.Router("/auth/login", &AuthController{}, "get:LoginGet;post:LoginPost") 14 | beego.Router("/auth/register", &AuthController{}, "get:RegisterGet;post:RegisterPost") 15 | 16 | beego.Router("/sso/sig", &SsoController{}, "get:Sig") 17 | beego.Router("/sso/user/:sig:string", &SsoController{}, "get:User") 18 | beego.Router("/sso/logout/:sig:string", &SsoController{}, "get:Logout") 19 | 20 | beego.Router("/user/query", &UserController{}, "get:Query") 21 | beego.Router("/user/in", &UserController{}, "get:In") 22 | beego.Router("/user/qrcode/:id:int", &UserController{}, "get:QrCode") 23 | beego.Router("/about/:name:string", &UserController{}, "get:About") 24 | 25 | beego.Router("/team/users", &TeamController{}, "get:Users") 26 | beego.Router("/team/query", &TeamController{}, "get:Query") 27 | beego.Router("/team/all", &TeamController{}, "get:All") 28 | 29 | loginRequired := 30 | beego.NewNamespace("/me", 31 | beego.NSCond(func(ctx *context.Context) bool { 32 | return true 33 | }), 34 | beego.NSBefore(base.FilterLoginUser), 35 | beego.NSRouter("/logout", &AuthController{}, "*:Logout"), 36 | beego.NSRouter("/info", &UserController{}, "get:Info"), 37 | beego.NSRouter("/profile", &UserController{}, "get:ProfileGet;post:ProfilePost"), 38 | beego.NSRouter("/chpwd", &UserController{}, "*:ChangePassword"), 39 | beego.NSRouter("/users", &UserController{}, "get:Users"), 40 | beego.NSRouter("/user/c", &UserController{}, "get:CreateUserGet;post:CreateUserPost"), 41 | beego.NSRouter("/teams", &TeamController{}, "get:Teams"), 42 | beego.NSRouter("/team/c", &TeamController{}, "get:CreateTeamGet;post:CreateTeamPost"), 43 | ) 44 | 45 | beego.AddNamespace(loginRequired) 46 | 47 | targetUserRequired := 48 | beego.NewNamespace("/target-user", 49 | beego.NSCond(func(ctx *context.Context) bool { 50 | return true 51 | }), 52 | beego.NSBefore(base.FilterLoginUser, base.FilterTargetUser), 53 | beego.NSRouter("/delete", &UserController{}, "*:DeleteUser"), 54 | beego.NSRouter("/edit", &UserController{}, "get:EditGet;post:EditPost"), 55 | beego.NSRouter("/chpwd", &UserController{}, "post:ResetPassword"), 56 | beego.NSRouter("/role", &UserController{}, "*:Role"), 57 | ) 58 | 59 | beego.AddNamespace(targetUserRequired) 60 | 61 | targetTeamRequired := 62 | beego.NewNamespace("/target-team", 63 | beego.NSCond(func(ctx *context.Context) bool { 64 | return true 65 | }), 66 | beego.NSBefore(base.FilterLoginUser, base.FilterTargetTeam), 67 | beego.NSRouter("/delete", &TeamController{}, "*:DeleteTeam"), 68 | beego.NSRouter("/edit", &TeamController{}, "get:EditGet;post:EditPost"), 69 | ) 70 | 71 | beego.AddNamespace(targetTeamRequired) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /http/uic/user_controller.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "github.com/open-falcon/fe/g" 5 | "github.com/open-falcon/fe/http/base" 6 | . "github.com/open-falcon/fe/model/uic" 7 | "github.com/open-falcon/fe/utils" 8 | "github.com/toolkits/rsc/qr" 9 | "github.com/toolkits/str" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type UserController struct { 15 | base.BaseController 16 | } 17 | 18 | func (this *UserController) CreateRoot() { 19 | password := strings.TrimSpace(this.GetString("password", "")) 20 | if password == "" { 21 | this.Ctx.Output.Body([]byte("password is blank")) 22 | return 23 | } 24 | 25 | userPtr := &User{ 26 | Name: "root", 27 | Passwd: str.Md5Encode(g.Config().Salt + password), 28 | Role: 2, 29 | } 30 | 31 | _, err := userPtr.Save() 32 | if err != nil { 33 | this.Ctx.Output.Body([]byte(err.Error())) 34 | } else { 35 | this.Ctx.Output.Body([]byte("success")) 36 | } 37 | } 38 | 39 | // 登录成功之后跳转的欢迎页面 40 | func (this *UserController) Info() { 41 | this.Data["CurrentUser"] = this.Ctx.Input.GetData("CurrentUser").(*User) 42 | this.TplName = "user/info.html" 43 | } 44 | 45 | // 展示当前用户的联系信息 46 | func (this *UserController) ProfileGet() { 47 | this.Data["CurrentUser"] = this.Ctx.Input.GetData("CurrentUser").(*User) 48 | this.TplName = "user/profile.html" 49 | } 50 | 51 | // 更新个人信息 52 | func (this *UserController) ProfilePost() { 53 | cnname := strings.TrimSpace(this.GetString("cnname", "")) 54 | email := strings.TrimSpace(this.GetString("email", "")) 55 | phone := strings.TrimSpace(this.GetString("phone", "")) 56 | im := strings.TrimSpace(this.GetString("im", "")) 57 | qq := strings.TrimSpace(this.GetString("qq", "")) 58 | 59 | if utils.HasDangerousCharacters(cnname) { 60 | this.ServeErrJson("cnname is invalid") 61 | return 62 | } 63 | 64 | if utils.HasDangerousCharacters(email) { 65 | this.ServeErrJson("email is invalid") 66 | return 67 | } 68 | 69 | if utils.HasDangerousCharacters(phone) { 70 | this.ServeErrJson("phone is invalid") 71 | return 72 | } 73 | 74 | if utils.HasDangerousCharacters(im) { 75 | this.ServeErrJson("im is invalid") 76 | return 77 | } 78 | 79 | if utils.HasDangerousCharacters(qq) { 80 | this.ServeErrJson("qq is invalid") 81 | return 82 | } 83 | 84 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 85 | me.Cnname = cnname 86 | me.Email = email 87 | me.Phone = phone 88 | me.IM = im 89 | me.QQ = qq 90 | 91 | me.Update() 92 | this.ServeOKJson() 93 | } 94 | 95 | // 为某个用户设置角色,要求当前用户得是root 96 | func (this *UserController) Role() { 97 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 98 | if me.Role <= 1 { 99 | this.ServeErrJson("no privilege") 100 | return 101 | } 102 | 103 | targetUser := this.Ctx.Input.GetData("TargetUser").(*User) 104 | 105 | if targetUser.Name == "root" { 106 | this.ServeErrJson("no privilege") 107 | return 108 | } 109 | 110 | newRole, err := this.GetInt("role", -1) 111 | if err != nil || newRole == -1 { 112 | this.ServeErrJson("parameter role is necessary") 113 | return 114 | } 115 | 116 | targetUser.Role = newRole 117 | _, err = targetUser.Update() 118 | if err != nil { 119 | this.ServeErrJson("occur error " + err.Error()) 120 | return 121 | } 122 | 123 | this.ServeOKJson() 124 | } 125 | 126 | func (this *UserController) ChangePassword() { 127 | oldPassword := strings.TrimSpace(this.GetString("old_password", "")) 128 | newPassword := strings.TrimSpace(this.GetString("new_password", "")) 129 | repeatPassword := strings.TrimSpace(this.GetString("repeat_password", "")) 130 | 131 | if newPassword != repeatPassword { 132 | this.ServeErrJson("password not equal the repeart one") 133 | return 134 | } 135 | 136 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 137 | if me.Passwd != str.Md5Encode(g.Config().Salt+oldPassword) { 138 | this.ServeErrJson("old password error") 139 | return 140 | } 141 | 142 | newPass := str.Md5Encode(g.Config().Salt + newPassword) 143 | if me.Passwd == newPass { 144 | this.ServeOKJson() 145 | return 146 | } 147 | 148 | me.Passwd = newPass 149 | _, err := me.Update() 150 | if err != nil { 151 | this.ServeErrJson("occur error " + err.Error()) 152 | return 153 | } 154 | 155 | RemoveSessionByUid(me.Id) 156 | this.ServeOKJson() 157 | } 158 | 159 | func (this *UserController) Users() { 160 | query := strings.TrimSpace(this.GetString("query", "")) 161 | if utils.HasDangerousCharacters(query) { 162 | this.ServeErrJson("query is invalid") 163 | return 164 | } 165 | 166 | per := this.MustGetInt("per", 20) 167 | 168 | users := QueryUsers(query) 169 | total, err := users.Count() 170 | if err != nil { 171 | this.ServeErrJson("occur error " + err.Error()) 172 | return 173 | } 174 | 175 | pager := this.SetPaginator(per, total) 176 | users = users.Limit(per, pager.Offset()) 177 | 178 | var us []User 179 | _, err = users.All(&us) 180 | if err != nil { 181 | this.ServeErrJson("occur error " + err.Error()) 182 | return 183 | } 184 | 185 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 186 | this.Data["Users"] = us 187 | this.Data["Query"] = query 188 | this.Data["Me"] = me 189 | this.Data["IamRoot"] = me.Name == "root" 190 | this.TplName = "user/list.html" 191 | } 192 | 193 | func (this *UserController) CreateUserGet() { 194 | this.TplName = "user/create.html" 195 | } 196 | 197 | func (this *UserController) CreateUserPost() { 198 | name := strings.TrimSpace(this.GetString("name", "")) 199 | password := strings.TrimSpace(this.GetString("password", "")) 200 | cnname := strings.TrimSpace(this.GetString("cnname", "")) 201 | email := strings.TrimSpace(this.GetString("email", "")) 202 | phone := strings.TrimSpace(this.GetString("phone", "")) 203 | im := strings.TrimSpace(this.GetString("im", "")) 204 | qq := strings.TrimSpace(this.GetString("qq", "")) 205 | 206 | if !utils.IsUsernameValid(name) { 207 | this.ServeErrJson("name pattern is invalid") 208 | return 209 | } 210 | 211 | if ReadUserIdByName(name) > 0 { 212 | this.ServeErrJson("name is already existent") 213 | return 214 | } 215 | 216 | if password == "" { 217 | this.ServeErrJson("password is blank") 218 | return 219 | } 220 | 221 | if utils.HasDangerousCharacters(cnname) { 222 | this.ServeErrJson("cnname is invalid") 223 | return 224 | } 225 | 226 | if utils.HasDangerousCharacters(email) { 227 | this.ServeErrJson("email is invalid") 228 | return 229 | } 230 | 231 | if utils.HasDangerousCharacters(phone) { 232 | this.ServeErrJson("phone is invalid") 233 | return 234 | } 235 | 236 | if utils.HasDangerousCharacters(im) { 237 | this.ServeErrJson("im is invalid") 238 | return 239 | } 240 | 241 | if utils.HasDangerousCharacters(qq) { 242 | this.ServeErrJson("qq is invalid") 243 | return 244 | } 245 | 246 | lastId, err := InsertRegisterUser(name, str.Md5Encode(g.Config().Salt+password)) 247 | if err != nil { 248 | this.ServeErrJson("insert user fail " + err.Error()) 249 | return 250 | } 251 | 252 | targetUser := ReadUserById(lastId) 253 | targetUser.Cnname = cnname 254 | targetUser.Email = email 255 | targetUser.Phone = phone 256 | targetUser.IM = im 257 | targetUser.QQ = qq 258 | 259 | if _, err := targetUser.Update(); err != nil { 260 | this.ServeErrJson("occur error " + err.Error()) 261 | return 262 | } 263 | 264 | this.ServeOKJson() 265 | } 266 | 267 | func (this *UserController) DeleteUser() { 268 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 269 | if me.Role <= 0 { 270 | this.ServeErrJson("no privilege") 271 | return 272 | } 273 | 274 | userPtr := this.Ctx.Input.GetData("TargetUser").(*User) 275 | 276 | _, err := userPtr.Remove() 277 | if err != nil { 278 | this.ServeErrJson("occur error " + err.Error()) 279 | return 280 | } 281 | 282 | this.ServeOKJson() 283 | } 284 | 285 | func (this *UserController) EditGet() { 286 | me := this.Ctx.Input.GetData("CurrentUser").(*User) 287 | if me.Role <= 0 { 288 | this.ServeErrJson("no privilege") 289 | return 290 | } 291 | 292 | this.Data["User"] = this.Ctx.Input.GetData("TargetUser").(*User) 293 | this.TplName = "user/edit.html" 294 | } 295 | 296 | func (this *UserController) EditPost() { 297 | cnname := strings.TrimSpace(this.GetString("cnname", "")) 298 | email := strings.TrimSpace(this.GetString("email", "")) 299 | phone := strings.TrimSpace(this.GetString("phone", "")) 300 | im := strings.TrimSpace(this.GetString("im", "")) 301 | qq := strings.TrimSpace(this.GetString("qq", "")) 302 | 303 | if utils.HasDangerousCharacters(cnname) { 304 | this.ServeErrJson("cnname is invalid") 305 | return 306 | } 307 | 308 | if utils.HasDangerousCharacters(email) { 309 | this.ServeErrJson("email is invalid") 310 | return 311 | } 312 | 313 | if utils.HasDangerousCharacters(phone) { 314 | this.ServeErrJson("phone is invalid") 315 | return 316 | } 317 | 318 | if utils.HasDangerousCharacters(im) { 319 | this.ServeErrJson("im is invalid") 320 | return 321 | } 322 | 323 | if utils.HasDangerousCharacters(qq) { 324 | this.ServeErrJson("qq is invalid") 325 | return 326 | } 327 | 328 | targetUser := this.Ctx.Input.GetData("TargetUser").(*User) 329 | if targetUser.Name == "root" { 330 | this.ServeErrJson("no privilege") 331 | return 332 | } 333 | 334 | targetUser.Cnname = cnname 335 | targetUser.Email = email 336 | targetUser.Phone = phone 337 | targetUser.IM = im 338 | targetUser.QQ = qq 339 | 340 | _, err := targetUser.Update() 341 | if err != nil { 342 | this.ServeErrJson("occur error " + err.Error()) 343 | return 344 | } 345 | 346 | this.ServeOKJson() 347 | } 348 | 349 | func (this *UserController) ResetPassword() { 350 | password := this.GetString("password", "") 351 | if password == "" { 352 | this.ServeErrJson("password is blank") 353 | return 354 | } 355 | 356 | targetUser := this.Ctx.Input.GetData("TargetUser").(*User) 357 | if targetUser.Name == "root" { 358 | this.ServeErrJson("no privilege") 359 | return 360 | } 361 | 362 | targetUser.Passwd = str.Md5Encode(g.Config().Salt + password) 363 | _, err := targetUser.Update() 364 | if err != nil { 365 | this.ServeErrJson("occur error " + err.Error()) 366 | return 367 | } 368 | 369 | this.ServeOKJson() 370 | } 371 | 372 | func (this *UserController) Query() { 373 | query := strings.TrimSpace(this.GetString("query", "")) 374 | limit := this.MustGetInt("limit", 10) 375 | 376 | if utils.HasDangerousCharacters(query) { 377 | this.ServeErrJson("query is invalid") 378 | return 379 | } 380 | 381 | var users []User 382 | QueryUsers(query).Limit(limit).All(&users, "Id", "Name", "Cnname", "Email") 383 | this.Data["json"] = map[string]interface{}{"users": users} 384 | this.ServeJSON() 385 | } 386 | 387 | func (this *UserController) In() { 388 | name := this.MustGetString("name", "") 389 | teamNames := this.MustGetString("teams", "") 390 | 391 | if name == "" || teamNames == "" { 392 | this.Ctx.Output.Body([]byte("0")) 393 | return 394 | } 395 | 396 | teamNames = strings.Replace(teamNames, ";", ",", -1) 397 | teamArr := strings.Split(teamNames, ",") 398 | for _, teamName := range teamArr { 399 | t := ReadTeamByName(teamName) 400 | if t == nil { 401 | continue 402 | } 403 | 404 | members := MembersByTeamName(teamName) 405 | for _, u := range members { 406 | if u.Name == name { 407 | this.Ctx.Output.Body([]byte("1")) 408 | return 409 | } 410 | } 411 | } 412 | 413 | this.Ctx.Output.Body([]byte("0")) 414 | } 415 | 416 | func (this *UserController) About() { 417 | name := this.Ctx.Input.Param(":name") 418 | u := ReadUserByName(name) 419 | if u == nil { 420 | this.NotFound("no such user") 421 | return 422 | } 423 | 424 | this.Data["User"] = u 425 | this.TplName = "user/about.html" 426 | } 427 | 428 | func (this *UserController) QrCode() { 429 | idStr := this.Ctx.Input.Param(":id") 430 | id, err := strconv.ParseInt(idStr, 10, 64) 431 | if err != nil { 432 | this.NotFound("no such user") 433 | return 434 | } 435 | 436 | u := ReadUserById(id) 437 | if u == nil { 438 | this.NotFound("no such user") 439 | return 440 | } 441 | 442 | c, err := qr.Encode("BEGIN:VCARD\nVERSION:3.0\nFN:"+u.Cnname+"\nTEL;WORK;VOICE:"+u.Phone+"\nEMAIL;PREF;INTERNET:"+u.Email+"\nORG:"+g.Config().Company+"\nEND:VCARD", qr.L) 443 | if err != nil { 444 | this.NotFound("no such user") 445 | return 446 | } 447 | 448 | this.Ctx.Output.ContentType("image") 449 | this.Ctx.Output.Body(c.PNG()) 450 | } 451 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/open-falcon/fe/cache" 10 | "github.com/open-falcon/fe/g" 11 | "github.com/open-falcon/fe/http" 12 | "github.com/open-falcon/fe/model" 13 | "github.com/toolkits/logger" 14 | ) 15 | 16 | func main() { 17 | cfg := flag.String("c", "cfg.json", "configuration file") 18 | version := flag.Bool("v", false, "show version") 19 | flag.Parse() 20 | 21 | if *version { 22 | fmt.Println(g.VERSION) 23 | os.Exit(0) 24 | } 25 | 26 | // parse config 27 | if err := g.ParseConfig(*cfg); err != nil { 28 | log.Fatalln(err) 29 | } 30 | 31 | logger.SetLevelWithDefault(g.Config().Log, "info") 32 | 33 | model.InitDatabase() 34 | cache.InitCache() 35 | 36 | http.Start() 37 | } 38 | -------------------------------------------------------------------------------- /model/database.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | "github.com/open-falcon/fe/g" 6 | . "github.com/open-falcon/fe/model/uic" 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | func InitDatabase() { 11 | // set default database 12 | config := g.Config() 13 | orm.RegisterDataBase("default", "mysql", config.Uic.Addr, config.Uic.Idle, config.Uic.Max) 14 | 15 | // register model 16 | orm.RegisterModel(new(User), new(Team), new(Session), new(RelTeamUser)) 17 | 18 | if config.Log == "debug" { 19 | orm.Debug = true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /model/uic/models.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type User struct { 8 | Id int64 `json:"id"` 9 | Name string `json:"name"` 10 | Cnname string `json:"cnname"` 11 | Passwd string `json:"-"` 12 | Email string `json:"email"` 13 | Phone string `json:"phone"` 14 | IM string `json:"im" orm:"column(im)"` 15 | QQ string `json:"qq" orm:"column(qq)"` 16 | Role int `json:"role"` 17 | Created time.Time `json:"-"` 18 | } 19 | 20 | type Team struct { 21 | Id int64 `json:"id"` 22 | Name string `json:"name"` 23 | Resume string `json:"resume"` 24 | Creator int64 `json:"creator"` 25 | Created time.Time `json:"-"` 26 | } 27 | 28 | type RelTeamUser struct { 29 | Id int64 30 | Tid int64 31 | Uid int64 32 | } 33 | 34 | type Session struct { 35 | Id int64 36 | Uid int64 37 | Sig string 38 | Expired int 39 | } 40 | -------------------------------------------------------------------------------- /model/uic/session.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/orm" 6 | "github.com/toolkits/cache" 7 | "github.com/toolkits/logger" 8 | "time" 9 | ) 10 | 11 | func SelectSessionBySig(sig string) *Session { 12 | if sig == "" { 13 | return nil 14 | } 15 | 16 | obj := Session{Sig: sig} 17 | err := orm.NewOrm().Read(&obj, "Sig") 18 | if err != nil { 19 | if err != orm.ErrNoRows { 20 | logger.Errorln(err) 21 | } 22 | return nil 23 | } 24 | return &obj 25 | } 26 | 27 | func ReadSessionBySig(sig string) *Session { 28 | if sig == "" { 29 | return nil 30 | } 31 | 32 | key := fmt.Sprintf("session:obj:%s", sig) 33 | var obj Session 34 | if err := cache.Get(key, &obj); err != nil { 35 | objPtr := SelectSessionBySig(sig) 36 | if objPtr != nil { 37 | go cache.Set(key, objPtr, time.Hour) 38 | } 39 | return objPtr 40 | } 41 | 42 | return &obj 43 | } 44 | 45 | func (this *Session) Save() (int64, error) { 46 | return orm.NewOrm().Insert(this) 47 | } 48 | 49 | func SaveSessionAttrs(uid int64, sig string, expired int) (int64, error) { 50 | s := &Session{Uid: uid, Sig: sig, Expired: expired} 51 | return s.Save() 52 | } 53 | 54 | func RemoveSessionByUid(uid int64) { 55 | var ss []Session 56 | Sessions().Filter("Uid", uid).All(&ss, "Id", "Sig") 57 | if ss == nil || len(ss) == 0 { 58 | return 59 | } 60 | 61 | for _, s := range ss { 62 | num, err := DeleteSessionById(s.Id) 63 | if err == nil && num > 0 { 64 | cache.Delete(fmt.Sprintf("session:obj:%s", s.Sig)) 65 | } 66 | } 67 | } 68 | 69 | func DeleteSessionById(id int64) (int64, error) { 70 | r, err := orm.NewOrm().Raw("DELETE FROM `session` WHERE `id` = ?", id).Exec() 71 | if err != nil { 72 | return 0, err 73 | } 74 | 75 | return r.RowsAffected() 76 | } 77 | 78 | func Sessions() orm.QuerySeter { 79 | return orm.NewOrm().QueryTable(new(Session)) 80 | } 81 | -------------------------------------------------------------------------------- /model/uic/team.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/orm" 6 | "github.com/toolkits/cache" 7 | "github.com/toolkits/logger" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func QueryMineTeams(query string, uid int64) (orm.QuerySeter, error) { 14 | qs := orm.NewOrm().QueryTable(new(Team)) 15 | 16 | condMine := orm.NewCondition() 17 | condMine = condMine.Or("Creator", uid) 18 | 19 | tids, err := Tids(uid) 20 | if err != nil { 21 | return qs, err 22 | } 23 | 24 | if len(tids) > 0 { 25 | condMine = condMine.Or("Id__in", tids) 26 | } 27 | 28 | condResult := orm.NewCondition().AndCond(condMine) 29 | 30 | if query != "" { 31 | condQuery := orm.NewCondition() 32 | condQuery = condQuery.And("Name__icontains", query) 33 | condResult = condResult.AndCond(condQuery) 34 | } 35 | 36 | qs = qs.SetCond(condResult) 37 | return qs, nil 38 | } 39 | 40 | func QueryAllTeams(query string) orm.QuerySeter { 41 | qs := orm.NewOrm().QueryTable(new(Team)) 42 | if query != "" { 43 | qs = qs.Filter("Name__icontains", query) 44 | } 45 | return qs 46 | } 47 | 48 | func Tids(uid int64) ([]int64, error) { 49 | type TidStruct struct { 50 | Tid int64 51 | } 52 | 53 | var tids []TidStruct 54 | _, err := orm.NewOrm().Raw("select tid from rel_team_user where uid = ?", uid).QueryRows(&tids) 55 | if err != nil { 56 | return []int64{}, err 57 | } 58 | 59 | size := len(tids) 60 | arr := make([]int64, size) 61 | for i := 0; i < size; i++ { 62 | arr[i] = tids[i].Tid 63 | } 64 | 65 | return arr, nil 66 | } 67 | 68 | func Uids(tid int64) ([]int64, error) { 69 | type UidStruct struct { 70 | Uid int64 71 | } 72 | 73 | var uids []UidStruct 74 | _, err := orm.NewOrm().Raw("select uid from rel_team_user where tid = ?", tid).QueryRows(&uids) 75 | if err != nil { 76 | return []int64{}, err 77 | } 78 | 79 | size := len(uids) 80 | arr := make([]int64, size) 81 | for i := 0; i < size; i++ { 82 | arr[i] = uids[i].Uid 83 | } 84 | 85 | return arr, nil 86 | } 87 | 88 | func SelectTeamById(id int64) *Team { 89 | if id <= 0 { 90 | return nil 91 | } 92 | 93 | obj := Team{Id: id} 94 | err := orm.NewOrm().Read(&obj, "Id") 95 | if err != nil { 96 | if err != orm.ErrNoRows { 97 | logger.Errorln(err) 98 | } 99 | return nil 100 | } 101 | return &obj 102 | } 103 | 104 | func ReadTeamById(id int64) *Team { 105 | if id <= 0 { 106 | return nil 107 | } 108 | 109 | key := fmt.Sprintf("team:obj:%d", id) 110 | var obj Team 111 | if err := cache.Get(key, &obj); err != nil { 112 | objPtr := SelectTeamById(id) 113 | if objPtr != nil { 114 | go cache.Set(key, objPtr, time.Hour) 115 | } 116 | return objPtr 117 | } 118 | 119 | return &obj 120 | } 121 | 122 | func SelectTeamIdByName(name string) int64 { 123 | if name == "" { 124 | return 0 125 | } 126 | 127 | type IdStruct struct { 128 | Id int64 129 | } 130 | 131 | var idObj IdStruct 132 | err := orm.NewOrm().Raw("select id from team where name = ?", name).QueryRow(&idObj) 133 | if err != nil { 134 | return 0 135 | } 136 | 137 | return idObj.Id 138 | } 139 | 140 | func ReadTeamIdByName(name string) int64 { 141 | if name == "" { 142 | return 0 143 | } 144 | 145 | key := fmt.Sprintf("team:id:%s", name) 146 | var id int64 147 | if err := cache.Get(key, &id); err != nil { 148 | id = SelectTeamIdByName(name) 149 | if id > 0 { 150 | go cache.Set(key, id, time.Hour) 151 | } 152 | } 153 | 154 | return id 155 | } 156 | 157 | func ReadTeamByName(name string) *Team { 158 | return ReadTeamById(ReadTeamIdByName(name)) 159 | } 160 | 161 | func (this *Team) Save() (int64, error) { 162 | return orm.NewOrm().Insert(this) 163 | } 164 | 165 | func SaveTeamAttrs(name, resume string, creator int64) (int64, error) { 166 | t := &Team{Name: name, Resume: resume, Creator: creator} 167 | return t.Save() 168 | } 169 | 170 | func PutUsersInTeam(tid int64, uids string) error { 171 | if uids == "" { 172 | return nil 173 | } 174 | 175 | uidArr := strings.Split(uids, ",") 176 | for i := 0; i < len(uidArr); i++ { 177 | uid := uidArr[i] 178 | if uid == "" { 179 | continue 180 | } 181 | 182 | id, err := strconv.Atoi(uid) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | _, err = orm.NewOrm().Raw("insert into rel_team_user(tid,uid) values(?, ?)", tid, id).Exec() 188 | if err != nil { 189 | return err 190 | } 191 | } 192 | 193 | return nil 194 | } 195 | 196 | func (this *Team) UpdateUsers(userIdstr string) error { 197 | if err := UnlinkByTeamId(this.Id); err != nil { 198 | return err 199 | } 200 | 201 | cache.Delete(fmt.Sprintf("t:uids:%d", this.Id)) 202 | 203 | return PutUsersInTeam(this.Id, userIdstr) 204 | } 205 | 206 | func UserIds(tid int64) []int64 { 207 | key := fmt.Sprintf("t:uids:%d", tid) 208 | uids := []int64{} 209 | if err := cache.Get(key, &uids); err != nil { 210 | uids, err = Uids(tid) 211 | if err == nil { 212 | go cache.Set(key, uids, time.Hour) 213 | } 214 | } 215 | return uids 216 | } 217 | 218 | func MembersByTeamName(name string) []*User { 219 | ret := []*User{} 220 | if name == "" { 221 | return ret 222 | } 223 | 224 | return MembersByTeamId(ReadTeamIdByName(name)) 225 | } 226 | 227 | func MembersByTeamId(tid int64) []*User { 228 | ret := []*User{} 229 | if tid <= 0 { 230 | return ret 231 | } 232 | 233 | uids := UserIds(tid) 234 | size := len(uids) 235 | if size == 0 { 236 | return ret 237 | } 238 | 239 | for _, uid := range uids { 240 | u := ReadUserById(uid) 241 | if u == nil { 242 | continue 243 | } 244 | 245 | ret = append(ret, u) 246 | } 247 | 248 | return ret 249 | } 250 | 251 | func (this *Team) Remove() error { 252 | err := UnlinkByTeamId(this.Id) 253 | if err != nil { 254 | return err 255 | } 256 | 257 | num, err := DeleteTeamById(this.Id) 258 | if err == nil && num > 0 { 259 | cache.Delete(fmt.Sprintf("team:id:%s", this.Name)) 260 | cache.Delete(fmt.Sprintf("team:obj:%d", this.Id)) 261 | } 262 | 263 | return err 264 | } 265 | 266 | func UnlinkByTeamId(id int64) error { 267 | _, err := orm.NewOrm().Raw("DELETE FROM `rel_team_user` WHERE `tid` = ?", id).Exec() 268 | return err 269 | } 270 | 271 | func UnlinkByUserId(id int64) error { 272 | _, err := orm.NewOrm().Raw("DELETE FROM `rel_team_user` WHERE `uid` = ?", id).Exec() 273 | return err 274 | } 275 | 276 | func DeleteTeamById(id int64) (int64, error) { 277 | r, err := orm.NewOrm().Raw("DELETE FROM `team` WHERE `id` = ?", id).Exec() 278 | if err != nil { 279 | return 0, err 280 | } 281 | 282 | return r.RowsAffected() 283 | } 284 | 285 | func (this *Team) UserIds() string { 286 | uids := UserIds(this.Id) 287 | size := len(uids) 288 | if size == 0 { 289 | return "" 290 | } 291 | 292 | arr := make([]string, size) 293 | for i := 0; i < size; i++ { 294 | arr[i] = fmt.Sprintf("%d", uids[i]) 295 | } 296 | 297 | return strings.Join(arr, ",") 298 | } 299 | 300 | func (this *Team) Update() (int64, error) { 301 | num, err := orm.NewOrm().Update(this) 302 | 303 | if err == nil && num > 0 { 304 | cache.Delete(fmt.Sprintf("team:obj:%d", this.Id)) 305 | } 306 | 307 | return num, err 308 | } 309 | -------------------------------------------------------------------------------- /model/uic/user.go: -------------------------------------------------------------------------------- 1 | package uic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/orm" 6 | "github.com/toolkits/cache" 7 | "github.com/toolkits/logger" 8 | "github.com/toolkits/slice" 9 | "time" 10 | ) 11 | 12 | func SelectUserById(id int64) *User { 13 | if id <= 0 { 14 | return nil 15 | } 16 | 17 | obj := User{Id: id} 18 | err := orm.NewOrm().Read(&obj, "Id") 19 | if err != nil { 20 | if err != orm.ErrNoRows { 21 | logger.Errorln(err) 22 | } 23 | return nil 24 | } 25 | return &obj 26 | } 27 | 28 | func ReadUserById(id int64) *User { 29 | if id <= 0 { 30 | return nil 31 | } 32 | 33 | key := fmt.Sprintf("user:obj:%d", id) 34 | var obj User 35 | if err := cache.Get(key, &obj); err != nil { 36 | objPtr := SelectUserById(id) 37 | if objPtr != nil { 38 | go cache.Set(key, objPtr, time.Hour) 39 | } 40 | return objPtr 41 | } 42 | 43 | return &obj 44 | } 45 | 46 | func SelectUserIdByName(name string) int64 { 47 | if name == "" { 48 | return 0 49 | } 50 | 51 | type IdStruct struct { 52 | Id int64 53 | } 54 | 55 | var idObj IdStruct 56 | err := orm.NewOrm().Raw("select id from user where name = ?", name).QueryRow(&idObj) 57 | if err != nil { 58 | return 0 59 | } 60 | 61 | return idObj.Id 62 | } 63 | 64 | func ReadUserIdByName(name string) int64 { 65 | if name == "" { 66 | return 0 67 | } 68 | 69 | key := fmt.Sprintf("user:id:%s", name) 70 | var id int64 71 | if err := cache.Get(key, &id); err != nil { 72 | id = SelectUserIdByName(name) 73 | if id > 0 { 74 | go cache.Set(key, id, time.Hour) 75 | } 76 | } 77 | 78 | return id 79 | } 80 | 81 | func ReadUserByName(name string) *User { 82 | return ReadUserById(ReadUserIdByName(name)) 83 | } 84 | 85 | func (this *User) Save() (int64, error) { 86 | id, err := orm.NewOrm().Insert(this) 87 | if err != nil { 88 | this.Id = id 89 | } 90 | return id, err 91 | } 92 | 93 | func InsertRegisterUser(name, password string) (int64, error) { 94 | userPtr := &User{ 95 | Name: name, 96 | Passwd: password, 97 | } 98 | return userPtr.Save() 99 | } 100 | 101 | func (this *User) Update() (int64, error) { 102 | num, err := orm.NewOrm().Update(this) 103 | 104 | if err == nil && num > 0 { 105 | cache.Delete(fmt.Sprintf("user:obj:%d", this.Id)) 106 | } 107 | 108 | return num, err 109 | } 110 | 111 | func (this *User) CanWrite(t *Team) bool { 112 | if this.Role > 0 { 113 | return true 114 | } 115 | 116 | uids, err := Uids(t.Id) 117 | if err != nil { 118 | return false 119 | } 120 | 121 | return slice.ContainsInt64(uids, this.Id) 122 | } 123 | 124 | func Users() orm.QuerySeter { 125 | return orm.NewOrm().QueryTable(new(User)) 126 | } 127 | 128 | func QueryUsers(query string) orm.QuerySeter { 129 | qs := orm.NewOrm().QueryTable(new(User)) 130 | if query != "" { 131 | cond := orm.NewCondition() 132 | cond = cond.Or("Name__icontains", query).Or("Email__icontains", query) 133 | qs = qs.SetCond(cond) 134 | } 135 | return qs 136 | } 137 | 138 | func (this *User) Remove() (int64, error) { 139 | num, err := DeleteUserById(this.Id) 140 | if err != nil { 141 | return num, err 142 | } 143 | 144 | cache.Delete(fmt.Sprintf("user:obj:%d", this.Id)) 145 | cache.Delete(fmt.Sprintf("user:id:%s", this.Name)) 146 | 147 | tids, err := Tids(this.Id) 148 | if err == nil && len(tids) > 0 { 149 | for i := 0; i < len(tids); i++ { 150 | cache.Delete(fmt.Sprintf("t:uids:%d", tids[i])) 151 | } 152 | } 153 | 154 | UnlinkByUserId(this.Id) 155 | 156 | return num, err 157 | } 158 | 159 | func DeleteUserById(id int64) (int64, error) { 160 | r, err := orm.NewOrm().Raw("DELETE FROM `user` WHERE `id` = ?", id).Exec() 161 | if err != nil { 162 | return 0, err 163 | } 164 | 165 | return r.RowsAffected() 166 | } 167 | -------------------------------------------------------------------------------- /scripts/schema.sql: -------------------------------------------------------------------------------- 1 | create database uic; 2 | use uic; 3 | set names utf8; 4 | 5 | drop table if exists team; 6 | CREATE TABLE `team` ( 7 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 8 | `name` varchar(64) NOT NULL, 9 | `resume` varchar(255) not null default '', 10 | `creator` int(10) unsigned NOT NULL DEFAULT '0', 11 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 12 | PRIMARY KEY (`id`), 13 | UNIQUE KEY `idx_team_name` (`name`) 14 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 15 | 16 | /** 17 | * role: -1:blocked 0:normal 1:admin 2:root 18 | */ 19 | drop table if exists `user`; 20 | CREATE TABLE `user` ( 21 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 22 | `name` varchar(64) NOT NULL, 23 | `passwd` varchar(64) not null default '', 24 | `cnname` varchar(128) not null default '', 25 | `email` varchar(255) not null default '', 26 | `phone` varchar(16) not null default '', 27 | `im` varchar(32) not null default '', 28 | `qq` varchar(16) not null default '', 29 | `role` tinyint not null default 0, 30 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `idx_user_name` (`name`) 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 34 | 35 | drop table if exists `rel_team_user`; 36 | CREATE TABLE `rel_team_user` ( 37 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 38 | `tid` int(10) unsigned not null, 39 | `uid` int(10) unsigned not null, 40 | PRIMARY KEY (`id`), 41 | KEY `idx_rel_tid` (`tid`), 42 | KEY `idx_rel_uid` (`uid`) 43 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 44 | 45 | drop table if exists `session`; 46 | CREATE TABLE `session` ( 47 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 48 | `uid` int(10) unsigned not null, 49 | `sig` varchar(128) not null, 50 | `expired` int(10) unsigned not null, 51 | PRIMARY KEY (`id`), 52 | KEY `idx_session_uid` (`uid`), 53 | UNIQUE KEY `idx_session_sig` (`sig`) 54 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 55 | 56 | -------------------------------------------------------------------------------- /static/css/g.css: -------------------------------------------------------------------------------- 1 | body, input, div, span, h1, h2, h3, h4, h5, table, th, td, ul, li, dl, dt, dd, a { 2 | font-family: 'verdana', 'Microsoft YaHei', 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono'; 3 | } 4 | .text-center { 5 | text-align: center; 6 | } 7 | 8 | ul.catlist { list-style: none; margin: 0; padding: 0; } 9 | ul.catlist li { display: inline; } 10 | ul.catlist li:after { content: "・"; } 11 | ul.catlist li:last-child:after { content: ""; } 12 | 13 | .cut-line { 14 | margin: 0 5px; 15 | color: #D6D6D6; 16 | } 17 | 18 | .font-bold { 19 | font-weight: bold; 20 | } 21 | 22 | .red { 23 | color: red; 24 | } 25 | 26 | .orange { 27 | color: orange; 28 | } 29 | 30 | .green { 31 | color: green; 32 | } 33 | 34 | .blue { 35 | color: blue; 36 | } 37 | 38 | .gray { 39 | color: #999 !important; 40 | } 41 | 42 | .mt0 { 43 | margin-top: 0px; 44 | } 45 | 46 | .mt10 { 47 | margin-top: 10px !important; 48 | } 49 | 50 | .mt20 { 51 | margin-top: 20px !important; 52 | } 53 | 54 | .mt30 { 55 | margin-top: 30px !important; 56 | } 57 | 58 | .mb20 { 59 | margin-bottom: 20px !important; 60 | } 61 | 62 | .thin-border { 63 | padding: 10px; 64 | line-height: 1; 65 | border: 1px solid #ddd; 66 | -webkit-border-radius: 4px; 67 | -moz-border-radius: 4px; 68 | border-radius: 4px; 69 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 70 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 71 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); 72 | background-color: #fff; 73 | } 74 | 75 | .links { 76 | margin:0 auto; 77 | width: 360px; 78 | line-height: 30px; 79 | } 80 | 81 | .links .link-head { 82 | margin-top: 10px; 83 | } 84 | 85 | .us { 86 | line-height: 28px; 87 | } 88 | 89 | code.users { 90 | font-family:'verdana','Consolas'; 91 | color: green; 92 | line-height: 26px; 93 | } -------------------------------------------------------------------------------- /static/css/select2-bootstrap.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Select2 Bootstrap CSS 3 | * Compatible with Select2 3.3.2, 3.4.1, 3.4.2 and Twitter Bootstrap 3.0.0 4 | * MIT License 5 | */ 6 | /** 7 | * Reset Bootstrap 3 .form-control styles which - if applied to the 8 | * original ',yes:function(c){var e=d.prompt.val();""===e?d.prompt.focus():e.replace(/\s/g,"").length>(a.length||1e3)?layer.tips("最多输入"+(a.length||1e3)+"个字数","#xubox_prompt",2):b&&b(e,c,d.prompt)},no:c},success:function(){d.prompt=$("#xubox_prompt"),d.prompt.focus()}};return 3===a.type&&(e.dialog.msg='"),$.layer(e)},layer.tab=function(a){var a=a||{},c=a.data||[],d={type:1,border:[0],area:["auto","auto"],bgcolor:"",title:!1,shade:a.shade,offset:a.offset,move:".xubox_tabmove",closeBtn:!1,page:{html:'
'}()+''+'
'+function(){var a=c.length,b=1,d="";if(a>0)for(d=''+c[0].title+"";a>b;b++)d+=""+c[b].title+"";return d}()+"
"+'"+'X'+"
"},success:function(a){var b=$(".xubox_tabtit").children(),c=$(".xubox_tab_main").children(),d=$(".xubox_tabclose");b.on("click",function(){var a=$(this),b=a.index();a.addClass("xubox_tabnow").siblings().removeClass("xubox_tabnow"),c.eq(b).show().siblings().hide()}),d.on("click",function(){layer.close(a.attr("times"))})}};return $.layer(d)},layer.photos=function(a){var b,c,d,e,f,g,h,i;if(a=a||{},b={imgIndex:1,end:null,html:$("html")},c=$(window),d=a.json,e=a.page,d){if(f=d.data,1!==d.status)return layer.msg("未请求到数据",2,8),void 0;if(b.imgLen=f.length,!(f.length>0))return layer.msg("没有任何图片",2,8),void 0;b.thissrc=f[d.start].src,b.pid=f[d.start].pid,b.imgsname=d.title||"",b.name=f[d.start].name,b.imgIndex=d.start+1}else g=$(e.parent).find("img"),h=g.eq(e.start),b.thissrc=h.attr("layer-img")||h.attr("src"),b.pid=h.attr("pid"),b.imgLen=g.length,b.imgsname=e.title||"",b.name=h.attr("alt"),b.imgIndex=e.start+1;return i={type:1,border:[0],area:[(a.html?915:600)+"px","auto"],title:!1,shade:[.9,"#000",!0],shadeClose:!0,offset:["25px",""],bgcolor:"",page:{html:'
'+(b.name||
'+function(){return b.imgLen>1?'':""}()+'
'+b.imgsname+" "+b.imgIndex+"/"+b.imgLen+"
"+function(){return a.html?'
'+a.html+"
":""}()},success:function(a){b.bigimg=a.find(".xubox_bigimg"),b.imgsee=b.bigimg.find(".xubox_imgsee"),b.imgbar=b.imgsee.find(".xubox_imgbar"),b.imgtit=b.imgbar.find(".xubox_imgtit"),b.layero=a;var c=b.imgs=b.bigimg.find("img");clearTimeout(b.timerr),b.timerr=setTimeout(function(){$("html").css("overflow","hidden").attr("layer-full",b.index)},10),c.load(function(){b.imgarea=[c.outerWidth(),c.outerHeight()],b.resize(a)}),b.event()},end:function(){layer.closeAll(),b.end=!0}},b.event=function(){b.bigimg.hover(function(){b.imgsee.show()},function(){b.imgsee.hide()}),i.imgprev=function(){b.imgIndex--,b.imgIndex<1&&(b.imgIndex=b.imgLen),b.tabimg()},b.bigimg.find(".xubox_prev").on("click",function(a){a.preventDefault(),i.imgprev()}),i.imgnext=function(){b.imgIndex++,b.imgIndex>b.imgLen&&(b.imgIndex=1),b.tabimg()},b.bigimg.find(".xubox_next").on("click",function(a){a.preventDefault(),i.imgnext()}),$(document).keyup(function(a){if(!b.end){var c=a.keyCode;a.preventDefault(),37===c?i.imgprev():39===c?i.imgnext():27===c&&layer.close(b.index)}}),b.tabimg=function(){var e,h,i,j,k;b.imgs.removeAttr("style"),d?(j=f[b.imgIndex-1],e=j.src,h=j.pid,i=j.name):(k=g.eq(b.imgIndex-1),e=k.attr("layer-img")||k.attr("src"),h=k.attr("layer-pid")||"",i=k.attr("alt")||""),b.imgs.attr({src:e,"layer-pid":h,alt:i}),b.imgtit.find("em").text(b.imgIndex+"/"+b.imgLen),b.imgsee.show(),a.tab&&a.tab({pid:h,name:i})}},b.resize=function(d){var g,e={},f=[c.width(),c.height()];e.limit=f[0]-f[0]/f[1]*(60*f[0]/f[1]),e.limit<600&&(e.limit=600),g=[e.limit,f[1]>400?f[1]-50:400],g[0]=a.html?g[0]:g[0]-300,layer.area(b.index,{width:g[0]+(a.html?15:0),height:g[1]}),e.flwidth=g[0]-(a.html?300:0),b.imgarea[0]>e.flwidth?b.imgs.css({width:e.flwidth}):b.imgs.css({width:b.imgarea[0]}),b.imgs.outerHeight()',h=['
'+f+''+e.msg+"
",'
'+a+"
",'','','
'+d.tips.msg+'
'],i="",j="",k=d.zIndex+c,l="z-index:"+k+"; background-color:"+d.shade[1]+"; opacity:"+d.shade[0]+"; filter:alpha(opacity="+100*d.shade[0]+");",d.shade[0]&&(i='
'),d.zIndex=k,m="",n="",o="z-index:"+(k-1)+"; background-color: "+d.border[2]+"; opacity:"+d.border[1]+"; filter:alpha(opacity="+100*d.border[1]+"); top:-"+d.border[0]+"px; left:-"+d.border[0]+"px;",d.border[0]&&(j='
'),!d.maxmin||1!==d.type&&2!==d.type||/^\d+%$/.test(d.area[0])&&/^\d+%$/.test(d.area[1])||(n=''),d.closeBtn[1]&&(n+=''),p="object"==typeof d.title,d.title&&(m='
'+(p?d.title[0]:d.title)+"
"),[i,'
'+'
'+h[d.type]+m+''+n+""+''+"
"+j+"
"]},h.pt.creat=function(){var k,l,m,a=this,b="",c=a.config,e=c.dialog,f=a.index,h=c.page,i=d("body"),j=function(c){var c=c||"";b=a.space(c),i.append(d(b[0]))};switch(c.type){case 0:c.title||(c.area=["auto","auto"]),d(".xubox_dialog")[0]&&layer.close(d(".xubox_dialog").parents("."+g[0]).attr("times"));break;case 1:if(""!==h.html)j('
'+h.html+"
"),i.append(d(b[1]));else if(""!==h.url)j('
'+h.html+"
"),i.append(d(b[1])),d.get(h.url,function(a){d("#xuboxPageHtml"+f).html(a.toString()),h.ok&&h.ok(a)});else{if(0!=d(h.dom).parents(g[4]).length)return;j(),d(h.dom).show().wrap(d(b[1]))}break;case 3:c.title=!1,c.area=["auto","auto"],c.closeBtn=["",!1],d(".xubox_loading")[0]&&layer.closeLoad();break;case 4:c.title=!1,c.area=["auto","auto"],c.fix=!1,c.border=[0],c.tips.more||layer.closeTips()}if(1!==c.type&&(j(),i.append(d(b[1]))),k=a.layerE=d("#"+g[0]+f),k.css({width:c.area[0],height:c.area[1]}),c.fix||k.css({position:"absolute"}),c.title&&(3!==c.type||4!==c.type))switch(l=0===c.type?e:c,m=k.find(".xubox_botton"),l.btn=c.btn||e.btn,l.btns){case 0:m.html("").hide();break;case 1:m.html(''+l.btn[0]+"");break;case 2:m.html(''+l.btn[0]+""+''+l.btn[1]+"")}"auto"===k.css("left")?(k.hide(),setTimeout(function(){k.show(),a.set(f)},500)):a.set(f),c.time<=0||a.autoclose(),a.callback()},f.fade=function(a,b,c){a.css({opacity:0}).animate({opacity:c},b)},h.pt.offset=function(){var a=this,b=a.config,c=a.layerE,d=c.outerHeight();a.offsetTop=""===b.offset[0]&&dc.maxWidth&&k.width(c.maxWidth),q.tipColor=c.tips.style[1],o[0]=k.outerWidth(),q.autoLeft=function(){q.left+o[0]-e.width()>0?(q.tipLeft=q.left+q.width-o[0],r.css({right:12,left:"auto"})):q.tipLeft=q.left},q.where=[function(){q.autoLeft(),q.tipTop=q.top-o[1]-10,r.removeClass("layerTipsB").addClass("layerTipsT").css({"border-right-color":q.tipColor})},function(){q.tipLeft=q.left+q.width+10,q.tipTop=q.top,r.removeClass("layerTipsL").addClass("layerTipsR").css({"border-bottom-color":q.tipColor})},function(){q.autoLeft(),q.tipTop=q.top+q.height+10,r.removeClass("layerTipsT").addClass("layerTipsB").css({"border-right-color":q.tipColor})},function(){q.tipLeft=q.left-o[0]+10,q.tipTop=q.top,r.removeClass("layerTipsR").addClass("layerTipsL").css({"border-bottom-color":q.tipColor})}],q.where[c.tips.guide](),0===c.tips.guide?q.top-(e.scrollTop()+o[1]+16)<0&&q.where[2]():1===c.tips.guide?e.width()-(q.left+q.width+o[0]+16)>0||q.where[3]():2===c.tips.guide?q.top-e.scrollTop()+q.height+o[1]+16-e.height()>0&&q.where[0]():3===c.tips.guide?o[0]+16-q.left>0&&q.where[1]():4===c.tips.guide,k.css({left:q.tipLeft,top:q.tipTop})}c.fadeIn&&(f.fade(k,c.fadeIn,1),f.fade(d("#xubox_shade"+a),c.fadeIn,c.shade[0])),c.fix&&""===c.offset[0]&&!c.shift&&e.on("resize",function(){k.css({top:(e.height()-k.outerHeight())/2})}),b.move()},h.pt.shift=function(a,b,c){var k,d=this,f=d.config,g=d.layerE,h=0,i=e.width(),j=e.height()+(f.fix?0:e.scrollTop());switch(h="50%"==f.offset[1]||""==f.offset[1]?g.outerWidth()/2:g.outerWidth(),k={t:{top:d.offsetTop},b:{top:j-g.outerHeight()-f.border[0]},cl:h+f.border[0],ct:-g.outerHeight(),cr:i-h-f.border[0]},a){case"left-top":g.css({left:k.cl,top:k.ct}).animate(k.t,b);break;case"top":g.css({top:k.ct}).animate(k.t,b);break;case"right-top":g.css({left:k.cr,top:k.ct}).animate(k.t,b);break;case"right-bottom":g.css({left:k.cr,top:j}).animate(c?k.t:k.b,b);break;case"bottom":g.css({top:j}).animate(c?k.t:k.b,b);break;case"left-bottom":g.css({left:k.cl,top:j}).animate(c?k.t:k.b,b);break;case"left":g.css({left:-g.outerWidth()}).animate({left:d.offsetLeft},b)}},h.pt.autoArea=function(a){var c,e,f,h,i,k,j,l,m,n,o,b=this;switch(a=a||b.index,c=b.config,e=c.page,f=d("#"+g[0]+a),h=f.find(g[2]),i=f.find(g[5]),j=c.title?h.innerHeight():0,l=0,"auto"===c.area[0]&&i.outerWidth()>=c.maxWidth&&f.css({width:c.maxWidth}),c.type){case 0:m=f.find(".xubox_botton>a"),k=f.find(g[3]).outerHeight()+20,m.length>0&&(l=m.outerHeight()+20);break;case 1:n=f.find(g[4]),k=d(e.dom).outerHeight(),"auto"===c.area[0]&&f.css({width:n.outerWidth()}),(""!==e.html||""!==e.url)&&(k=n.outerHeight());break;case 2:f.find("iframe").css({width:f.outerWidth(),height:f.outerHeight()-(c.title?h.innerHeight():0)});break;case 3:o=f.find(".xubox_loading"),k=o.outerHeight(),i.css({width:o.width()})}"auto"===c.area[1]&&i.css({height:j+k+l}),d("#xubox_border"+a).css({width:f.outerWidth()+2*c.border[0],height:f.outerHeight()+2*c.border[0]}),layer.ie6&&"auto"!==c.area[0]&&i.css({width:f.outerWidth()}),"50%"!==c.offset[1]&&""!=c.offset[1]||4===c.type?f.css({marginLeft:0}):f.css({marginLeft:-f.outerWidth()/2})},h.pt.move=function(){var a=this,b=a.config,c={setY:0,moveLayer:function(){var a;a=0==parseInt(c.layerE.css("margin-left"))?parseInt(c.move.css("left")):parseInt(c.move.css("left"))+-parseInt(c.layerE.css("margin-left")),"fixed"!==c.layerE.css("position")&&(a-=c.layerE.parent().offset().left,c.setY=0),c.layerE.css({left:a,top:parseInt(c.move.css("top"))-c.setY})}},f=a.layerE.find(b.move);b.move&&f.attr("move","ok"),b.move?f.css({cursor:"move"}):f.css({cursor:"auto"}),d(b.move).on("mousedown",function(a){if(a.preventDefault(),"ok"===d(this).attr("move")){c.ismove=!0,c.layerE=d(this).parents("."+g[0]);var f=c.layerE.offset().left,h=c.layerE.offset().top,i=c.layerE.width()-6,j=c.layerE.height()-6;d("#xubox_moves")[0]||d("body").append('
'),c.move=d("#xubox_moves"),b.moveType&&c.move.css({opacity:0}),c.moveX=a.pageX-c.move.position().left,c.moveY=a.pageY-c.move.position().top,"fixed"!==c.layerE.css("position")||(c.setY=e.scrollTop())}}),d(document).mousemove(function(a){var d,f,g,h;c.ismove&&(d=a.pageX-c.moveX,f=a.pageY-c.moveY,a.preventDefault(),b.moveOut||(c.setY=e.scrollTop(),g=e.width()-c.move.outerWidth()-b.border[0],h=b.border[0]+c.setY,dg&&(d=g),h>f&&(f=h),f>e.height()-c.move.outerHeight()-b.border[0]+c.setY&&(f=e.height()-c.move.outerHeight()-b.border[0]+c.setY)),c.move.css({left:d,top:f}),b.moveType&&c.moveLayer(),d=null,f=null,g=null,h=null)}).mouseup(function(){try{c.ismove&&(c.moveLayer(),c.move.remove()),c.ismove=!1}catch(a){c.ismove=!1}b.moveEnd&&b.moveEnd()})},h.pt.autoclose=function(){var a=this,b=a.config.time,c=function(){b--,0===b&&(layer.close(a.index),clearInterval(a.autotime))};a.autotime=setInterval(c,1e3)},f.config={end:{}},h.pt.callback=function(){var a=this,b=a.layerE,c=a.config,e=c.dialog;a.openLayer(),a.config.success(b),layer.ie6&&a.IE6(b),b.find(".xubox_close").on("click",function(){c.close(a.index),layer.close(a.index)}),b.find(".xubox_yes").on("click",function(){c.yes?c.yes(a.index):e.yes(a.index)}),b.find(".xubox_no").on("click",function(){c.no?c.no(a.index):e.no(a.index),layer.close(a.index)}),a.config.shadeClose&&d("#xubox_shade"+a.index).on("click",function(){layer.close(a.index)}),b.find(".xubox_min").on("click",function(){layer.min(a.index,c),c.min&&c.min(b)}),b.find(".xubox_max").on("click",function(){d(this).hasClass("xubox_maxmin")?(layer.restore(a.index),c.restore&&c.restore(b)):(layer.full(a.index,c),c.full&&c.full(b))}),f.config.end[a.index]=c.end},f.reselect=function(){d.each(d("select"),function(){var c=d(this);c.parents("."+g[0])[0]||1==c.attr("layer")&&d("."+g[0]).length<1&&c.removeAttr("layer").show(),c=null})},h.pt.IE6=function(a){var f,b=this,c=a.offset().top;f=b.config.fix?function(){a.css({top:e.scrollTop()+c})}:function(){a.css({top:c})},f(),e.scroll(f),d.each(d("select"),function(){var c=d(this);c.parents("."+g[0])[0]||"none"==c.css("display")||c.attr({layer:"1"}).hide(),c=null})},h.pt.openLayer=function(){var a=this;a.layerE,layer.autoArea=function(b){return a.autoArea(b)},layer.shift=function(b,c,d){a.shift(b,c,d)},layer.setMove=function(){return a.move()},layer.zIndex=a.config.zIndex,layer.setTop=function(a){var b=function(){layer.zIndex++,a.css("z-index",layer.zIndex+1)};return layer.zIndex=parseInt(a[0].style.zIndex),a.on("mousedown",b),layer.zIndex}},f.isauto=function(a,b,c){"auto"===b.area[0]&&(b.area[0]=a.outerWidth()),"auto"===b.area[1]&&(b.area[1]=a.outerHeight()),a.attr({area:b.area+","+c}),a.find(".xubox_max").addClass("xubox_maxmin")},f.rescollbar=function(a){g.html.attr("layer-full")==a&&(g.html[0].style.removeProperty?g.html[0].style.removeProperty("overflow"):g.html[0].style.removeAttribute("overflow"),g.html.removeAttr("layer-full"))},layer.getIndex=function(a){return d(a).parents("."+g[0]).attr("times")},layer.getChildFrame=function(a,b){return b=b||d("."+g[1]).parents("."+g[0]).attr("times"),d("#"+g[0]+b).find("."+g[1]).contents().find(a)},layer.getFrameIndex=function(a){return d(a?"#"+a:"."+g[1]).parents("."+g[0]).attr("times")},layer.iframeAuto=function(a){var b,c,e,f,h;a=a||d("."+g[1]).parents("."+g[0]).attr("times"),b=layer.getChildFrame("body",a).outerHeight(),c=d("#"+g[0]+a),e=c.find(g[2]),f=0,e&&(f=e.height()),c.css({height:b+f}),h=-parseInt(d("#xubox_border"+a).css("top")),d("#xubox_border"+a).css({height:b+2*h+f}),d("#"+g[1]+a).css({height:b})},layer.iframeSrc=function(a,b){d("#"+g[0]+a).find("iframe").attr("src",b)},layer.area=function(a,b){var j,c=[d("#"+g[0]+a),d("#xubox_border"+a)],e=c[0].attr("type"),h=c[0].find(g[5]),i=c[0].find(g[2]);(e===f.type[1]||e===f.type[2])&&(c[0].css(b),h.css({width:b.width,height:b.height}),e===f.type[2]&&(j=c[0].find("iframe"),j.css({width:b.width,height:i?b.height-i.innerHeight():b.height})),"0px"!==c[0].css("margin-left")&&(b.hasOwnProperty("top")&&c[0].css({top:b.top-(c[1][0]?parseFloat(c[1].css("top")):0)}),b.hasOwnProperty("left")&&c[0].css({left:b.left+c[0].outerWidth()/2-(c[1][0]?parseFloat(c[1].css("left")):0)}),c[0].css({marginLeft:-c[0].outerWidth()/2})),c[1][0]&&c[1].css({width:parseFloat(b.width)-2*parseFloat(c[1].css("left")),height:parseFloat(b.height)-2*parseFloat(c[1].css("top"))}))},layer.min=function(a,b){var c=d("#"+g[0]+a),e=[c.position().top,c.position().left+parseFloat(c.css("margin-left"))];f.isauto(c,b,e),layer.area(a,{width:180,height:35}),c.find(".xubox_min").hide(),"page"===c.attr("type")&&c.find(g[4]).hide(),f.rescollbar(a)},layer.restore=function(a){var b=d("#"+g[0]+a),c=b.attr("area").split(",");b.attr("type"),layer.area(a,{width:parseFloat(c[0]),height:parseFloat(c[1]),top:parseFloat(c[2]),left:parseFloat(c[3])}),b.find(".xubox_max").removeClass("xubox_maxmin"),b.find(".xubox_min").show(),"page"===b.attr("type")&&b.find(g[4]).show(),f.rescollbar(a)},layer.full=function(a,b){var i,c=d("#"+g[0]+a),h=2*b.border[0]||6,j=[c.position().top,c.position().left+parseFloat(c.css("margin-left"))];f.isauto(c,b,j),g.html.attr("layer-full")||g.html.css("overflow","hidden").attr("layer-full",a),clearTimeout(i),i=setTimeout(function(){layer.area(a,{top:"fixed"===c.css("position")?0:e.scrollTop(),left:"fixed"===c.css("position")?0:e.scrollLeft(),width:e.width()-h,height:e.height()-h})},100)},layer.title=function(a,b){var c=d("#"+g[0]+(b||layer.index)).find(".xubox_title>em");c.html(a)},layer.close=function(a){var h,b=d("#"+g[0]+a),c=b.attr("type"),e=d("#xubox_moves, #xubox_shade"+a);if(b[0]){if(c==f.type[1])if(b.find(".xuboxPageHtml")[0])b[0].innerHTML="",b.remove();else for(b.find(".xubox_setwin,.xubox_close,.xubox_botton,.xubox_title,.xubox_border").remove(),h=0;3>h;h++)b.find(".layer_pageContent").unwrap().hide();else b[0].innerHTML="",b.remove();e.remove(),layer.ie6&&f.reselect(),f.rescollbar(a),"function"==typeof f.config.end[a]&&f.config.end[a](),delete f.config.end[a]}},layer.closeLoad=function(){layer.close(d(".xubox_loading").parents("."+g[0]).attr("times"))},layer.closeTips=function(){layer.closeAll("tips")},layer.closeAll=function(a){d.each(d("."+g[0]),function(){var b=d(this),c=a?b.attr("type")===a:1;c&&layer.close(b.attr("times")),c=null})},f.run=function(){d=jQuery,e=d(a),g.html=d("html"),layer.use("skin/layer.css"),d.layer=function(a){var b=new h(a);return b.index},(new Image).src=layer.path+"skin/default/xubox_ico0.png"},i="../../init/jquery",a.seajs?define([i],function(a,b,c){f.run(),c.exports=layer}):f.run()}(window); -------------------------------------------------------------------------------- /static/layer/skin/default/icon_ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/icon_ext.png -------------------------------------------------------------------------------- /static/layer/skin/default/textbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/textbg.png -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_ico0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_ico0.png -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_loading0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_loading0.gif -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_loading1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_loading1.gif -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_loading2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_loading2.gif -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_loading3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_loading3.gif -------------------------------------------------------------------------------- /static/layer/skin/default/xubox_title0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/layer/skin/default/xubox_title0.png -------------------------------------------------------------------------------- /static/layer/skin/layer.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer's style 4 | @Date: 2012.09.15 5 | @Author: 贤心 6 | @blog: sentsin.com 7 | 8 | **/ 9 | 10 | *html{background-image:url(about:blank); background-attachment:fixed;} 11 | 12 | /** common **/ 13 | .xubox_shade, .xubox_layer{position:fixed; _position:absolute;} 14 | .xubox_shade{top:0; left:0; width:100%; height:100%; _height:expression(document.body.offsetHeight+"px");} 15 | .xubox_layer{top:150px; left:50%; height:auto; width:310px; margin-left:-155px;} 16 | .xubox_border, .xubox_title, .xubox_title i, .xubox_page, .xubox_iframe, .xubox_title em, .xubox_close, .xubox_msgico, .xubox_moves{position:absolute;} 17 | .xubox_border{border-radius: 5px;} 18 | .xubox_title{left:0; top:0;} 19 | .xubox_main{position:relative; height:100%; _float:left;} 20 | .xubox_page{top:0; left:0;} 21 | .xubox_load{background:url(default/xubox_loading0.gif) #fff center center no-repeat;} 22 | .xubox_loading{display:block; float:left; text-decoration:none; color:#FFF; _float:none; } 23 | .xulayer_png32{background:url(default/xubox_ico0.png) no-repeat;} 24 | .xubox_moves{border:3px solid #666; cursor:move; background-color:rgba(255,255,255,.3); background-color:#fff\9; filter:alpha(opacity=50);} 25 | 26 | .xubox_msgico{width:32px; height:32px; top:52px; left:15px; background:url(default/xubox_ico0.png) no-repeat;} 27 | .xubox_text{ padding-left:55px; float:left; line-height:25px; word-break:break-all; padding-right:20px; overflow:hidden; font-size:14px;} 28 | .xubox_msgtype0{background-position:-91px -38px;} 29 | .xubox_msgtype1{background-position:-128px -38px } 30 | .xubox_msgtype2{background-position:-163px -38px;} 31 | .xubox_msgtype3{background-position:-91px -75px;} 32 | .xubox_msgtype4{background-position:-163px -75px;} 33 | .xubox_msgtype5{background-position:-163px -112px;} 34 | .xubox_msgtype6{background-position:-163px -148px;} 35 | .xubox_msgtype7{background-position:-128px -75px;} 36 | .xubox_msgtype8{background-position:-91px -6px;} 37 | .xubox_msgtype9{background-position:-129px -6px;} 38 | .xubox_msgtype10{background-position:-163px -6px;} 39 | .xubox_msgtype11{background-position:-206px -6px;} 40 | .xubox_msgtype12{background-position:-206px -44px;} 41 | .xubox_msgtype13{background-position:-206px -81px;} 42 | .xubox_msgtype14{background-position:-206px -122px;} 43 | .xubox_msgtype15{background-position:-206px -157px;} 44 | .xubox_loading_0{width:60px; height:24px; background:url(default/xubox_loading0.gif) no-repeat;} 45 | .xubox_loading_1{width:37px; height:37px; background:url(default/xubox_loading1.gif) no-repeat;} 46 | .xubox_loading_2, .xubox_msgtype16{width:32px; height:32px; background:url(default/xubox_loading2.gif) no-repeat;} 47 | .xubox_loading_3{width:126px; height:22px; background:url(default/xubox_loading3.gif) no-repeat;} 48 | 49 | .xubox_setwin{position:absolute; right:10px; *right:0; top:10px; font-size:0;} 50 | .xubox_setwin a{position:relative; display:inline-block; *display:inline; *zoom:1; vertical-align:top; width: 14px; height:14px; margin-left:10px; font-size:12px; _overflow:hidden;} 51 | .xubox_setwin .xubox_min cite{position:absolute; width:14px; height:2px; left:0; top:50%; margin-top:-1px; background-color:#919191; cursor:pointer; _overflow:hidden;} 52 | .xubox_setwin .xubox_min:hover cite{background-color:#2D93CA; } 53 | .xubox_setwin .xubox_max{background-position:-6px -189px;} 54 | .xubox_setwin .xubox_max:hover{background-position:-6px -206px;} 55 | .xubox_setwin .xubox_maxmin{background-position:-29px -189px;} 56 | .xubox_setwin .xubox_maxmin:hover{background-position:-29px -206px;} 57 | .xubox_setwin .xubox_close0{ width:14px; height:14px; background-position: -31px -7px; cursor:pointer;} 58 | .xubox_setwin .xubox_close0:hover{background-position:-51px -7px;} 59 | .xubox_setwin .xubox_close1{position:absolute; right:-28px; top:-28px; width:30px; height:30px; margin-left:0; background-position:-60px -195px; *right:-18px; _right:-15px; _top:-23px; _width:14px; _height:14px; _background-position:-31px -7px;} 60 | .xubox_setwin .xubox_close1:hover{ background-position:-91px -195px; _background-position:-51px -7px;} 61 | 62 | .xubox_title{width:100%; height:35px; line-height:35px; border-bottom:1px solid #D5D5D5; background:url(default/xubox_title0.png) #EBEBEB repeat-x; font-size:14px; color:#333;} 63 | .xubox_title em{height:20px; line-height:20px; width:60%; top:7px; left:10px; font-style:normal; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;} 64 | 65 | .xubox_botton a{position:absolute; bottom:10px; left:50%; background:url(default/xubox_ico0.png) repeat; text-decoration:none; color:#FFF; font-size:14px; text-align:center; font-weight:bold; overflow:hidden; } 66 | .xubox_botton a:hover{text-decoration:none; color:#FFF; } 67 | .xubox_botton .xubox_botton1{ width:79px; height:32px; line-height:32px; margin-left:-39px; background-position:-6px -34px;} 68 | .xubox_botton1:hover{background-position:-6px -72px;} 69 | .xubox_botton .xubox_botton2{margin-left:-76px; width:71px; height:29px; line-height:29px; background-position:-5px -114px;} 70 | .xubox_botton2:hover{ background-position:-5px -146px;} 71 | .xubox_botton .xubox_botton3{width:71px; height:29px; line-height:29px; margin-left:10px; background-position:-81px -114px;} 72 | .xubox_botton3:hover{background-position:-81px -146px;} 73 | .xubox_tips{position:relative; line-height:20px; min-width: 12px; padding:3px 30px 3px 10px; font-size:12px; _float:left; border-radius:3px; box-shadow: 1px 1px 3px rgba(0,0,0,.3);} 74 | .xubox_tips i.layerTipsG{ position:absolute; width:0; height:0; border-width:8px; border-color:transparent; border-style:dashed; *overflow:hidden;} 75 | .xubox_tips i.layerTipsT, .xubox_tips i.layerTipsB{left:5px; border-right-style:solid;} 76 | .xubox_tips i.layerTipsT{bottom:-8px;} 77 | .xubox_tips i.layerTipsB{top:-8px;} 78 | .xubox_tips i.layerTipsR, .xubox_tips i.layerTipsL{top:1px; border-bottom-style:solid;} 79 | .xubox_tips i.layerTipsR{left:-8px;} 80 | .xubox_tips i.layerTipsL{right:-8px;} 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /static/layer/skin/layer.ext.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @Name: layer拓展样式 4 | @Date: 2012.12.13 5 | @Author: 贤心 6 | @blog: sentsin.com 7 | 8 | **/ 9 | 10 | .xubox_iconext{background:url(default/icon_ext.png) no-repeat;} 11 | 12 | /* prompt模式 */ 13 | .xubox_layer .xubox_form{width:240px; height:30px; line-height:30px; padding: 0 5px; border: 1px solid #ccc; background: url(default/textbg.png) #fff repeat-x; color:#333;} 14 | .xubox_layer .xubox_formArea{width:300px; height:100px; line-height:20px;} 15 | 16 | /* tab模式 */ 17 | .xubox_layer .xubox_tab{position:relative; background-color:#fff; box-shadow:1px 1px 50px rgba(0,0,0,.4)} 18 | .xubox_layer .xubox_tabmove{position:absolute; width:600px; height:30px; top:0; left:0;} 19 | .xubox_layer .xubox_tabtit{ display:block; height:34px; border-bottom:1px solid #ccc; background-color:#eee;} 20 | .xubox_layer .xubox_tabtit span{position:relative; float:left; width:120px; height:34px; line-height:34px; text-align:center; cursor:default;} 21 | .xubox_layer .xubox_tabtit span.xubox_tabnow{left:-1px; _top:1px; height:35px; border-left:1px solid #ccc; border-right:1px solid #ccc; background-color:#fff; z-index:10;} 22 | .xubox_layer .xubox_tab_main{line-height:24px; clear:both;} 23 | .xubox_layer .xubox_tab_main .xubox_tabli{display:none;} 24 | .xubox_layer .xubox_tab_main .xubox_tabli.xubox_tab_layer{display:block;} 25 | .xubox_layer .xubox_tabclose{position:absolute; right:10px; top:5px; cursor:pointer;} 26 | 27 | /* photo模式 */ 28 | .xubox_bigimg, .xubox_intro{height:300px} 29 | .xubox_bigimg{position:relative; display:block; width:600px; text-align:center; background:url(default/xubox_loading1.gif) center center no-repeat #000; overflow:hidden; } 30 | .xubox_bigimg img{position:relative; display:inline-block; visibility: hidden;} 31 | .xubox_intro{position:absolute; right:-315px; top:0; width:300px; background-color:#fff; overflow-x:hidden; overflow-y:auto;} 32 | .xubox_imgsee{display:none;} 33 | .xubox_prev, .xubox_next{position:absolute; top:50%; width:27px; _width:44px; height:44px; margin-top:-22px; outline:none;blr:expression(this.onFocus=this.blur());} 34 | .xubox_prev{left:10px; background-position:-5px -5px; _background-position:-70px -5px;} 35 | .xubox_prev:hover{background-position:-33px -5px; _background-position:-120px -5px;} 36 | .xubox_next{right:10px; _right:8px; background-position:-5px -50px; _background-position:-70px -50px;} 37 | .xubox_next:hover{background-position:-33px -50px; _background-position:-120px -50px;} 38 | .xubox_imgbar{position:absolute; left:0; bottom:0; width:100%; height:32px; line-height:32px; background-color:rgba(0,0,0,.8); background-color:#000\9; filter:Alpha(opacity=80); color:#fff; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; font-size:0;} 39 | .xubox_imgtit{/*position:absolute; left:20px;*/} 40 | .xubox_imgtit *{display:inline-block; *display:inline; *zoom:1; vertical-align:top; font-size:12px;} 41 | .xubox_imgtit a{max-width:65%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; color:#fff;} 42 | .xubox_imgtit a:hover{color:#fff; text-decoration:underline;} 43 | .xubox_imgtit em{padding-left:10px;} 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/select2/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/select2/select2-spinner.gif -------------------------------------------------------------------------------- /static/select2/select2.css: -------------------------------------------------------------------------------- 1 | /* 2 | Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 EDT 2014 3 | */ 4 | .select2-container { 5 | margin: 0; 6 | position: relative; 7 | display: inline-block; 8 | /* inline-block for ie7 */ 9 | zoom: 1; 10 | *display: inline; 11 | vertical-align: middle; 12 | } 13 | 14 | .select2-container, 15 | .select2-drop, 16 | .select2-search, 17 | .select2-search input { 18 | /* 19 | Force border-box so that % widths fit the parent 20 | container without overlap because of margin/padding. 21 | More Info : http://www.quirksmode.org/css/box.html 22 | */ 23 | -webkit-box-sizing: border-box; /* webkit */ 24 | -moz-box-sizing: border-box; /* firefox */ 25 | box-sizing: border-box; /* css3 */ 26 | } 27 | 28 | .select2-container .select2-choice { 29 | display: block; 30 | height: 26px; 31 | padding: 0 0 0 8px; 32 | overflow: hidden; 33 | position: relative; 34 | 35 | border: 1px solid #aaa; 36 | white-space: nowrap; 37 | line-height: 26px; 38 | color: #444; 39 | text-decoration: none; 40 | 41 | border-radius: 4px; 42 | 43 | background-clip: padding-box; 44 | 45 | -webkit-touch-callout: none; 46 | -webkit-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | 51 | background-color: #fff; 52 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); 53 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); 54 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); 55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); 56 | background-image: linear-gradient(to top, #eee 0%, #fff 50%); 57 | } 58 | 59 | html[dir="rtl"] .select2-container .select2-choice { 60 | padding: 0 8px 0 0; 61 | } 62 | 63 | .select2-container.select2-drop-above .select2-choice { 64 | border-bottom-color: #aaa; 65 | 66 | border-radius: 0 0 4px 4px; 67 | 68 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); 69 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); 70 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); 71 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); 72 | background-image: linear-gradient(to bottom, #eee 0%, #fff 90%); 73 | } 74 | 75 | .select2-container.select2-allowclear .select2-choice .select2-chosen { 76 | margin-right: 42px; 77 | } 78 | 79 | .select2-container .select2-choice > .select2-chosen { 80 | margin-right: 26px; 81 | display: block; 82 | overflow: hidden; 83 | 84 | white-space: nowrap; 85 | 86 | text-overflow: ellipsis; 87 | float: none; 88 | width: auto; 89 | } 90 | 91 | html[dir="rtl"] .select2-container .select2-choice > .select2-chosen { 92 | margin-left: 26px; 93 | margin-right: 0; 94 | } 95 | 96 | .select2-container .select2-choice abbr { 97 | display: none; 98 | width: 12px; 99 | height: 12px; 100 | position: absolute; 101 | right: 24px; 102 | top: 8px; 103 | 104 | font-size: 1px; 105 | text-decoration: none; 106 | 107 | border: 0; 108 | background: url('select2.png') right top no-repeat; 109 | cursor: pointer; 110 | outline: 0; 111 | } 112 | 113 | .select2-container.select2-allowclear .select2-choice abbr { 114 | display: inline-block; 115 | } 116 | 117 | .select2-container .select2-choice abbr:hover { 118 | background-position: right -11px; 119 | cursor: pointer; 120 | } 121 | 122 | .select2-drop-mask { 123 | border: 0; 124 | margin: 0; 125 | padding: 0; 126 | position: fixed; 127 | left: 0; 128 | top: 0; 129 | min-height: 100%; 130 | min-width: 100%; 131 | height: auto; 132 | width: auto; 133 | opacity: 0; 134 | z-index: 9998; 135 | /* styles required for IE to work */ 136 | background-color: #fff; 137 | filter: alpha(opacity=0); 138 | } 139 | 140 | .select2-drop { 141 | width: 100%; 142 | margin-top: -1px; 143 | position: absolute; 144 | z-index: 9999; 145 | top: 100%; 146 | 147 | background: #fff; 148 | color: #000; 149 | border: 1px solid #aaa; 150 | border-top: 0; 151 | 152 | border-radius: 0 0 4px 4px; 153 | 154 | -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 155 | box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 156 | } 157 | 158 | .select2-drop.select2-drop-above { 159 | margin-top: 1px; 160 | border-top: 1px solid #aaa; 161 | border-bottom: 0; 162 | 163 | border-radius: 4px 4px 0 0; 164 | 165 | -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 166 | box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 167 | } 168 | 169 | .select2-drop-active { 170 | border: 1px solid #5897fb; 171 | border-top: none; 172 | } 173 | 174 | .select2-drop.select2-drop-above.select2-drop-active { 175 | border-top: 1px solid #5897fb; 176 | } 177 | 178 | .select2-drop-auto-width { 179 | border-top: 1px solid #aaa; 180 | width: auto; 181 | } 182 | 183 | .select2-drop-auto-width .select2-search { 184 | padding-top: 4px; 185 | } 186 | 187 | .select2-container .select2-choice .select2-arrow { 188 | display: inline-block; 189 | width: 18px; 190 | height: 100%; 191 | position: absolute; 192 | right: 0; 193 | top: 0; 194 | 195 | border-left: 1px solid #aaa; 196 | border-radius: 0 4px 4px 0; 197 | 198 | background-clip: padding-box; 199 | 200 | background: #ccc; 201 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); 202 | background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); 203 | background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); 204 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); 205 | background-image: linear-gradient(to top, #ccc 0%, #eee 60%); 206 | } 207 | 208 | html[dir="rtl"] .select2-container .select2-choice .select2-arrow { 209 | left: 0; 210 | right: auto; 211 | 212 | border-left: none; 213 | border-right: 1px solid #aaa; 214 | border-radius: 4px 0 0 4px; 215 | } 216 | 217 | .select2-container .select2-choice .select2-arrow b { 218 | display: block; 219 | width: 100%; 220 | height: 100%; 221 | background: url('select2.png') no-repeat 0 1px; 222 | } 223 | 224 | html[dir="rtl"] .select2-container .select2-choice .select2-arrow b { 225 | background-position: 2px 1px; 226 | } 227 | 228 | .select2-search { 229 | display: inline-block; 230 | width: 100%; 231 | min-height: 26px; 232 | margin: 0; 233 | padding-left: 4px; 234 | padding-right: 4px; 235 | 236 | position: relative; 237 | z-index: 10000; 238 | 239 | white-space: nowrap; 240 | } 241 | 242 | .select2-search input { 243 | width: 100%; 244 | height: auto !important; 245 | min-height: 26px; 246 | padding: 4px 20px 4px 5px; 247 | margin: 0; 248 | 249 | outline: 0; 250 | font-family: sans-serif; 251 | font-size: 1em; 252 | 253 | border: 1px solid #aaa; 254 | border-radius: 0; 255 | 256 | -webkit-box-shadow: none; 257 | box-shadow: none; 258 | 259 | background: #fff url('select2.png') no-repeat 100% -22px; 260 | background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 261 | background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 262 | background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 263 | background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 264 | } 265 | 266 | html[dir="rtl"] .select2-search input { 267 | padding: 4px 5px 4px 20px; 268 | 269 | background: #fff url('select2.png') no-repeat -37px -22px; 270 | background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 271 | background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 272 | background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 273 | background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 274 | } 275 | 276 | .select2-drop.select2-drop-above .select2-search input { 277 | margin-top: 4px; 278 | } 279 | 280 | .select2-search input.select2-active { 281 | background: #fff url('select2-spinner.gif') no-repeat 100%; 282 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 283 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 284 | background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 285 | background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 286 | } 287 | 288 | .select2-container-active .select2-choice, 289 | .select2-container-active .select2-choices { 290 | border: 1px solid #5897fb; 291 | outline: none; 292 | 293 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 294 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 295 | } 296 | 297 | .select2-dropdown-open .select2-choice { 298 | border-bottom-color: transparent; 299 | -webkit-box-shadow: 0 1px 0 #fff inset; 300 | box-shadow: 0 1px 0 #fff inset; 301 | 302 | border-bottom-left-radius: 0; 303 | border-bottom-right-radius: 0; 304 | 305 | background-color: #eee; 306 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); 307 | background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); 308 | background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); 309 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 310 | background-image: linear-gradient(to top, #fff 0%, #eee 50%); 311 | } 312 | 313 | .select2-dropdown-open.select2-drop-above .select2-choice, 314 | .select2-dropdown-open.select2-drop-above .select2-choices { 315 | border: 1px solid #5897fb; 316 | border-top-color: transparent; 317 | 318 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); 319 | background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); 320 | background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); 321 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 322 | background-image: linear-gradient(to bottom, #fff 0%, #eee 50%); 323 | } 324 | 325 | .select2-dropdown-open .select2-choice .select2-arrow { 326 | background: transparent; 327 | border-left: none; 328 | filter: none; 329 | } 330 | html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow { 331 | border-right: none; 332 | } 333 | 334 | .select2-dropdown-open .select2-choice .select2-arrow b { 335 | background-position: -18px 1px; 336 | } 337 | 338 | html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b { 339 | background-position: -16px 1px; 340 | } 341 | 342 | .select2-hidden-accessible { 343 | border: 0; 344 | clip: rect(0 0 0 0); 345 | height: 1px; 346 | margin: -1px; 347 | overflow: hidden; 348 | padding: 0; 349 | position: absolute; 350 | width: 1px; 351 | } 352 | 353 | /* results */ 354 | .select2-results { 355 | max-height: 200px; 356 | padding: 0 0 0 4px; 357 | margin: 4px 4px 4px 0; 358 | position: relative; 359 | overflow-x: hidden; 360 | overflow-y: auto; 361 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 362 | } 363 | 364 | html[dir="rtl"] .select2-results { 365 | padding: 0 4px 0 0; 366 | margin: 4px 0 4px 4px; 367 | } 368 | 369 | .select2-results ul.select2-result-sub { 370 | margin: 0; 371 | padding-left: 0; 372 | } 373 | 374 | .select2-results li { 375 | list-style: none; 376 | display: list-item; 377 | background-image: none; 378 | } 379 | 380 | .select2-results li.select2-result-with-children > .select2-result-label { 381 | font-weight: bold; 382 | } 383 | 384 | .select2-results .select2-result-label { 385 | padding: 3px 7px 4px; 386 | margin: 0; 387 | cursor: pointer; 388 | 389 | min-height: 1em; 390 | 391 | -webkit-touch-callout: none; 392 | -webkit-user-select: none; 393 | -moz-user-select: none; 394 | -ms-user-select: none; 395 | user-select: none; 396 | } 397 | 398 | .select2-results-dept-1 .select2-result-label { padding-left: 20px } 399 | .select2-results-dept-2 .select2-result-label { padding-left: 40px } 400 | .select2-results-dept-3 .select2-result-label { padding-left: 60px } 401 | .select2-results-dept-4 .select2-result-label { padding-left: 80px } 402 | .select2-results-dept-5 .select2-result-label { padding-left: 100px } 403 | .select2-results-dept-6 .select2-result-label { padding-left: 110px } 404 | .select2-results-dept-7 .select2-result-label { padding-left: 120px } 405 | 406 | .select2-results .select2-highlighted { 407 | background: #3875d7; 408 | color: #fff; 409 | } 410 | 411 | .select2-results li em { 412 | background: #feffde; 413 | font-style: normal; 414 | } 415 | 416 | .select2-results .select2-highlighted em { 417 | background: transparent; 418 | } 419 | 420 | .select2-results .select2-highlighted ul { 421 | background: #fff; 422 | color: #000; 423 | } 424 | 425 | 426 | .select2-results .select2-no-results, 427 | .select2-results .select2-searching, 428 | .select2-results .select2-selection-limit { 429 | background: #f4f4f4; 430 | display: list-item; 431 | padding-left: 5px; 432 | } 433 | 434 | /* 435 | disabled look for disabled choices in the results dropdown 436 | */ 437 | .select2-results .select2-disabled.select2-highlighted { 438 | color: #666; 439 | background: #f4f4f4; 440 | display: list-item; 441 | cursor: default; 442 | } 443 | .select2-results .select2-disabled { 444 | background: #f4f4f4; 445 | display: list-item; 446 | cursor: default; 447 | } 448 | 449 | .select2-results .select2-selected { 450 | display: none; 451 | } 452 | 453 | .select2-more-results.select2-active { 454 | background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; 455 | } 456 | 457 | .select2-more-results { 458 | background: #f4f4f4; 459 | display: list-item; 460 | } 461 | 462 | /* disabled styles */ 463 | 464 | .select2-container.select2-container-disabled .select2-choice { 465 | background-color: #f4f4f4; 466 | background-image: none; 467 | border: 1px solid #ddd; 468 | cursor: default; 469 | } 470 | 471 | .select2-container.select2-container-disabled .select2-choice .select2-arrow { 472 | background-color: #f4f4f4; 473 | background-image: none; 474 | border-left: 0; 475 | } 476 | 477 | .select2-container.select2-container-disabled .select2-choice abbr { 478 | display: none; 479 | } 480 | 481 | 482 | /* multiselect */ 483 | 484 | .select2-container-multi .select2-choices { 485 | height: auto !important; 486 | height: 1%; 487 | margin: 0; 488 | padding: 0 5px 0 0; 489 | position: relative; 490 | 491 | border: 1px solid #aaa; 492 | cursor: text; 493 | overflow: hidden; 494 | 495 | background-color: #fff; 496 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); 497 | background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); 498 | background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); 499 | background-image: linear-gradient(to bottom, #eee 1%, #fff 15%); 500 | } 501 | 502 | html[dir="rtl"] .select2-container-multi .select2-choices { 503 | padding: 0 0 0 5px; 504 | } 505 | 506 | .select2-locked { 507 | padding: 3px 5px 3px 5px !important; 508 | } 509 | 510 | .select2-container-multi .select2-choices { 511 | min-height: 26px; 512 | } 513 | 514 | .select2-container-multi.select2-container-active .select2-choices { 515 | border: 1px solid #5897fb; 516 | outline: none; 517 | 518 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 519 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 520 | } 521 | .select2-container-multi .select2-choices li { 522 | float: left; 523 | list-style: none; 524 | } 525 | html[dir="rtl"] .select2-container-multi .select2-choices li 526 | { 527 | float: right; 528 | } 529 | .select2-container-multi .select2-choices .select2-search-field { 530 | margin: 0; 531 | padding: 0; 532 | white-space: nowrap; 533 | } 534 | 535 | .select2-container-multi .select2-choices .select2-search-field input { 536 | padding: 5px; 537 | margin: 1px 0; 538 | 539 | font-family: sans-serif; 540 | font-size: 100%; 541 | color: #666; 542 | outline: 0; 543 | border: 0; 544 | -webkit-box-shadow: none; 545 | box-shadow: none; 546 | background: transparent !important; 547 | } 548 | 549 | .select2-container-multi .select2-choices .select2-search-field input.select2-active { 550 | background: #fff url('select2-spinner.gif') no-repeat 100% !important; 551 | } 552 | 553 | .select2-default { 554 | color: #999 !important; 555 | } 556 | 557 | .select2-container-multi .select2-choices .select2-search-choice { 558 | padding: 3px 5px 3px 18px; 559 | margin: 3px 0 3px 5px; 560 | position: relative; 561 | 562 | line-height: 13px; 563 | color: #333; 564 | cursor: default; 565 | border: 1px solid #aaaaaa; 566 | 567 | border-radius: 3px; 568 | 569 | -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 570 | box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 571 | 572 | background-clip: padding-box; 573 | 574 | -webkit-touch-callout: none; 575 | -webkit-user-select: none; 576 | -moz-user-select: none; 577 | -ms-user-select: none; 578 | user-select: none; 579 | 580 | background-color: #e4e4e4; 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); 582 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); 583 | background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 584 | background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 585 | background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 586 | } 587 | html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice 588 | { 589 | margin: 3px 5px 3px 0; 590 | padding: 3px 18px 3px 5px; 591 | } 592 | .select2-container-multi .select2-choices .select2-search-choice .select2-chosen { 593 | cursor: default; 594 | } 595 | .select2-container-multi .select2-choices .select2-search-choice-focus { 596 | background: #d4d4d4; 597 | } 598 | 599 | .select2-search-choice-close { 600 | display: block; 601 | width: 12px; 602 | height: 13px; 603 | position: absolute; 604 | right: 3px; 605 | top: 4px; 606 | 607 | font-size: 1px; 608 | outline: none; 609 | background: url('select2.png') right top no-repeat; 610 | } 611 | html[dir="rtl"] .select2-search-choice-close { 612 | right: auto; 613 | left: 3px; 614 | } 615 | 616 | .select2-container-multi .select2-search-choice-close { 617 | left: 3px; 618 | } 619 | 620 | html[dir="rtl"] .select2-container-multi .select2-search-choice-close { 621 | left: auto; 622 | right: 2px; 623 | } 624 | 625 | .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { 626 | background-position: right -11px; 627 | } 628 | .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { 629 | background-position: right -11px; 630 | } 631 | 632 | /* disabled styles */ 633 | .select2-container-multi.select2-container-disabled .select2-choices { 634 | background-color: #f4f4f4; 635 | background-image: none; 636 | border: 1px solid #ddd; 637 | cursor: default; 638 | } 639 | 640 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { 641 | padding: 3px 5px 3px 5px; 642 | border: 1px solid #ddd; 643 | background-image: none; 644 | background-color: #f4f4f4; 645 | } 646 | 647 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; 648 | background: none; 649 | } 650 | /* end multiselect */ 651 | 652 | 653 | .select2-result-selectable .select2-match, 654 | .select2-result-unselectable .select2-match { 655 | text-decoration: underline; 656 | } 657 | 658 | .select2-offscreen, .select2-offscreen:focus { 659 | clip: rect(0 0 0 0) !important; 660 | width: 1px !important; 661 | height: 1px !important; 662 | border: 0 !important; 663 | margin: 0 !important; 664 | padding: 0 !important; 665 | overflow: hidden !important; 666 | position: absolute !important; 667 | outline: 0 !important; 668 | left: 0px !important; 669 | top: 0px !important; 670 | } 671 | 672 | .select2-display-none { 673 | display: none; 674 | } 675 | 676 | .select2-measure-scrollbar { 677 | position: absolute; 678 | top: -10000px; 679 | left: -10000px; 680 | width: 100px; 681 | height: 100px; 682 | overflow: scroll; 683 | } 684 | 685 | /* Retina-ize icons */ 686 | 687 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) { 688 | .select2-search input, 689 | .select2-search-choice-close, 690 | .select2-container .select2-choice abbr, 691 | .select2-container .select2-choice .select2-arrow b { 692 | background-image: url('select2x2.png') !important; 693 | background-repeat: no-repeat !important; 694 | background-size: 60px 40px !important; 695 | } 696 | 697 | .select2-search input { 698 | background-position: 100% -21px !important; 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /static/select2/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/select2/select2.png -------------------------------------------------------------------------------- /static/select2/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-falcon-archive/fe/96a683823d95f6d8a98873f86475f9d4620d288d/static/select2/select2x2.png -------------------------------------------------------------------------------- /utils/ldap.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/toolkits/ldap" 6 | ) 7 | 8 | func LdapBind(addr, 9 | BaseDN, 10 | BindDN, 11 | BindPasswd, 12 | UserField, 13 | user, 14 | password string) (sucess bool, err error) { 15 | 16 | filter := "(" + UserField + "=" + user + ")" 17 | conn, err := ldap.Dial("tcp", addr) 18 | 19 | if err != nil { 20 | return false, fmt.Errorf("dial ldap fail: %s", err.Error()) 21 | } 22 | 23 | defer conn.Close() 24 | if BindDN != "" { 25 | err = conn.Bind(BindDN, BindPasswd) 26 | } 27 | if err != nil { 28 | return false, fmt.Errorf("ldap Bind fail: %s", err.Error()) 29 | } 30 | search := ldap.NewSearchRequest( 31 | BaseDN, 32 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, 33 | filter, 34 | nil, 35 | nil) 36 | 37 | sr, err := conn.Search(search) 38 | 39 | if err != nil { 40 | 41 | return false, fmt.Errorf("ldap search fail: %s", err.Error()) 42 | } 43 | 44 | defer func() { 45 | if err := recover(); err != nil { 46 | //fmt.Println("ERROR:", err) 47 | sucess = false 48 | } 49 | }() 50 | err = conn.Bind(sr.Entries[0].DN, password) 51 | return err == nil, err 52 | } 53 | 54 | func Ldapsearch(addr, 55 | BaseDN, 56 | BindDN, 57 | BindPasswd, 58 | UserField, 59 | user string, 60 | Attributes []string) (map[string]string, error) { 61 | 62 | filter := "(" + UserField + "=" + user + ")" 63 | conn, err := ldap.Dial("tcp", addr) 64 | 65 | if err != nil { 66 | return nil, fmt.Errorf("dial ldap fail: %s", err.Error()) 67 | } 68 | 69 | defer conn.Close() 70 | 71 | if BindDN != "" { 72 | err = conn.Bind(BindDN, BindPasswd) 73 | } 74 | if err != nil { 75 | return nil, fmt.Errorf("ldap Bind fail: %s", err.Error()) 76 | } 77 | search := ldap.NewSearchRequest( 78 | BaseDN, 79 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, 80 | filter, 81 | Attributes, 82 | nil) 83 | 84 | sr, err := conn.Search(search) 85 | defer func() { 86 | if err := recover(); err != nil { 87 | fmt.Println("ERROR:", err) 88 | } 89 | }() 90 | if err != nil { 91 | return nil, fmt.Errorf("ldap search fail: %s", err.Error()) 92 | } 93 | var User_Attributes map[string]string 94 | User_Attributes = make(map[string]string) 95 | 96 | userSn := sr.Entries[0].GetAttributeValue(Attributes[0]) 97 | userMail := sr.Entries[0].GetAttributeValue(Attributes[1]) 98 | userTel := sr.Entries[0].GetAttributeValue(Attributes[2]) 99 | 100 | User_Attributes["sn"] = userSn 101 | User_Attributes["telephoneNumber"] = userTel 102 | User_Attributes["mail"] = userMail 103 | return User_Attributes, err 104 | } 105 | -------------------------------------------------------------------------------- /utils/ldap_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | addr = "ldap.example.com:3268" 10 | baseDN = "dc=example,dc=com" 11 | bindDN = "cn=Manager,dc=example,dc=com" 12 | bindPasswd = "12345678" 13 | user = "test" 14 | password = "12345678" 15 | UserField = "uid" 16 | ) 17 | 18 | var Attributes []string = []string{"cn", "mail", "telephoneNumber"} 19 | 20 | func Test_ldap_bind_fe(t *testing.T) { 21 | sucess, err := LdapBind(addr, baseDN, bindDN, bindPasswd, UserField, user, password) 22 | log.Println("sucess:", sucess) 23 | log.Println("err", err) 24 | } 25 | func Test_ldap_search_fe(t *testing.T) { 26 | user_attributes, err := Ldapsearch(addr, baseDN, bindDN, bindPasswd, UserField, user, Attributes) 27 | log.Println("user_attributes", user_attributes) 28 | log.Println("err", err) 29 | } 30 | -------------------------------------------------------------------------------- /utils/regexp.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func IsUsernameValid(name string) bool { 9 | match, err := regexp.Match("^[a-zA-Z0-9\\-\\_\\.]+$", []byte(name)) 10 | if err != nil { 11 | return false 12 | } 13 | 14 | return match 15 | } 16 | 17 | func HasDangerousCharacters(str string) bool { 18 | if strings.Contains(str, "<") { 19 | return true 20 | } 21 | 22 | if strings.Contains(str, ">") { 23 | return true 24 | } 25 | 26 | if strings.Contains(str, "&") { 27 | return true 28 | } 29 | 30 | if strings.Contains(str, "'") { 31 | return true 32 | } 33 | 34 | if strings.Contains(str, "\"") { 35 | return true 36 | } 37 | 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /utils/uuid.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/satori/go.uuid" 5 | "strings" 6 | ) 7 | 8 | func GenerateUUID() string { 9 | sig := uuid.NewV1().String() 10 | sig = strings.Replace(sig, "-", "", -1) 11 | return sig 12 | } 13 | -------------------------------------------------------------------------------- /views/auth/index.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | sso index page 4 | 5 | 如果用户没有登录,跳转到登录页面 6 | 7 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/auth/login.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 13 | 14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |

22 | Sign in 23 |

24 |
25 |
26 |
27 |
28 | 30 |
31 |
32 | 34 |
35 |
36 | 39 |
40 | 42 | 43 | {{if .CanRegister}} 44 |
45 | no account? sign up 46 |
47 | {{end}} 48 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | 59 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/auth/register.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 4 | 14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |

Sign up

23 |
24 |
25 | 26 | {{if .CanRegister}} 27 |
28 |
29 | 31 |
32 |
33 | 35 |
36 |
37 | 40 |
41 | 43 | 44 |
45 | has account already? sign in 46 |
47 |
48 | {{else}} 49 | 系统暂未开放注册,需要联系管理员创建账号:-) 50 | {{end}} 51 | 52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 | 60 | 61 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/home/index.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 | 5 |

OpsPlatform

6 |
7 | 8 |
9 | 10 | 49 | 50 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/layout/foot.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/layout/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpsPlatform 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /views/layout/paginator.html: -------------------------------------------------------------------------------- 1 | {{if gt .paginator.PageNums 1}} 2 |
    3 | {{if .paginator.HasPrev}} 4 |
  • 首页
  • 5 |
  • <
  • 6 | {{else}} 7 |
  • 首页
  • 8 |
  • <
  • 9 | {{end}} 10 | {{range $index, $page := .paginator.Pages}} 11 | 12 | {{$page}} 13 | 14 | {{end}} 15 | {{if .paginator.HasNext}} 16 |
  • >
  • 17 |
  • 尾页
  • 18 | {{else}} 19 |
  • >
  • 21 |
  • 尾页
  • 23 | {{end}} 24 |
25 | {{end}} 26 | -------------------------------------------------------------------------------- /views/team/create.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 4 | 5 | 6 |
7 |
8 |
9 |
10 | 11 | 16 | 17 |
18 |
19 |

Create Team

20 |
21 |
22 |
23 | 25 |
26 |
27 | 29 |
30 |
31 | 33 |
34 | 38 | 39 | 40 | 返回 41 | 42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 | 78 | 79 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/team/edit.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 4 | 5 | 6 |
7 |
8 |
9 |
10 | 11 | 16 | 17 |
18 |
19 |

Edit Team: {{.TargetTeam.Name}}

20 |
21 |
22 |
23 | 25 |
26 |
27 | 29 |
30 | 34 | 35 | 36 | 返回 37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 | 47 | 79 | 80 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/team/list.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 | 24 | 25 |
26 |
27 | 29 |
30 | 34 | 35 | 36 | 37 | Add 38 | 39 |
40 | 41 |
42 | {{range .Teams}} 43 |
44 |
45 | 46 | {{.Name}} 47 | {{.Resume}} 48 | 49 | 50 | 51 | ¦ 52 | 53 | 54 | 55 |
56 |
57 | {{range member .Id}} 58 | {{.Name}} 59 | {{end}} 60 |
61 |
62 |
63 | {{end}} 64 |
65 | 66 | {{template "layout/paginator.html" .}} 67 | 68 |
69 | 70 |
71 |
72 |
73 | 74 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/user/about.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 | 11 |
12 |
13 | 姓名:{{.User.Cnname}}
14 | 手机:{{.User.Phone}}
15 | 邮箱:{{.User.Email}}
16 | QQ:{{.User.QQ}}
17 | IM:{{.User.IM}}
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/user/create.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 |
5 |
6 |
7 | 8 | 13 | 14 |
15 |
16 |

Create User

17 |
18 |
19 |
20 | 22 |
23 |
24 | 26 |
27 |
28 | 30 |
31 |
32 | 34 |
35 |
36 | 38 |
39 |
40 | 42 |
43 |
44 | 46 |
47 | 51 | 52 | 53 | 返回 54 | 55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 | 63 | {{template "layout/foot.html" .}} 64 | -------------------------------------------------------------------------------- /views/user/edit.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |

Profile of {{.User.Name}}

11 |
12 |
13 |
14 | 17 |
18 |
19 | 21 |
22 |
23 | 25 |
26 |
27 | 30 |
31 |
32 | 34 |
35 | 39 | 40 | 41 | 返回 42 | 43 |
44 |
45 | 46 |
47 |
48 |

Reset Password

49 |
50 |
51 |
52 | 54 |
55 | 59 | 60 | 61 | 返回 62 | 63 |
64 |
65 | 66 |
67 |
68 |
69 |
70 | 71 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/user/info.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |

登录成功

11 |
12 |
13 | 14 |
welcome, {{.CurrentUser.Name}} :-)
15 | 16 | 24 | 25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 33 | {{template "layout/foot.html" .}} -------------------------------------------------------------------------------- /views/user/list.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 | 13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 | 24 | 25 |
26 |
27 | 29 |
30 | 34 | 35 | 36 | 37 | Add 38 | 39 |
40 | 41 |
42 | {{range .Users}} 43 |
44 | {{if $.IamRoot}} 45 |
46 | {{if gt .Role 0}} 47 | 48 | {{else}} 49 | 50 | {{end}} 51 | 管理员 52 |
53 | {{end}} 54 | 登录账号:{{.Name}}, 中文名:{{.Cnname}} 55 | 56 | 57 | 58 |
59 | 邮箱: {{.Email}}
60 | 手机: {{.Phone}}
61 | IM:{{.IM}}, QQ:{{.QQ}} 62 | 63 | {{if gt $.Me.Role 0}} 64 |
65 | 66 | 67 | 68 | ¦ 69 | 70 | 71 | 72 |
73 | {{end}} 74 |
75 |
76 | {{end}} 77 |
78 | 79 | {{template "layout/paginator.html" .}} 80 | 81 |
82 | 83 |
84 |
85 |
86 | 87 | {{template "layout/foot.html" .}} 88 | -------------------------------------------------------------------------------- /views/user/profile.html: -------------------------------------------------------------------------------- 1 | {{template "layout/head.html" .}} 2 | 3 |
4 |
5 |
6 |
7 | 8 | 12 | 13 |
14 |
15 |

Profile of {{.CurrentUser.Name}}

16 |
17 |
18 |
19 | 22 |
23 |
24 | 26 |
27 |
28 | 30 |
31 |
32 | 35 |
36 |
37 | 39 |
40 | 44 | 45 | 46 | 返回 47 | 48 |
49 |
50 | 51 |
52 |
53 |

Change Password

54 |
55 |
56 |
57 | 59 |
60 |
61 | 63 |
64 |
65 | 67 |
68 | 72 | 73 | 74 | 返回 75 | 76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 | 84 | {{template "layout/foot.html" .}} --------------------------------------------------------------------------------