From 54e95fa367d8f9394522ce1fa6905a38974cbd23 Mon Sep 17 00:00:00 2001
From: Unknown <joe2010xtmf@163.com>
Date: Mon, 12 May 2014 14:06:42 -0400
Subject: [PATCH] Finish add new milestone

---
 cmd/web.go                                |  3 +-
 gogs.go                                   |  2 +-
 models/issue.go                           | 42 +++++++++-
 models/models.go                          |  6 +-
 models/repo.go                            | 47 +++++------
 modules/auth/issue.go                     | 35 ---------
 modules/auth/{setting.go => publickey.go} |  0
 modules/auth/release.go                   | 36 ---------
 modules/auth/repo.go                      | 96 ++++++++++++++++++++++-
 modules/middleware/repo.go                |  1 +
 routers/repo/issue.go                     | 60 ++++++++++++--
 templates/admin/dashboard.tmpl            |  2 +-
 templates/issue/milestone.tmpl            | 40 +++-------
 templates/issue/milestone_new.tmpl        |  2 +-
 14 files changed, 236 insertions(+), 136 deletions(-)
 delete mode 100644 modules/auth/issue.go
 rename modules/auth/{setting.go => publickey.go} (100%)
 delete mode 100644 modules/auth/release.go

diff --git a/cmd/web.go b/cmd/web.go
index d7a760405c..b8cfc5a4f5 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -186,7 +186,8 @@ func runWeb(*cli.Context) {
 		r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
 		r.Post("/issues/:index/assignee", repo.UpdateAssignee)
 		r.Get("/issues/milestones", repo.Milestones)
-		r.Get("/issues/milestones/new", repo.NewMilestones)
+		r.Get("/issues/milestones/new", repo.NewMilestone)
+		r.Post("/issues/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
 		r.Get("/issues/milestones/edit", repo.UpdateMilestones)
 		r.Post("/comment/:action", repo.Comment)
 		r.Get("/releases/new", repo.ReleasesNew)
diff --git a/gogs.go b/gogs.go
index 8d5b021c34..912cd77788 100644
--- a/gogs.go
+++ b/gogs.go
@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/base"
 )
 
-const APP_VER = "0.3.3.0511 Alpha"
+const APP_VER = "0.3.3.0512 Alpha"
 
 func init() {
 	base.AppVer = APP_VER
diff --git a/models/issue.go b/models/issue.go
index 40d3bab04d..fa9d9427ae 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -20,9 +20,9 @@ var (
 // Issue represents an issue or pull request of repository.
 type Issue struct {
 	Id              int64
+	RepoId          int64 `xorm:"INDEX"`
 	Index           int64 // Index in one repository.
 	Name            string
-	RepoId          int64       `xorm:"INDEX"`
 	Repo            *Repository `xorm:"-"`
 	PosterId        int64
 	Poster          *User `xorm:"-"`
@@ -390,7 +390,7 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
 // Label represents a label of repository for issues.
 type Label struct {
 	Id              int64
-	Rid             int64 `xorm:"INDEX"`
+	RepoId          int64 `xorm:"INDEX"`
 	Name            string
 	Color           string
 	NumIssues       int
@@ -401,17 +401,53 @@ type Label struct {
 // Milestone represents a milestone of repository.
 type Milestone struct {
 	Id              int64
-	Rid             int64 `xorm:"INDEX"`
+	RepoId          int64 `xorm:"INDEX"`
+	Index           int64
 	Name            string
 	Content         string
+	RenderedContent string `xorm:"-"`
 	IsClosed        bool
 	NumIssues       int
 	NumClosedIssues int
+	NumOpenIssues   int `xorm:"-"`
 	Completeness    int // Percentage(1-100).
 	Deadline        time.Time
 	ClosedDate      time.Time
 }
 
+// CalOpenIssues calculates the open issues of milestone.
+func (m *Milestone) CalOpenIssues() {
+	m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
+}
+
+// NewMilestone creates new milestone of repository.
+func NewMilestone(m *Milestone) (err error) {
+	sess := orm.NewSession()
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	if _, err = sess.Insert(m); err != nil {
+		sess.Rollback()
+		return err
+	}
+
+	rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
+	if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
+		sess.Rollback()
+		return err
+	}
+	return sess.Commit()
+}
+
+// GetMilestones returns a list of milestones of given repository and status.
+func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
+	miles := make([]*Milestone, 0, 10)
+	err := orm.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
+	return miles, err
+}
+
 // Issue types.
 const (
 	IT_PLAIN  = iota // Pure comment.
diff --git a/models/models.go b/models/models.go
index 02141b7a8f..3adec1a56c 100644
--- a/models/models.go
+++ b/models/models.go
@@ -34,7 +34,8 @@ var (
 func init() {
 	tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
 		new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
-		new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser))
+		new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
+		new(Milestone))
 }
 
 func LoadModelsConfig() {
@@ -141,7 +142,7 @@ type Statistic struct {
 	Counter struct {
 		User, PublicKey, Repo, Watch, Action, Access,
 		Issue, Comment, Mirror, Oauth, Release,
-		LoginSource, Webhook int64
+		LoginSource, Webhook, Milestone int64
 	}
 }
 
@@ -159,6 +160,7 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Release, _ = orm.Count(new(Release))
 	stats.Counter.LoginSource, _ = orm.Count(new(LoginSource))
 	stats.Counter.Webhook, _ = orm.Count(new(Webhook))
+	stats.Counter.Milestone, _ = orm.Count(new(Milestone))
 	return
 }
 
diff --git a/models/repo.go b/models/repo.go
index 32baf36dae..569c9571b7 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -92,28 +92,31 @@ func NewRepoContext() {
 
 // Repository represents a git repository.
 type Repository struct {
-	Id              int64
-	OwnerId         int64 `xorm:"unique(s)"`
-	Owner           *User `xorm:"-"`
-	ForkId          int64
-	LowerName       string `xorm:"unique(s) index not null"`
-	Name            string `xorm:"index not null"`
-	Description     string
-	Website         string
-	NumWatches      int
-	NumStars        int
-	NumForks        int
-	NumIssues       int
-	NumClosedIssues int
-	NumOpenIssues   int `xorm:"-"`
-	NumTags         int `xorm:"-"`
-	IsPrivate       bool
-	IsMirror        bool
-	IsBare          bool
-	IsGoget         bool
-	DefaultBranch   string
-	Created         time.Time `xorm:"created"`
-	Updated         time.Time `xorm:"updated"`
+	Id                  int64
+	OwnerId             int64 `xorm:"unique(s)"`
+	Owner               *User `xorm:"-"`
+	ForkId              int64
+	LowerName           string `xorm:"unique(s) index not null"`
+	Name                string `xorm:"index not null"`
+	Description         string
+	Website             string
+	NumWatches          int
+	NumStars            int
+	NumForks            int
+	NumIssues           int
+	NumClosedIssues     int
+	NumOpenIssues       int `xorm:"-"`
+	NumMilestones       int `xorm:"NOT NULL DEFAULT 0"`
+	NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
+	NumOpenMilestones   int `xorm:"-"`
+	NumTags             int `xorm:"-"`
+	IsPrivate           bool
+	IsMirror            bool
+	IsBare              bool
+	IsGoget             bool
+	DefaultBranch       string
+	Created             time.Time `xorm:"created"`
+	Updated             time.Time `xorm:"updated"`
 }
 
 func (repo *Repository) GetOwner() (err error) {
diff --git a/modules/auth/issue.go b/modules/auth/issue.go
deleted file mode 100644
index f3cad520ce..0000000000
--- a/modules/auth/issue.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 auth
-
-import (
-	"net/http"
-	"reflect"
-
-	"github.com/go-martini/martini"
-
-	"github.com/gogits/gogs/modules/base"
-	"github.com/gogits/gogs/modules/middleware/binding"
-)
-
-type CreateIssueForm struct {
-	IssueName   string `form:"title" binding:"Required;MaxSize(50)"`
-	MilestoneId int64  `form:"milestoneid"`
-	AssigneeId  int64  `form:"assigneeid"`
-	Labels      string `form:"labels"`
-	Content     string `form:"content"`
-}
-
-func (f *CreateIssueForm) Name(field string) string {
-	names := map[string]string{
-		"IssueName": "Issue name",
-	}
-	return names[field]
-}
-
-func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
-	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
-	validate(errors, data, f)
-}
diff --git a/modules/auth/setting.go b/modules/auth/publickey.go
similarity index 100%
rename from modules/auth/setting.go
rename to modules/auth/publickey.go
diff --git a/modules/auth/release.go b/modules/auth/release.go
deleted file mode 100644
index 7fadb3c080..0000000000
--- a/modules/auth/release.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 auth
-
-import (
-	"net/http"
-	"reflect"
-
-	"github.com/go-martini/martini"
-
-	"github.com/gogits/gogs/modules/base"
-	"github.com/gogits/gogs/modules/middleware/binding"
-)
-
-type NewReleaseForm struct {
-	TagName    string `form:"tag_name" binding:"Required"`
-	Title      string `form:"title" binding:"Required"`
-	Content    string `form:"content" binding:"Required"`
-	Prerelease bool   `form:"prerelease"`
-}
-
-func (f *NewReleaseForm) Name(field string) string {
-	names := map[string]string{
-		"TagName": "Tag name",
-		"Title":   "Release title",
-		"Content": "Release content",
-	}
-	return names[field]
-}
-
-func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
-	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
-	validate(errors, data, f)
-}
diff --git a/modules/auth/repo.go b/modules/auth/repo.go
index f880cdf5d5..92ca839f85 100644
--- a/modules/auth/repo.go
+++ b/modules/auth/repo.go
@@ -14,6 +14,13 @@ import (
 	"github.com/gogits/gogs/modules/middleware/binding"
 )
 
+// __________                           .__  __
+// \______   \ ____ ______   ____  _____|__|/  |_  ___________ ___.__.
+//  |       _// __ \\____ \ /  _ \/  ___/  \   __\/  _ \_  __ <   |  |
+//  |    |   \  ___/|  |_> >  <_> )___ \|  ||  | (  <_> )  | \/\___  |
+//  |____|_  /\___  >   __/ \____/____  >__||__|  \____/|__|   / ____|
+//         \/     \/|__|              \/                       \/
+
 type CreateRepoForm struct {
 	RepoName    string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
 	Private     bool   `form:"private"`
@@ -63,7 +70,7 @@ func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, co
 type RepoSettingForm struct {
 	RepoName    string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
 	Description string `form:"desc" binding:"MaxSize(100)"`
-	Website     string `form:"url" binding:"Url;MaxSize(100)"`
+	Website     string `form:"site" binding:"Url;MaxSize(100)"`
 	Branch      string `form:"branch"`
 	Interval    int    `form:"interval"`
 	Private     bool   `form:"private"`
@@ -84,6 +91,13 @@ func (f *RepoSettingForm) Validate(errors *binding.Errors, req *http.Request, co
 	validate(errors, data, f)
 }
 
+//  __      __      ___.   .__    .__            __
+// /  \    /  \ ____\_ |__ |  |__ |  |__   ____ |  | __
+// \   \/\/   // __ \| __ \|  |  \|  |  \ /  _ \|  |/ /
+//  \        /\  ___/| \_\ \   Y  \   Y  (  <_> )    <
+//   \__/\  /  \___  >___  /___|  /___|  /\____/|__|_ \
+//        \/       \/    \/     \/     \/            \/
+
 type NewWebhookForm struct {
 	Url         string `form:"url" binding:"Required;Url"`
 	ContentType string `form:"content_type" binding:"Required"`
@@ -104,3 +118,83 @@ func (f *NewWebhookForm) Validate(errors *binding.Errors, req *http.Request, con
 	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
 	validate(errors, data, f)
 }
+
+// .___
+// |   | ______ ________ __   ____
+// |   |/  ___//  ___/  |  \_/ __ \
+// |   |\___ \ \___ \|  |  /\  ___/
+// |___/____  >____  >____/  \___  >
+//          \/     \/            \/
+
+type CreateIssueForm struct {
+	IssueName   string `form:"title" binding:"Required;MaxSize(50)"`
+	MilestoneId int64  `form:"milestoneid"`
+	AssigneeId  int64  `form:"assigneeid"`
+	Labels      string `form:"labels"`
+	Content     string `form:"content"`
+}
+
+func (f *CreateIssueForm) Name(field string) string {
+	names := map[string]string{
+		"IssueName": "Issue name",
+	}
+	return names[field]
+}
+
+func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errors, data, f)
+}
+
+//    _____  .__.__                   __
+//   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
+//  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
+// /    Y    \  |  |_\  ___/ \___ \  |  | (  <_> )   |  \  ___/
+// \____|__  /__|____/\___  >____  > |__|  \____/|___|  /\___  >
+//         \/             \/     \/                   \/     \/
+
+type CreateMilestoneForm struct {
+	Title    string `form:"title" binding:"Required;MaxSize(50)"`
+	Content  string `form:"content"`
+	Deadline string `form:"due_date"`
+}
+
+func (f *CreateMilestoneForm) Name(field string) string {
+	names := map[string]string{
+		"Title": "Milestone name",
+	}
+	return names[field]
+}
+
+func (f *CreateMilestoneForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errors, data, f)
+}
+
+// __________       .__
+// \______   \ ____ |  |   ____ _____    ______ ____
+//  |       _// __ \|  | _/ __ \\__  \  /  ___// __ \
+//  |    |   \  ___/|  |_\  ___/ / __ \_\___ \\  ___/
+//  |____|_  /\___  >____/\___  >____  /____  >\___  >
+//         \/     \/          \/     \/     \/     \/
+
+type NewReleaseForm struct {
+	TagName    string `form:"tag_name" binding:"Required"`
+	Title      string `form:"title" binding:"Required"`
+	Content    string `form:"content" binding:"Required"`
+	Prerelease bool   `form:"prerelease"`
+}
+
+func (f *NewReleaseForm) Name(field string) string {
+	names := map[string]string{
+		"TagName": "Tag name",
+		"Title":   "Release title",
+		"Content": "Release content",
+	}
+	return names[field]
+}
+
+func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+	data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+	validate(errors, data, f)
+}
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index ff99f82897..1bd1ea475d 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -128,6 +128,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
 		}
 
 		repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
+		repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
 		ctx.Repo.Repository = repo
 		ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
 
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 3ef6236aac..2958d8eb1a 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"net/url"
 	"strings"
+	"time"
 
 	"github.com/Unknwon/com"
 	"github.com/go-martini/martini"
@@ -144,9 +145,9 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
 		form.AssigneeId = 0
 	}
 	issue := &models.Issue{
+		RepoId:      ctx.Repo.Repository.Id,
 		Index:       int64(ctx.Repo.Repository.NumIssues) + 1,
 		Name:        form.IssueName,
-		RepoId:      ctx.Repo.Repository.Id,
 		PosterId:    ctx.User.Id,
 		MilestoneId: form.MilestoneId,
 		AssigneeId:  form.AssigneeId,
@@ -385,7 +386,6 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
-	// TODO: check collaborators
 	// Check if issue owner changes the status of issue.
 	var newStatus string
 	if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
@@ -488,15 +488,64 @@ func Milestones(ctx *middleware.Context) {
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
 
+	isShowClosed := ctx.Query("state") == "closed"
+
+	miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed)
+	if err != nil {
+		ctx.Handle(500, "issue.Milestones(GetMilestones)", err)
+		return
+	}
+	for _, m := range miles {
+		m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink))
+		m.CalOpenIssues()
+	}
+	ctx.Data["Milestones"] = miles
+
+	if isShowClosed {
+		ctx.Data["State"] = "closed"
+	} else {
+		ctx.Data["State"] = "open"
+	}
 	ctx.HTML(200, "issue/milestone")
 }
 
