| @@ -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" .}} | |||