mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 16:01:32 +01:00 
			
		
		
		
	Merge branch 'develop' of github.com:gogits/gogs into develop
This commit is contained in:
		
						commit
						c08baee085
					
				| @ -6,11 +6,13 @@ go: | ||||
|   - 1.4 | ||||
|   - tip | ||||
| 
 | ||||
| sudo: false | ||||
| before_install: | ||||
|   - sudo apt-get update -qq | ||||
|   - sudo apt-get install -y libpam-dev | ||||
| 
 | ||||
| script: go build -v | ||||
| 
 | ||||
| notifications: | ||||
|   email: | ||||
|     - u@gogs.io | ||||
|   slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx | ||||
|   slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx | ||||
|  | ||||
| @ -102,7 +102,7 @@ func runServ(c *cli.Context) { | ||||
| 
 | ||||
| 	cmd := os.Getenv("SSH_ORIGINAL_COMMAND") | ||||
| 	if cmd == "" { | ||||
| 		println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") | ||||
| 		fmt.Printf("Hi, %s! You've successfully authenticated, but Gogs does not provide shell access.\n", user.Name) | ||||
| 		if user.IsAdmin { | ||||
| 			println("If this is unexpected, please log in with password and setup Gogs under another user.") | ||||
| 		} | ||||
|  | ||||
| @ -619,6 +619,7 @@ auths.smtp_auth = SMTP Authorization Type | ||||
| auths.smtphost = SMTP Host | ||||
| auths.smtpport = SMTP Port | ||||
| auths.enable_tls = Enable TLS Encryption | ||||
| auths.pam_service_name = PAM Service Name | ||||
| auths.enable_auto_register = Enable Auto Registration | ||||
| auths.tips = Tips | ||||
| auths.edit = Edit Authorization Setting | ||||
|  | ||||
| @ -17,6 +17,7 @@ import ( | ||||
| 	"github.com/go-xorm/xorm" | ||||
| 
 | ||||
| 	"github.com/gogits/gogs/modules/auth/ldap" | ||||
| 	"github.com/gogits/gogs/modules/auth/pam" | ||||
| 	"github.com/gogits/gogs/modules/log" | ||||
| 	"github.com/gogits/gogs/modules/uuid" | ||||
| ) | ||||
| @ -28,6 +29,7 @@ const ( | ||||
| 	PLAIN | ||||
| 	LDAP | ||||
| 	SMTP | ||||
| 	PAM | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -39,12 +41,14 @@ var ( | ||||
| var LoginTypes = map[LoginType]string{ | ||||
| 	LDAP: "LDAP", | ||||
| 	SMTP: "SMTP", | ||||
| 	PAM: "PAM", | ||||
| } | ||||
| 
 | ||||
| // Ensure structs implemented interface. | ||||
| var ( | ||||
| 	_ core.Conversion = &LDAPConfig{} | ||||
| 	_ core.Conversion = &SMTPConfig{} | ||||
| 	_ core.Conversion = &PAMConfig{} | ||||
| ) | ||||
| 
 | ||||
| type LDAPConfig struct { | ||||
| @ -74,6 +78,18 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) { | ||||
| 	return json.Marshal(cfg) | ||||
| } | ||||
| 
 | ||||
| type PAMConfig struct { | ||||
| 	ServiceName string // pam service (e.g. system-auth) | ||||
| } | ||||
| 
 | ||||
| func (cfg *PAMConfig) FromDB(bs []byte) error { | ||||
| 	return json.Unmarshal(bs, &cfg) | ||||
| } | ||||
| 
 | ||||
| func (cfg *PAMConfig) ToDB() ([]byte, error) { | ||||
| 	return json.Marshal(cfg) | ||||
| } | ||||
| 
 | ||||
| type LoginSource struct { | ||||
| 	Id                int64 | ||||
| 	Type              LoginType | ||||
| @ -97,6 +113,10 @@ func (source *LoginSource) SMTP() *SMTPConfig { | ||||
| 	return source.Cfg.(*SMTPConfig) | ||||
| } | ||||
| 
 | ||||
| func (source *LoginSource) PAM() *PAMConfig { | ||||
| 	return source.Cfg.(*PAMConfig) | ||||
| } | ||||
| 
 | ||||
| func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | ||||
| 	if colName == "type" { | ||||
| 		ty := (*val).(int64) | ||||
| @ -105,6 +125,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | ||||
| 			source.Cfg = new(LDAPConfig) | ||||
| 		case SMTP: | ||||
| 			source.Cfg = new(SMTPConfig) | ||||
| 		case PAM: | ||||
| 			source.Cfg = new(PAMConfig) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -169,7 +191,7 @@ func UserSignIn(uname, passwd string) (*User, error) { | ||||
| 	// For plain login, user must exist to reach this line. | ||||
| 	// Now verify password. | ||||
| 	if u.LoginType == PLAIN { | ||||
| 		if !u.ValidtePassword(passwd) { | ||||
| 		if !u.ValidatePassword(passwd) { | ||||
| 			return nil, ErrUserNotExist | ||||
| 		} | ||||
| 		return u, nil | ||||
| @ -197,6 +219,13 @@ func UserSignIn(uname, passwd string) (*User, error) { | ||||
| 					return u, nil | ||||
| 				} | ||||
| 				log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err) | ||||
| 			} else if source.Type == PAM { | ||||
| 				u, err := LoginUserPAMSource(nil, uname, passwd, | ||||
| 					source.Id, source.Cfg.(*PAMConfig), true) | ||||
| 				if err == nil { | ||||
| 					return u, nil | ||||
| 				} | ||||
| 				log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @ -218,6 +247,8 @@ func UserSignIn(uname, passwd string) (*User, error) { | ||||
| 		return LoginUserLdapSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*LDAPConfig), false) | ||||
| 	case SMTP: | ||||
| 		return LoginUserSMTPSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*SMTPConfig), false) | ||||
| 	case PAM: | ||||
| 		return LoginUserPAMSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*PAMConfig), false) | ||||
| 	} | ||||
| 	return nil, ErrUnsupportedLoginType | ||||
| } | ||||
| @ -359,3 +390,33 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP | ||||
| 	err := CreateUser(u) | ||||
| 	return u, err | ||||
| } | ||||
| 
 | ||||
