Browse Source

add admin management

tags/vopendata0.1.2
yan 5 years ago
parent
commit
b0b68fdcb6
7 changed files with 307 additions and 13 deletions
  1. +84
    -13
      models/dataset.go
  2. +12
    -0
      options/locale/locale_zh-CN.ini
  3. +115
    -0
      routers/admin/dataset.go
  4. +5
    -0
      routers/routes/routes.go
  5. +59
    -0
      templates/admin/dataset/list.tmpl
  6. +29
    -0
      templates/admin/dataset/search.tmpl
  7. +3
    -0
      templates/admin/navbar.tmpl

+ 84
- 13
models/dataset.go View File

@@ -9,6 +9,12 @@ import (
"xorm.io/builder"
)

const (
DatasetStatusPrivate int32 = iota
DatasetStatusPublic
DatasetStatusDeleted
)

type Dataset struct {
ID int64 `xorm:"pk autoincr"`
Title string `xorm:"INDEX NOT NULL"`
@@ -23,6 +29,7 @@ type Dataset struct {
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

User *User `xorm:"-"`
Attachments []*Attachment `xorm:"-"`
}

@@ -41,14 +48,36 @@ func (d *Dataset) IsPrivate() bool {

type DatasetList []*Dataset

const (
DatasetStatusPrivate int32 = iota
DatasetStatusPublic
DatasetStatusDeleted
)
func (datasets DatasetList) loadAttributes(e Engine) error {
if len(datasets) == 0 {
return nil
}

set := make(map[int64]struct{})
datasetIDs := make([]int64, len(datasets))
for i := range datasets {
set[datasets[i].UserID] = struct{}{}
datasetIDs[i] = datasets[i].ID
}

// Load owners.
users := make(map[int64]*User, len(set))
if err := e.
Where("id > 0").
In("id", keysInt64(set)).
Find(&users); err != nil {
return fmt.Errorf("find users: %v", err)
}
for i := range datasets {
datasets[i].User = users[datasets[i].UserID]
}

return nil
}

type SearchDatasetOptions struct {
Keyword string
doerID int64
OwnerID int64
IsPublic bool
ListOptions
@@ -71,20 +100,30 @@ func SearchDataset(opts *SearchDatasetOptions) (DatasetList, int64, error) {
func SearchDatasetCondition(opts *SearchDatasetOptions) builder.Cond {
var cond = builder.NewCond()
cond = cond.And(builder.Neq{"status": DatasetStatusDeleted})

if len(opts.Keyword) > 0 {
cond = cond.And(builder.Like{"title", opts.Keyword})
}

if opts.IsPublic {
cond = cond.And(builder.Eq{"status": DatasetStatusPublic})
if opts.OwnerID > 0 {
cond = cond.Or(builder.Eq{"user_id": opts.OwnerID})
if opts.doerID > 0 {
doer, err := GetUserByID(opts.doerID)
if err != nil {
return nil
}
if doer.IsAdmin {

}

} else {
if opts.IsPublic {
cond = cond.And(builder.Eq{"status": DatasetStatusPublic})
if opts.OwnerID > 0 {
cond = cond.Or(builder.Eq{"user_id": opts.OwnerID})
}
}
}

if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"user_id": opts.OwnerID})
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"user_id": opts.OwnerID})
}
}

return cond
@@ -114,6 +153,10 @@ func SearchDatasetByCondition(opts *SearchDatasetOptions, cond builder.Cond) (Da
return nil, 0, fmt.Errorf("Dataset: %v", err)
}

if err = datasets.loadAttributes(sess); err != nil {
return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
}

return datasets, count, nil
}

@@ -216,6 +259,34 @@ func GetDatasetByID(id int64) (*Dataset, error) {
return rel, nil
}

func DeleteDataset(datasetID int64, uid int64) error {
var err error
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

dataset := &Dataset{ID: datasetID, UserID: uid}
has, err := sess.Get(dataset)
if err != nil {
return err
} else if !has {
return errors.New("not found")
}

if cnt, err := sess.ID(datasetID).Delete(new(Dataset)); err != nil {
return err
} else if cnt != 1 {
return errors.New("not found")
}
if err = sess.Commit(); err != nil {
sess.Close()
return fmt.Errorf("Commit: %v", err)
}
return nil
}

