├── static
├── img
│ ├── build.png
│ ├── guy.jpg
│ ├── refresh.png
│ └── refresh2.png
├── layer
│ ├── skin
│ │ ├── default
│ │ │ ├── textbg.png
│ │ │ ├── icon_ext.png
│ │ │ ├── xubox_ico0.png
│ │ │ ├── xubox_title0.png
│ │ │ ├── xubox_loading0.gif
│ │ │ ├── xubox_loading1.gif
│ │ │ ├── xubox_loading2.gif
│ │ │ └── xubox_loading3.gif
│ │ ├── layer.ext.css
│ │ └── layer.css
│ ├── extend
│ │ └── layer.ext.js
│ └── layer.min.js
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.svg
├── css
│ ├── g.css
│ └── select2-bootstrap.css
└── js
│ ├── g.js
│ └── bootstrap.min.js
├── tpls
├── dir
│ ├── gunicorn
│ │ ├── gunicorn.conf
│ │ └── control
│ ├── nodejs
│ │ └── control
│ ├── tomcat6
│ │ └── control
│ ├── tomcat7
│ │ └── control
│ └── php
│ │ └── control
├── nodejs.tpl
├── tomcat6.tpl
├── tomcat7.tpl
├── gunicorn.tpl
└── php.tpl
├── models
├── dto.go
├── build.go
├── user.go
└── models.go
├── .gitignore
├── main.go
├── docker_build.sh
├── g
├── sso.go
└── g.go
├── routers
├── router.go
├── api.go
├── page.go
├── base.go
└── index.go
├── conf
└── app.conf.example
├── views
├── history.html
├── progress.html
├── index.html
└── layout.html
├── schema.sql
└── README.md
/static/img/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/img/build.png
--------------------------------------------------------------------------------
/static/img/guy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/img/guy.jpg
--------------------------------------------------------------------------------
/static/img/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/img/refresh.png
--------------------------------------------------------------------------------
/static/img/refresh2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/img/refresh2.png
--------------------------------------------------------------------------------
/static/layer/skin/default/textbg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/textbg.png
--------------------------------------------------------------------------------
/static/layer/skin/default/icon_ext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/icon_ext.png
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_ico0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_ico0.png
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_title0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_title0.png
--------------------------------------------------------------------------------
/tpls/dir/gunicorn/gunicorn.conf:
--------------------------------------------------------------------------------
1 | workers = 4
2 | bind = '0.0.0.0:8080'
3 | limit_request_field_size = 0
4 | limit_request_line = 0
5 |
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_loading0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_loading0.gif
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_loading1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_loading1.gif
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_loading2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_loading2.gif
--------------------------------------------------------------------------------
/static/layer/skin/default/xubox_loading3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/layer/skin/default/xubox_loading3.gif
--------------------------------------------------------------------------------
/models/dto.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type ReturnDto struct {
4 | Msg string `json:"msg"`
5 | Data interface{} `json:"data"`
6 | }
7 |
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dinp/builder/HEAD/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | *.swp
3 | *.swo
4 | *.so
5 | /builder
6 | /paas-builder
7 | /builder.tar.gz
8 | .DS_Store
9 | /_tmp
10 | /t
11 | /_log
12 | /conf/app.conf
13 |
14 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | _ "github.com/dinp/builder/g"
6 | _ "github.com/dinp/builder/routers"
7 | )
8 |
9 | func main() {
10 | beego.Run()
11 | }
12 |
--------------------------------------------------------------------------------
/tpls/nodejs.tpl:
--------------------------------------------------------------------------------
1 | FROM {{.Registry}}/nodejs:base
2 |
3 | WORKDIR {{.AppDir}}
4 |
5 | ADD dir/nodejs/* {{.AppDir}}/
6 | ADD {{.Tarball}} {{.AppDir}}/
7 |
8 | EXPOSE 8080
9 |
10 | RUN chmod +x control
11 | CMD ["./control", "start", "8080"]
12 |
13 |
--------------------------------------------------------------------------------
/tpls/dir/nodejs/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p /opt/logs
4 | touch /opt/logs/std.log
5 |
6 | sed -i -e "s/\$SCRIBE_IP/$SCRIBE_IP/g" /etc/supervisord.d/scribed.conf
7 | sed -i -e "s/\$APP_NAME/$APP_NAME/g" /etc/supervisord.d/scribed.conf
8 |
9 | supervisord -n -c /etc/supervisord.conf
10 |
11 |
--------------------------------------------------------------------------------
/tpls/tomcat6.tpl:
--------------------------------------------------------------------------------
1 | FROM {{.Registry}}/javaweb:super
2 |
3 | RUN mkdir -p /opt/bin
4 | ADD dir/tomcat6/* /opt/bin/
5 |
6 | ADD {{.Tarball}} {{.AppDir}}/
7 |
8 | WORKDIR {{.AppDir}}
9 | RUN unzip -q {{.Tarball}} && rm -rf {{.Tarball}}
10 |
11 | EXPOSE 8080
12 |
13 | RUN chmod +x /opt/bin/control
14 | CMD ["/opt/bin/control", "start", "8080"]
15 |
16 |
--------------------------------------------------------------------------------
/tpls/tomcat7.tpl:
--------------------------------------------------------------------------------
1 | FROM {{.Registry}}/tomcat:7.0.56
2 |
3 | RUN mkdir -p /opt/bin
4 | ADD dir/tomcat7/* /opt/bin/
5 |
6 | ADD {{.Tarball}} {{.AppDir}}/
7 |
8 | WORKDIR {{.AppDir}}
9 | RUN unzip -q {{.Tarball}} && rm -rf {{.Tarball}}
10 |
11 | EXPOSE 8080
12 |
13 | RUN chmod +x /opt/bin/control
14 | CMD ["/opt/bin/control", "start", "8080"]
15 |
16 |
--------------------------------------------------------------------------------
/tpls/dir/gunicorn/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p /opt/logs
4 |
5 | touch /opt/logs/error.log
6 | touch /opt/logs/std.log
7 |
8 | sed -i -e "s/\$SCRIBE_IP/$SCRIBE_IP/g" /etc/supervisord.d/scribed.conf
9 | sed -i -e "s/\$APP_NAME/$APP_NAME/g" /etc/supervisord.d/scribed.conf
10 |
11 | /usr/local/bin/python /usr/local/bin/supervisord -n -c /etc/supervisord.conf
12 |
--------------------------------------------------------------------------------
/tpls/dir/tomcat6/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p /opt/logs
4 |
5 | touch /opt/tomcat/logs/localhost_access_log.txt
6 | touch /opt/tomcat/logs/catalina.out
7 |
8 | sed -i -e "s/\$SCRIBE_IP/$SCRIBE_IP/g" /etc/supervisord.d/scribed.conf
9 | sed -i -e "s/\$APP_NAME/$APP_NAME/g" /etc/supervisord.d/scribed.conf
10 |
11 | supervisord -n -c /etc/supervisord.conf
12 |
--------------------------------------------------------------------------------
/tpls/dir/tomcat7/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p /opt/logs
4 |
5 | touch /opt/tomcat/logs/localhost_access_log.txt
6 | touch /opt/tomcat/logs/catalina.out
7 |
8 | sed -i -e "s/\$SCRIBE_IP/$SCRIBE_IP/g" /etc/supervisord.d/scribed.conf
9 | sed -i -e "s/\$APP_NAME/$APP_NAME/g" /etc/supervisord.d/scribed.conf
10 |
11 | supervisord -n -c /etc/supervisord.conf
12 |
--------------------------------------------------------------------------------
/tpls/dir/php/control:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p /opt/logs
4 |
5 | touch /opt/php/var/log/php-fpm.log
6 | touch /opt/nginx/logs/access.log
7 | touch /opt/nginx/logs/error.log
8 |
9 | sed -i -e "s/\$SCRIBE_IP/$SCRIBE_IP/g" /etc/supervisord.d/scribed.conf
10 | sed -i -e "s/\$APP_NAME/$APP_NAME/g" /etc/supervisord.d/scribed.conf
11 |
12 | supervisord -n -c /etc/supervisord.conf
13 |
--------------------------------------------------------------------------------
/docker_build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | WORKDIR=$1
4 | USERNAME=$2
5 | APPNAME=$3
6 | VERSION=$4
7 | REGISTRY=$5
8 | LOGFILE=$6
9 |
10 | cd $WORKDIR
11 |
12 | cp -r ../../tpls/dir .
13 |
14 | docker build -t $USERNAME/$APPNAME:$VERSION . >> $LOGFILE
15 | docker tag $USERNAME/$APPNAME:$VERSION $REGISTRY/$USERNAME/$APPNAME:$VERSION >> $LOGFILE
16 | docker push $REGISTRY/$USERNAME/$APPNAME:$VERSION >> $LOGFILE
17 |
--------------------------------------------------------------------------------
/tpls/gunicorn.tpl:
--------------------------------------------------------------------------------
1 | FROM {{.Registry}}/python:super
2 |
3 | WORKDIR {{.AppDir}}
4 |
5 | ADD dir/gunicorn/* {{.AppDir}}/
6 | ADD {{.Tarball}} {{.AppDir}}/
7 |
8 | RUN [[ -f requirements.txt ]] && pip install -r requirements.txt || echo "no requirements.txt"
9 | RUN [[ -f pip_requirements.txt ]] && pip install -r pip_requirements.txt || echo "no pip_requirements.txt"
10 |
11 | EXPOSE 8080
12 |
13 | RUN chmod +x control
14 | CMD ["./control", "start", "8080"]
15 |
16 |
--------------------------------------------------------------------------------
/g/sso.go:
--------------------------------------------------------------------------------
1 | package g
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego/httplib"
6 | )
7 |
8 | func GetSig() (sig string, err error) {
9 | uri := fmt.Sprintf("%s/sso/sig", UicInternal)
10 | req := httplib.Get(uri)
11 | sig, err = req.String()
12 | return
13 | }
14 |
15 | func Logout(sig string) error {
16 | uri := fmt.Sprintf("%s/sso/logout/%s", UicInternal, sig)
17 | req := httplib.Get(uri)
18 | req.Param("token", Token)
19 | _, err := req.String()
20 | return err
21 | }
22 |
--------------------------------------------------------------------------------
/routers/router.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | )
6 |
7 | func init() {
8 | beego.Router("/", &MainController{})
9 | beego.Router("/health", &ApiController{}, "get:Health")
10 | beego.Router("/logout", &ApiController{}, "get:Logout")
11 | beego.Router("/build", &MainController{}, "post:Build")
12 | beego.Router("/progress/:id:int", &MainController{}, "get:Progress")
13 | beego.Router("/history", &MainController{}, "get:History;post:DeleteHistory")
14 | beego.Router("/log/:id:int", &MainController{}, "get:Log")
15 | }
16 |
--------------------------------------------------------------------------------
/conf/app.conf.example:
--------------------------------------------------------------------------------
1 | appname = builder
2 | httpport = 7788
3 | runmode = prod
4 |
5 | dbuser = builder
6 | dbpass =
7 | dbhost = 127.0.0.1
8 | dbport = 3306
9 | dbname = builder
10 |
11 | tmpdir = ./_tmp
12 | logdir = ./_log
13 |
14 | # in minute
15 | buildtimeout = 15
16 |
17 | uicinternal = http://10.1.2.3:8081
18 | uicexternal = http://10.1.2.3:8081
19 | registry = 10.1.2.3:5000
20 |
21 | buildscript = ./docker_build.sh
22 | tplmapping = tomcat6=>tpls/tomcat6.tpl, tomcat7=>tpls/tomcat7.tpl, php=>tpls/php.tpl, gunicorn=>tpls/gunicorn.tpl, nodejs=>tpls/nodejs.tpl
23 |
24 | token = same-to-uic-token
25 |
--------------------------------------------------------------------------------
/routers/api.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "github.com/dinp/builder/g"
5 | )
6 |
7 | type ApiController struct {
8 | BaseController
9 | }
10 |
11 | func (this *ApiController) Health() {
12 | this.Ctx.WriteString("ok")
13 | }
14 |
15 | func (this *ApiController) Logout() {
16 | sig := this.Ctx.GetCookie("sig")
17 | if sig == "" {
18 | this.Ctx.WriteString("u'r not login")
19 | return
20 | }
21 |
22 | err := g.Logout(sig)
23 | if err != nil {
24 | this.Ctx.WriteString("logout from uic fail")
25 | return
26 | }
27 |
28 | this.Ctx.SetCookie("sig", "", 0, "/")
29 | this.Redirect("/", 302)
30 | }
31 |
--------------------------------------------------------------------------------
/models/build.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego/orm"
6 | )
7 |
8 | func Builds() orm.QuerySeter {
9 | return orm.NewOrm().QueryTable(new(Build))
10 | }
11 |
12 | func BuildHistory(userId int64) []Build {
13 | var builds []Build
14 | Builds().Filter("UserId", userId).OrderBy("-CreateAt").All(&builds)
15 | return builds
16 | }
17 |
18 | func DeleteHistory(hid, uid int64) error {
19 | exist := Builds().Filter("Id", hid).Filter("UserId", uid).Exist()
20 | if exist {
21 | _, err := Builds().Filter("Id", hid).Delete()
22 | return err
23 | } else {
24 | return fmt.Errorf("no privilege")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tpls/php.tpl:
--------------------------------------------------------------------------------
1 | FROM {{.Registry}}/nginxphp:super
2 |
3 | RUN mkdir -p /opt/t
4 |
5 | ADD {{.Tarball}} /opt/t/
6 |
7 | RUN [[ -f /opt/t/config/nginx.conf ]] && mv /opt/t/config/nginx.conf /opt/nginx/conf/nginx.conf || echo "no nginx.conf"
8 | RUN [[ -f /opt/t/config/php.ini ]] && mv /opt/t/config/php.ini /opt/php/etc/php.ini || echo "no php.ini"
9 | RUN [[ -f /opt/t/config/php-fpm.conf ]] && mv /opt/t/config/php-fpm.conf /opt/php/etc/php-fpm.conf || echo "no php-fpm.conf"
10 |
11 | RUN rm -rf {{.AppDir}}
12 | RUN mv /opt/t/htdocs {{.AppDir}}
13 |
14 | RUN mkdir -p /opt/bin
15 | ADD dir/php/* /opt/bin/
16 |
17 | WORKDIR {{.AppDir}}
18 |
19 | EXPOSE 8080
20 |
21 | RUN chmod +x /opt/bin/control
22 | CMD ["/opt/bin/control", "start", "8080"]
23 |
24 |
--------------------------------------------------------------------------------
/views/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | | App |
5 | Version |
6 | Resume |
7 | Image |
8 | CreateAt |
9 | Operation |
10 |
11 |
12 |
13 | {{range .BuildHistory}}
14 |
15 | | {{.App}} |
16 | {{.Version}} |
17 | {{.Resume}} |
18 | {{.Image}} |
19 | {{dateformat .CreateAt "01-02 15:04"}} |
20 | |
21 |
22 | {{end}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/schema.sql:
--------------------------------------------------------------------------------
1 | USE builder;
2 |
3 | SET NAMES utf8;
4 |
5 | DROP TABLE IF EXISTS build;
6 | CREATE TABLE build (
7 | id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
8 | app VARCHAR(64) NOT NULL DEFAULT '',
9 | version VARCHAR(64) NOT NULL DEFAULT '',
10 | resume VARCHAR(255) NOT NULL DEFAULT '',
11 | base VARCHAR(1024) NOT NULL DEFAULT '',
12 | image VARCHAR(1024) NOT NULL DEFAULT '',
13 | tarball VARCHAR(1024) NOT NULL DEFAULT '',
14 | repo VARCHAR(255) NOT NULL DEFAULT '',
15 | branch VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'branch or tag',
16 | status VARCHAR(255) NOT NULL DEFAULT 'saved in db',
17 | user_id INT UNSIGNED NOT NULL,
18 | user_name VARCHAR(64) NOT NULL,
19 | create_at DATETIME NOT NULL,
20 | KEY idx_build_app(app),
21 | KEY idx_build_user_id(user_id)
22 | )
23 | ENGINE =innodb
24 | DEFAULT CHARSET =utf8
25 | COLLATE =utf8_general_ci;
26 |
27 |
28 |
--------------------------------------------------------------------------------
/static/css/g.css:
--------------------------------------------------------------------------------
1 | body, input, div, span, h1, h2, h3, h4, h5, table, th, td, ul, li, dl,
2 | dt, dd, a {
3 | font-family: 'verdana', 'Microsoft YaHei', 'Consolas',
4 | 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
5 | }
6 |
7 | .cut-line {
8 | margin: 0 5px;
9 | color: #D6D6D6;
10 | }
11 |
12 | .red {
13 | color: red;
14 | }
15 |
16 | .orange {
17 | color: orange;
18 | }
19 |
20 | .green {
21 | color: green;
22 | }
23 |
24 | .blue {
25 | color: blue;
26 | }
27 |
28 | .gray {
29 | color: #999 !important;
30 | }
31 |
32 | .mt0 {
33 | margin-top: 0px;
34 | }
35 |
36 | .mt10 {
37 | margin-top: 10px !important;
38 | }
39 |
40 | .mt20 {
41 | margin-top: 20px !important;
42 | }
43 |
44 | .thin-border {
45 | padding: 10px;
46 | line-height: 1;
47 | border: 1px solid #ddd;
48 | -webkit-border-radius: 4px;
49 | -moz-border-radius: 4px;
50 | border-radius: 4px;
51 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
52 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
53 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
54 | background-color: #fff;
55 | }
56 |
--------------------------------------------------------------------------------
/routers/page.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "fmt"
5 | "github.com/dinp/builder/g"
6 | "github.com/dinp/builder/models"
7 | )
8 |
9 | type PageController struct {
10 | BaseController
11 | }
12 |
13 | func (this *PageController) hasBusinessCookie() bool {
14 | if this.CurrentUser != nil {
15 | return true
16 | }
17 |
18 | sigInCookie := this.Ctx.GetCookie("sig")
19 | if sigInCookie != "" {
20 | u, err := models.GetUser(sigInCookie)
21 | if err != nil || u == nil {
22 | return false
23 | }
24 |
25 | this.CurrentUser = u
26 | return true
27 | }
28 |
29 | return false
30 | }
31 |
32 | func (this *PageController) CheckLogin() {
33 | if this.hasBusinessCookie() {
34 | this.Data["CurrentUser"] = this.CurrentUser
35 | return
36 | }
37 |
38 | sig, err := g.GetSig()
39 | if err != nil {
40 | this.ServeErrJson(fmt.Sprintf("curl get sig fail: %v", err))
41 | return
42 | }
43 |
44 | this.Ctx.SetCookie("sig", sig, 2592000, "/")
45 | this.Redirect(fmt.Sprintf("%s/auth/login?sig=%s&callback=http://%s:%d%s", g.UicExternal, sig, this.Ctx.Input.Host(), this.Ctx.Input.Port(), this.Ctx.Input.Uri()), 302)
46 | }
47 |
--------------------------------------------------------------------------------
/static/js/g.js:
--------------------------------------------------------------------------------
1 | function err_message_quietly(msg, f) {
2 | $.layer({
3 | title : false,
4 | closeBtn : false,
5 | time : 2,
6 | dialog : {
7 | msg : msg
8 | },
9 | end : f
10 | });
11 | }
12 |
13 | function ok_message_quietly(msg, f) {
14 | $.layer({
15 | title : false,
16 | closeBtn : false,
17 | time : 1,
18 | dialog : {
19 | msg : msg,
20 | type : 1
21 | },
22 | end : f
23 | });
24 | }
25 |
26 | function my_confirm(msg, btns, yes_func, no_func) {
27 | $.layer({
28 | shade : [ 0 ],
29 | area : [ 'auto', 'auto' ],
30 | dialog : {
31 | msg : msg,
32 | btns : 2,
33 | type : 4,
34 | btn : btns,
35 | yes : yes_func,
36 | no : no_func
37 | }
38 | });
39 | }
40 |
41 | function handle_quietly(json, f) {
42 | if (json.msg.length > 0) {
43 | err_message_quietly(json.msg);
44 | } else {
45 | ok_message_quietly("successfully:-)", f);
46 | }
47 | }
48 |
49 | // - business function -
50 | function delete_history(hid) {
51 | $.post("/history", {"id": hid}, function(json) {
52 | handle_quietly(json, function() {
53 | location.reload();
54 | });
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/views/progress.html:
--------------------------------------------------------------------------------
1 |
6 | the image will be: {{.Image}}
7 |
15 |
16 |
--------------------------------------------------------------------------------
/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/astaxie/beego/httplib"
7 | "github.com/dinp/builder/g"
8 | )
9 |
10 | type User struct {
11 | Id int64 `json:"id"`
12 | Name string `json:"name"`
13 | Email string `json:"email"`
14 | Im string `json:"im"`
15 | Phone string `json:"phone"`
16 | }
17 |
18 | func GetUser(sig string) (*User, error) {
19 | key := fmt.Sprintf("u:%s", sig)
20 | u := g.Cache.Get(key)
21 | if u != nil {
22 | uobj := u.(User)
23 | return &uobj, nil
24 | }
25 |
26 | uri := fmt.Sprintf("%s/sso/user/%s", g.UicInternal, sig)
27 | req := httplib.Get(uri)
28 | req.Param("token", g.Token)
29 | resp, err := req.Response()
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | if resp.StatusCode != 200 {
35 | return nil, fmt.Errorf("StatusCode: %d", resp.StatusCode)
36 | }
37 |
38 | decoder := json.NewDecoder(resp.Body)
39 | type TmpStruct struct {
40 | User *User `json:"user"`
41 | }
42 | var t TmpStruct
43 | err = decoder.Decode(&t)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | // don't worry cache expired. we just use username which can not modify
49 | g.Cache.Put(key, *t.User, int64(360000))
50 |
51 | return t.User, nil
52 | }
53 |
--------------------------------------------------------------------------------
/routers/base.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "github.com/astaxie/beego"
5 | "github.com/dinp/builder/models"
6 | "github.com/toolkits/web"
7 | "strconv"
8 | )
9 |
10 | type Checker interface {
11 | CheckLogin()
12 | }
13 |
14 | type BaseController struct {
15 | beego.Controller
16 | CurrentUser *models.User
17 | }
18 |
19 | func (this *BaseController) Prepare() {
20 | if app, ok := this.AppController.(Checker); ok {
21 | app.CheckLogin()
22 | }
23 | }
24 |
25 | func (this *BaseController) SetPaginator(per int, nums int64) *web.Paginator {
26 | p := web.NewPaginator(this.Ctx.Request, per, nums)
27 | this.Data["paginator"] = p
28 | return p
29 | }
30 |
31 | func (this *BaseController) GetIntWithDefault(paramKey string, defaultVal int) int {
32 | valStr := this.GetString(paramKey)
33 | var val int
34 | if valStr == "" {
35 | val = defaultVal
36 | } else {
37 | var err error
38 | val, err = strconv.Atoi(valStr)
39 | if err != nil {
40 | val = defaultVal
41 | }
42 | }
43 | return val
44 | }
45 |
46 | func (this *BaseController) ServeErrJson(msg string) {
47 | this.Data["json"] = &models.ReturnDto{Msg: msg}
48 | this.ServeJson()
49 | }
50 |
51 | func (this *BaseController) ServeOKJson() {
52 | this.Data["json"] = &models.ReturnDto{Msg: ""}
53 | this.ServeJson()
54 | }
55 |
56 | func (this *BaseController) ServeDataJson(val interface{}) {
57 | this.Data["json"] = &models.ReturnDto{Msg: "", Data: val}
58 | this.ServeJson()
59 | }
60 |
--------------------------------------------------------------------------------
/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
填写以下信息build docker image
4 |
5 |
6 |
7 |
8 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/views/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | PaaS Builder
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |

33 |
34 |
35 |
PaaS Builder
36 |
37 |
38 | welcome, {{.CurrentUser.Name}}
¦ logout
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{.LayoutContent}}
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Builder
2 | =======
3 |
4 | 这是DINP中的一个编配平台,核心作用就是把用户提供的代码包,变成一个image推送到Docker-Registry
5 |
6 | # 设计理念
7 |
8 | - 平台不管编译,用户自己编译好,然后把编译好的代码扔给平台
9 | - 不管是什么语言的代码,最终都打包成一个docker image,通过docker来做规范化
10 |
11 | ## 编译与否的问题
12 |
13 | 用户的代码要部署在PaaS平台上,首先要做的就是把代码扔给平台,那么问题来了,通过什么方式扔给平台呢?通过git?还是直接上传?扔上来的是源代码?还是已经编译完成的?解释型语言当然是不需要编译的,但是编译型的呢,比如golang,java,我们要让用户给我们.go、.java文件然后我们去编译?还是用户编译好,直接给我们二进制,给我们.class文件?
14 |
15 | 平台刚起步,我们希望在可接受的范围内,做的事情越少越好,这样出错的几率就越少,之后平台慢慢发展,可以再加入一些新的feature。所以我们选择让用户把编译之后的代码交给我们,比如java的话,就扔个war包给我们即可;golang的话需要编译成二进制(编译成64位Linux的,因为平台是64位Linux)。
16 |
17 | 有些PaaS,比如tsuru,是让用户把代码push到某个指定repo的指定分支的,然后后端的git receiver就会触发编译、上线脚本。看起来是挺好的,但是这样处理比较麻烦,坑太多,以后再说吧
18 |
19 | 接下来是要确定通过什么方式把代码扔给平台,我们目前支持两种方式,一个是在页面直接上传,一个是给一个可以下载的http地址,平台去下载。
20 |
21 | ## 使用docker来做规范化打包
22 |
23 | 用户的代码可能是不同语言写的,比较常见的比如Java、PHP、Python,用户给我们的是一个tar.gz包,我们要怎么把其变成一个docker image呢?
24 |
25 | 最简单的实现方式:dinp提供一个base image,里边就只是Ubuntu或者centos的环境,用户自己搞定runtime依赖,比如Java的话,用户的tar.gz包中应该包含JDK、tomcat、webapp,然后约定一个启动脚本,比如就是根目录的control文件,平台只需要`./control start`即可启动。如果用户是PHP的代码,需要把nginx、php-fpm、code打包进来。
26 |
27 | 但是这种方式太麻烦,如果是golang的话可以使用上面的方式,因为golang是静态编译的,也没啥依赖。Java、php、Python、Ruby让用户这么搞,那不得哭了……
28 |
29 | 我们可以针对不同的语言做不同的base image,比如Java的,base image中提前把JDK和tomcat做好,用户直接上传一个war包,搞定!比如PHP的base image,提前把nginx、php-fpm之类的做好,用户直接把php code上传,搞定!
30 |
31 | 嗯,这也是Builder平台现在采用的方式,我们做了一些base image,让用户在页面上选择使用哪个,用户选择了base image,也就间接的告诉了我们他是什么类型的代码了(比如java的程序肯定要选择一个java的base image)。然后我们根据不同类型的程序生成一个Dockerfile,把用户的代码和base image揉在一起,搞定!
32 |
33 | # 安装方法
34 |
35 | 代码在 [这里](https://github.com/dinp/builder) ,这是个golang的项目,使用beego框架,安装起来也比较简单,就是通常的golang项目的编译方式
36 |
37 | mkdir -p $GOPATH/src/github.com/dinp # 假设你已经配置好了GOROOT和GOPATH
38 | cd $GOPATH/src/github.com/dinp
39 | git clone https://github.com/dinp/builder.git
40 | cd builder
41 | go get ./...
42 | go build
43 | mv conf/app.conf.example conf/app.conf
44 | # modify conf/app.conf
45 | ./builder
46 |
47 | 项目采用[UIC](http://ulricqin.com/project/uic/)作为单点登录系统,所以要想运行测试的话得先把UIC搭建起来
48 |
49 | 简单解释一下`conf/app.conf`各项配置的作用
50 |
51 | - appname、httpport没啥好说的
52 | - runmode取值是dev或者prod,可以参看beego的文档
53 | - db打头的是数据库配置,数据库初始化脚本在schema.sql
54 | - tmpdir、logdir是就是一些临时目录,不解释
55 | - buildtimeout是编译超时时间,超过了这个时间就会被kill,单位是分钟
56 | - uicinternal、uicexternal是UIC的配置,为啥分成两个呢?Builder和UIC通常是在一个内网的,相互之间的访问可以走内网,所以有个uicinternal,但是有的时候UIC的内网地址用户是没法访问的,所以在sso登录的时候还是需要redirect到UIC的外网地址
57 | - registry是docker私有源,读者可以使用docker registry搭建
58 | - buildscript是Builder内部用到的一个脚本文件地址,默认配置即可
59 | - tplmapping这个很重要,这是base image的配置,在registry中增加了base image,也要在此配置一下,这样用户才能在页面上看到
60 | - token,这是与UIC通信的凭证,与UIC的token配置成一样即可
61 |
62 |
63 | # 问题
64 |
65 | 编配的时候用户要查看log,这造成了单点状态,目前可以通过挂载分布式文件系统解决
66 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego/orm"
6 | "github.com/dinp/builder/g"
7 | _ "github.com/go-sql-driver/mysql"
8 | systool "github.com/toolkits/sys"
9 | "html/template"
10 | "log"
11 | "os"
12 | "os/exec"
13 | "time"
14 | )
15 |
16 | type Build struct {
17 | Id int64 `json:"id"`
18 | App string `json:"app" orm:"size(64)"`
19 | Version string `json:"version" orm:"size(64)"`
20 | Resume string `json:"resume" orm:"size(255)"`
21 | Base string `json:"base" orm:"size(1024)"`
22 | Image string `json:"image" orm:"size(1024)"`
23 | Tarball string `json:"tarball" orm:"size(1024)"`
24 | Repo string `json:"repo" orm:"size(255)"`
25 | Branch string `json:"branch" orm:"size(64)"`
26 | Status string `json:"status" orm:"size(255)"`
27 | UserId int64 `json:"user_id" orm:"index"`
28 | UserName string `json:"user_name" orm:"size(64)"`
29 | CreateAt time.Time `json:"create_at" orm:"auto_now_add;type(datetime)"`
30 | }
31 |
32 | func (this *Build) TableEngine() string {
33 | return "INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci"
34 | }
35 |
36 | func init() {
37 | orm.RegisterModel(new(Build))
38 | }
39 |
40 | func (this *Build) UpdateBuildStatus(status string) (int64, error) {
41 | this.Status = status
42 | return orm.NewOrm().Update(this, "Status")
43 | }
44 |
45 | func (this *Build) UpdateImage(image string) (int64, error) {
46 | this.Image = image
47 | return orm.NewOrm().Update(this, "Image")
48 | }
49 |
50 | func (this *Build) GenDockerfile(workDir, tarball string) error {
51 | tpl, ok := g.TplMapping[this.Base]
52 | if !ok {
53 | return fmt.Errorf("no such base image: %s", this.Base)
54 | }
55 |
56 | t, err := template.ParseFiles(tpl)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | out, err := os.Create(fmt.Sprintf("%s/Dockerfile", workDir))
62 | if err != nil {
63 | return err
64 | }
65 |
66 | defer out.Close()
67 |
68 | return t.Execute(out, map[string]interface{}{
69 | "Registry": g.Registry,
70 | "Tarball": tarball,
71 | "AppDir": "/opt/app",
72 | })
73 | }
74 |
75 | func (this *Build) DockerBuild(workDir string) {
76 | this.UpdateBuildStatus("docker building...")
77 | logFile := fmt.Sprintf("%s/%d.log", g.LogDir, this.Id)
78 | cmd := exec.Command(g.BuildScript, workDir, this.UserName, this.App, this.Version, g.Registry, logFile)
79 | err := cmd.Start()
80 | if err != nil {
81 | this.UpdateBuildStatus(fmt.Sprintf("error occur when build: %v", err))
82 | log.Printf("start cmd fail: %v when docker build %s/Dockerfile", err, workDir)
83 | return
84 | }
85 |
86 | err, timeout := systool.CmdRunWithTimeout(cmd, g.BuildTimeout)
87 |
88 | if err != nil {
89 | log.Printf("docker build %s/Dockerfile fail: %v", workDir, err)
90 | }
91 |
92 | if timeout {
93 | log.Printf("docker build %s/Dockerfile timeout", workDir)
94 | }
95 |
96 | if !timeout && err == nil {
97 | this.UpdateBuildStatus("successfully:-)")
98 | image := fmt.Sprintf("%s/%s/%s:%s", g.Registry, this.UserName, this.App, this.Version)
99 | this.UpdateImage(image)
100 | f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0600)
101 | if err == nil {
102 | defer f.Close()
103 | f.WriteString("\ndone:-)\n")
104 | }
105 | os.RemoveAll(workDir)
106 | } else {
107 | this.UpdateBuildStatus(fmt.Sprintf("is_timeout: %v, err: %v", timeout, err))
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/g/g.go:
--------------------------------------------------------------------------------
1 | package g
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego"
6 | "github.com/astaxie/beego/cache"
7 | "github.com/astaxie/beego/orm"
8 | _ "github.com/go-sql-driver/mysql"
9 | filetool "github.com/toolkits/file"
10 | "log"
11 | "runtime"
12 | "strings"
13 | "time"
14 | )
15 |
16 | var (
17 | Debug bool
18 | TmpDir string
19 | LogDir string
20 | BuildTimeout time.Duration
21 | UicInternal string
22 | UicExternal string
23 | Registry string
24 | Cache cache.Cache
25 | BuildScript string
26 | TplMapping map[string]string = make(map[string]string)
27 | Token string
28 | )
29 |
30 | func init() {
31 | runtime.GOMAXPROCS(runtime.NumCPU())
32 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
33 | ParseConfig()
34 | }
35 |
36 | func ParseConfig() {
37 |
38 | runMode := beego.AppConfig.String("runmode")
39 | if runMode == "dev" {
40 | Debug = true
41 | } else {
42 | Debug = false
43 | }
44 |
45 | TmpDir = beego.AppConfig.String("tmpdir")
46 | if TmpDir == "" {
47 | log.Fatalln("configuration tmpdir is blank")
48 | }
49 |
50 | err := filetool.InsureDir(TmpDir)
51 | if err != nil {
52 | log.Fatalf("create dir: %s fail: %v", TmpDir, err)
53 | }
54 |
55 | TmpDir, err = filetool.RealPath(TmpDir)
56 | if err != nil {
57 | log.Fatalf("get real path of %s fail: %v", TmpDir, err)
58 | }
59 |
60 | LogDir = beego.AppConfig.String("logdir")
61 | if LogDir == "" {
62 | log.Fatalln("configuration logdir is blank")
63 | }
64 |
65 | err = filetool.InsureDir(LogDir)
66 | if err != nil {
67 | log.Fatalf("create dir: %s fail: %v", LogDir, err)
68 | }
69 |
70 | LogDir, err = filetool.RealPath(LogDir)
71 | if err != nil {
72 | log.Fatalf("get real path of %s fail: %v", LogDir, err)
73 | }
74 |
75 | Token = beego.AppConfig.String("token")
76 |
77 | UicInternal = beego.AppConfig.String("uicinternal")
78 | if UicInternal == "" {
79 | log.Fatalln("configuration uicinternal is blank")
80 | }
81 |
82 | UicExternal = beego.AppConfig.String("uicexternal")
83 | if UicExternal == "" {
84 | log.Fatalln("configuration uicexternal is blank")
85 | }
86 |
87 | BuildScript = beego.AppConfig.String("buildscript")
88 | if BuildScript == "" {
89 | log.Fatalln("configuration buildscript is blank")
90 | }
91 |
92 | Registry = beego.AppConfig.String("registry")
93 | if Registry == "" {
94 | log.Fatalln("configuration registry is blank")
95 | }
96 |
97 | _buildTimeout, err := beego.AppConfig.Int64("buildtimeout")
98 | if err != nil {
99 | log.Fatalf("parse configuration buildtimeout fail: %v", err)
100 | }
101 |
102 | BuildTimeout = time.Duration(_buildTimeout) * time.Minute
103 |
104 | // tpl mapping
105 | tpl_mapping := beego.AppConfig.String("tplmapping")
106 | tpl_mapping = strings.TrimSpace(tpl_mapping)
107 | mappings := strings.Split(tpl_mapping, ",")
108 | for i := 0; i < len(mappings); i++ {
109 | _mappings := strings.TrimSpace(mappings[i])
110 | kv := strings.Split(_mappings, "=>")
111 | if len(kv) != 2 {
112 | log.Fatalf("split %s fail", _mappings)
113 | }
114 |
115 | TplMapping[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
116 | }
117 |
118 | // cache
119 | Cache, err = cache.NewCache("memory", `{"interval":60}`)
120 | if err != nil {
121 | log.Fatalln("start cache fail :-(")
122 | }
123 |
124 | // db
125 | dbuser := beego.AppConfig.String("dbuser")
126 | dbpass := beego.AppConfig.String("dbpass")
127 | dbhost := beego.AppConfig.String("dbhost")
128 | dbport := beego.AppConfig.String("dbport")
129 | dbname := beego.AppConfig.String("dbname")
130 | dblink := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", dbuser, dbpass, dbhost, dbport, dbname)
131 | // dblink = "root:1234@/uic?charset=utf8&loc=Asia%2FChongqing"
132 |
133 | orm.RegisterDriver("mysql", orm.DR_MySQL)
134 | orm.RegisterDataBase("default", "mysql", dblink+"&loc=Asia%2FChongqing", 30, 200)
135 | // orm.DefaultTimeLoc = time.UTC
136 |
137 | if Debug {
138 | orm.Debug = true
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/routers/index.go:
--------------------------------------------------------------------------------
1 | package routers
2 |
3 | import (
4 | "fmt"
5 | "github.com/astaxie/beego/orm"
6 | "github.com/dinp/builder/g"
7 | "github.com/dinp/builder/models"
8 | filetool "github.com/toolkits/file"
9 | str_ "github.com/toolkits/str"
10 | "regexp"
11 | "strconv"
12 | "strings"
13 | )
14 |
15 | type MainController struct {
16 | PageController
17 | }
18 |
19 | func (this *MainController) Get() {
20 | this.Data["Mappings"] = g.TplMapping
21 | this.Layout = "layout.html"
22 | this.TplNames = "index.html"
23 | }
24 |
25 | func (this *MainController) Build() {
26 | o := &models.Build{}
27 | o.App = this.GetString("app")
28 | o.Version = this.GetString("version")
29 | o.Resume = this.GetString("resume")
30 | o.Base = this.GetString("base")
31 | o.Tarball = this.GetString("tarball")
32 |
33 | defer func() {
34 | this.Data["Mappings"] = g.TplMapping
35 | this.Data["O"] = o
36 | this.Layout = "layout.html"
37 | this.TplNames = "index.html"
38 | }()
39 |
40 | if o.App == "" {
41 | this.Data["Msg"] = "app名称不能为空"
42 | return
43 | }
44 |
45 | // app should be a-zA-Z0-9_-
46 | var appPattern = regexp.MustCompile(`^[a-zA-Z_]+[a-zA-Z0-9\-\_]*$`)
47 | if !appPattern.MatchString(o.App) {
48 | this.Data["Msg"] = "app名称应该符合正则:/^[a-zA-Z_]+[a-zA-Z0-9\\-\\_]*$/"
49 | return
50 | }
51 |
52 | if o.Version == "" {
53 | this.Data["Msg"] = "版本不能为空"
54 | return
55 | }
56 |
57 | // version should be digit and .
58 | var versionPattern = regexp.MustCompile(`^[0-9]+[0-9\.]*$`)
59 | if !versionPattern.MatchString(o.Version) {
60 | this.Data["Msg"] = "version应该符合正则:/^[0-9]+[0-9\\.]*$/"
61 | return
62 | }
63 |
64 | if o.Base == "" {
65 | this.Data["Msg"] = "Base Image不能为空"
66 | return
67 | }
68 |
69 | workDir := fmt.Sprintf("%s/%s", g.TmpDir, str_.RandSeq(6))
70 | err := filetool.InsureDir(workDir)
71 | if err != nil {
72 | this.Data["Msg"] = fmt.Sprintf("create temp dir fail: %v", err)
73 | return
74 | }
75 |
76 | var fileName, filePath string
77 |
78 | if o.Tarball == "" {
79 | _, header, err := this.GetFile("file")
80 | if err != nil {
81 | this.Data["Msg"] = err.Error()
82 | return
83 | }
84 |
85 | // handle upload file
86 | fileName = header.Filename
87 | filePath = fmt.Sprintf("%s/%s", workDir, fileName)
88 | err = this.SaveToFile("file", filePath)
89 | if err != nil {
90 | this.Data["Msg"] = fmt.Sprintf("save file fail: %v", err)
91 | return
92 | }
93 | } else {
94 | if !strings.HasPrefix(o.Tarball, "http://") {
95 | this.Data["Msg"] = "tarball地址应该是一个http地址"
96 | return
97 | }
98 |
99 | if !(strings.HasSuffix(o.Tarball, ".tar.gz") || strings.HasSuffix(o.Tarball, ".war")) {
100 | this.Data["Msg"] = "tarball地址应该以.tar.gz或.war结尾"
101 | return
102 | }
103 |
104 | idx := strings.LastIndex(o.Tarball, "/")
105 | fileName = o.Tarball[idx+1:]
106 | filePath = fmt.Sprintf("%s/%s", workDir, fileName)
107 |
108 | err = filetool.Download(filePath, o.Tarball)
109 | if err != nil {
110 | this.Data["Msg"] = fmt.Sprintf("download tarball fail: %v", err)
111 | return
112 | }
113 | }
114 |
115 | o.UserId = this.CurrentUser.Id
116 | o.UserName = this.CurrentUser.Name
117 | o.Status = "saved meta in db"
118 | _, err = orm.NewOrm().Insert(o)
119 | if err != nil {
120 | this.Data["Msg"] = fmt.Sprintf("save meta to db fail: %v", err)
121 | return
122 | }
123 |
124 | err = o.GenDockerfile(workDir, fileName)
125 | if err != nil {
126 | this.Data["Msg"] = fmt.Sprintf("generate Dockerfile fail: %v", err)
127 | return
128 | }
129 |
130 | go o.DockerBuild(workDir)
131 |
132 | this.Redirect(fmt.Sprintf("/progress/%d", o.Id), 302)
133 | }
134 |
135 | func (this *MainController) Progress() {
136 | idStr := this.Ctx.Input.Param(":id")
137 | this.Data["Id"] = idStr
138 |
139 | buildId, err := strconv.ParseInt(idStr, 10, 64)
140 | build := models.Build{Id: buildId}
141 | err = orm.NewOrm().Read(&build)
142 | if err != nil {
143 | this.ServeErrJson(err.Error())
144 | return
145 | }
146 |
147 | this.Data["Image"] = fmt.Sprintf("%s/%s/%s:%s", g.Registry, build.UserName, build.App, build.Version)
148 | this.Layout = "layout.html"
149 | this.TplNames = "progress.html"
150 | }
151 |
152 | func (this *MainController) Log() {
153 | idStr := this.Ctx.Input.Param(":id")
154 | buildId, err := strconv.ParseInt(idStr, 10, 64)
155 | build := models.Build{Id: buildId}
156 | err = orm.NewOrm().Read(&build)
157 | if err != nil {
158 | this.ServeErrJson(err.Error())
159 | return
160 | }
161 |
162 | content, err := filetool.ToString(fmt.Sprintf("%s/%d.log", g.LogDir, buildId))
163 | if err != nil {
164 | this.ServeErrJson(err.Error())
165 | return
166 | }
167 |
168 | content = strings.Replace(content, "\n", "
", -1)
169 |
170 | this.ServeDataJson(map[string]interface{}{"build": build, "log": content})
171 | }
172 |
173 | func (this *MainController) History() {
174 | this.Data["BuildHistory"] = models.BuildHistory(this.CurrentUser.Id)
175 | this.Layout = "layout.html"
176 | this.TplNames = "history.html"
177 | }
178 |
179 | func (this *MainController) DeleteHistory() {
180 | id, err := this.GetInt("id")
181 | if err != nil {
182 | this.ServeErrJson("id invalid")
183 | return
184 | }
185 |
186 | err = models.DeleteHistory(int64(id), this.CurrentUser.Id)
187 | if err != nil {
188 | this.ServeErrJson(err.Error())
189 | } else {
190 | this.ServeOKJson()
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/static/layer/extend/layer.ext.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | @Name: layer拓展类,依赖于layer
4 | @Date: 2014.08.13
5 | @Author: 贤心
6 | @Versions:1.8.5-ext
7 | @Api:http://sentsin.com/jquery/layer
8 | @Desc: 本拓展会持续更新
9 |
10 | */
11 |
12 | layer.use("skin/layer.ext.css",function(){layer.ext&&layer.ext()}),layer.prompt=function(a,b,c){var d={},a=a||{},e={area:["auto","auto"],offset:[a.top||"",""],title:a.title||"信息",dialog:{btns:2,type:-1,msg:'',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}()+"
"+'
'+function(){var a=c.length,b=1,d="";if(a>0)for(d='- '+(c[0].content||"content未传入")+"
";a>b;b++)d+='- '+(c[b].content||"content未传入")+"
";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:'
'+function(){return b.imgLen>1?'
':""}()+'
"+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()-element the Select2-plugin may be run against -
9 | * are copied to the .select2-container.
10 | *
11 | * 1. Overwrite .select2-container's original display:inline-block
12 | * with Bootstrap 3's default for .form-control, display:block;
13 | * courtesy of @juristr (@see https://github.com/fk/select2-bootstrap-css/pull/1)
14 | */
15 | .select2-container.form-control {
16 | background: transparent;
17 | border: none;
18 | display: block;
19 | /* 1 */
20 | margin: 0;
21 | padding: 0;
22 | }
23 |
24 | /**
25 | * Adjust Select2 inputs to fit Bootstrap 3 default .form-control appearance.
26 | */
27 | .select2-container .select2-choices .select2-search-field input,
28 | .select2-container .select2-choice,
29 | .select2-container .select2-choices {
30 | background: none;
31 | padding: 0;
32 | border-color: #cccccc;
33 | border-radius: 4px;
34 | color: #555555;
35 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
36 | background-color: white;
37 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
38 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
39 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
40 | }
41 |
42 | .select2-search input {
43 | border-color: #cccccc;
44 | border-radius: 4px;
45 | color: #555555;
46 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
47 | background-color: white;
48 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
49 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
51 | }
52 |
53 | .select2-container .select2-choices .select2-search-field input {
54 | -webkit-box-shadow: none;
55 | box-shadow: none;
56 | }
57 |
58 | /**
59 | * Adjust Select2 input heights to match the Bootstrap default.
60 | */
61 | .select2-container .select2-choice {
62 | height: 34px;
63 | line-height: 1.42857;
64 | }
65 |
66 | /**
67 | * Address Multi Select2's height which - depending on how many elements have been selected -
68 | * may grown higher than their initial size.
69 | */
70 | .select2-container.select2-container-multi.form-control {
71 | height: auto;
72 | }
73 |
74 | /**
75 | * Address Bootstrap 3 control sizing classes
76 | * @see http://getbootstrap.com/css/#forms-control-sizes
77 | */
78 | .select2-container.input-sm .select2-choice,
79 | .input-group-sm .select2-container .select2-choice {
80 | height: 30px;
81 | line-height: 1.5;
82 | border-radius: 3px;
83 | }
84 |
85 | .select2-container.input-lg .select2-choice,
86 | .input-group-lg .select2-container .select2-choice {
87 | height: 45px;
88 | line-height: 1.33;
89 | border-radius: 6px;
90 | }
91 |
92 | .select2-container-multi .select2-choices .select2-search-field input {
93 | height: 32px;
94 | }
95 |
96 | .select2-container-multi.input-sm .select2-choices .select2-search-field input,
97 | .input-group-sm .select2-container-multi .select2-choices .select2-search-field input {
98 | height: 28px;
99 | }
100 |
101 | .select2-container-multi.input-lg .select2-choices .select2-search-field input,
102 | .input-group-lg .select2-container-multi .select2-choices .select2-search-field input {
103 | height: 43px;
104 | }
105 |
106 | /**
107 | * Adjust height and line-height for .select2-search-field amd multi-select Select2 widgets.
108 | *
109 | * 1. Class repetition to address missing .select2-chosen in Select2 < 3.3.2.
110 | */
111 | .select2-container-multi .select2-choices .select2-search-field input {
112 | margin: 0;
113 | }
114 |
115 | .select2-chosen,
116 | .select2-choice > span:first-child,
117 | .select2-container .select2-choices .select2-search-field input {
118 | padding: 6px 12px;
119 | }
120 |
121 | .input-sm .select2-chosen,
122 | .input-group-sm .select2-chosen,
123 | .input-sm .select2-choice > span:first-child,
124 | .input-group-sm .select2-choice > span:first-child,
125 | .input-sm .select2-choices .select2-search-field input,
126 | .input-group-sm .select2-choices .select2-search-field input {
127 | padding: 5px 10px;
128 | }
129 |
130 | .input-lg .select2-chosen,
131 | .input-group-lg .select2-chosen,
132 | .input-lg .select2-choice > span:first-child,
133 | .input-group-lg .select2-choice > span:first-child,
134 | .input-lg .select2-choices .select2-search-field input,
135 | .input-group-lg .select2-choices .select2-search-field input {
136 | padding: 10px 16px;
137 | }
138 |
139 | .select2-container-multi .select2-choices .select2-search-choice {
140 | margin-top: 5px;
141 | margin-bottom: 3px;
142 | }
143 |
144 | .select2-container-multi.input-sm .select2-choices .select2-search-choice,
145 | .input-group-sm .select2-container-multi .select2-choices .select2-search-choice {
146 | margin-top: 3px;
147 | margin-bottom: 2px;
148 | }
149 |
150 | .select2-container-multi.input-lg .select2-choices .select2-search-choice,
151 | .input-group-lg .select2-container-multi .select2-choices .select2-search-choice {
152 | line-height: 24px;
153 | }
154 |
155 | /**
156 | * Adjust the single Select2's dropdown arrow button appearance.
157 | *
158 | * 1. For Select2 v.3.3.2.
159 | */
160 | .select2-container .select2-choice .select2-arrow,
161 | .select2-container .select2-choice div {
162 | border-left: 1px solid #cccccc;
163 | background: none;
164 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
165 | }
166 |
167 | .select2-dropdown-open .select2-choice .select2-arrow,
168 | .select2-dropdown-open .select2-choice div {
169 | border-left-color: transparent;
170 | background: none;
171 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
172 | }
173 |
174 | /**
175 | * Adjust the dropdown arrow button icon position for the single-select Select2 elements
176 | * to make it line up vertically now that we increased the height of .select2-container.
177 | *
178 | * 1. Class repetition to address missing .select2-chosen in Select2 v.3.3.2.
179 | */
180 | .select2-container .select2-choice .select2-arrow b,
181 | .select2-container .select2-choice div b {
182 | background-position: 0 3px;
183 | }
184 |
185 | .select2-dropdown-open .select2-choice .select2-arrow b,
186 | .select2-dropdown-open .select2-choice div b {
187 | background-position: -18px 3px;
188 | }
189 |
190 | .select2-container.input-sm .select2-choice .select2-arrow b,
191 | .input-group-sm .select2-container .select2-choice .select2-arrow b,
192 | .select2-container.input-sm .select2-choice div b,
193 | .input-group-sm .select2-container .select2-choice div b {
194 | background-position: 0 1px;
195 | }
196 |
197 | .select2-dropdown-open.input-sm .select2-choice .select2-arrow b,
198 | .input-group-sm .select2-dropdown-open .select2-choice .select2-arrow b,
199 | .select2-dropdown-open.input-sm .select2-choice div b,
200 | .input-group-sm .select2-dropdown-open .select2-choice div b {
201 | background-position: -18px 1px;
202 | }
203 |
204 | .select2-container.input-lg .select2-choice .select2-arrow b,
205 | .input-group-lg .select2-container .select2-choice .select2-arrow b,
206 | .select2-container.input-lg .select2-choice div b,
207 | .input-group-lg .select2-container .select2-choice div b {
208 | background-position: 0 9px;
209 | }
210 |
211 | .select2-dropdown-open.input-lg .select2-choice .select2-arrow b,
212 | .input-group-lg .select2-dropdown-open .select2-choice .select2-arrow b,
213 | .select2-dropdown-open.input-lg .select2-choice div b,
214 | .input-group-lg .select2-dropdown-open .select2-choice div b {
215 | background-position: -18px 9px;
216 | }
217 |
218 | /**
219 | * Address Bootstrap's validation states and change Select2's border colors and focus states.
220 | * Apply .has-warning, .has-danger or .has-succes to #select2-drop to match Bootstraps' colors.
221 | */
222 | .has-warning .select2-choice,
223 | .has-warning .select2-choices {
224 | border-color: #c09853;
225 | }
226 | .has-warning .select2-container-active .select2-choice,
227 | .has-warning .select2-container-multi.select2-container-active .select2-choices {
228 | border-color: #a47e3c;
229 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
230 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
231 | }
232 | .has-warning.select2-drop-active {
233 | border-color: #a47e3c;
234 | }
235 | .has-warning.select2-drop-active.select2-drop.select2-drop-above {
236 | border-top-color: #a47e3c;
237 | }
238 |
239 | .has-error .select2-choice,
240 | .has-error .select2-choices {
241 | border-color: #b94a48;
242 | }
243 | .has-error .select2-container-active .select2-choice,
244 | .has-error .select2-container-multi.select2-container-active .select2-choices {
245 | border-color: #953b39;
246 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
247 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
248 | }
249 | .has-error.select2-drop-active {
250 | border-color: #953b39;
251 | }
252 | .has-error.select2-drop-active.select2-drop.select2-drop-above {
253 | border-top-color: #953b39;
254 | }
255 |
256 | .has-success .select2-choice,
257 | .has-success .select2-choices {
258 | border-color: #468847;
259 | }
260 | .has-success .select2-container-active .select2-choice,
261 | .has-success .select2-container-multi.select2-container-active .select2-choices {
262 | border-color: #356635;
263 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
264 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
265 | }
266 | .has-success.select2-drop-active {
267 | border-color: #356635;
268 | }
269 | .has-success.select2-drop-active.select2-drop.select2-drop-above {
270 | border-top-color: #356635;
271 | }
272 |
273 | /**
274 | * Make Select2's active-styles - applied to .select2-container when the widget receives focus -
275 | * fit Bootstrap 3's .form-element:focus appearance.
276 | */
277 | .select2-container-active .select2-choice,
278 | .select2-container-multi.select2-container-active .select2-choices {
279 | border-color: #66afe9;
280 | outline: none;
281 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
282 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
283 | -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
284 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
285 | }
286 |
287 | .select2-drop-active {
288 | border-color: #66afe9;
289 | }
290 |
291 | .select2-drop-auto-width,
292 | .select2-drop.select2-drop-above.select2-drop-active {
293 | border-top-color: #66afe9;
294 | }
295 |
296 | /**
297 | * Select2 widgets in Bootstrap Input Groups
298 | *
299 | * When Select2 widgets are combined with other elements using Bootstrap 3's
300 | * "Input Group" component, we don't want specific edges of the Select2 container
301 | * to have a border-radius.
302 | *
303 | * In Bootstrap 2, input groups required a markup where these style adjustments
304 | * could be bound to a CSS-class identifying if the additional elements are appended,
305 | * prepended or both.
306 | *
307 | * Bootstrap 3 doesn't rely on these classes anymore, so we have to use our own.
308 | * Use .select2-bootstrap-prepend and .select2-bootstrap-append on a Bootstrap 3 .input-group
309 | * to let the contained Select2 widget know which edges should not be rounded as they are
310 | * directly followed by another element.
311 | *
312 | * @see http://getbootstrap.com/components/#input-groups
313 | */
314 | .input-group.select2-bootstrap-prepend [class^="select2-choice"] {
315 | border-bottom-left-radius: 0 !important;
316 | border-top-left-radius: 0 !important;
317 | }
318 |
319 | .input-group.select2-bootstrap-append [class^="select2-choice"] {
320 | border-bottom-right-radius: 0 !important;
321 | border-top-right-radius: 0 !important;
322 | }
323 |
324 | .select2-dropdown-open [class^="select2-choice"] {
325 | border-bottom-right-radius: 0 !important;
326 | border-bottom-left-radius: 0 !important;
327 | }
328 |
329 | .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
330 | border-top-right-radius: 0 !important;
331 | border-top-left-radius: 0 !important;
332 | }
333 |
334 | /**
335 | * Adjust Select2's choices hover and selected styles to match Bootstrap 3's default dropdown styles.
336 | */
337 | .select2-results .select2-highlighted {
338 | color: white;
339 | background-color: #428bca;
340 | }
341 |
342 | /**
343 | * Adjust alignment of Bootstrap 3 buttons in Bootstrap 3 Input Groups to address
344 | * Multi Select2's height which - depending on how many elements have been selected -
345 | * may grown higher than their initial size.
346 | */
347 | .select2-bootstrap-append .select2-container-multiple,
348 | .select2-bootstrap-append .input-group-btn,
349 | .select2-bootstrap-append .input-group-btn .btn,
350 | .select2-bootstrap-prepend .select2-container-multiple,
351 | .select2-bootstrap-prepend .input-group-btn,
352 | .select2-bootstrap-prepend .input-group-btn .btn {
353 | vertical-align: top;
354 | }
355 |
356 | /**
357 | * Make Multi Select2's choices match Bootstrap 3's default button styles.
358 | */
359 | .select2-container-multi .select2-choices .select2-search-choice {
360 | color: #555555;
361 | background: white;
362 | border-color: #cccccc;
363 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
364 | -webkit-box-shadow: none;
365 | box-shadow: none;
366 | }
367 |
368 | .select2-container-multi .select2-choices .select2-search-choice-focus {
369 | background: #ebebeb;
370 | border-color: #adadad;
371 | color: #333333;
372 | -webkit-box-shadow: none;
373 | box-shadow: none;
374 | }
375 |
376 | /**
377 | * Address Multi Select2's choice close-button vertical alignment.
378 | */
379 | .select2-search-choice-close {
380 | margin-top: -7px;
381 | top: 50%;
382 | }
383 |
384 | /**
385 | * Adjust the single Select2's clear button position (used to reset the select box
386 | * back to the placeholder value and visible once a selection is made
387 | * activated by Select2's "allowClear" option).
388 | */
389 | .select2-container .select2-choice abbr {
390 | top: 50%;
391 | }
392 |
393 | /**
394 | * Adjust "no results" and "selection limit" messages to make use
395 | * of Bootstrap 3's default "Alert" style.
396 | *
397 | * @see http://getbootstrap.com/components/#alerts-default
398 | */
399 | .select2-results .select2-no-results,
400 | .select2-results .select2-searching,
401 | .select2-results .select2-selection-limit {
402 | background-color: #fcf8e3;
403 | color: #c09853;
404 | }
405 |
406 | /**
407 | * Address disabled Select2 styles.
408 | *
409 | * 1. For Select2 v.3.3.2.
410 | * 2. Revert border-left:0 inherited from Select2's CSS to prevent the arrow
411 | * from jumping when switching from disabled to enabled state and vice versa.
412 | */
413 | .select2-container.select2-container-disabled .select2-choice,
414 | .select2-container.select2-container-disabled .select2-choices {
415 | cursor: not-allowed;
416 | background-color: #eeeeee;
417 | border-color: #cccccc;
418 | }
419 | .select2-container.select2-container-disabled .select2-choice .select2-arrow,
420 | .select2-container.select2-container-disabled .select2-choice div,
421 | .select2-container.select2-container-disabled .select2-choices .select2-arrow,
422 | .select2-container.select2-container-disabled .select2-choices div {
423 | background-color: transparent;
424 | border-left: 1px solid transparent;
425 | /* 2 */
426 | }
427 |
428 | /**
429 | * Address Select2's loading indicator position - which should not stick
430 | * to the right edge of Select2's search input.
431 | *
432 | * 1. in .select2-search input
433 | * 2. in Multi Select2's .select2-search-field input
434 | * 3. in the status-message of infinite-scroll with remote data (@see http://ivaynberg.github.io/select2/#infinite)
435 | *
436 | * These styles alter Select2's default background-position of 100%
437 | * and supply the new background-position syntax to browsers which support it:
438 | *
439 | * 1. Android, Safari < 6/Mobile, IE<9: change to a relative background-position of 99%
440 | * 2. Chrome 25+, Firefox 13+, IE 9+, Opera 10.5+: use the new CSS3-background-position syntax
441 | *
442 | * @see http://www.w3.org/TR/css3-background/#background-position
443 | *
444 | * @todo Since both Select2 and Bootstrap 3 only support IE8 and above,
445 | * we could use the :after-pseudo-element to display the loading indicator.
446 | * Alternatively, we could supply an altered loading indicator image which already
447 | * contains an offset to the right.
448 | */
449 | .select2-search input.select2-active,
450 | .select2-container-multi .select2-choices .select2-search-field input.select2-active,
451 | .select2-more-results.select2-active {
452 | background-position: 99%;
453 | /* 4 */
454 | background-position: right 4px center;
455 | /* 5 */
456 | }
457 |
--------------------------------------------------------------------------------
/static/layer/layer.min.js:
--------------------------------------------------------------------------------
1 | /****************************************
2 |
3 | @Name:layer v1.8.5 弹层组件压缩版
4 | @Author:贤心
5 | @Date:2014-08-13
6 | @Blog:http://sentsin.com
7 | @Copyright:Sentsin Xu(贤心)
8 | @官网:http://sentsin.com/jquery/layer
9 |
10 | */
11 |
12 | ;!function(a,b){
13 | "use strict";
14 | var d,e,g,h,i,
15 | c="", //组件存放目录,为空表示自动获取(不用填写host,相对站点的根目录即可)。
16 | f={host:"http://"+location.host,getPath:function(){var a=document.scripts,b=a[a.length-1].src;return c?f.host+c:b.substring(0,b.lastIndexOf("/")+1)},type:["dialog","page","iframe","loading","tips"]};a.layer={v:"1.8.5",ie6:!-[1,]&&!a.XMLHttpRequest,index:0,path:f.getPath(),use:function(a,b){var f,g,h,e=d("head")[0];a=a.replace(/\s/g,""),f=/\.css$/.test(a),g=document.createElement(f?"link":"script"),h=a.replace(/\.|\//g,""),f&&(g.type="text/css",g.rel="stylesheet"),g[f?"href":"src"]=/^http:\/\//.test(a)?a:layer.path+a,g.id=h,d("#"+h)[0]||e.appendChild(g),b&&(document.all?d(g).ready(b):d(g).load(b))},alert:function(a,b,c,e){var f="function"==typeof c,g={dialog:{msg:a,type:b,yes:f?c:e},area:["auto","auto"]};return f||(g.title=c),d.layer(g)},confirm:function(a,b,c,e){var f="function"==typeof c,g={dialog:{msg:a,type:4,btns:2,yes:b,no:f?c:e}};return f||(g.title=c),d.layer(g)},msg:function(a,c,e,f){var g={title:!1,closeBtn:!1,time:c===b?2:c,dialog:{msg:""===a||a===b?" ":a},end:f};return"object"==typeof e?(g.dialog.type=e.type,g.shade=e.shade,g.shift=e.rate):"function"==typeof e?g.end=e:g.dialog.type=e,d.layer(g)},load:function(a,b){return"string"==typeof a?layer.msg(a,b||0,16):d.layer({time:a,loading:{type:b},bgcolor:b?"#fff":"",shade:b?[.1,"#000"]:[0],border:3!==b&&b?[6,.3,"#000"]:[0],type:3,title:["",!1],closeBtn:[0,!1]})},tips:function(a,b,c,e,f,g){var h={type:4,shade:!1,success:function(a){this.closeBtn||a.find(".xubox_tips").css({"padding-right":10})},bgcolor:"",tips:{msg:a,follow:b}};return h.time="object"==typeof c?c.time:0|c,c=c||{},h.closeBtn=c.closeBtn||!1,h.maxWidth=c.maxWidth||e,h.tips.guide=c.guide||f,h.tips.style=c.style||g,h.tips.more=c.more,d.layer(h)}},g=["xubox_layer","xubox_iframe",".xubox_title",".xubox_text",".xubox_page",".xubox_main"],h=function(a){var b=this,c=b.config;layer.index++,b.index=layer.index,b.config=d.extend({},c,a),b.config.dialog=d.extend({},c.dialog,a.dialog),b.config.page=d.extend({},c.page,a.page),b.config.iframe=d.extend({},c.iframe,a.iframe),b.config.loading=d.extend({},c.loading,a.loading),b.config.tips=d.extend({},c.tips,a.tips),b.creat()},h.pt=h.prototype,h.pt.config={type:0,shade:[.3,"#000"],fix:!0,move:".xubox_title",title:"信息",offset:["","50%"],area:["310px","auto"],closeBtn:[0,!0],time:0,bgcolor:"#fff",border:[6,.3,"#000"],zIndex:19891014,maxWidth:400,dialog:{btns:1,btn:["确定","取消"],type:8,msg:"",yes:function(a){layer.close(a)},no:function(a){layer.close(a)}},page:{dom:"#xulayer",html:"",url:""},iframe:{src:"http://sentsin.com",scrolling:"auto"},loading:{type:0},tips:{msg:"",follow:"",guide:0,isGuide:!0,style:["background-color:#FF9900; color:#fff;","#FF9900"]},success:function(){},close:function(a){layer.close(a)},end:function(){}},h.pt.space=function(a){var c,d,e,f,h,i,j,k,l,m,n,o,p,b=this;return a=a||"",c=b.index,d=b.config,e=d.dialog,f=-1===e.type?"":'',h=[''+f+''+e.msg+"
",''+a+"
",'','',''],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/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;e?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(150):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var f=function(){c.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",f).emulateTransitionEnd(150):f()}else b&&b()},c.prototype.checkScrollbar=function(){document.body.clientWidth>=window.innerWidth||(this.scrollbarWidth=this.scrollbarWidth||this.measureScrollbar())},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.2.0",c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var c=a.contains(document.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!c)return;var d=this,e=this.tip(),f=this.getUID(this.type);this.setContent(),e.attr("id",f),this.$element.attr("aria-describedby",f),this.options.animation&&e.addClass("fade");var g="function"==typeof this.options.placement?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,i=h.test(g);i&&(g=g.replace(h,"")||"top"),e.detach().css({top:0,left:0,display:"block"}).addClass(g).data("bs."+this.type,this),this.options.container?e.appendTo(this.options.container):e.insertAfter(this.$element);var j=this.getPosition(),k=e[0].offsetWidth,l=e[0].offsetHeight;if(i){var m=g,n=this.$element.parent(),o=this.getPosition(n);g="bottom"==g&&j.top+j.height+l-o.scroll>o.height?"top":"top"==g&&j.top-o.scroll-l<0?"bottom":"right"==g&&j.right+k>o.width?"left":"left"==g&&j.left-kg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){clearTimeout(this.timeout),this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.2.0",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").empty()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.2.0",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.2.0",c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.closest("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},c.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one("bsTransitionEnd",e).emulateTransitionEnd(150):e(),f.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(c){c.preventDefault(),b.call(a(this),"show")})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.2.0",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=a(document).height(),d=this.$target.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=b-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){null!=this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:b-this.$element.height()-h}))}}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},d.offsetBottom&&(d.offset.bottom=d.offsetBottom),d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/static/fonts/glyphicons-halflings-regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------