#2052 advanced select ops for system notices
This commit is contained in:
		
							parent
							
								
									e82ee40e9e
								
							
						
					
					
						commit
						f41360d864
					
				
					 13 changed files with 186 additions and 54 deletions
				
			
		|  | @ -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> | ||||
|  |  | |||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							|  | @ -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 | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -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" .}} | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue