@@ -5,7 +5,7 @@ Gogs - Go Git Service [ | |||
##### Current version: 0.7.30 Beta | |||
##### Current version: 0.7.31 Beta | |||
<table> | |||
<tr> | |||
@@ -280,7 +280,7 @@ func runWeb(ctx *cli.Context) { | |||
m.Group("/notices", func() { | |||
m.Get("", admin.Notices) | |||
m.Get("/:id:int/delete", admin.DeleteNotice) | |||
m.Post("/delete", admin.DeleteNotices) | |||
m.Get("/empty", admin.EmptyNotices) | |||
}) | |||
}, adminReq) | |||
@@ -32,7 +32,7 @@ USER_PAGING_NUM = 50 | |||
; Number of repos that are showed in one page | |||
REPO_PAGING_NUM = 50 | |||
; Number of notices that are showed in one page | |||
NOTICE_PAGING_NUM = 50 | |||
NOTICE_PAGING_NUM = 25 | |||
; Number of organization that are showed in one page | |||
ORG_PAGING_NUM = 50 | |||
@@ -991,12 +991,18 @@ monitor.start = Start Time | |||
monitor.execute_time = Execution Time | |||
notices.system_notice_list = System Notices | |||
notices.empty_all = Remove All Notices | |||
notices.view_detail_header = View Notice Detail | |||
notices.actions = Actions | |||
notices.select_all = Select All | |||
notices.deselect_all = Deselect All | |||
notices.inverse_selection = Inverse Selection | |||
notices.delete_selected = Delete Selected | |||
notices.delete_all = Delete All Notices | |||
notices.type = Type | |||
notices.type_1 = Repository | |||
notices.desc = Description | |||
notices.op = Op. | |||
notices.delete_success = System notice has been deleted successfully. | |||
notices.delete_success = System notices have been deleted successfully. | |||
[action] | |||
create_repo = created repository <a href="%s">%s</a> | |||
@@ -17,7 +17,7 @@ import ( | |||
"github.com/gogits/gogs/modules/setting" | |||
) | |||
const APP_VER = "0.7.30.1204 Beta" | |||
const APP_VER = "0.7.31.1205 Beta" | |||
func init() { | |||
runtime.GOMAXPROCS(runtime.NumCPU()) | |||
@@ -5,9 +5,12 @@ | |||
package models | |||
import ( | |||
"strings" | |||
"time" | |||
"github.com/Unknwon/com" | |||
"github.com/gogits/gogs/modules/base" | |||
) | |||
type NoticeType int | |||
@@ -18,7 +21,7 @@ const ( | |||
// Notice represents a system notice for admin. | |||
type Notice struct { | |||
Id int64 | |||
ID int64 `xorm:"pk autoincr"` | |||
Type NoticeType | |||
Description string `xorm:"TEXT"` | |||
Created time.Time `xorm:"CREATED"` | |||
@@ -71,3 +74,12 @@ func DeleteNotices(start, end int64) error { | |||
_, err := sess.Delete(new(Notice)) | |||
return err | |||
} | |||
// DeleteNoticesByIDs deletes notices by given IDs. | |||
func DeleteNoticesByIDs(ids []int64) error { | |||
if len(ids) == 0 { | |||
return nil | |||
} | |||
_, err := x.Where("id IN (" + strings.Join(base.Int64sToStrings(ids), ",") + ")").Delete(new(Notice)) | |||
return err | |||
} |
@@ -2999,12 +2999,18 @@ footer .container .links > *:first-child { | |||
padding: 0; | |||
font-size: 13px; | |||
} | |||
.admin .table.segment:not(.striped) { | |||
padding-top: 5px; | |||
} | |||
.admin .table.segment:not(.striped) thead th:last-child { | |||
padding-right: 5px !important; | |||
} | |||
.admin .table.segment th { | |||
padding-top: 5px; | |||
padding-bottom: 5px; | |||
} | |||
.admin .table.segment th:first-of-type, | |||
.admin .table.segment td:first-of-type { | |||
.admin .table.segment:not(.select) th:first-of-type, | |||
.admin .table.segment:not(.select) td:first-of-type { | |||
padding-left: 15px !important; | |||
} | |||
.admin .ui.header, | |||
@@ -319,23 +319,23 @@ function initRepository() { | |||
$('#edit-title').click(editTitleToggle); | |||
$('#cancel-edit-title').click(editTitleToggle); | |||
$('#save-edit-title').click(editTitleToggle). | |||
click(function () { | |||
if ($edit_input.val().length == 0 || | |||
$edit_input.val() == $issue_title.text()) { | |||
$edit_input.val($issue_title.text()); | |||
return false; | |||
} | |||
$.post($(this).data('update-url'), { | |||
"_csrf": csrf, | |||
"title": $edit_input.val() | |||
}, | |||
function (data) { | |||
$edit_input.val(data.title); | |||
$issue_title.text(data.title); | |||
}); | |||
click(function () { | |||
if ($edit_input.val().length == 0 || | |||
$edit_input.val() == $issue_title.text()) { | |||
$edit_input.val($issue_title.text()); | |||
return false; | |||
}); | |||
} | |||
$.post($(this).data('update-url'), { | |||
"_csrf": csrf, | |||
"title": $edit_input.val() | |||
}, | |||
function (data) { | |||
$edit_input.val(data.title); | |||
$issue_title.text(data.title); | |||
}); | |||
return false; | |||
}); | |||
// Edit issue or comment content | |||
$('.edit-content').click(function () { | |||
@@ -607,6 +607,50 @@ function initAdmin() { | |||
} | |||
}); | |||
} | |||
// Notice | |||
if ($('.admin.notice')) { | |||
var $detail_modal = $('#detail-modal'); | |||
// Attach view detail modals | |||
$('.view-detail').click(function () { | |||
$detail_modal.find('.content p').text($(this).data('content')); | |||
$detail_modal.modal('show'); | |||
return false; | |||
}); | |||
// Select actions | |||
var $checkboxes = $('.select.table .ui.checkbox'); | |||
$('.select.action').click(function () { | |||
switch ($(this).data('action')) { | |||
case 'select-all': | |||
$checkboxes.checkbox('check'); | |||
break; | |||
case 'deselect-all': | |||
$checkboxes.checkbox('uncheck'); | |||
break; | |||
case 'inverse': | |||
$checkboxes.checkbox('toggle'); | |||
break; | |||
} | |||
}); | |||
$('#delete-selection').click(function () { | |||
var $this = $(this); | |||
$this.addClass("loading disabled"); | |||
var ids = []; | |||
$checkboxes.each(function () { | |||
if ($(this).checkbox('is checked')) { | |||
ids.push($(this).data('id')); | |||
} | |||
}); | |||
$.post($this.data('link'), { | |||
"_csrf": csrf, | |||
"ids": ids | |||
}).done(function () { | |||
window.location.href = $this.data('redirect'); | |||
}); | |||
}); | |||
} | |||
} | |||
function buttonsClickOnEnter() { | |||
@@ -734,9 +778,9 @@ $(document).ready(function () { | |||
// Show exact time | |||
$('.time-since').each(function () { | |||
$(this).addClass('poping up'). | |||
attr('data-content', $(this).attr('title')). | |||
attr('data-variation', 'inverted tiny'). | |||
attr('title', ''); | |||
attr('data-content', $(this).attr('title')). | |||
attr('data-variation', 'inverted tiny'). | |||
attr('title', ''); | |||
}); | |||
// Semantic UI modules. | |||
@@ -750,6 +794,9 @@ $(document).ready(function () { | |||
$('.slide.up.dropdown').dropdown({ | |||
transition: 'slide up' | |||
}); | |||
$('.upward.dropdown').dropdown({ | |||
direction: 'upward' | |||
}); | |||
$('.ui.accordion').accordion(); | |||
$('.ui.checkbox').checkbox(); | |||
$('.ui.progress').progress({ | |||
@@ -5,13 +5,27 @@ | |||
.table.segment { | |||
padding: 0; | |||
font-size: 13px; | |||
&:not(.striped) { | |||
padding-top: 5px; | |||
thead { | |||
th:last-child { | |||
padding-right: 5px !important; | |||
} | |||
} | |||
} | |||
th { | |||
padding-top: 5px; | |||
padding-bottom: 5px; | |||
} | |||
th, td { | |||
&:first-of-type { | |||
padding-left: 15px !important; | |||
&:not(.select) { | |||
th, td { | |||
&:first-of-type { | |||
padding-left: 15px !important; | |||
} | |||
} | |||
} | |||
} | |||
@@ -5,6 +5,7 @@ | |||
package admin | |||
import ( | |||
"github.com/Unknwon/com" | |||
"github.com/Unknwon/paginater" | |||
"github.com/gogits/gogs/models" | |||
@@ -41,15 +42,23 @@ func Notices(ctx *middleware.Context) { | |||
ctx.HTML(200, NOTICES) | |||
} | |||
func DeleteNotice(ctx *middleware.Context) { | |||
id := ctx.ParamsInt64(":id") | |||
if err := models.DeleteNotice(id); err != nil { | |||
ctx.Handle(500, "DeleteNotice", err) | |||
return | |||
func DeleteNotices(ctx *middleware.Context) { | |||
strs := ctx.QueryStrings("ids[]") | |||
ids := make([]int64, 0, len(strs)) | |||
for i := range strs { | |||
id := com.StrTo(strs[i]).MustInt64() | |||
if id > 0 { | |||
ids = append(ids, id) | |||
} | |||
} | |||
if err := models.DeleteNoticesByIDs(ids); err != nil { | |||
ctx.Flash.Error("DeleteNoticesByIDs: " + err.Error()) | |||
ctx.Status(500) | |||
} else { | |||
ctx.Flash.Success(ctx.Tr("admin.notices.delete_success")) | |||
ctx.Status(200) | |||
} | |||
log.Trace("System notice deleted by admin (%s): %d", ctx.User.Name, id) | |||
ctx.Flash.Success(ctx.Tr("admin.notices.delete_success")) | |||
ctx.Redirect(setting.AppSubUrl + "/admin/notices") | |||
} | |||
func EmptyNotices(ctx *middleware.Context) { | |||
@@ -1 +1 @@ | |||
0.7.30.1204 Beta | |||
0.7.31.1205 Beta |
@@ -1,5 +1,5 @@ | |||
{{template "base/head" .}} | |||
<div class="admin user"> | |||
<div class="admin notice"> | |||
<div class="ui container"> | |||
<div class="ui grid"> | |||
{{template "admin/navbar" .}} | |||
@@ -7,32 +7,62 @@ | |||
{{template "base/alert" .}} | |||
<h4 class="ui top attached header"> | |||
{{.i18n.Tr "admin.notices.system_notice_list"}} ({{.i18n.Tr "admin.total" .Total}}) | |||
<div class="ui right"> | |||
<a class="ui red tiny button" href="{{AppSubUrl}}/admin/notices/empty">{{.i18n.Tr "admin.notices.empty_all"}}</a> | |||
</div> | |||
</h4> | |||
<div class="ui attached table segment"> | |||
<table class="ui very basic striped table"> | |||
<table class="ui very basic select selectable table"> | |||
<thead> | |||
<tr> | |||
<th></th> | |||
<th>ID</th> | |||
<th>{{.i18n.Tr "admin.notices.type"}}</th> | |||
<th>{{.i18n.Tr "admin.notices.desc"}}</th> | |||
<th>{{.i18n.Tr "admin.users.created"}}</th> | |||
<th width="100px">{{.i18n.Tr "admin.users.created"}}</th> | |||
<th>{{.i18n.Tr "admin.notices.op"}}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{{range .Notices}} | |||
<tr> | |||
<td>{{.Id}}</td> | |||
<td class="collapsing"> | |||
<div class="ui fitted checkbox" data-id="{{.ID}}"> | |||
<input type="checkbox"> <label></label> | |||
</div> | |||
</td> | |||
<td>{{.ID}}</td> | |||
<td>{{$.i18n.Tr .TrStr}}</td> | |||
<td><span>{{.Description}}</span></td> | |||
<td>{{.Created}}</td> | |||
<td><a href="{{AppSubUrl}}/admin/notices/{{.Id}}/delete"><i class="fa fa-trash-o text-red"></i></a></td> | |||
<td>{{SubStr .Description 0 120}}...</td> | |||
<td><span class="poping up" data-content="{{.Created}}" data-variation="inverted tiny">{{DateFmtShort .Created}}</span></td> | |||
<td><a href="#"><i class="browser icon view-detail" data-content="{{.Description}}"></i></a></td> | |||
</tr> | |||
{{end}} | |||
</tbody> | |||
<tfoot class="full-width"> | |||
<tr> | |||
<th></th> | |||
<th colspan="5"> | |||
<div class="ui right"> | |||
<a class="ui red small button" href="{{AppSubUrl}}/admin/notices/empty">{{.i18n.Tr "admin.notices.delete_all"}}</a> | |||
</div> | |||
<div class="ui floating upward dropdown small button"> | |||
<span class="text">{{.i18n.Tr "admin.notices.actions"}}</span> | |||
<div class="menu"> | |||
<div class="item select action" data-action="select-all"> | |||
{{.i18n.Tr "admin.notices.select_all"}} | |||
</div> | |||
<div class="item select action" data-action="deselect-all"> | |||
{{.i18n.Tr "admin.notices.deselect_all"}} | |||
</div> | |||
<div class="item select action" data-action="inverse"> | |||
{{.i18n.Tr "admin.notices.inverse_selection"}} | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui small teal button" id="delete-selection" data-link="{{.Link}}/delete" data-redirect="{{.Link}}?page={{.Page.Current}}"> | |||
{{.i18n.Tr "admin.notices.delete_selected"}} | |||
</div> | |||
</th> | |||
</tr> | |||
</tfoot> | |||
</table> | |||
</div> | |||
@@ -63,4 +93,12 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui modal" id="detail-modal"> | |||
<i class="close icon"></i> | |||
<div class="header">{{$.i18n.Tr "admin.notices.view_detail_header"}}</div> | |||
<div class="content"> | |||
<p></p> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |