add tree view

release/v1.15
Lunny Xiao 2014-03-14 23:54:16 +08:00
commit b27e8e87f8
34 changed files with 716 additions and 166 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ gogs
.DS_Store
*.db
*.log
custom/
custom/
.vendor/

View File

@ -3,6 +3,8 @@ Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0b
Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language, it currently supports Linux and Max OS X, but Windows has **NOT** supported yet due to installation problem with [libgit2](http://libgit2.github.com/) in Windows.
##### Current version: 0.0.7 Alpha
## Purpose
There are some very good products in this category such as [gitlab](http://gitlab.com), but the environment setup steps often make us crazy. So our goal of Gogs is to build a GitHub-like clone with very easy setup steps, which take advantages of the Go Programming Language.
@ -15,7 +17,8 @@ Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, devel
- SSH protocal support.
- Register/delete account.
- Create public repository.
- Create/delete public repository.
- User/repository home page.
- Git repository manipulation.
## Installation

View File

@ -3,8 +3,8 @@ RUN_USER = lunny
[repository]
ROOT = /Users/lunny/git/gogs-repositories
LANG_IGNS=Google Go
LICENSES=Apache v2 License
LANG_IGNS=Google Go|C
LICENSES=Apache v2 License|BSD (3-Clause) License
[server]
HTTP_ADDR =

18
conf/gitignore/C Normal file
View File

@ -0,0 +1,18 @@
# Object files
*.o
*.ko
# Libraries
*.lib
*.a
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app

View File

@ -0,0 +1,27 @@
Copyright (c) 2014
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -20,7 +20,7 @@ import (
// Test that go1.1 tag above is included in builds. main.go refers to this definition.
const go11tag = true
const APP_VER = "0.0.6.0313"
const APP_VER = "0.0.7.0314"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())

View File

@ -9,6 +9,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"time"
@ -270,6 +271,7 @@ type RepoFile struct {
Id *git.Oid
Type int
Name string
Path string
Message string
Created time.Time
}
@ -282,7 +284,7 @@ func (f *RepoFile) IsDir() bool {
return f.Type == git.FilemodeTree
}
func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, error) {
func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile, error) {
f := RepoPath(userName, reposName)
repo, err := git.OpenRepository(f)
if err != nil {
@ -299,8 +301,28 @@ func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, er
if err != nil {
return nil, err
}
var i uint64 = 0
for ; i < tree.EntryCount(); i++ {
//var i uint64 = 0
if rpath != "" {
rpath = rpath + "/"
}
fmt.Println("...", rpath, "...")
tree.Walk(func(dirname string, entry *git.TreeEntry) int {
if dirname == rpath {
fmt.Println("====", dirname, "==", entry.Name)
repofiles = append(repofiles, &RepoFile{
entry.Id,
entry.Filemode,
entry.Name,
path.Join(dirname, entry.Name),
lastCommit.Message(),
lastCommit.Committer().When,
})
}
return 0
})
/*for ; i < tree.EntryCount(); i++ {
entry := tree.EntryByIndex(i)
repofiles = append(repofiles, &RepoFile{
@ -310,7 +332,7 @@ func GetReposFiles(userName, reposName, treeName, rpath string) ([]*RepoFile, er
lastCommit.Message(),
lastCommit.Committer().When,
})
}
}*/
return repofiles, nil
}
@ -354,6 +376,10 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
session.Rollback()
return err
}
if _, err := session.Delete(&Access{UserName: userName, RepoName: repo.Name}); err != nil {
session.Rollback()
return err
}
if _, err = session.Exec("update user set num_repos = num_repos - 1 where id = ?", userId); err != nil {
session.Rollback()
return err

View File

@ -48,7 +48,10 @@ type User struct {
NumFollowings int
NumStars int
NumRepos int
Avatar string `xorm:"varchar(2048) not null"`
Avatar string `xorm:"varchar(2048) not null"`
AvatarEmail string `xorm:"not null"`
Location string
Website string
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
@ -104,6 +107,7 @@ func RegisterUser(user *User) (err error) {
user.LowerName = strings.ToLower(user.Name)
user.Avatar = base.EncodeMd5(user.Email)
user.AvatarEmail = user.Email
if err = user.EncodePasswd(); err != nil {
return err
}

View File

@ -23,7 +23,7 @@ type Form interface {
}
type RegisterForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
RetypePasswd string `form:"retypepasswd"`
@ -59,7 +59,7 @@ func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, conte
}
type LogInForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
}
@ -90,11 +90,6 @@ func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context
validate(errors, data, f)
}
type FeedsForm struct {
UserId int64 `form:"userid" binding:"Required"`
Offset int64 `form:"offset"`
}
func getMinMaxSize(field reflect.StructField) string {
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {

View File

@ -5,10 +5,15 @@
package auth
import (
"net/http"
"reflect"
"github.com/codegangsta/martini"
"github.com/martini-contrib/render"
"github.com/martini-contrib/sessions"
"github.com/gogits/binding"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
@ -83,3 +88,76 @@ func SignOutRequire() martini.Handler {
}
}
}
type FeedsForm struct {
UserId int64 `form:"userid" binding:"Required"`
Offset int64 `form:"offset"`
}
type UpdateProfileForm struct {
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Website string `form:"website" binding:"MaxSize(50)"`
Location string `form:"location" binding:"MaxSize(50)"`
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
}
func (f *UpdateProfileForm) Name(field string) string {
names := map[string]string{
"Email": "Email address",
"Website": "Website",
"Location": "Location",
"Avatar": "Gravatar Email",
}
return names[field]
}
func (f *UpdateProfileForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
data["HasError"] = true
if len(errors.Overall) > 0 {
for _, err := range errors.Overall {
log.Error("UpdateProfileForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}
type UpdatePasswdForm struct {
OldPasswd string `form:"oldpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
NewPasswd string `form:"newpasswd" binding:"Required;MinSize(6);MaxSize(30)"`
RetypePasswd string `form:"retypepasswd"`
}
func (f *UpdatePasswdForm) Name(field string) string {
names := map[string]string{
"OldPasswd": "Old password",
"NewPasswd": "New password",
"RetypePasswd": "Re-type password",
}
return names[field]
}
func (f *UpdatePasswdForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
data["HasError"] = true
if len(errors.Overall) > 0 {
for _, err := range errors.Overall {
log.Error("UpdatePasswdForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}

View File

@ -7,6 +7,8 @@ package base
import (
"crypto/md5"
"encoding/hex"
"fmt"
"time"
)
// Encode string to md5 hex value
@ -15,3 +17,64 @@ func EncodeMd5(str string) string {
m.Write([]byte(str))
return hex.EncodeToString(m.Sum(nil))
}
// Seconds-based time units
const (
Minute = 60
Hour = 60 * Minute
Day = 24 * Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
)
// TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(then time.Time) string {
now := time.Now()
lbl := "ago"
diff := now.Unix() - then.Unix()
if then.After(now) {
lbl = "from now"
diff = then.Unix() - now.Unix()
}
switch {
case diff <= 0:
return "now"
case diff <= 2:
return fmt.Sprintf("1 second %s", lbl)
case diff < 1*Minute:
return fmt.Sprintf("%d seconds %s", diff, lbl)
case diff < 2*Minute:
return fmt.Sprintf("1 minute %s", lbl)
case diff < 1*Hour:
return fmt.Sprintf("%d minutes %s", diff/Minute, lbl)
case diff < 2*Hour:
return fmt.Sprintf("1 hour %s", lbl)
case diff < 1*Day:
return fmt.Sprintf("%d hours %s", diff/Hour, lbl)
case diff < 2*Day:
return fmt.Sprintf("1 day %s", lbl)
case diff < 1*Week:
return fmt.Sprintf("%d days %s", diff/Day, lbl)
case diff < 2*Week:
return fmt.Sprintf("1 week %s", lbl)
case diff < 1*Month:
return fmt.Sprintf("%d weeks %s", diff/Week, lbl)
case diff < 2*Month:
return fmt.Sprintf("1 month %s", lbl)
case diff < 1*Year:
return fmt.Sprintf("%d months %s", diff/Month, lbl)
case diff < 18*Month:
return fmt.Sprintf("1 year %s", lbl)
}
return then.String()
}

View File

@ -6,7 +6,13 @@
package log
import (
"fmt"
"github.com/martini-contrib/render"
"github.com/gogits/logs"
"github.com/gogits/gogs/modules/base"
)
var logger *logs.BeeLogger
@ -35,3 +41,9 @@ func Warn(format string, v ...interface{}) {
func Critical(format string, v ...interface{}) {
logger.Critical(format, v...)
}
func Handle(status int, title string, data base.TmplData, r render.Render, err error) {
data["ErrorMsg"] = err
Error("%s: %v", title, err)
r.HTML(status, fmt.Sprintf("status/%d", status), data)
}

View File

@ -137,6 +137,11 @@ body {
margin-top: 50px;
}
#gogs-body .btn-default {
background-color: #FFF;
background-image: linear-gradient(to bottom, #FFF 0, #FAFAFA 100%);
}
#gogs-body-nav {
margin-top: 52px;
margin-bottom: -50px;
@ -269,7 +274,8 @@ body {
/* gogits user setting */
#gogs-user-setting-nav > h4, #gogs-user-setting-container > h4, #gogs-ssh-keys > h4, #gogs-user-delete > h4 ,#gogs-repo-setting-container .tab-pane > h4{
#gogs-user-setting-nav > h4, #gogs-user-setting-container > h4, #gogs-user-setting-container > div > h4,
#gogs-ssh-keys > h4, #gogs-user-delete > h4, #gogs-repo-setting-container .tab-pane > h4 {
padding-bottom: 18px;
margin-bottom: 18px;
border-bottom: 1px solid #CCC;
@ -380,6 +386,7 @@ body {
}
#gogs-feed-right .repo-panel .list-group-item:hover {
background-color: #eafffd;
background-color: rgba(65, 131, 196, 0.1);
}
@ -391,6 +398,12 @@ body {
/* gogits repo single page */
#gogs-body-nav.gogs-repo-nav {
padding-top: 16px;
padding-bottom: 30px;
height: auto;
}
.gogs-repo-nav h3 .fa {
color: #BBB;
}
@ -429,32 +442,82 @@ body {
margin-bottom: 4px;
}
#gogs-repo-toolbar{
#gogs-repo-toolbar {
margin-top: 51px;
margin-bottom: -50px;
border-bottom: 1px solid #BBB;
background-color: #FFF;
height: 40px;
font-size: 14px;
}
#gogs-repo-toolbar .navbar-default{
#gogs-repo-toolbar .navbar-default {
border: none;
height: 39px;
}
#gogs-repo-toolbar .nav > li > a{
#gogs-repo-toolbar .nav > li > a {
height: 39px;
}
#gogs-repo-toolbar .navbar-toolbar.navbar-default .navbar-nav>.active>a:after{
#gogs-repo-toolbar .navbar-toolbar.navbar-default .navbar-nav > .active > a:after {
border-bottom-color: #999;
}
#gogs-repo-toolbar .navbar.nav-toolbar{
#gogs-repo-toolbar .navbar.nav-toolbar {
margin-bottom: 0;
}
#gogs-repo-toolbar .navbar-collapse{
#gogs-repo-toolbar .navbar-collapse {
padding: 0;
}
/* #gogs-source */
#gogs-source-toolbar:after {
clear: both;
}
#gogs-source-toolbar .branch-switch {
display: inline-block;
}
#gogs-source-toolbar .breadcrumb {
margin: 0 .5em;
font-size: 16px;
vertical-align: middle;
display: inline-block;
background-color: transparent;
}
#gogs-source-table {
margin-top: 1.5em;
font-size: 14px;
}
#gogs-source-table .fa{
font-size: 15px;
width: 16px;
text-align: center;
color: #666;
}
#gogs-source-table .name{
width: 160px;
}
#gogs-source-table .size{
width: 80px;
}
#gogs-source-table .date{
width: 120px;
}
#gogs-source-table .is-dir .name {
font-weight: bold;
}
#gogs-source-table.table-hover > tbody > tr:hover > td {
background-color: #FEFEFE;
}

View File

@ -78,7 +78,6 @@ function initRegister() {
rules: {
"username": {
required: true,
minlength: 5,
maxlength: 30
},
"email": {

View File

@ -5,9 +5,10 @@
package repo
import (
"net/http"
"github.com/martini-contrib/render"
"github.com/martini-contrib/sessions"
"net/http"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
@ -46,7 +47,7 @@ func Create(form auth.CreateRepoForm, req *http.Request, r render.Render, data b
if err == nil {
if _, err = models.CreateRepository(user,
form.RepoName, form.Description, form.Language, form.License,
form.Visibility == "private", form.InitReadme == "true"); err == nil {
form.Visibility == "private", form.InitReadme == "on"); err == nil {
if err == nil {
data["RepoName"] = user.Name + "/" + form.RepoName
r.HTML(200, "repo/created", data)
@ -63,9 +64,7 @@ func Create(form auth.CreateRepoForm, req *http.Request, r render.Render, data b
return
}
data["ErrorMsg"] = err
log.Error("repo.Create: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "repo.Create", data, r, err)
}
func Delete(form auth.DeleteRepoForm, req *http.Request, r render.Render, data base.TmplData, session sessions.Session) {
@ -77,13 +76,11 @@ func Delete(form auth.DeleteRepoForm, req *http.Request, r render.Render, data b
}
if err := models.DeleteRepository(form.UserId, form.RepoId, form.UserName); err != nil {
data["ErrorMsg"] = err
log.Error("repo.Delete: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "repo.Delete", data, r, err)
return
}
r.Redirect("/", 200)
r.Redirect("/", 302)
}
func List(req *http.Request, r render.Render, data base.TmplData, session sessions.Session) {
@ -96,9 +93,7 @@ func List(req *http.Request, r render.Render, data base.TmplData, session sessio
data["Title"] = "Repositories"
repos, err := models.GetRepositories(u)
if err != nil {
data["ErrorMsg"] = err
log.Error("repo.List: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "repo.List", data, r, err)
return
}

View File

@ -1,6 +1,7 @@
package repo
import (
"strings"
"github.com/codegangsta/martini"
"github.com/martini-contrib/render"
@ -13,15 +14,27 @@ func Single(params martini.Params, r render.Render, data base.TmplData) {
if !data["IsRepositoryValid"].(bool) {
return
}
files, err := models.GetReposFiles(params["username"], params["reponame"], "HEAD", "/")
if params["branchname"] == "" {
params["branchname"] = "master"
}
treename := params["_1"]
files, err := models.GetReposFiles(params["username"], params["reponame"],
params["branchname"], treename)
if err != nil {
data["ErrorMsg"] = err
log.Error("repo.List: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "repo.Single", data, r, err)
return
}
data["Username"] = params["username"]
data["Reponame"] = params["reponame"]
data["Branchname"] = params["branchname"]
treenames := strings.Split(treename, "/")
Paths := make([]string, 0)
for i, _ := range treenames {
Paths = append(Paths, strings.Join(treenames[0:i+1], "/"))
}
data["Paths"] = Paths
data["Treenames"] = treenames
data["IsRepoToolbarSource"] = true
data["Files"] = files

View File

@ -17,12 +17,72 @@ import (
"github.com/gogits/gogs/modules/log"
)
func Setting(r render.Render, data base.TmplData, session sessions.Session) {
func Setting(form auth.UpdateProfileForm, r render.Render, data base.TmplData, req *http.Request, session sessions.Session) {
data["Title"] = "Setting"
data["PageIsUserSetting"] = true
user := auth.SignedInUser(session)
data["Owner"] = user
if req.Method == "GET" {
r.HTML(200, "user/setting", data)
return
}
if hasErr, ok := data["HasError"]; ok && hasErr.(bool) {
r.HTML(200, "user/setting", data)
return
}
user.Email = form.Email
user.Website = form.Website
user.Location = form.Location
user.Avatar = base.EncodeMd5(form.Avatar)
user.AvatarEmail = form.Avatar
if err := models.UpdateUser(user); err != nil {
log.Handle(200, "setting.Setting", data, r, err)
return
}
data["IsSuccess"] = true
r.HTML(200, "user/setting", data)
}
func SettingPassword(form auth.UpdatePasswdForm, r render.Render, data base.TmplData, session sessions.Session, req *http.Request) {
data["Title"] = "Password"
data["PageIsUserSetting"] = true
if req.Method == "GET" {
r.HTML(200, "user/password", data)
return
}
user := auth.SignedInUser(session)
newUser := &models.User{Passwd: form.NewPasswd}
if err := newUser.EncodePasswd(); err != nil {
log.Handle(200, "setting.SettingPassword", data, r, err)
return
}
if user.Passwd != newUser.Passwd {
data["HasError"] = true
data["ErrorMsg"] = "Old password is not correct"
} else if form.NewPasswd != form.RetypePasswd {
data["HasError"] = true
data["ErrorMsg"] = "New password and re-type password are not same"
} else {
user.Passwd = newUser.Passwd
if err := models.UpdateUser(user); err != nil {
log.Handle(200, "setting.SettingPassword", data, r, err)
return
}
data["IsSuccess"] = true
}
data["Owner"] = user
r.HTML(200, "user/password", data)
}
func SettingSSHKeys(form auth.AddSSHKeyForm, r render.Render, data base.TmplData, req *http.Request, session sessions.Session) {
data["Title"] = "SSH Keys"
@ -94,3 +154,17 @@ func SettingSSHKeys(form auth.AddSSHKeyForm, r render.Render, data base.TmplData
data["Keys"] = keys
r.HTML(200, "user/publickey", data)
}
func SettingNotification(r render.Render, data base.TmplData) {
// todo user setting notification
data["Title"] = "Notification"
data["PageIsUserSetting"] = true
r.HTML(200, "user/notification", data)
}
func SettingSecurity(r render.Render, data base.TmplData) {
// todo user setting security
data["Title"] = "Security"
data["PageIsUserSetting"] = true
r.HTML(200, "user/security", data)
}

View File

@ -22,9 +22,7 @@ func Dashboard(r render.Render, data base.TmplData, session sessions.Session) {
data["PageIsUserDashboard"] = true
repos, err := models.GetRepositories(&models.User{Id: auth.SignedInId(session)})
if err != nil {
data["ErrorMsg"] = err
log.Error("dashboard: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "user.Dashboard", data, r, err)
return
}
data["MyRepos"] = repos
@ -37,14 +35,11 @@ func Profile(params martini.Params, r render.Render, data base.TmplData, session
// TODO: Need to check view self or others.
user, err := models.GetUserByName(params["username"])
if err != nil {
data["ErrorMsg"] = err
log.Error("user.Profile: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "user.Profile", data, r, err)
return
}
data["Avatar"] = user.Avatar
data["Username"] = user.Name
data["Owner"] = user
r.HTML(200, "user/profile", data)
}
@ -71,9 +66,7 @@ func SignIn(form auth.LogInForm, data base.TmplData, req *http.Request, r render
return
}
data["ErrorMsg"] = err
log.Error("user.SignIn: %v", err)
r.HTML(200, "base/error", data)
log.Handle(200, "user.SignIn", data, r, err)
return
}
@ -130,9 +123,7 @@ func SignUp(form auth.RegisterForm, data base.TmplData, req *http.Request, r ren
data["ErrorMsg"] = "E-mail address has been already used"
r.HTML(200, "user/signup", data)
default:
data["ErrorMsg"] = err
log.Error("user.SignUp: %v", data)
r.HTML(200, "base/error", nil)
log.Handle(200, "user.SignUp", data, r, err)
}
return
}
@ -156,9 +147,7 @@ func Delete(data base.TmplData, req *http.Request, session sessions.Session, r r
case models.ErrUserOwnRepos.Error():
data["ErrorMsg"] = "Your account still have ownership of repository, you have to delete or transfer them first."
default:
data["ErrorMsg"] = err
log.Error("user.Delete: %v", data)
r.HTML(200, "base/error", nil)
log.Handle(200, "user.Delete", data, r, err)
return
}
}

View File

@ -1,6 +1,17 @@
<div id="gogs-body-nav" class="gogs-repo-nav">
<div class="container">
<div class="gogs-repo-btns pull-right">
<div class="btn-group" id="gogs-repo-clone">
<button type="button" class="btn btn-default"><i class="fa fa-download"></i>Clone</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" role="menu">
<div data-val="down-http">http link</div>
<div data-val="down-git">git link</div>
</div>
</div>
<div class="btn-group" id="gogs-repo-watching">
<button type="button" class="btn btn-default"><i class="fa fa-eye"></i>Watch {x}</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">

View File

@ -3,11 +3,58 @@
{{template "repo/nav" .}}
{{template "repo/toolbar" .}}
<div id="gogs-body" class="container">
<h4>Source Files:</h4>
<ul>
{{range .Files}}
<li>{{.Name}} - {{.Id}} - {{.Message}} - {{.Created}} - {{.IsFile}} - {{.IsDir}}</li>
{{end}}
</ul>
<div id="gogs-source">
<div id="gogs-source-toolbar">
<button class="btn btn-default pull-right"><i class="fa fa-plus-square"></i>Add File</button>
<div class="dropdown branch-switch">
<a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>master&nbsp;&nbsp;
<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a class="current" href="/{{.RepositoryLink}}/branch/master">master</a></li>
<li><a href="//{{.RepositoryLink}}/branch/develop">develop</a></li>
</ul>
</div>
<ol class="breadcrumb">
<li class="root dir">{{.Repository.Name}}</li>
{{$paths := .Paths}}
{{ $username := .Username}}
{{ $reponame := .Reponame}}
{{ $branchname := .Branchname}}
{{ $treenames := .Treenames}}
{{ $n := len $treenames}}
{{ $l := Subtract $n 1}}
{{range $i, $v := $treenames}}
<li class="dir">
{{if eq $i $l}}{{$v}}
{{else}}
<a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}/{{index $paths $i}}">{{$v}}</a>&nbsp;
{{end}}</li>
{{end}}
</ol>
</div>
<table id="gogs-source-table" class="table table-hover">
<thead class="hidden">
<tr>
<th class="name">Filename</th>
<th class="date">Date modified</th>
<th class="text">Message</th>
</tr>
</thead>
<tbody>
{{range .Files}}
<tr {{if .IsDir}}class="is-dir"{{end}}>
<td class="name"><i class="fa {{if .IsDir}}fa-folder{{else}}fa-file{{end}}"></i>
{{if .IsDir}}
<a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}/{{.Path}}">{{.Name}}</a>
{{else}}
<a href="#">{{.Name}}</a>
{{end}}</td>
<td class="date"><time datetime="{{.Created}}" data-title="true" title="{{.Created}}">{{TimeSince .Created}}</time></td>
<td class="text">{{.Message}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
{{template "base/footer" .}}

View File

@ -1,40 +1,32 @@
<div id="gogs-repo-toolbar">
<div class="container">
<nav class="navbar navbar-toolbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Branches <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">master</a></li>
<li><a href="#">develop</a></li>
</ul>
</li>
<li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
<li><a href="#">Commits</a></li>
<li><a href="#">Issues <span class="badge">42</span></a></li>
<li><a href="#">Pull Requests</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Release</a></li>
<li><a href="#">Wiki</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Statistic <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Graphic</a></li>
<li><a href="#">Pulse</a></li>
<li><a href="#">Network</a></li>
</ul>
</li>{{if .IsRepositoryOwner}}
<li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a></li>{{end}}
</ul>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
<li><a href="/{{.RepositoryLink}}/commits">Commits</a></li>
<li><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
<li><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/{{.RepositoryLink}}/release">Release</a></li>
<li><a href="//{{.RepositoryLink}}/wiki">Wiki</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Statistic <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Graphic</a></li>
<li><a href="#">Pulse</a></li>
<li><a href="#">Network</a></li>
</ul>
</li>{{if .IsRepositoryOwner}}
<li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a>
</li>{{end}}
</ul>
</div>
</nav>
</div>

View File

@ -5,10 +5,10 @@
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item"><a href="#">Emails and Password</a></li>
<li class="list-group-item"><a href="#">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/password">Password</a></li>
<li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item"><a href="#">Security</a></li>
<li class="list-group-item"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item list-group-item-success"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>

View File

@ -0,0 +1,19 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item"><a href="/user/setting/password">Password</a></li>
<li class="list-group-item list-group-item-success"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>
<div id="gogs-user-setting-container" class="col-md-9">
<h4>Notification</h4>
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,51 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item list-group-item-success"><a href="/user/setting/password">Password</a></li>
<li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>
<div id="gogs-user-setting-container" class="col-md-9">
<div id="gogs-setting-pwd">
<h4>Password</h4>
<form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting/password">{{if .IsSuccess}}
<p class="alert alert-success">Password is changed successfully. You can now sign in via new password.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<div class="form-group">
<label class="col-md-2 control-label">Old Password<strong class="text-danger">*</strong></label>
<div class="col-md-8">
<input type="password" name="oldpasswd" class="form-control" placeholder="Type your current password" required="required">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">New Password<strong class="text-danger">*</strong></label>
<div class="col-md-8">
<input type="password" name="newpasswd" class="form-control" placeholder="Type your new password" required="required">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Re-Type<strong class="text-danger">*</strong></label>
<div class="col-md-8">
<input type="password" name="retypepasswd" class="form-control" placeholder="Re-type your new password" required="required">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-8">
<button type="submit" class="btn btn-primary">Change Password</button>&nbsp;&nbsp;
<a href="/forget-password/">Forgot your password?</a>
</div>
</div>
</form>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -4,16 +4,16 @@
<div id="gogs-user-profile" class="col-md-3">
<div class="profile-avatar text-center">
<a href="#" class="center-block" data-toggle="tooltip" data-placement="bottom" title="Change Avatar">
<img id="gogs-user-avatar" src="http://1.gravatar.com/avatar/{{.Avatar}}?s=200" alt="user-avatar" title="username"/>
<img id="gogs-user-avatar" src="http://1.gravatar.com/avatar/{{.Owner.Avatar}}?s=200" alt="user-avatar" title="username"/>
</a>
<span id="gogs-user-name" class="center-block" href="#">{{.Username}}</span>
<span id="gogs-user-name" class="center-block" href="#">{{.Owner.Name}}</span>
</div>
<div class="profile-info">
<ul class="list-group">
<li class="list-group-item"><i class="fa fa-thumb-tack"></i>City, County, State, Nation</li>
<li class="list-group-item"><i class="fa fa-envelope"></i><a href="#">Email@EmailAddress.com</a></li>
<li class="list-group-item"><i class="fa fa-link"></i><a href="#">http://yousite/</a></li>
<li class="list-group-item"><i class="fa fa-clock-o"></i>Joined At 03.02, 2014</li>
<li class="list-group-item"><i class="fa fa-thumb-tack"></i>{{.Owner.Location}}</li>
<li class="list-group-item"><i class="fa fa-envelope"></i><a href="mailto:{{.Owner.Email}}">{{.Owner.Email}}</a></li>
<li class="list-group-item"><i class="fa fa-link"></i><a target="_blank" href="{{.Owner.Website}}">{{.Owner.Website}}</a></li>
<li class="list-group-item"><i class="fa fa-clock-o"></i>{{.Owner.Created}}</li>
</ul>
</div>
</div>

View File

@ -5,10 +5,10 @@
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item"><a href="#">Emails and Password</a></li>
<li class="list-group-item"><a href="#">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/Password">Password</a></li>
<li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item list-group-item-success"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item"><a href="#">Security</a></li>
<li class="list-group-item"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>

View File

@ -1,26 +0,0 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container" id="gogs-body">
<form action="/user/publickey/add" method="post" class="form-horizontal">
<div class="form-group">
<label class="col-md-4 control-label">Name of this public key: </label>
<div class="col-md-3">
<input name="keyname" class="form-control" placeholder="Type your preferred name" value="{{.KeyName}}">
</div>
</div>
<div class="form-group">
<label class="col-md-4 control-label">Paste your key here: </label>
<div class="col-md-3">
<textarea name="key_content" cols="30" rows="10" class="form-control">{{.KeyContent}}</textarea>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-4 col-md-3">
<button type="submit" class="btn btn-info">Add public key</button>
</div>
</div>
</form>
</div>
{{template "base/footer" .}}

View File

@ -1,8 +0,0 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container">
<div class="form-group">
publickey added
</div>
</div>
{{template "base/footer" .}}

View File

@ -1,12 +0,0 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container" id="gogs-body">
<div><a href="/user/publickey/add">Add publick key</a></div>
<ul>
{{range .Keys}}
<li>{{.Name}}</li>
<li>{{.Content}}</li>
{{end}}
</ul>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,19 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item"><a href="/user/setting/password">Password</a></li>
<li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item list-group-item-success"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>
<div id="gogs-user-setting-container" class="col-md-9">
<h4>Security</h4>
</div>
</div>
{{template "base/footer" .}}

View File

@ -5,15 +5,54 @@
<h4>Account Setting</h4>
<ul class="list-group">
<li class="list-group-item list-group-item-success"><a href="/user/setting">Account Profile</a></li>
<li class="list-group-item"><a href="#">Emails and Password</a></li>
<li class="list-group-item"><a href="#">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh">SSH Keys</a></li>
<li class="list-group-item"><a href="#">Security</a></li>
<li class="list-group-item"><a href="/user/setting/password">Password</a></li>
<li class="list-group-item"><a href="/user/setting/notification">Notifications</a></li>
<li class="list-group-item"><a href="/user/setting/ssh/">SSH Keys</a></li>
<li class="list-group-item"><a href="/user/setting/security">Security</a></li>
<li class="list-group-item"><a href="/user/delete">Delete Account</a></li>
</ul>
</div>
<div id="gogs-user-setting-container" class="col-md-9">
setting container
<div id="gogs-setting-pwd">
<h4>Account Profile</h4>
<form class="form-horizontal" id="gogs-password-form" method="post" action="/user/setting">{{if .IsSuccess}}
<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p>
<div class="form-group">
<label class="col-md-2 control-label">Email</label>
<div class="col-md-8">
<input type="text" name="email" class="form-control" placeholder="Type your e-mail address" value="{{.Owner.Email}}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Website</label>
<div class="col-md-8">
<input type="text" name="website" class="form-control" placeholder="Type your website URL" value="{{.Owner.Website}}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Location</label>
<div class="col-md-8">
<input type="text" name="location" class="form-control" placeholder="Type your current location" value="{{.Owner.Location}}">
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Gravatar Email<strong class="text-danger">*</strong></label>
<div class="col-md-8">
<input type="text" name="avatar" class="form-control" placeholder="Type your Gravatar e-mail address" required="required" value="{{.Owner.AvatarEmail}}">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-8">
<button type="submit" class="btn btn-primary">Update Profile</button>
</div>
</div>
</form>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -7,7 +7,7 @@
<div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}">
<label class="col-md-4 control-label">Username: </label>
<div class="col-md-6">
<input name="username" class="form-control" placeholder="Type your username" value="{{.username}}" required="required" title="Username must contain at least 5 characters">
<input name="username" class="form-control" placeholder="Type your username" value="{{.username}}" required="required">
</div>
</div>

60
web.go
View File

@ -33,6 +33,55 @@ gogs web`,
Flags: []cli.Flag{},
}
func Subtract(left interface{}, right interface{}) interface{} {
var rleft, rright int64
var fleft, fright float64
var isInt bool = true
switch left.(type) {
case int:
rleft = int64(left.(int))
case int8:
rleft = int64(left.(int8))
case int16:
rleft = int64(left.(int16))
case int32:
rleft = int64(left.(int32))
case int64:
rleft = left.(int64)
case float32:
fleft = float64(left.(float32))
isInt = false
case float64:
fleft = left.(float64)
isInt = false
}
switch right.(type) {
case int:
rright = int64(right.(int))
case int8:
rright = int64(right.(int8))
case int16:
rright = int64(right.(int16))
case int32:
rright = int64(right.(int32))
case int64:
rright = right.(int64)
case float32:
fright = float64(left.(float32))
isInt = false
case float64:
fleft = left.(float64)
isInt = false
}
if isInt {
return rleft - rright
} else {
return fleft + float64(rleft) - (fright + float64(rright))
}
}
var AppHelpers template.FuncMap = map[string]interface{}{
"AppName": func() string {
return base.Cfg.MustValue("", "APP_NAME")
@ -40,6 +89,8 @@ var AppHelpers template.FuncMap = map[string]interface{}{
"AppVer": func() string {
return APP_VER
},
"TimeSince": base.TimeSince,
"Subtract": Subtract,
}
func runWeb(*cli.Context) {
@ -63,8 +114,11 @@ func runWeb(*cli.Context) {
m.Any("/user/delete", auth.SignInRequire(true), user.Delete)
m.Get("/user/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
m.Any("/user/setting", auth.SignInRequire(true), user.Setting)
m.Any("/user/setting", auth.SignInRequire(true), binding.BindIgnErr(auth.UpdateProfileForm{}), user.Setting)
m.Any("/user/setting/password", auth.SignInRequire(true), binding.BindIgnErr(auth.UpdatePasswdForm{}), user.SettingPassword)
m.Any("/user/setting/ssh", auth.SignInRequire(true), binding.BindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
m.Any("/user/setting/notification", auth.SignInRequire(true), user.SettingNotification)
m.Any("/user/setting/security", auth.SignInRequire(true), user.SettingSecurity)
m.Get("/user/:username", auth.SignInRequire(false), user.Profile)
@ -73,6 +127,10 @@ func runWeb(*cli.Context) {
m.Any("/repo/list", auth.SignInRequire(false), repo.List)
m.Get("/:username/:reponame/settings", auth.SignInRequire(false), auth.RepoAssignment(true), repo.Setting)
m.Get("/:username/:reponame/tree/:branchname/**",
auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/tree/:branchname",
auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame", auth.SignInRequire(false), auth.RepoAssignment(true), repo.Single)
//m.Get("/:username/:reponame", repo.Repo)