]> source.dussan.org Git - gitea.git/commitdiff
Page: Manage social accounts
authorUnknwon <joe2010xtmf@163.com>
Sun, 10 Aug 2014 00:25:02 +0000 (17:25 -0700)
committerUnknwon <joe2010xtmf@163.com>
Sun, 10 Aug 2014 00:25:02 +0000 (17:25 -0700)
14 files changed:
conf/locale/locale_en-US.ini
conf/locale/locale_zh-CN.ini
models/oauth2.go
modules/middleware/repo.go
public/ng/css/gogs.css
public/ng/less/gogs/settings.less
routers/user/auth.go
routers/user/setting.go
routers/user/social.go
templates/repo/settings/options.tmpl
templates/user/settings/social.tmpl
templates/user/signin.tmpl
templates/user/signup.tmpl
templates/user/social.tmpl [deleted file]

index 42fffa08da297b0c9e3dd86c4ba4512a962fc264..9e2302195d70bf74f4444044d56643ac58de4bc5 100644 (file)
@@ -5,6 +5,7 @@ dashboard = Dashboard
 explore = Explore
 help = Help
 sign_in = Sign In
+social_sign_in = Social Sign In: 2nd Step <small>associate account</small>
 sign_out = Sign Out
 sign_up = Sign Up
 register = Register
@@ -49,6 +50,7 @@ my_mirrors = My Mirrors
 [auth]
 create_new_account = Create New Account
 register_hepler_msg = Already have an account? Sign in now!
+social_register_hepler_msg = Already have an account? Bind now!
 disable_register_prompt = Sorry, registration has been disabled. Please contact the site administrator.
 remember_me = Remember Me
 forget_password = Fotget password?
@@ -129,8 +131,12 @@ add_on = Added on
 last_used = Last used on
 no_activity = No recent activity
 
-manage_orgs = Manage Organizations
 manage_social = Manage Associated Social Accounts
+social_desc = This is a list of associated social accounts. Remove any binding that you do not recognize.
+unbind = Unbind
+unbind_success = Social account has been unbinded.
+
+manage_orgs = Manage Organizations
 
 delete_account = Delete Your Account
 delete_prompt = The operation will delete your account permanently, and <strong>CANNOT</strong> be undo!
index fb49ade8c49fd12fce76f6485e195f43e57ee59a..31b99c6024f7fabd421ae7f66837dd3217426f9a 100644 (file)
@@ -49,6 +49,7 @@ my_mirrors = 我的镜像
 [auth]
 create_new_account = 创建帐户
 register_hepler_msg = 已经注册?立即登录!
+social_register_hepler_msg = 已经注册?立即绑定!
 disable_register_prompt = 对不起,注册功能已被关闭。请联系网站管理员。
 remember_me = 记住登录
 forget_password = 忘记密码?
@@ -129,8 +130,12 @@ add_on = 增加于
 last_used = 上次使用在
 no_activity = 没有最近活动
 
-manage_orgs = 管理我的组织
 manage_social = 管理关联社交帐户
+social_desc = 以下是与您帐户所关联的社交帐号,如果您发现有陌生的关联,请立即解除绑定!
+unbind = 解除绑定
+unbind_success = 社交帐号解除绑定成功!
+
+manage_orgs = 管理我的组织
 
 delete_account = 删除当前帐户
 delete_prompt = 删除操作会永久清除您的帐户信息,并且 <strong>不可恢复</strong>!
index 4b024a26e48ab9871ea3f3e7901cd1468f57b1f8..46e8e492a37d3620ecac9f6c7ec1e76ee39e1160 100644 (file)
@@ -6,6 +6,7 @@ package models
 
 import (
        "errors"
+       "time"
 )
 
 type OauthType int
