mirror of
https://github.com/cuigh/swirl
synced 2024-12-30 15:53:24 +00:00
Add permission control for service resource
This commit is contained in:
parent
8ce6adf478
commit
1f652bb6f3
@ -1350,6 +1350,63 @@ var Swirl;
|
|||||||
})(Node = Swirl.Node || (Swirl.Node = {}));
|
})(Node = Swirl.Node || (Swirl.Node = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
var Swirl;
|
var Swirl;
|
||||||
|
(function (Swirl) {
|
||||||
|
var Perm;
|
||||||
|
(function (Perm) {
|
||||||
|
var Dispatcher = Swirl.Core.Dispatcher;
|
||||||
|
var Modal = Swirl.Core.Modal;
|
||||||
|
class EditPage {
|
||||||
|
constructor() {
|
||||||
|
$("#txt-query").keydown(this.searchUser);
|
||||||
|
$("#btn-add-user").click(this.addUser);
|
||||||
|
Dispatcher.bind("#div-users").on("delete-user", this.deleteUser.bind(this));
|
||||||
|
}
|
||||||
|
deleteUser(e) {
|
||||||
|
$(e.target).closest("div.control").remove();
|
||||||
|
}
|
||||||
|
searchUser(e) {
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
let query = $.trim($(e.target).val());
|
||||||
|
if (query.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$ajax.post("/system/user/search", { query: query }).encoder("form").json((users) => {
|
||||||
|
let $panel = $("#nav-users");
|
||||||
|
$panel.find("label.panel-block").remove();
|
||||||
|
for (let user of users) {
|
||||||
|
$panel.append(`<label class="panel-block">
|
||||||
|
<input type="checkbox" value="${user.id}" data-name="${user.name}"> ${user.name}
|
||||||
|
</label>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addUser() {
|
||||||
|
let users = {};
|
||||||
|
$("#div-users").find("input").each((i, e) => {
|
||||||
|
users[$(e).val()] = true;
|
||||||
|
});
|
||||||
|
let $panel = $("#nav-users");
|
||||||
|
$panel.find("input:checked").each((i, e) => {
|
||||||
|
let $el = $(e);
|
||||||
|
if (users[$el.val()]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#div-users").append(`<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-info">${$el.data("name")}</span>
|
||||||
|
<a class="tag is-delete" data-action="delete-user"></a>
|
||||||
|
<input name="users[]" value="${$el.val()}" type="hidden">
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
|
Modal.close();
|
||||||
|
$panel.find("label.panel-block").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Perm.EditPage = EditPage;
|
||||||
|
})(Perm = Swirl.Perm || (Swirl.Perm = {}));
|
||||||
|
})(Swirl || (Swirl = {}));
|
||||||
|
var Swirl;
|
||||||
(function (Swirl) {
|
(function (Swirl) {
|
||||||
var Registry;
|
var Registry;
|
||||||
(function (Registry) {
|
(function (Registry) {
|
||||||
@ -1762,49 +1819,35 @@ var Swirl;
|
|||||||
this.table.on("delete-service", this.deleteService.bind(this))
|
this.table.on("delete-service", this.deleteService.bind(this))
|
||||||
.on("scale-service", this.scaleService.bind(this))
|
.on("scale-service", this.scaleService.bind(this))
|
||||||
.on("rollback-service", this.rollbackService.bind(this));
|
.on("rollback-service", this.rollbackService.bind(this));
|
||||||
$("#btn-delete").click(this.deleteServices.bind(this));
|
|
||||||
}
|
}
|
||||||
deleteService(e) {
|
deleteService(e) {
|
||||||
let $tr = $(e.target).closest("tr");
|
let $tr = $(e.target).closest("tr");
|
||||||
let name = $tr.find("td:eq(1)").text().trim();
|
let name = $tr.find("td:eq(0)").text().trim();
|
||||||
Modal.confirm(`Are you sure to remove service: <strong>${name}</strong>?`, "Delete service", (dlg, e) => {
|
Modal.confirm(`Are you sure to remove service: <strong>${name}</strong>?`, "Delete service", (dlg, e) => {
|
||||||
$ajax.post("delete", { names: name }).trigger(e.target).encoder("form").json(() => {
|
$ajax.post(`${name}/delete`, { names: name }).trigger(e.target).encoder("form").json(() => {
|
||||||
$tr.remove();
|
$tr.remove();
|
||||||
dlg.close();
|
dlg.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
deleteServices() {
|
|
||||||
let names = this.table.selectedKeys();
|
|
||||||
if (names.length == 0) {
|
|
||||||
Modal.alert("Please select one or more items.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Modal.confirm(`Are you sure to remove ${names.length} services?`, "Delete services", (dlg, e) => {
|
|
||||||
$ajax.post("delete", { names: names.join(",") }).trigger(e.target).encoder("form").json(() => {
|
|
||||||
this.table.selectedRows().remove();
|
|
||||||
dlg.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
scaleService(e) {
|
scaleService(e) {
|
||||||
let $btn = $(e.target).closest("button");
|
let $btn = $(e.target).closest("button");
|
||||||
let $tr = $btn.closest("tr");
|
let $tr = $btn.closest("tr");
|
||||||
let data = {
|
let data = {
|
||||||
name: $tr.find("td:eq(1)").text().trim(),
|
name: $tr.find("td:eq(0)").text().trim(),
|
||||||
count: $btn.data("replicas"),
|
count: $btn.data("replicas"),
|
||||||
};
|
};
|
||||||
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
|
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
|
||||||
data.count = dlg.find("input[name=count]").val();
|
data.count = dlg.find("input[name=count]").val();
|
||||||
$ajax.post("scale", data).trigger(e.target).encoder("form").json(() => {
|
$ajax.post(`${data.name}/scale`, data).trigger(e.target).encoder("form").json(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rollbackService(e) {
|
rollbackService(e) {
|
||||||
let $btn = $(e.target).closest("button"), $tr = $btn.closest("tr"), name = $tr.find("td:eq(1)").text().trim();
|
let $btn = $(e.target).closest("button"), $tr = $btn.closest("tr"), name = $tr.find("td:eq(0)").text().trim();
|
||||||
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
|
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
|
||||||
$ajax.post("rollback", { name: name }).trigger(e.target).encoder("form").json(() => {
|
$ajax.post(`${name}/rollback`, { name: name }).trigger(e.target).encoder("form").json(() => {
|
||||||
dlg.close();
|
dlg.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
File diff suppressed because one or more lines are too long
61
assets/swirl/ts/perm/edit.ts
Normal file
61
assets/swirl/ts/perm/edit.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
///<reference path="../core/core.ts" />
|
||||||
|
namespace Swirl.Perm {
|
||||||
|
import Dispatcher = Swirl.Core.Dispatcher;
|
||||||
|
import Modal = Swirl.Core.Modal;
|
||||||
|
|
||||||
|
export class EditPage {
|
||||||
|
constructor() {
|
||||||
|
// bind events
|
||||||
|
$("#txt-query").keydown(this.searchUser);
|
||||||
|
$("#btn-add-user").click(this.addUser);
|
||||||
|
Dispatcher.bind("#div-users").on("delete-user", this.deleteUser.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteUser(e: JQueryEventObject) {
|
||||||
|
$(e.target).closest("div.control").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private searchUser(e: JQueryEventObject) {
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
let query = $.trim($(e.target).val());
|
||||||
|
if (query.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$ajax.post("/system/user/search", {query: query}).encoder("form").json((users: any) => {
|
||||||
|
let $panel = $("#nav-users");
|
||||||
|
$panel.find("label.panel-block").remove();
|
||||||
|
for (let user of users) {
|
||||||
|
$panel.append(`<label class="panel-block">
|
||||||
|
<input type="checkbox" value="${user.id}" data-name="${user.name}"> ${user.name}
|
||||||
|
</label>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addUser() {
|
||||||
|
let users: { [index: string]: boolean } = {};
|
||||||
|
$("#div-users").find("input").each((i, e) => {
|
||||||
|
users[$(e).val()] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
let $panel = $("#nav-users");
|
||||||
|
$panel.find("input:checked").each((i, e) => {
|
||||||
|
let $el = $(e);
|
||||||
|
if (users[$el.val()]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#div-users").append(`<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-info">${$el.data("name")}</span>
|
||||||
|
<a class="tag is-delete" data-action="delete-user"></a>
|
||||||
|
<input name="users[]" value="${$el.val()}" type="hidden">
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
Modal.close();
|
||||||
|
$panel.find("label.panel-block").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,45 +14,29 @@ namespace Swirl.Service {
|
|||||||
this.table.on("delete-service", this.deleteService.bind(this))
|
this.table.on("delete-service", this.deleteService.bind(this))
|
||||||
.on("scale-service", this.scaleService.bind(this))
|
.on("scale-service", this.scaleService.bind(this))
|
||||||
.on("rollback-service", this.rollbackService.bind(this));
|
.on("rollback-service", this.rollbackService.bind(this));
|
||||||
$("#btn-delete").click(this.deleteServices.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteService(e: JQueryEventObject) {
|
private deleteService(e: JQueryEventObject) {
|
||||||
let $tr = $(e.target).closest("tr");
|
let $tr = $(e.target).closest("tr");
|
||||||
let name = $tr.find("td:eq(1)").text().trim();
|
let name = $tr.find("td:eq(0)").text().trim();
|
||||||
Modal.confirm(`Are you sure to remove service: <strong>${name}</strong>?`, "Delete service", (dlg, e) => {
|
Modal.confirm(`Are you sure to remove service: <strong>${name}</strong>?`, "Delete service", (dlg, e) => {
|
||||||
$ajax.post("delete", {names: name}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
$ajax.post(`${name}/delete`, {names: name}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
||||||
$tr.remove();
|
$tr.remove();
|
||||||
dlg.close();
|
dlg.close();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteServices() {
|
|
||||||
let names = this.table.selectedKeys();
|
|
||||||
if (names.length == 0) {
|
|
||||||
Modal.alert("Please select one or more items.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Modal.confirm(`Are you sure to remove ${names.length} services?`, "Delete services", (dlg, e) => {
|
|
||||||
$ajax.post("delete", {names: names.join(",")}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
|
||||||
this.table.selectedRows().remove();
|
|
||||||
dlg.close();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private scaleService(e: JQueryEventObject) {
|
private scaleService(e: JQueryEventObject) {
|
||||||
let $btn = $(e.target).closest("button");
|
let $btn = $(e.target).closest("button");
|
||||||
let $tr = $btn.closest("tr");
|
let $tr = $btn.closest("tr");
|
||||||
let data = {
|
let data = {
|
||||||
name: $tr.find("td:eq(1)").text().trim(),
|
name: $tr.find("td:eq(0)").text().trim(),
|
||||||
count: $btn.data("replicas"),
|
count: $btn.data("replicas"),
|
||||||
};
|
};
|
||||||
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
|
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
|
||||||
data.count = dlg.find("input[name=count]").val();
|
data.count = dlg.find("input[name=count]").val();
|
||||||
$ajax.post("scale", data).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
$ajax.post(`${data.name}/scale`, data).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -61,9 +45,9 @@ namespace Swirl.Service {
|
|||||||
private rollbackService(e: JQueryEventObject) {
|
private rollbackService(e: JQueryEventObject) {
|
||||||
let $btn = $(e.target).closest("button"),
|
let $btn = $(e.target).closest("button"),
|
||||||
$tr = $btn.closest("tr"),
|
$tr = $btn.closest("tr"),
|
||||||
name = $tr.find("td:eq(1)").text().trim();
|
name = $tr.find("td:eq(0)").text().trim();
|
||||||
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
|
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
|
||||||
$ajax.post("rollback", {name: name}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
$ajax.post(`${name}/rollback`, {name: name}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
|
||||||
dlg.close();
|
dlg.close();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
87
biz/perm.go
Normal file
87
biz/perm.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cuigh/auxo/net/web"
|
||||||
|
"github.com/cuigh/swirl/dao"
|
||||||
|
"github.com/cuigh/swirl/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perm return a perm biz instance.
|
||||||
|
var (
|
||||||
|
Perm = &permBiz{}
|
||||||
|
ErrForbidden = web.NewError(http.StatusForbidden)
|
||||||
|
)
|
||||||
|
|
||||||
|
type permBiz struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *permBiz) Delete(resType, resID string, user web.User) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
err = d.PermDelete(resType, resID)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *permBiz) Get(resType, resID string) (perm *model.Perm, err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
perm, err = d.PermGet(resType, resID)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *permBiz) Update(perm *model.Perm, user web.User) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
err = d.PermUpdate(perm)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *permBiz) Check(user web.User, scope string, resType, resID string) (err error) {
|
||||||
|
au := user.(*model.AuthUser)
|
||||||
|
if au.Admin() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
var perm *model.Perm
|
||||||
|
perm, err = d.PermGet(resType, resID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if perm == nil || perm.Scope == model.PermNone || (scope == "read" && perm.Scope == model.PermWrite) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range perm.Users {
|
||||||
|
if user.ID() == u {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range perm.Roles {
|
||||||
|
if au.IsInRole(r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = ErrForbidden
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *permBiz) Apply(next web.HandlerFunc) web.HandlerFunc {
|
||||||
|
return func(ctx web.Context) error {
|
||||||
|
opt := ctx.Handler().Option("perm")
|
||||||
|
if opt != "" {
|
||||||
|
array := strings.Split(opt, ",")
|
||||||
|
err := b.Check(ctx.User(), array[0], array[1], ctx.P(array[2]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cuigh/auxo/data/guid"
|
"github.com/cuigh/auxo/data/guid"
|
||||||
@ -40,6 +41,34 @@ func (b *templateBiz) Get(id string) (tpl *model.Template, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *templateBiz) FillInfo(id string, si *model.ServiceInfo) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
var (
|
||||||
|
tpl *model.Template
|
||||||
|
registry *model.Registry
|
||||||
|
)
|
||||||
|
|
||||||
|
tpl, err = d.TemplateGet(id)
|
||||||
|
if err != nil || tpl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(tpl.Content), si)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if si.Registry != "" {
|
||||||
|
registry, err = Registry.Get(si.Registry)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
si.RegistryURL = registry.URL
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (b *templateBiz) Delete(id string, user web.User) (err error) {
|
func (b *templateBiz) Delete(id string, user web.User) (err error) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
var tpl *model.Template
|
var tpl *model.Template
|
||||||
|
@ -12,6 +12,7 @@ button.confirm: Confirm
|
|||||||
button.delete: Delete
|
button.delete: Delete
|
||||||
button.prune: Prune
|
button.prune: Prune
|
||||||
button.new: New
|
button.new: New
|
||||||
|
button.add: Add
|
||||||
button.edit: Edit
|
button.edit: Edit
|
||||||
button.save: Save
|
button.save: Save
|
||||||
button.update: Update
|
button.update: Update
|
||||||
@ -42,6 +43,8 @@ field.image: Image
|
|||||||
field.address: Address
|
field.address: Address
|
||||||
field.driver: Driver
|
field.driver: Driver
|
||||||
field.scope: Scope
|
field.scope: Scope
|
||||||
|
field.role: Roles
|
||||||
|
field.user: Users
|
||||||
|
|
||||||
# menu
|
# menu
|
||||||
menu.dashboard: Dashboard
|
menu.dashboard: Dashboard
|
||||||
@ -73,6 +76,7 @@ menu.detail: Detail
|
|||||||
menu.raw: Raw
|
menu.raw: Raw
|
||||||
menu.edit: Edit
|
menu.edit: Edit
|
||||||
menu.log: Logs
|
menu.log: Logs
|
||||||
|
menu.perm: Permission
|
||||||
|
|
||||||
# login page
|
# login page
|
||||||
login.title: Sign in to Swirl
|
login.title: Sign in to Swirl
|
||||||
|
@ -12,6 +12,7 @@ button.confirm: 确定
|
|||||||
button.delete: 删除
|
button.delete: 删除
|
||||||
button.prune: 清理
|
button.prune: 清理
|
||||||
button.new: 新建
|
button.new: 新建
|
||||||
|
button.add: 添加
|
||||||
button.edit: 编辑
|
button.edit: 编辑
|
||||||
button.save: 保存
|
button.save: 保存
|
||||||
button.update: 更新
|
button.update: 更新
|
||||||
@ -42,6 +43,8 @@ field.image: 镜像
|
|||||||
field.address: 地址
|
field.address: 地址
|
||||||
field.driver: 驱动
|
field.driver: 驱动
|
||||||
field.scope: 范围
|
field.scope: 范围
|
||||||
|
field.role: 角色
|
||||||
|
field.user: 用户
|
||||||
|
|
||||||
# menu
|
# menu
|
||||||
menu.dashboard: 仪表盘
|
menu.dashboard: 仪表盘
|
||||||
@ -73,6 +76,7 @@ menu.detail: 详情
|
|||||||
menu.raw: 原始
|
menu.raw: 原始
|
||||||
menu.edit: 编辑
|
menu.edit: 编辑
|
||||||
menu.log: 日志
|
menu.log: 日志
|
||||||
|
menu.perm: 权限
|
||||||
|
|
||||||
# login page
|
# login page
|
||||||
login.title: 登录到 Swirl
|
login.title: 登录到 Swirl
|
||||||
|
57
controller/perm.go
Normal file
57
controller/perm.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cuigh/auxo/data"
|
||||||
|
"github.com/cuigh/auxo/net/web"
|
||||||
|
"github.com/cuigh/swirl/biz"
|
||||||
|
"github.com/cuigh/swirl/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func permEdit(ctx web.Context, resType, resID, tpl string, m data.Map) error {
|
||||||
|
perm, err := biz.Perm.Get(resType, resID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if perm == nil {
|
||||||
|
perm = &model.Perm{}
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := biz.Role.List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checkedRoles := data.Set{}
|
||||||
|
checkedRoles.AddSlice(perm.Roles, func(i int) interface{} {
|
||||||
|
return perm.Roles[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
var users []*model.User
|
||||||
|
for _, id := range perm.Users {
|
||||||
|
var user *model.User
|
||||||
|
if user, err = biz.User.GetByID(id); err != nil {
|
||||||
|
return err
|
||||||
|
} else if user != nil {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Set("Perm", perm).Set("Roles", roles).Set("CheckedRoles", checkedRoles).Set("Users", users)
|
||||||
|
return ctx.Render(tpl, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func permUpdate(resType, argName string) web.HandlerFunc {
|
||||||
|
return func(ctx web.Context) error {
|
||||||
|
perm := &model.Perm{
|
||||||
|
ResType: resType,
|
||||||
|
ResID: ctx.P(argName),
|
||||||
|
}
|
||||||
|
err := ctx.Bind(perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = biz.Perm.Update(perm, ctx.User())
|
||||||
|
return ajaxResult(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -18,16 +17,18 @@ import (
|
|||||||
// ServiceController is a controller of docker service
|
// ServiceController is a controller of docker service
|
||||||
type ServiceController struct {
|
type ServiceController struct {
|
||||||
List web.HandlerFunc `path:"/" name:"service.list" authorize:"!" desc:"service list page"`
|
List web.HandlerFunc `path:"/" name:"service.list" authorize:"!" desc:"service list page"`
|
||||||
Detail web.HandlerFunc `path:"/:name/detail" name:"service.detail" authorize:"!" desc:"service detail page"`
|
Detail web.HandlerFunc `path:"/:name/detail" name:"service.detail" authorize:"!" perm:"read,service,name"`
|
||||||
Raw web.HandlerFunc `path:"/:name/raw" name:"service.raw" authorize:"!" desc:"service raw page"`
|
Raw web.HandlerFunc `path:"/:name/raw" name:"service.raw" authorize:"!" perm:"read,service,name"`
|
||||||
Logs web.HandlerFunc `path:"/:name/logs" name:"service.logs" authorize:"!" desc:"service logs page"`
|
Logs web.HandlerFunc `path:"/:name/logs" name:"service.logs" authorize:"!" perm:"read,service,name"`
|
||||||
Delete web.HandlerFunc `path:"/delete" method:"post" name:"service.delete" authorize:"!" desc:"delete service"`
|
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"service.delete" authorize:"!" perm:"write,service,name"`
|
||||||
Scale web.HandlerFunc `path:"/scale" method:"post" name:"service.scale" authorize:"!" desc:"scale service"`
|
Scale web.HandlerFunc `path:"/:name/scale" method:"post" name:"service.scale" authorize:"!" perm:"write,service,name"`
|
||||||
Rollback web.HandlerFunc `path:"/rollback" method:"post" name:"service.rollback" authorize:"!" desc:"rollback service"`
|
Rollback web.HandlerFunc `path:"/:name/rollback" method:"post" name:"service.rollback" authorize:"!" perm:"write,service,name"`
|
||||||
New web.HandlerFunc `path:"/new" name:"service.new" authorize:"!" desc:"new service page"`
|
New web.HandlerFunc `path:"/new" name:"service.new" authorize:"!" desc:"new service page"`
|
||||||
Create web.HandlerFunc `path:"/new" method:"post" name:"service.create" authorize:"!" desc:"create service"`
|
Create web.HandlerFunc `path:"/new" method:"post" name:"service.create" authorize:"!" desc:"create service"`
|
||||||
Edit web.HandlerFunc `path:"/:name/edit" name:"service.edit" authorize:"!" desc:"service edit page"`
|
Edit web.HandlerFunc `path:"/:name/edit" name:"service.edit" authorize:"!" perm:"write,service,name"`
|
||||||
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"service.update" authorize:"!" desc:"update service"`
|
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"service.update" authorize:"!" perm:"write,service,name"`
|
||||||
|
PermEdit web.HandlerFunc `path:"/:name/perm" name:"service.perm.edit" authorize:"!" perm:"write,service,name"`
|
||||||
|
PermUpdate web.HandlerFunc `path:"/:name/perm" method:"post" name:"service.perm.update" authorize:"!" perm:"write,service,name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service creates an instance of ServiceController
|
// Service creates an instance of ServiceController
|
||||||
@ -44,6 +45,8 @@ func Service() (c *ServiceController) {
|
|||||||
Update: serviceUpdate,
|
Update: serviceUpdate,
|
||||||
Scale: serviceScale,
|
Scale: serviceScale,
|
||||||
Rollback: serviceRollback,
|
Rollback: serviceRollback,
|
||||||
|
PermEdit: servicePermEdit,
|
||||||
|
PermUpdate: permUpdate("service", "name"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,37 +129,20 @@ func serviceDelete(ctx web.Context) error {
|
|||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if err := docker.ServiceRemove(name); err != nil {
|
if err := docker.ServiceRemove(name); err != nil {
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
} else {
|
|
||||||
biz.Event.CreateService(model.EventActionDelete, name, ctx.User())
|
|
||||||
}
|
}
|
||||||
|
biz.Event.CreateService(model.EventActionDelete, name, ctx.User())
|
||||||
}
|
}
|
||||||
return ajaxSuccess(ctx, nil)
|
return ajaxSuccess(ctx, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceNew(ctx web.Context) error {
|
func serviceNew(ctx web.Context) error {
|
||||||
service := &model.ServiceInfo{}
|
info := &model.ServiceInfo{}
|
||||||
tid := ctx.Q("template")
|
tid := ctx.Q("template")
|
||||||
if tid != "" {
|
if tid != "" {
|
||||||
tpl, err := biz.Template.Get(tid)
|
err := biz.Template.FillInfo(tid, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tpl != nil {
|
|
||||||
err = json.Unmarshal([]byte(tpl.Content), service)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Registry != "" {
|
|
||||||
var registry *model.Registry
|
|
||||||
registry, err = biz.Registry.Get(service.Registry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
service.RegistryURL = registry.URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
networks, err := docker.NetworkList()
|
networks, err := docker.NetworkList()
|
||||||
@ -177,11 +163,11 @@ func serviceNew(ctx web.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkedNetworks := data.NewSet()
|
checkedNetworks := data.NewSet()
|
||||||
checkedNetworks.AddSlice(service.Networks, func(i int) interface{} {
|
checkedNetworks.AddSlice(info.Networks, func(i int) interface{} {
|
||||||
return service.Networks[i]
|
return info.Networks[i]
|
||||||
})
|
})
|
||||||
|
|
||||||
m := newModel(ctx).Set("Service", service).Set("Registries", registries).
|
m := newModel(ctx).Set("Service", info).Set("Registries", registries).
|
||||||
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
|
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
|
||||||
Set("Secrets", secrets).Set("Configs", configs)
|
Set("Secrets", secrets).Set("Configs", configs)
|
||||||
return ctx.Render("service/new", m)
|
return ctx.Render("service/new", m)
|
||||||
@ -279,3 +265,9 @@ func serviceRollback(ctx web.Context) error {
|
|||||||
}
|
}
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func servicePermEdit(ctx web.Context) error {
|
||||||
|
name := ctx.P("name")
|
||||||
|
m := newModel(ctx).Set("Name", name)
|
||||||
|
return permEdit(ctx, "service", name, "service/perm", m)
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ type UserController struct {
|
|||||||
Block web.HandlerFunc `path:"/block" method:"post" name:"user.block" authorize:"!" desc:"block user"`
|
Block web.HandlerFunc `path:"/block" method:"post" name:"user.block" authorize:"!" desc:"block user"`
|
||||||
Unblock web.HandlerFunc `path:"/unblock" method:"post" name:"user.unblock" authorize:"!" desc:"unblock user"`
|
Unblock web.HandlerFunc `path:"/unblock" method:"post" name:"user.unblock" authorize:"!" desc:"unblock user"`
|
||||||
Delete web.HandlerFunc `path:"/delete" method:"post" name:"user.delete" authorize:"!" desc:"delete user"`
|
Delete web.HandlerFunc `path:"/delete" method:"post" name:"user.delete" authorize:"!" desc:"delete user"`
|
||||||
|
Search web.HandlerFunc `path:"/search" method:"post" name:"user.search" authorize:"?" desc:"search users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User creates an instance of UserController
|
// User creates an instance of UserController
|
||||||
@ -32,6 +33,7 @@ func User() (c *UserController) {
|
|||||||
Block: userBlock,
|
Block: userBlock,
|
||||||
Unblock: userUnblock,
|
Unblock: userUnblock,
|
||||||
Delete: userDelete,
|
Delete: userDelete,
|
||||||
|
Search: userSearch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,3 +161,29 @@ func userDelete(ctx web.Context) error {
|
|||||||
err := biz.User.Delete(id)
|
err := biz.User.Delete(id)
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userSearch(ctx web.Context) error {
|
||||||
|
query := ctx.F("query")
|
||||||
|
args := &model.UserListArgs{
|
||||||
|
Query: query,
|
||||||
|
PageIndex: 1,
|
||||||
|
PageSize: 10,
|
||||||
|
}
|
||||||
|
users, _, err := biz.User.List(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
list := make([]User, len(users))
|
||||||
|
for i, user := range users {
|
||||||
|
list[i] = User{
|
||||||
|
ID: user.ID,
|
||||||
|
Name: user.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx.JSON(list)
|
||||||
|
}
|
||||||
|
@ -56,6 +56,10 @@ type Interface interface {
|
|||||||
EventCreate(event *model.Event) error
|
EventCreate(event *model.Event) error
|
||||||
EventList(args *model.EventListArgs) (events []*model.Event, count int, err error)
|
EventList(args *model.EventListArgs) (events []*model.Event, count int, err error)
|
||||||
|
|
||||||
|
PermGet(resType, resID string) (*model.Perm, error)
|
||||||
|
PermUpdate(perm *model.Perm) error
|
||||||
|
PermDelete(resType, resID string) error
|
||||||
|
|
||||||
SettingGet() (setting *model.Setting, err error)
|
SettingGet() (setting *model.Setting, err error)
|
||||||
SettingUpdate(setting *model.Setting) error
|
SettingUpdate(setting *model.Setting) error
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,9 @@ var (
|
|||||||
"template": {
|
"template": {
|
||||||
mgo.Index{Key: []string{"name"}, Unique: true},
|
mgo.Index{Key: []string{"name"}, Unique: true},
|
||||||
},
|
},
|
||||||
|
"perm": {
|
||||||
|
mgo.Index{Key: []string{"res_type", "res_id"}, Unique: true},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
53
dao/mongo/perm.go
Normal file
53
dao/mongo/perm.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package mongo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cuigh/swirl/model"
|
||||||
|
"gopkg.in/mgo.v2"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Dao) PermGet(resType, resID string) (p *model.Perm, err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
p = &model.Perm{}
|
||||||
|
q := bson.M{
|
||||||
|
"res_type": resType,
|
||||||
|
"res_id": resID,
|
||||||
|
}
|
||||||
|
err = db.C("perm").Find(q).One(p)
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
p, err = nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) PermUpdate(perm *model.Perm) (err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
q := bson.M{
|
||||||
|
"res_type": perm.ResType,
|
||||||
|
"res_id": perm.ResID,
|
||||||
|
}
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"scope": perm.Scope,
|
||||||
|
"roles": perm.Roles,
|
||||||
|
"users": perm.Users,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = db.C("perm").Upsert(q, update)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) PermDelete(resType, resID string) (err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
q := bson.M{
|
||||||
|
"res_type": resType,
|
||||||
|
"res_id": resID,
|
||||||
|
}
|
||||||
|
err = db.C("perm").Remove(q)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
4
main.go
4
main.go
@ -24,7 +24,7 @@ func main() {
|
|||||||
misc.BindOptions()
|
misc.BindOptions()
|
||||||
|
|
||||||
app.Name = "Swirl"
|
app.Name = "Swirl"
|
||||||
app.Version = "0.6.3"
|
app.Version = "0.6.4"
|
||||||
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||||
app.Action = func(ctx *app.Context) {
|
app.Action = func(ctx *app.Context) {
|
||||||
misc.LoadOptions()
|
misc.LoadOptions()
|
||||||
@ -91,7 +91,7 @@ func server() *web.Server {
|
|||||||
g.Handle("/profile", controller.Profile())
|
g.Handle("/profile", controller.Profile())
|
||||||
g.Handle("/registry", controller.Registry())
|
g.Handle("/registry", controller.Registry())
|
||||||
g.Handle("/node", controller.Node())
|
g.Handle("/node", controller.Node())
|
||||||
g.Handle("/service", controller.Service())
|
g.Handle("/service", controller.Service(), biz.Perm)
|
||||||
g.Handle("/service/template", controller.Template())
|
g.Handle("/service/template", controller.Template())
|
||||||
g.Handle("/stack", controller.Stack())
|
g.Handle("/stack", controller.Stack())
|
||||||
g.Handle("/network", controller.Network())
|
g.Handle("/network", controller.Network())
|
||||||
|
@ -65,6 +65,7 @@ type Session struct {
|
|||||||
|
|
||||||
type AuthUser struct {
|
type AuthUser struct {
|
||||||
user *User
|
user *User
|
||||||
|
roles []*Role
|
||||||
perms map[string]struct{}
|
perms map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ func NewAuthUser(user *User, roles []*Role) *AuthUser {
|
|||||||
}
|
}
|
||||||
u := &AuthUser{
|
u := &AuthUser{
|
||||||
user: user,
|
user: user,
|
||||||
|
roles: roles,
|
||||||
perms: make(map[string]struct{}),
|
perms: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
@ -100,6 +102,15 @@ func (u *AuthUser) Admin() bool {
|
|||||||
return u.user.Admin
|
return u.user.Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *AuthUser) IsInRole(roleID string) bool {
|
||||||
|
for _, role := range u.roles {
|
||||||
|
if role.ID == roleID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (u *AuthUser) IsAllowed(perm string) bool {
|
func (u *AuthUser) IsAllowed(perm string) bool {
|
||||||
if u.user.Admin {
|
if u.user.Admin {
|
||||||
return true
|
return true
|
||||||
|
@ -2,17 +2,26 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// LDAP security policy
|
||||||
const (
|
const (
|
||||||
LDAPSecurityNone = 0
|
LDAPSecurityNone = 0
|
||||||
LDAPSecurityTLS = 1
|
LDAPSecurityTLS = 1
|
||||||
LDAPSecurityStartTLS = 2
|
LDAPSecurityStartTLS = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LDAP auth type
|
||||||
const (
|
const (
|
||||||
LDAPAuthSimple = 0
|
LDAPAuthSimple = 0
|
||||||
LDAPAuthBind = 1
|
LDAPAuthBind = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Perm control scope
|
||||||
|
const (
|
||||||
|
PermNone = 0
|
||||||
|
PermWrite = 1
|
||||||
|
PermReadWrite = 2
|
||||||
|
)
|
||||||
|
|
||||||
// Setting represents the options of swirl.
|
// Setting represents the options of swirl.
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
LDAP struct {
|
LDAP struct {
|
||||||
@ -38,3 +47,12 @@ type Setting struct {
|
|||||||
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
||||||
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perm holds permissions of Docker resource.
|
||||||
|
type Perm struct {
|
||||||
|
ResType string `json:"res_type"`
|
||||||
|
ResID string `json:"res_id"`
|
||||||
|
Scope int32 `json:"scope"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
Users []string `json:"users"`
|
||||||
|
}
|
@ -29,6 +29,7 @@
|
|||||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/logs">{{ i18n("menu.log") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Service.Spec.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/logs">{{ i18n("menu.log") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab is-active" href="/service/{{.Service.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Service.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -31,9 +31,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Right side -->
|
<!-- Right side -->
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<p class="level-item">
|
|
||||||
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fa fa-remove"></i></span><span>{{ i18n("button.delete") }}</span></button>
|
|
||||||
</p>
|
|
||||||
<p class="level-item">
|
<p class="level-item">
|
||||||
<a class="button is-success" href="new"><span class="icon"><i class="fa fa-plus"></i></span><span>{{ i18n("button.new") }}</span></a>
|
<a class="button is-success" href="new"><span class="icon"><i class="fa fa-plus"></i></span><span>{{ i18n("button.new") }}</span></a>
|
||||||
</p>
|
</p>
|
||||||
@ -43,7 +40,6 @@
|
|||||||
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="30"><input type="checkbox" data-action="check-all"></th>
|
|
||||||
<th>{{ i18n("field.name") }}</th>
|
<th>{{ i18n("field.name") }}</th>
|
||||||
<th>{{ i18n("field.image") }}</th>
|
<th>{{ i18n("field.image") }}</th>
|
||||||
<th width="145">Mode</th>
|
<th width="145">Mode</th>
|
||||||
@ -54,7 +50,6 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{range .Services}}
|
{{range .Services}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" value="{{.Name}}" data-action="check"></td>
|
|
||||||
<td><a href="{{.Name}}/detail">{{.Name}}</a></td>
|
<td><a href="{{.Name}}/detail">{{.Name}}</a></td>
|
||||||
<td>{{ limit(.Image, 60) }}</td>
|
<td>{{ limit(.Image, 60) }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
<a class="navbar-item is-tab is-active" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/perm">{{ i18n("menu.perm") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
104
views/service/perm.jet
Normal file
104
views/service/perm.jet
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
{{ extends "_base" }}
|
||||||
|
{{ import "../_modules/form" }}
|
||||||
|
|
||||||
|
{{ block script() }}
|
||||||
|
<script>$(() => new Swirl.Perm.EditPage())</script>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ block body_content() }}
|
||||||
|
<div class="container">
|
||||||
|
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||||
|
<li><a href="/service/">{{ i18n("menu.service") }}</a></li>
|
||||||
|
<li class="is-active"><a>{{ i18n("menu.perm") }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<section class="hero is-small is-light">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="title is-2">{{ .Name }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<nav class="navbar has-shadow">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Name}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Name}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Name}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
|
<a class="navbar-item is-tab is-active" href="/service/{{.Name}}/perm">{{ i18n("menu.perm") }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<form method="post" data-form="ajax-json" data-url="/service/">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">{{ i18n("field.scope") }}</label>
|
||||||
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
<div class="control">
|
||||||
|
{{ yield radio(name="scope", value=0, label="None", checked=.Perm.Scope) content }} data-type="integer"{{ end }}
|
||||||
|
{{ yield radio(name="scope", value=1, label="Write", checked=.Perm.Scope) content }} data-type="integer"{{ end }}
|
||||||
|
{{ yield radio(name="scope", value=2, label="Read & Write", checked=.Perm.Scope) content }} data-type="integer"{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">{{ i18n("field.role") }}</label>
|
||||||
|
<div class="control">
|
||||||
|
{{ roles := .CheckedRoles }}
|
||||||
|
{{range .Roles}}
|
||||||
|
{{ yield checkbox(name="roles", value=.ID, label=.Name, checked=isset(roles[.ID])) }}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" style="display: inline-block">{{ i18n("field.user") }}</label>
|
||||||
|
<button type="button" class="button is-small is-success is-outlined tooltip is-tooltip-bottom modal-trigger" data-tooltip="{{ i18n("button.add") }}" data-action="add-user" data-target="dlg-add-user">
|
||||||
|
<span class="icon"><i class="fa fa-plus"></i></span>
|
||||||
|
</button>
|
||||||
|
<div id="div-users" class="field is-grouped is-grouped-multiline">
|
||||||
|
{{range .Users}}
|
||||||
|
<div class="control">
|
||||||
|
<div class="tags has-addons">
|
||||||
|
<span class="tag is-info">{{ .Name }}</span>
|
||||||
|
<a class="tag is-delete" data-action="delete-user"></a>
|
||||||
|
<input name="users[]" value="{{ .ID }}" type="hidden">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ yield form_submit(url="/service/") }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div id="dlg-add-user" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Add user</p>
|
||||||
|
<button class="delete"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body" style="max-height: 400px; overflow-y: auto">
|
||||||
|
<nav id="nav-users" class="panel">
|
||||||
|
<div class="panel-block">
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input id="txt-query" class="input is-small" type="text" placeholder="Searching user...">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<button id="btn-add-user" type="button" class="button is-primary">{{ i18n("button.confirm") }}</button>
|
||||||
|
<button type="button" class="button dismiss">{{ i18n("button.cancel") }}</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
@ -37,6 +37,7 @@
|
|||||||
<a class="navbar-item is-tab is-active" href="/service/{{.Service}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab is-active" href="/service/{{.Service}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/service/{{.Service}}/perm">{{ i18n("menu.perm") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
Loading…
Reference in New Issue
Block a user