| // Query if name/passwd can login against PAM | ||||
| // Create a local user if success | ||||
| // Return the same LoginUserPlain semantic | ||||
| func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) { | ||||
| 	if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil { | ||||
| 		if strings.Contains(err.Error(), "Authentication failure") { | ||||
| 			return nil, ErrUserNotExist | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if !autoRegister { | ||||
| 		return u, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// fake a local user creation | ||||
| 	u = &User{ | ||||
| 		LowerName:   strings.ToLower(name), | ||||
| 		Name:        strings.ToLower(name), | ||||
| 		LoginType:   PAM, | ||||
| 		LoginSource: sourceId, | ||||
| 		LoginName:   name, | ||||
| 		IsActive:    true, | ||||
| 		Passwd:      passwd, | ||||
| 		Email:       name, | ||||
| 	} | ||||
| 	err := CreateUser(u) | ||||
| 	return u, err | ||||
| } | ||||
|  | ||||
| @ -40,6 +40,7 @@ var ( | ||||
| 	ErrRepoFileNotLoaded = errors.New("Repository file not loaded") | ||||
| 	ErrMirrorNotExist    = errors.New("Mirror does not exist") | ||||
| 	ErrInvalidReference  = errors.New("Invalid reference specified") | ||||
| 	ErrNameEmpty         = errors.New("Name is empty") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -242,10 +243,11 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) { | ||||
| 	if err = repo.GetOwner(); err != nil { | ||||
| 		return cl, err | ||||
| 	} | ||||
| 
 | ||||
| 	if setting.SSHPort != 22 { | ||||
| 		cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.Domain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName) | ||||
| 		cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName) | ||||
| 	} else { | ||||
| 		cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, repo.Owner.LowerName, repo.LowerName) | ||||
| 		cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName) | ||||
| 	} | ||||
| 	cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName) | ||||
| 	return cl, nil | ||||
| @ -258,7 +260,11 @@ var ( | ||||
| 
 | ||||
| // IsUsableName checks if name is reserved or pattern of name is not allowed. | ||||
| func IsUsableName(name string) error { | ||||
| 	name = strings.ToLower(name) | ||||
| 	name = strings.TrimSpace(strings.ToLower(name)) | ||||
| 	if utf8.RuneCountInString(name) == 0 { | ||||
| 		return ErrNameEmpty | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range reservedNames { | ||||
| 		if name == reservedNames[i] { | ||||
| 			return ErrNameReserved{name} | ||||
|  | ||||
| @ -143,8 +143,8 @@ func (u *User) EncodePasswd() { | ||||
| 	u.Passwd = fmt.Sprintf("%x", newPasswd) | ||||
| } | ||||
| 
 | ||||
| // ValidtePassword checks if given password matches the one belongs to the user. | ||||
| func (u *User) ValidtePassword(passwd string) bool { | ||||
| // ValidatePassword checks if given password matches the one belongs to the user. | ||||
| func (u *User) ValidatePassword(passwd string) bool { | ||||
| 	newUser := &User{Passwd: passwd, Salt: u.Salt} | ||||
| 	newUser.EncodePasswd() | ||||
| 	return u.Passwd == newUser.Passwd | ||||
|  | ||||
| @ -30,6 +30,7 @@ type AuthenticationForm struct { | ||||
| 	SMTPPort          int    `form:"smtp_port"` | ||||
| 	TLS               bool   `form:"tls"` | ||||
| 	AllowAutoRegister bool   `form:"allowautoregister"` | ||||
| 	PAMServiceName    string | ||||
| } | ||||
| 
 | ||||
| func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||||
|  | ||||
							
								
								
									
										35
									
								
								modules/auth/pam/pam.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								modules/auth/pam/pam.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| // +build !windows | ||||
| 
 | ||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package pam | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 
 | ||||
| 	"github.com/msteinert/pam" | ||||
| ) | ||||
| 
 | ||||
| func PAMAuth(serviceName, userName, passwd string) error { | ||||
| 	t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) { | ||||
| 		switch s { | ||||
| 		case pam.PromptEchoOff: | ||||
| 			return passwd, nil | ||||
| 		case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo: | ||||
| 			return "", nil | ||||
| 		} | ||||
| 		return "", errors.New("Unrecognized PAM message style") | ||||
| 	}) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = t.Authenticate(0); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										15
									
								
								modules/auth/pam/pam_stub.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								modules/auth/pam/pam_stub.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| // +build windows | ||||
| 
 | ||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package pam | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
| 
 | ||||
| func PAMAuth(serviceName, userName, passwd string) error { | ||||
| 	return errors.New("PAM not supported") | ||||
| } | ||||
| @ -139,6 +139,13 @@ func (ctx *Context) Handle(status int, title string, err error) { | ||||
| 	ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status))) | ||||
| } | ||||
| 
 | ||||
| func (ctx *Context) HandleText(status int, title string) { | ||||
| 	if (status / 100 == 4) || (status / 100 == 5) { | ||||
| 		log.Error(4, "%s", title) | ||||
| 	} | ||||
| 	ctx.RenderData(status, []byte(title)) | ||||
| } | ||||
| 
 | ||||
| func (ctx *Context) HandleAPI(status int, obj interface{}) { | ||||
| 	var message string | ||||
| 	if err, ok := obj.(error); ok { | ||||
|  | ||||
| @ -53,6 +53,7 @@ var ( | ||||
| 	HttpAddr, HttpPort string | ||||
| 	DisableSSH         bool | ||||
| 	SSHPort            int | ||||
| 	SSHDomain          string | ||||
| 	OfflineMode        bool | ||||
| 	DisableRouterLog   bool | ||||
| 	CertFile, KeyFile  string | ||||
| @ -232,6 +233,7 @@ func NewConfigContext() { | ||||
| 	HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") | ||||
| 	HttpPort = sec.Key("HTTP_PORT").MustString("3000") | ||||
| 	DisableSSH = sec.Key("DISABLE_SSH").MustBool() | ||||
| 	SSHDomain = sec.Key("SSH_DOMAIN").MustString(Domain) | ||||
| 	SSHPort = sec.Key("SSH_PORT").MustInt(22) | ||||
| 	OfflineMode = sec.Key("OFFLINE_MODE").MustBool() | ||||
| 	DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() | ||||
|  | ||||
| @ -753,10 +753,17 @@ function initAdmin() { | ||||
|         if (v == 2) { | ||||
|             $('.ldap').toggleShow(); | ||||
|             $('.smtp').toggleHide(); | ||||
|             $('.pam').toggleHide(); | ||||
|         } | ||||
|         if (v == 3) { | ||||
|             $('.smtp').toggleShow(); | ||||
|             $('.ldap').toggleHide(); | ||||
|             $('.pam').toggleHide(); | ||||
|         } | ||||
|         if (v == 4) { | ||||
|             $('.pam').toggleShow(); | ||||
|             $('.smtp').toggleHide(); | ||||
|             $('.ldap').toggleHide(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|  | ||||
| @ -25,6 +25,11 @@ The register and sign-in page style | ||||
|   .form-label { | ||||
|     width: 160px; | ||||
|   } | ||||
|   .chk-label { | ||||
|     width: auto; | ||||
|     text-align: left; | ||||
|     margin-left: 176px; | ||||
|   } | ||||
|   .alert{ | ||||
|     margin:0 30px 24px 30px; | ||||
|   } | ||||
| @ -60,4 +65,4 @@ The register and sign-in page style | ||||
|     background-color: #FFF; | ||||
|     margin-left: -15px; | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -84,6 +84,10 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | ||||
| 			Port: form.SMTPPort, | ||||
| 			TLS:  form.TLS, | ||||
| 		} | ||||
| 	case models.PAM: | ||||
| 		u = &models.PAMConfig{ | ||||
| 			ServiceName: form.PAMServiceName, | ||||
| 		} | ||||
| 	default: | ||||
| 		ctx.Error(400) | ||||
| 		return | ||||
| @ -166,6 +170,10 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | ||||
| 			Port: form.SMTPPort, | ||||
| 			TLS:  form.TLS, | ||||
| 		} | ||||
| 	case models.PAM: | ||||
| 		config = &models.PAMConfig{ | ||||
| 			ServiceName: form.PAMServiceName, | ||||
| 		} | ||||
| 	default: | ||||
| 		ctx.Error(400) | ||||
| 		return | ||||
|  | ||||
| @ -164,7 +164,7 @@ func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) { | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	if !u.ValidtePassword(ctx.Query("password")) { | ||||
| 	if !u.ValidatePassword(ctx.Query("password")) { | ||||
| 		ctx.HandleAPI(422, "Username or password is not correct.") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| @ -96,12 +96,12 @@ func Http(ctx *middleware.Context) { | ||||
| 		// FIXME: middlewares/context.go did basic auth check already, | ||||
| 		// maybe could use that one. | ||||
| 		if len(auths) != 2 || auths[0] != "Basic" { | ||||
| 			ctx.Handle(401, "no basic auth and digit auth", nil) | ||||
| 			ctx.HandleText(401, "no basic auth and digit auth") | ||||
| 			return | ||||
| 		} | ||||
| 		authUsername, authPasswd, err = base.BasicAuthDecode(auths[1]) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(401, "no basic auth and digit auth", nil) | ||||
| 			ctx.HandleText(401, "no basic auth and digit auth") | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| @ -116,7 +116,7 @@ func Http(ctx *middleware.Context) { | ||||
| 			token, err := models.GetAccessTokenBySha(authUsername) | ||||
| 			if err != nil { | ||||
| 				if err == models.ErrAccessTokenNotExist { | ||||
| 					ctx.Handle(401, "invalid token", nil) | ||||
| 					ctx.HandleText(401, "invalid token") | ||||
| 				} else { | ||||
| 					ctx.Handle(500, "GetAccessTokenBySha", err) | ||||
| 				} | ||||
| @ -138,23 +138,23 @@ func Http(ctx *middleware.Context) { | ||||
| 
 | ||||
| 			has, err := models.HasAccess(authUser, repo, tp) | ||||
| 			if err != nil { | ||||
| 				ctx.Handle(401, "no basic auth and digit auth", nil) | ||||
| 				ctx.HandleText(401, "no basic auth and digit auth") | ||||
| 				return | ||||
| 			} else if !has { | ||||
| 				if tp == models.ACCESS_MODE_READ { | ||||
| 					has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE) | ||||
| 					if err != nil || !has { | ||||
| 						ctx.Handle(401, "no basic auth and digit auth", nil) | ||||
| 						ctx.HandleText(401, "no basic auth and digit auth") | ||||
| 						return | ||||
| 					} | ||||
| 				} else { | ||||
| 					ctx.Handle(401, "no basic auth and digit auth", nil) | ||||
| 					ctx.HandleText(401, "no basic auth and digit auth") | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if !isPull && repo.IsMirror { | ||||
| 				ctx.Handle(401, "can't push to mirror", nil) | ||||
| 				ctx.HandleText(401, "can't push to mirror") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -33,7 +33,7 @@ LOGFILE=${GOGS_HOME}/log/gogs.log | ||||
| RETVAL=0 | ||||
| 
 | ||||
| # Read configuration from /etc/sysconfig/gogs to override defaults | ||||
| [ -r /etc/sysconfig/$NAME ] && ./etc/sysconfig/$NAME | ||||
| [ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME | ||||
| 
 | ||||
| # Don't do anything if nothing is installed | ||||
| [ -x ${GOGS_PATH} ] || exit 0 | ||||
|  | ||||
| @ -91,6 +91,12 @@ | ||||
|                                     <label class="req" for="smtp_port">{{.i18n.Tr "admin.auths.smtpport"}}</label> | ||||
|                                     <input class="ipt ipt-large ipt-radius {{if .Err_SmtpPort}}ipt-error{{end}}" id="smtp_port" name="smtp_port" value="{{.Source.SMTP.Port}}" /> | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 {{else if eq $type 4}} | ||||
|                                 <div class="field"> | ||||
|                                     <label class="req" for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> | ||||
|                                     <input class="ipt ipt-large ipt-radius {{if .Err_PAMServiceName}}ipt-error{{end}}" id="pam_service_name" name="pam_service_name" value="{{.Source.PAM.ServiceName}}" /> | ||||
|                                 </div> | ||||
|                                 {{end}} | ||||
| 
 | ||||
|                                 <div class="field"> | ||||
|  | ||||
| @ -86,6 +86,12 @@ | ||||
|                                         <input class="ipt ipt-large ipt-radius {{if .Err_SmtpPort}}ipt-error{{end}}" id="smtp_port" name="smtp_port" value="{{.smtp_port}}" /> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div class="pam hidden"> | ||||
|                                     <div class="field"> | ||||
|                                         <label class="req" for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> | ||||
|                                         <input class="ipt ipt-large ipt-radius {{if .Err_PAMServiceName}}ipt-error{{end}}" id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" /> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div class="field"> | ||||
|                                     <div class="smtp hidden"> | ||||
|                                         <label></label> | ||||
|  | ||||
| @ -2,24 +2,26 @@ | ||||
| {{template "ng/base/header" .}} | ||||
| <div id="setting-wrapper" class="main-wrapper"> | ||||
|     <div id="org-setting" class="container clear"> | ||||
| 	    {{template "explore/nav" .}} | ||||
|         {{template "explore/nav" .}} | ||||
|         <div class="grid-4-5 left"> | ||||
|             <div class="setting-content"> | ||||
|             	<div id="org-repo-list"> | ||||
| 					{{range .Repos}} | ||||
| 					<div class="org-repo-item"> | ||||
| 			            <ul class="org-repo-status right"> | ||||
| 			                <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | ||||
| 			                <li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> | ||||
| 			            </ul> | ||||
| 						<h2><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a></h2> | ||||
| 						<p class="org-repo-description">{{.Description}}</p> | ||||
| 						<p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | ||||
| 					</div> | ||||
| 					{{end}} | ||||
|             	</div> | ||||
| 			</div> | ||||
|                 <div id="org-repo-list"> | ||||
|                     {{range .Repos}} | ||||
|                     <div class="org-repo-item"> | ||||
|                         <ul class="org-repo-status right"> | ||||
|                             <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | ||||
|                             <li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> | ||||
|                         </ul> | ||||
|                         <h2> | ||||
|                             <a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Owner.Name}} / {{.Name}}</a> | ||||
|                         </h2> | ||||
|                         <p class="org-repo-description">{{.Description}}</p> | ||||
|                         <p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | ||||
|                     </div> | ||||
|                     {{end}} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| {{template "ng/base/footer" .}} | ||||
| {{template "ng/base/footer" .}} | ||||
|  | ||||
| @ -17,8 +17,9 @@ | ||||
|             </div> | ||||
|             {{if not .IsSocialLogin}} | ||||
|             <div class="field"> | ||||
|                 <span class="form-label"></span> | ||||
|                 <input class="ipt-chk" id="remember" name="remember" type="checkbox"/>    <strong>{{.i18n.Tr "auth.remember_me"}}</strong> | ||||
|                 <label class="chk-label"> | ||||
|                     <input class="ipt-chk" id="remember" name="remember" type="checkbox"/>    <strong>{{.i18n.Tr "auth.remember_me"}}</strong> | ||||
|                 </label> | ||||
|             </div> | ||||
|             {{end}} | ||||
|             <div class="field"> | ||||
| @ -41,4 +42,4 @@ | ||||
|         </div> | ||||
|     </form> | ||||
| </div> | ||||
| {{template "ng/base/footer" .}} | ||||
| {{template "ng/base/footer" .}} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user