@@ -26,12 +27,15 @@ var (
 )
 
 type Oauth2 struct {
-       Id       int64
-       Uid      int64  `xorm:"unique(s)"` // userId
-       User     *User  `xorm:"-"`
-       Type     int    `xorm:"unique(s) unique(oauth)"` // twitter,github,google...
-       Identity string `xorm:"unique(s) unique(oauth)"` // id..
-       Token    string `xorm:"TEXT not null"`
+       Id                int64
+       Uid               int64     `xorm:"unique(s)"` // userId
+       User              *User     `xorm:"-"`
+       Type              int       `xorm:"unique(s) unique(oauth)"` // twitter,github,google...
+       Identity          string    `xorm:"unique(s) unique(oauth)"` // id..
+       Token             string    `xorm:"TEXT not null"`
+       Created           time.Time `xorm:"CREATED"`
+       Updated           time.Time
+       HasRecentActivity bool `xorm:"-"`
 }
 
 func BindUserOauth2(userId, oauthId int64) error {
@@ -69,10 +73,24 @@ func GetOauth2ById(id int64) (oa *Oauth2, err error) {
        return oa, nil
 }
 
+// UpdateOauth2 updates given OAuth2.
+func UpdateOauth2(oa *Oauth2) error {
+       _, err := x.Id(oa.Id).AllCols().Update(oa)
+       return err
+}
+
 // GetOauthByUserId returns list of oauthes that are releated to given user.
-func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) {
-       err = x.Find(&oas, Oauth2{Uid: uid})
-       return oas, err
+func GetOauthByUserId(uid int64) ([]*Oauth2, error) {
+       socials := make([]*Oauth2, 0, 5)
+       err := x.Find(&socials, Oauth2{Uid: uid})
+       if err != nil {
+               return nil, err
+       }
+
+       for _, social := range socials {
+               social.HasRecentActivity = social.Updated.Add(7 * 24 * time.Hour).After(time.Now())
+       }
+       return socials, err
 }
 
 // DeleteOauth2ById deletes a oauth2 by ID.
index 16bd7defb8befbc1fb1addafbf17c323e95573a8..e1c5c68c75c58c2751c3c05e62e14a8b6141e8e0 100644 (file)
@@ -253,7 +253,10 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
                }
 
                if ctx.IsSigned {
-                       ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id)
+                       ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.Id, repo.Id)
+               }
+               if ctx.Repo.Repository.IsBare {
+                       return
                }
 
                ctx.Data["TagName"] = ctx.Repo.TagName
@@ -276,7 +279,6 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
 
                ctx.Data["BranchName"] = ctx.Repo.BranchName
                ctx.Data["CommitId"] = ctx.Repo.CommitId
-               ctx.Data["IsWatchingRepo"] = ctx.Repo.IsWatching
        }
 }
 
index 0c3a40db381d266e6f443adf6d217fdb42056da7..9633ed279105f886721cdc5b5b7eed4e4b7cd329 100644 (file)
@@ -1365,32 +1365,38 @@ The register and sign-in page style
 }
 #repo-hooks-panel,
 #repo-hooks-history-panel,
+#user-social-panel,
 #user-ssh-panel {
   margin-bottom: 20px;
 }
 #repo-hooks-panel .setting-list,
 #repo-hooks-history-panel .setting-list,
+#user-social-panel .setting-list,
 #user-ssh-panel .setting-list {
   background-color: #FFF;
 }
 #repo-hooks-panel .setting-list li,
 #repo-hooks-history-panel .setting-list li,
+#user-social-panel .setting-list li,
 #user-ssh-panel .setting-list li {
   padding: 8px 20px;
   border-bottom: 1px solid #eaeaea;
 }
 #repo-hooks-panel .setting-list li.ssh:hover,
 #repo-hooks-history-panel .setting-list li.ssh:hover,
+#user-social-panel .setting-list li.ssh:hover,
 #user-ssh-panel .setting-list li.ssh:hover {
   background-color: #ffffEE;
 }
 #repo-hooks-panel .setting-list li i,
 #repo-hooks-history-panel .setting-list li i,
+#user-social-panel .setting-list li i,
 #user-ssh-panel .setting-list li i {
   padding-right: 5px;
 }
 #repo-hooks-panel .active-icon,
 #repo-hooks-history-panel .active-icon,
+#user-social-panel .active-icon,
 #user-ssh-panel .active-icon {
   width: 10px;
   height: 10px;
@@ -1401,24 +1407,29 @@ The register and sign-in page style
 }
 #repo-hooks-panel .ssh-content,
 #repo-hooks-history-panel .ssh-content,
+#user-social-panel .ssh-content,
 #user-ssh-panel .ssh-content {
   margin-left: 24px;
 }
 #repo-hooks-panel .ssh-content .octicon,
 #repo-hooks-history-panel .ssh-content .octicon,