func GetOwnerDatasetByID(id int64, user *User) (*Dataset, error) {
dataset, err := GetDatasetByID(id)
if err != nil {


+ 12
- 0
options/locale/locale_zh-CN.ini View File

@@ -641,6 +641,12 @@ visibility_description=只有组织所有人或拥有权利的组织成员才能
visibility_helper=将数据集设为私有
visibility_helper_forced=站点管理员强制要求新数据集为私有。
visibility_fork_helper=(修改该值将会影响到所有派生数据集)
settings.delete=删除本据集
settings.delete_desc=删除据集是永久性的, 无法撤消。
settings.delete_notices_1=- 此操作 <strong>不可以</strong> 被回滚。
settings.delete_notices_2=- 此操作将永久删除据集 <strong>%s</strong>。
settings.delete_notices_fork_1=- 在此仓库删除后,它的派生据集将变成独立据集。
settings.deletion_success=据集已被删除。

[repo]
owner=拥有者
@@ -1846,6 +1852,7 @@ teams.all_repositories_admin_permission_desc=该团队拥有 <strong>管理</str
dashboard=管理面板
users=帐户管理
organizations=组织管理
datasets=数据集
repositories=仓库管理
hooks=默认Web钩子
systemhooks=系统 Web 钩子
@@ -1992,6 +1999,11 @@ repos.forks=派生数
repos.issues=工单数
repos.size=大小

datasets.dataset_manage_panel=数据集管理
datasets.owner=所有者
datasets.name=名称
datasets.private=私有

hooks.desc=当某些 Gitea 事件触发时, Web 钩子会自动向服务器发出 HTTP POST 请求。此处定义的 Web 钩子是默认值, 将复制到所有新建仓库中。参阅 <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">Web钩子指南</a> 获取更多内容。
hooks.add_webhook=新增默认Web钩子
hooks.update_webhook=更新默认Web钩子


+ 115
- 0
routers/admin/dataset.go View File

@@ -0,0 +1,115 @@
package admin

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)

const (
tplDatasets base.TplName = "admin/dataset/list"
)

func Datasets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.datasets")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminDatasets"] = true

page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}

var (
datasets []*models.Dataset
count int64
err error
orderBy models.SearchOrderBy
)

ctx.Data["SortType"] = ctx.Query("sort")
switch ctx.Query("sort") {
case "newest":
orderBy = models.SearchOrderByNewest
case "oldest":
orderBy = models.SearchOrderByOldest
case "recentupdate":
orderBy = models.SearchOrderByRecentUpdated
case "leastupdate":
orderBy = models.SearchOrderByLeastUpdated
case "reversealphabetically":
orderBy = models.SearchOrderByAlphabeticallyReverse
case "alphabetically":
orderBy = models.SearchOrderByAlphabetically
case "reversesize":
orderBy = models.SearchOrderBySizeReverse
case "size":
orderBy = models.SearchOrderBySize
case "moststars":
orderBy = models.SearchOrderByStarsReverse
case "feweststars":
orderBy = models.SearchOrderByStars
case "mostforks":
orderBy = models.SearchOrderByForksReverse
case "fewestforks":
orderBy = models.SearchOrderByForks
default:
ctx.Data["SortType"] = "recentupdate"
orderBy = models.SearchOrderByRecentUpdated
}

keyword := strings.Trim(ctx.Query("q"), " ")
var uid int64
if ctx.User != nil {
uid = ctx.User.ID
} else {
uid = 0
}

datasets, count, err = models.SearchDataset(&models.SearchDatasetOptions{
ListOptions: models.ListOptions{
Page: page,
PageSize: setting.UI.ExplorePagingNum,
},
Keyword: keyword,
OwnerID: uid,
SearchOrderBy: orderBy,
})
if err != nil {
ctx.ServerError("SearchDataset", err)
return
}

ctx.Data["Keyword"] = keyword
ctx.Data["Total"] = count
ctx.Data["Datasets"] = datasets

pager := context.NewPagination(int(count), setting.UI.ExplorePagingNum, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager

ctx.HTML(200, tplDatasets)
}