-func NewMilestones(ctx *middleware.Context) {
-	ctx.Data["Title"] = "New Milestones"
+func NewMilestone(ctx *middleware.Context) {
+	ctx.Data["Title"] = "New Milestone"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+	ctx.HTML(200, "issue/milestone_new")
+}
+
+func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
+	ctx.Data["Title"] = "New Milestone"
 	ctx.Data["IsRepoToolbarIssues"] = true
 	ctx.Data["IsRepoToolbarIssuesList"] = true
 
-	ctx.HTML(200, "issue/milestone_new")
+	var deadline time.Time
+	var err error
+	if len(form.Deadline) == 0 {
+		deadline = time.Now().AddDate(100, 0, 0)
+	} else {
+		deadline, err = time.Parse("01/02/2006", form.Deadline)
+		if err != nil {
+			ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err)
+			return
+		}
+	}
+
+	m := &models.Milestone{
+		RepoId:   ctx.Repo.Repository.Id,
+		Index:    int64(ctx.Repo.Repository.NumMilestones) + 1,
+		Name:     form.Title,
+		Content:  form.Content,
+		Deadline: deadline,
+	}
+	if err = models.NewMilestone(m); err != nil {
+		ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err)
+		return
+	}
+
+	ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
 }
 
 func UpdateMilestones(ctx *middleware.Context) {
@@ -506,4 +555,3 @@ func UpdateMilestones(ctx *middleware.Context) {
 
 	ctx.HTML(200, "issue/milestone_edit")
 }
-
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index 2de10b3448..f709cb3f28 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -10,7 +10,7 @@
             </div>
 
             <div class="panel-body">
-                Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, <b>{{.Stats.Counter.Access}}</b> accesses, <b>{{.Stats.Counter.Issue}}</b> issues, <b>{{.Stats.Counter.Comment}}</b> comments, <b>{{.Stats.Counter.Mirror}}</b> mirrors, <b>{{.Stats.Counter.Oauth}}</b> oauthes, <b>{{.Stats.Counter.Release}}</b> releases, <b>{{.Stats.Counter.LoginSource}}</b> login sources, <b>{{.Stats.Counter.Webhook}}</b> webhooks.
+                Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, <b>{{.Stats.Counter.Access}}</b> accesses, <b>{{.Stats.Counter.Issue}}</b> issues, <b>{{.Stats.Counter.Comment}}</b> comments, <b>{{.Stats.Counter.Mirror}}</b> mirrors, <b>{{.Stats.Counter.Oauth}}</b> oauthes, <b>{{.Stats.Counter.Release}}</b> releases, <b>{{.Stats.Counter.LoginSource}}</b> login sources, <b>{{.Stats.Counter.Webhook}}</b> webhooks, <b>{{.Stats.Counter.Milestone}}</b> milestones.
             </div>
         </div>
 
diff --git a/templates/issue/milestone.tmpl b/templates/issue/milestone.tmpl
index 0b69a75deb..3c2cf93a6c 100644
--- a/templates/issue/milestone.tmpl
+++ b/templates/issue/milestone.tmpl
@@ -6,10 +6,8 @@
     <div id="issue">
         <div class="col-md-3 filter-list">
             <ul class="list-unstyled">
-                <li><a href="{{.RepoLink}}/issues/milestones" class="active">Open Milestones <strong class="pull-right">1</strong></a></li>
-                <!-- <li><a href="#">Assigned to you</a></li> -->
-                <li><a href="{{.RepoLink}}/issues/milestones">Close Milestones <strong class="pull-right">0</strong></a></li>
-                <!-- <li><a href="#">Mentioned</a></li> -->
+                <li><a href="{{.RepoLink}}/issues/milestones"{{if eq .State "open"}} class="active"{{end}}>Open Milestones <strong class="pull-right">{{.Repository.NumOpenMilestones}}</strong></a></li>
+                <li><a href="{{.RepoLink}}/issues/milestones?state=closed"{{if eq .State "closed"}} class="active"{{end}}>Close Milestones <strong class="pull-right">{{.Repository.NumClosedMilestones}}</strong></a></li>
             </ul>
             <hr/>
             <a href="{{.RepoLink}}/issues/milestones/new" class="text-center">
@@ -18,34 +16,22 @@
         </div>
         <div class="col-md-9">
             <div class="milestones list-group">
+                {{range .Milestones}}
                 <div class="list-group-item milestone-item">
-                    <h4 class="title pull-left"><a href="#">Milestone Title</a></h4>
-                    <span class="issue-open label label-success">12</span>
-                    <span class="issue-close label label-warning">2</span>
+                    <h4 class="title pull-left"><a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">{{.Name}}</a></h4>
+                    <span class="issue-open label label-success">{{.NumClosedIssues}}</span>
+                    <span class="issue-close label label-warning">{{.NumOpenIssues}}</span>
                     <p class="actions pull-right">
-                        <a href="{{.RepoLink}}/issues/milestones/edit">Edit</a>
-                        <a href="#">Open</a>
-                        <a href="#">Close</a>
-                        <a class="text-danger" href="#">Delete</a>
-                        <a href="#">Issues</a>
+                        <!-- <a href="{{$.RepoLink}}/issues/milestones/{{.Index}}/edit">Edit</a> -->
+                        <!-- <a href="#">Open</a>
+                        <a href="#">Close</a> -->
+                        <!-- <a class="text-danger" href="#">Delete</a> -->
+                        <a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">Issues</a>
                     </p>
                     <hr/>
-                    <p class="description">In this version of release, users are able to register and log in/out on Gogs, setting up SSH keys and do most of Git operations through SSH with public repositories. And Web UI only for view of Git data, no extra features are supported.</p>
-                </div>
-                <div class="list-group-item milestone-item">
-                    <h4 class="title pull-left"><a href="#">Milestone Title</a></h4>
-                    <span class="issue-open label label-success">12</span>
-                    <span class="issue-close label label-warning">2</span>
-                    <p class="actions pull-right">
-                        <a href="{{.RepoLink}}/issues/milestones/edit">Edit</a>
-                        <a href="#">Open</a>
-                        <a href="#">Close</a>
-                        <a class="text-danger" href="#">Delete</a>
-                        <a href="#">Issues</a>
-                    </p>
-                    <hr/>
-                    <p class="description">In this version of release, users are able to register and log in/out on Gogs, setting up SSH keys and do most of Git operations through SSH with public repositories. And Web UI only for view of Git data, no extra features are supported.</p>
+                    <p class="description">{{.RenderedContent | str2html}}</p>
                 </div>
+                {{end}}
             </div>
         </div>
     </div>
diff --git a/templates/issue/milestone_new.tmpl b/templates/issue/milestone_new.tmpl
index 2db55ae91b..044b7d1061 100644
--- a/templates/issue/milestone_new.tmpl
+++ b/templates/issue/milestone_new.tmpl
@@ -34,7 +34,7 @@
                 <div class="text-right panel-body">
                     <div class="form-group">
                         <input type="hidden" value="id" name="repo-id"/>
-                        <button class="btn-success btn">Create new issue</button>
+                        <button class="btn-success btn">Create new milestone</button>
                     </div>
                 </div>
             </div>