+#user-social-panel .ssh-content .octicon,
 #user-ssh-panel .ssh-content .octicon {
   margin-right: 4px;
 }
 #repo-hooks-panel .ssh-content .print,
 #repo-hooks-history-panel .ssh-content .print,
+#user-social-panel .ssh-content .print,
 #user-ssh-panel .ssh-content .print,
 #repo-hooks-panel .ssh-content .activity,
 #repo-hooks-history-panel .ssh-content .activity,
+#user-social-panel .ssh-content .activity,
 #user-ssh-panel .ssh-content .activity {
   color: #888;
 }
 #repo-hooks-panel .ssh-delete-btn,
 #repo-hooks-history-panel .ssh-delete-btn,
+#user-social-panel .ssh-delete-btn,
 #user-ssh-panel .ssh-delete-btn {
   margin-top: 6px;
 }
index af38ca28f531512b9ed088f424c27afe168dc301..1a492b03c24688b8dda0e95ae07acc899f0063ad 100644 (file)
@@ -53,6 +53,7 @@
 
 #repo-hooks-panel,
 #repo-hooks-history-panel,
+#user-social-panel,
 #user-ssh-panel {
     margin-bottom: 20px;
     .setting-list {
index 710d048f39d00aa4c65e8add897c9ad925784508..191da0a21919677b77cbfe8eeea5fc1f89e6bebf 100644 (file)
@@ -14,7 +14,7 @@ import (
        "github.com/gogits/gogs/modules/auth"
        "github.com/gogits/gogs/modules/base"
        "github.com/gogits/gogs/modules/log"
-       // "github.com/gogits/gogs/modules/mailer"
+       "github.com/gogits/gogs/modules/mailer"
        "github.com/gogits/gogs/modules/middleware"
        "github.com/gogits/gogs/modules/setting"
 )
@@ -157,23 +157,22 @@ func SignOut(ctx *middleware.Context) {
 }
 
 func oauthSignUp(ctx *middleware.Context, sid int64) {
-       // ctx.Data["Title"] = "OAuth Sign Up"
-       // ctx.Data["PageIsSignUp"] = true
+       ctx.Data["Title"] = ctx.Tr("sign_up")
 
-       // if _, err := models.GetOauth2ById(sid); err != nil {
-       //      if err == models.ErrOauth2RecordNotExist {
-       //              ctx.Handle(404, "user.oauthSignUp(GetOauth2ById)", err)
-       //      } else {
-       //              ctx.Handle(500, "user.oauthSignUp(GetOauth2ById)", err)
-       //      }
-       //      return
-       // }
+       if _, err := models.GetOauth2ById(sid); err != nil {
+               if err == models.ErrOauth2RecordNotExist {
+                       ctx.Handle(404, "GetOauth2ById", err)
+               } else {
+                       ctx.Handle(500, "GetOauth2ById", err)
+               }
+               return
+       }
 
-       // ctx.Data["IsSocialLogin"] = true
-       // ctx.Data["username"] = strings.Replace(ctx.Session.Get("socialName").(string), " ", "", -1)
-       // ctx.Data["email"] = ctx.Session.Get("socialEmail")
-       // log.Trace("user.oauthSignUp(social ID): %v", ctx.Session.Get("socialId"))
-       // ctx.HTML(200, SIGNUP)
+       ctx.Data["IsSocialLogin"] = true
+       ctx.Data["uname"] = strings.Replace(ctx.Session.Get("socialName").(string), " ", "", -1)
+       ctx.Data["email"] = ctx.Session.Get("socialEmail")
+       log.Trace("social ID: %v", ctx.Session.Get("socialId"))
+       ctx.HTML(200, SIGNUP)
 }
 
 func SignUp(ctx *middleware.Context) {
@@ -202,10 +201,10 @@ func SignUpPost(ctx *middleware.Context, cpt *captcha.Captcha, form auth.Registe
        }
 
        isOauth := false
-       // sid, isOauth := ctx.Session.Get("socialId").(int64)
-       // if isOauth {
-       //      ctx.Data["IsSocialLogin"] = true
-       // }
+       sid, isOauth := ctx.Session.Get("socialId").(int64)
+       if isOauth {
+               ctx.Data["IsSocialLogin"] = true
+       }
 
        // May redirect from home page.
        if ctx.Query("from") == "home" {
@@ -268,28 +267,28 @@ func SignUpPost(ctx *middleware.Context, cpt *captcha.Captcha, form auth.Registe
        log.Trace("Account created: %s", u.Name)
 
        // Bind social account.
-       // if isOauth {
-       //      if err = models.BindUserOauth2(u.Id, sid); err != nil {
-       //              ctx.Handle(500, "user.SignUp(BindUserOauth2)", err)
-       //              return
-       //      }
-       //      ctx.Session.Delete("socialId")
-       //      log.Trace("%s OAuth binded: %s -> %d", ctx.Req.RequestURI, form.UserName, sid)
-       // }
+       if isOauth {
+               if err := models.BindUserOauth2(u.Id, sid); err != nil {
+                       ctx.Handle(500, "BindUserOauth2", err)
+                       return
+               }
+               ctx.Session.Delete("socialId")
+               log.Trace("%s OAuth binded: %s -> %d", ctx.Req.RequestURI, form.UserName, sid)
+       }
 
        // Send confirmation e-mail, no need for social account.
-       // if !isOauth && setting.Service.RegisterEmailConfirm && u.Id > 1 {
-       //      mailer.SendRegisterMail(ctx.Render, u)
-       //      ctx.Data["IsSendRegisterMail"] = true
-       //      ctx.Data["Email"] = u.Email
-       //      ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
-       //      ctx.HTML(200, "user/activate")
-
-       //      if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
-       //              log.Error("Set cache(MailResendLimit) fail: %v", err)
-       //      }
-       //      return
-       // }
+       if !isOauth && setting.Service.RegisterEmailConfirm && u.Id > 1 {
+               mailer.SendRegisterMail(ctx.Render, u)
+               ctx.Data["IsSendRegisterMail"] = true
+               ctx.Data["Email"] = u.Email
+               ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
+               ctx.HTML(200, "user/activate")
+
+               if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
+                       log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
+               }
+               return
+       }
 
        ctx.Redirect("/user/login")
 }
index 761052144f2465dc86605581b90788d6c3ccc9c1..739a30d032619b378238bdc892c0e91679a693ab 100644 (file)
@@ -200,36 +200,29 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
        ctx.HTML(200, SETTINGS_SSH_KEYS)
 }
 
-// func SettingSocial(ctx *middleware.Context) {
-//     ctx.Data["Title"] = "Social Account"
-//     ctx.Data["PageIsUserSetting"] = true
-//     ctx.Data["IsUserPageSettingSocial"] = true
-
-//     // Unbind social account.
-//     remove, _ := base.StrTo(ctx.Query("remove")).Int64()
-//     if remove > 0 {
-//             if err := models.DeleteOauth2ById(remove); err != nil {
-//                     ctx.Handle(500, "user.SettingSocial(DeleteOauth2ById)", err)
-//                     return
-//             }
-//             ctx.Flash.Success("OAuth2 has been unbinded.")
-//             ctx.Redirect("/user/settings/social")
-//             return
-//     }
-
-//     var err error
-//     ctx.Data["Socials"], err = models.GetOauthByUserId(ctx.User.Id)
-//     if err != nil {
-//             ctx.Handle(500, "user.SettingSocial(GetOauthByUserId)", err)
-//             return
-//     }
-//     ctx.HTML(200, SOCIAL)
-// }
-
 func SettingsSocial(ctx *middleware.Context) {
        ctx.Data["Title"] = ctx.Tr("settings")
        ctx.Data["PageIsUserSettings"] = true
        ctx.Data["PageIsSettingsSocial"] = true
+
+       // Unbind social account.
+       remove, _ := com.StrTo(ctx.Query("remove")).Int64()
+       if remove > 0 {
+               if err := models.DeleteOauth2ById(remove); err != nil {
+                       ctx.Handle(500, "DeleteOauth2ById", err)
+                       return
+               }
+               ctx.Flash.Success(ctx.Tr("settings.unbind_success"))
+               ctx.Redirect("/user/settings/social")
+               return
+       }
+
+       socials, err := models.GetOauthByUserId(ctx.User.Id)
+       if err != nil {
+               ctx.Handle(500, "GetOauthByUserId", err)
+               return
+       }
+       ctx.Data["Socials"] = socials
        ctx.HTML(200, SETTINGS_SOCIAL)
 }
 
index ef83cd5b42d84d13a7aa77ea8e849cf9f9078ad0..d7486dad2b427e347f6da1d610a2d35160dc5ec8 100644 (file)
@@ -10,6 +10,7 @@ import (
        "fmt"
        "net/url"
        "strings"
+       "time"
 
        "github.com/gogits/gogs/models"
        "github.com/gogits/gogs/modules/log"
@@ -67,8 +68,8 @@ func SocialSignIn(ctx *middleware.Context) {
        oa, err := models.GetOauth2(ui.Identity)
        switch err {
        case nil:
-               ctx.Session.Set("userId", oa.User.Id)
-               ctx.Session.Set("userName", oa.User.Name)
+               ctx.Session.Set("uid", oa.User.Id)
+               ctx.Session.Set("uname", oa.User.Name)
        case models.ErrOauth2RecordNotExist:
                raw, _ := json.Marshal(tk)
                oa = &models.Oauth2{
@@ -89,6 +90,11 @@ func SocialSignIn(ctx *middleware.Context) {
                return
        }
 
+       oa.Updated = time.Now()
+       if err = models.UpdateOauth2(oa); err != nil {
+               log.Error(4, "UpdateOauth2: %v", err)
+       }
+
        ctx.Session.Set("socialId", oa.Id)
        ctx.Session.Set("socialName", ui.Name)
        ctx.Session.Set("socialEmail", ui.Email)
index e6ebb9aa22bfe1ae428b779a18bb79cce11cf2a3..4cc67c9d573dabe6bc96c4df8d78378603e7a567 100644 (file)
@@ -30,6 +30,7 @@
                                    </div>
                                    <hr>        
                                    <br>
+                                       {{if not .Repository.IsBare}}
                                                    <div class="field">
                                                        <label>{{.i18n.Tr "repo.default_branch"}}</label>
                                                        <select name="branch">
@@ -39,6 +40,7 @@
                                                {{end}}
                                                        </select>
                                                    </div>
+                                                   {{end}}
                                                    {{if .Repository.IsMirror}}
                                    <div class="field">
                                        <label for="interval">{{.i18n.Tr "repo.mirror_interval"}}</label>
index 7ff2ea237fc692e4958541d482bae6568781bded..bcbc1fc5670b39cdc08871951deef115c3d5f22a 100644 (file)
@@ -7,8 +7,23 @@
             <div class="setting-content">
                 {{template "ng/base/alert" .}}
                 <div id="setting-content">
-                    <div id="user-profile-setting-content" class="panel panel-radius">
-                        <p class="panel-header"><strong>{{.i18n.Tr "settings.manage_social"}}</strong></p>
+                    <div id="user-social-panel" class="panel panel-radius">
+                        <div class="panel-header"><strong>{{.i18n.Tr "settings.manage_social"}}</strong></div>
+                        <ul class="panel-body setting-list">
+                            <li>{{.i18n.Tr "settings.social_desc"}}</li>
+                            {{range .Socials}}
+                            <li class="ssh clear">
+                                <span class="active-icon left label label-{{if .HasRecentActivity}}green{{else}}gray{{end}} label-radius"></span>
+                                <i class="fa {{Oauth2Icon .Type}} fa-2x left"></i>
+                                <div class="ssh-content left">
+                                    <p><strong>{{Oauth2Name .Type}}</strong></p>
+                                    <p class="print">{{.Identity}}</p>
+                                    <p class="activity"><i>{{$.i18n.Tr "settings.add_on"}} {{DateFormat .Created "M d, Y"}} —  <i class="octicon octicon-info"></i>{{$.i18n.Tr "settings.last_used"}} {{DateFormat .Updated "M d, Y"}}</i></p>
+                                </div>
+                                <a class="right btn btn-small btn-red btn-header btn-radius" href="/user/settings/social?remove={{.Id}}">{{$.i18n.Tr "settings.unbind"}}</a>
+                            </li>
+                            {{end}}
+                        </ul>
                     </div>
                 </div>
             </div>
index 9db98cd0080820cecfcc426b82cda4f38f17239e..bc5c0c0c86899b31fd4a2095a95267a57d771623 100644 (file)
@@ -3,7 +3,7 @@
 <div id="sign-wrapper">
     <form class="form-align form panel sign-panel sign-form container panel-radius" id="sign-up-form" action="/user/login" method="post">
         <div class="panel-header">
-            <h2>{{.i18n.Tr "sign_in"}}</h2>
+            <h2>{{if .IsSocialLogin}}{{.i18n.Tr "social_sign_in" | Str2html}}{{else}}{{.i18n.Tr "sign_in"}}{{end}}</h2>
         </div>
         <div class="panel-content">
             {{template "ng/base/alert" .}}
                 <label class="req" for="password">{{.i18n.Tr "password"}}</label>
                 <input class="ipt ipt-large ipt-radius {{if .Err_Password}}ipt-error{{end}}" id="password" name="password" type="password" required/>
             </p>
+            {{if not .IsSocialLogin}}
             <p class="field">
                 <span class="form-label"></span>
                 <input class="ipt-chk" id="remember" name="remember" type="checkbox"/>&nbsp;&nbsp;&nbsp;&nbsp;<strong>{{.i18n.Tr "auth.remember_me"}}</strong>
             </p>
+            {{end}}
             <p class="field">
                 <span class="form-label"></span>
                 <button class="btn btn-green btn-large btn-radius">{{.i18n.Tr "sign_in"}}</button>&nbsp;&nbsp;&nbsp;&nbsp;
-                <a href="/user/forget_password">{{.i18n.Tr "auth.forget_password"}}</a>
+                {{if not .IsSocialLogin}}<a href="/user/forget_password">{{.i18n.Tr "auth.forget_password"}}</a>{{end}}
             </p>
+            {{if not .IsSocialLogin}}
             <p class="field">
                 <span class="form-label"></span>
                 <a href="/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a>
@@ -34,6 +37,7 @@
                 {{template "ng/base/social" .}}
             </div>
             {{end}}
+            {{end}}
         </div>
     </form>
 </div>
index 723314956c4b6fce917c0b843a4080357d994f1c..8d4a572ef58c1bcd7d79e7dae3b09c58519b22b4 100644 (file)
@@ -3,7 +3,7 @@
 <div id="sign-wrapper">
     <form class="form-align form panel panel-radius sign-panel sign-form container" id="sign-up-form" action="/user/sign_up" method="post">
         <div class="panel-header">
-            <h2>{{.i18n.Tr "sign_up"}}</h2>
+            <h2>{{if .IsSocialLogin}}{{.i18n.Tr "social_sign_in" | Str2html}}{{else}}{{.i18n.Tr "sign_up"}}{{end}}</h2>
         </div>
         <div class="panel-content">
             {{template "ng/base/alert" .}}
@@ -40,7 +40,7 @@
             </p>
             <p class="field">
                 <span class="form-label"></span>
-                <a href="/user/login">{{.i18n.Tr "auth.register_hepler_msg"}}</a>
+                <a href="/user/login">{{if .IsSocialLogin}}{{.i18n.Tr "auth.social_register_hepler_msg"}}{{else}}{{.i18n.Tr "auth.register_hepler_msg"}}{{end}}</a>
             </p>
                {{end}}
         </div>
diff --git a/templates/user/social.tmpl b/templates/user/social.tmpl
deleted file mode 100644 (file)
index 7814cc0..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-{{template "base/head" .}}
-{{template "base/navbar" .}}
-<div id="body" class="container" data-page="user">
-    {{template "user/setting_nav" .}}
-    <div id="repo-setting-container" class="col-md-10">
-        {{template "base/alert" .}}
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                Social Account
-            </div>
-
-            <div class="panel-body">
-                <table class="table">
-                    <thead>
-                        <tr>
-                            <th></th>
-                            <th>Name</th>
-                            <th>Identity</th>
-                            <th>Op.</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        {{range .Socials}}
-                        <tr>
-                            <td><i class="fa {{Oauth2Icon .Type}} fa-2x"></i></td>
-                            <td>{{Oauth2Name .Type}}</td>
-                            <td>{{.Identity}}</td>
-                            <td><a href="/user/settings/social?remove={{.Id}}">Unbind</a></td>
-                        </tr>
-                        {{end}}
-                    </tbody>
-                </table>
-            </div>
-        </div>
-    </div>
-</div>
-{{template "base/footer" .}}
\ No newline at end of file