func DeleteDataset(ctx *context.Context) {
dataset, err := models.GetDatasetByID(ctx.QueryInt64("id"))
if err != nil {
ctx.ServerError("GetDatasetByID", err)
return
}

if err := models.DeleteDataset(dataset.ID, ctx.User.ID); err != nil {
ctx.ServerError("DeleteDataset", err)
return
}
log.Trace("DeleteDataset deleted: %s", dataset.ID)

ctx.Flash.Success(ctx.Tr("dataset.settings.deletion_success"))
ctx.JSON(200, map[string]interface{}{
"redirect": setting.AppSubURL + "/admin/datasets?page=" + ctx.Query("page") + "&sort=" + ctx.Query("sort"),
})
}

+ 5
- 0
routers/routes/routes.go View File

@@ -464,6 +464,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/delete", admin.DeleteRepo)
})

m.Group("/datasets", func() {
m.Get("", admin.Datasets)
m.Post("/delete", admin.DeleteDataset)
})

m.Group("/^:configType(hooks|system-hooks)$", func() {
m.Get("", admin.DefaultOrSystemWebhooks)
m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)


+ 59
- 0
templates/admin/dataset/list.tmpl View File

@@ -0,0 +1,59 @@
{{template "base/head" .}}
<div class="admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{.i18n.Tr "admin.datasets.dataset_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}})
</h4>
<div class="ui attached segment">
{{template "admin/dataset/search" .}}
</div>
<div class="ui attached table segment">
<table class="ui very basic striped table">
<thead>
<tr>
<th>ID</th>
<th>{{.i18n.Tr "admin.datasets.owner"}}</th>
<th>{{.i18n.Tr "admin.datasets.name"}}</th>
<th>{{.i18n.Tr "admin.datasets.private"}}</th>
<th>{{.i18n.Tr "admin.users.created"}}</th>
<th>{{.i18n.Tr "admin.notices.op"}}</th>
</tr>
</thead>
<tbody>
{{range .Datasets}}
<tr>
<td>{{.ID}}</td>
<td>
<a href="{{AppSubUrl}}/{{.User.Name}}">{{.User.Name}}</a>
{{if .User.Visibility.IsPrivate}}
<span class="text gold">{{svg "octicon-lock" 16}}</span>
{{end}}
</td>
<td><a href="{{AppSubUrl}}/">{{.Title}}</a></td>
<td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td>
<td><span title="{{.CreatedUnix.FormatLong}}">{{.CreatedUnix.FormatShort}}</span></td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Title}}"><i class="trash icon text red"></i></a></td>
</tr>
{{end}}
</tbody>
</table>
</div>

{{template "base/paginate" .}}
</div>
</div>

<div class="ui small basic delete modal">
<div class="ui icon header">
<i class="trash icon"></i>
{{.i18n.Tr "dataset.settings.delete"}}
</div>
<div class="content">
<p>{{.i18n.Tr "dataset.settings.delete_desc"}}</p>
{{.i18n.Tr "dataset.settings.delete_notices_2" `<span class="name"></span>` | Safe}}<br>
</div>
{{template "base/delete_modal_actions" .}}
</div>
{{template "base/footer" .}}

+ 29
- 0
templates/admin/dataset/search.tmpl View File

@@ -0,0 +1,29 @@
<div class="ui right floated secondary filter menu">
<!-- Sort -->
<div class="ui dropdown type jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}}
<i class="dropdown icon"></i>
</span>
<div class="menu">
<a class='{{if or (eq .SortType "oldest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}'>{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class='{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}'>{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a>
<a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a>
<a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a>
<a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a>
<a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a>
<a class="{{if eq .SortType "size"}}active{{end}} item" href="{{$.Link}}?sort=size&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.by_size"}}</a>
<a class="{{if eq .SortType "reversesize"}}active{{end}} item" href="{{$.Link}}?sort=reversesize&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_by_size"}}</a>
</div>
</div>
</div>
<form class="ui form ignore-dirty" style="max-width: 90%">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>

+ 3
- 0
templates/admin/navbar.tmpl View File

@@ -11,6 +11,9 @@
<a class="{{if .PageIsAdminRepositories}}active{{end}} item" href="{{AppSubUrl}}/admin/repos">
{{.i18n.Tr "admin.repositories"}}
</a>
<a class="{{if .PageIsAdminDatasets}}active{{end}} item" href="{{AppSubUrl}}/admin/datasets">
{{.i18n.Tr "admin.datasets"}}
</a>
<a class="{{if .PageIsAdminHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks">
{{.i18n.Tr "admin.hooks"}}
</a>


Loading…
Cancel
Save