diff --git a/controllers/role.go b/controllers/role.go
new file mode 100644
index 00000000..d17b20f2
--- /dev/null
+++ b/controllers/role.go
@@ -0,0 +1,116 @@
+// Copyright 2021 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package controllers
+
+import (
+ "encoding/json"
+
+ "github.com/astaxie/beego/utils/pagination"
+ "github.com/casbin/casdoor/object"
+ "github.com/casbin/casdoor/util"
+)
+
+// GetRoles
+// @Title GetRoles
+// @Tag Role API
+// @Description get roles
+// @Param owner query string true "The owner of roles"
+// @Success 200 {array} object.Role The Response object
+// @router /get-roles [get]
+func (c *ApiController) GetRoles() {
+ owner := c.Input().Get("owner")
+ limit := c.Input().Get("pageSize")
+ page := c.Input().Get("p")
+ field := c.Input().Get("field")
+ value := c.Input().Get("value")
+ sortField := c.Input().Get("sortField")
+ sortOrder := c.Input().Get("sortOrder")
+ if limit == "" || page == "" {
+ c.Data["json"] = object.GetRoles(owner)
+ c.ServeJSON()
+ } else {
+ limit := util.ParseInt(limit)
+ paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
+ roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
+ c.ResponseOk(roles, paginator.Nums())
+ }
+}
+
+// @Title GetRole
+// @Tag Role API
+// @Description get role
+// @Param id query string true "The id of the role"
+// @Success 200 {object} object.Role The Response object
+// @router /get-role [get]
+func (c *ApiController) GetRole() {
+ id := c.Input().Get("id")
+
+ c.Data["json"] = object.GetRole(id)
+ c.ServeJSON()
+}
+
+// @Title UpdateRole
+// @Tag Role API
+// @Description update role
+// @Param id query string true "The id of the role"
+// @Param body body object.Role true "The details of the role"
+// @Success 200 {object} controllers.Response The Response object
+// @router /update-role [post]
+func (c *ApiController) UpdateRole() {
+ id := c.Input().Get("id")
+
+ var role object.Role
+ err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
+ if err != nil {
+ panic(err)
+ }
+
+ c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
+ c.ServeJSON()
+}
+
+// @Title AddRole
+// @Tag Role API
+// @Description add role
+// @Param body body object.Role true "The details of the role"
+// @Success 200 {object} controllers.Response The Response object
+// @router /add-role [post]
+func (c *ApiController) AddRole() {
+ var role object.Role
+ err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
+ if err != nil {
+ panic(err)
+ }
+
+ c.Data["json"] = wrapActionResponse(object.AddRole(&role))
+ c.ServeJSON()
+}
+
+// @Title DeleteRole
+// @Tag Role API
+// @Description delete role
+// @Param body body object.Role true "The details of the role"
+// @Success 200 {object} controllers.Response The Response object
+// @router /delete-role [post]
+func (c *ApiController) DeleteRole() {
+ var role object.Role
+ err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
+ if err != nil {
+ panic(err)
+ }
+
+ c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))
+ c.ServeJSON()
+}
diff --git a/object/adapter.go b/object/adapter.go
index a5637cac..aaef6baf 100644
--- a/object/adapter.go
+++ b/object/adapter.go
@@ -105,6 +105,11 @@ func (a *Adapter) createTable() {
panic(err)
}
+ err = a.Engine.Sync2(new(Role))
+ if err != nil {
+ panic(err)
+ }
+
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
diff --git a/object/role.go b/object/role.go
new file mode 100644
index 00000000..287d1f61
--- /dev/null
+++ b/object/role.go
@@ -0,0 +1,126 @@
+// Copyright 2021 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package object
+
+import (
+ "fmt"
+
+ "github.com/casbin/casdoor/util"
+ "xorm.io/core"
+)
+
+type Role struct {
+ Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
+ Name string `xorm:"varchar(100) notnull pk" json:"name"`
+ CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
+ DisplayName string `xorm:"varchar(100)" json:"displayName"`
+
+ Users []string `xorm:"mediumtext" json:"users"`
+ Roles []string `xorm:"mediumtext" json:"roles"`
+ IsEnabled bool `json:"isEnabled"`
+}
+
+func GetRoleCount(owner, field, value string) int {
+ session := adapter.Engine.Where("owner=?", owner)
+ if field != "" && value != "" {
+ session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
+ }
+ count, err := session.Count(&Role{})
+ if err != nil {
+ panic(err)
+ }
+
+ return int(count)
+}
+
+func GetRoles(owner string) []*Role {
+ roles := []*Role{}
+ err := adapter.Engine.Desc("created_time").Find(&roles, &Role{Owner: owner})
+ if err != nil {
+ panic(err)
+ }
+
+ return roles
+}
+
+func GetPaginationRoles(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Role {
+ roles := []*Role{}
+ session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
+ err := session.Find(&roles)
+ if err != nil {
+ panic(err)
+ }
+
+ return roles
+}
+
+func getRole(owner string, name string) *Role {
+ if owner == "" || name == "" {
+ return nil
+ }
+
+ role := Role{Owner: owner, Name: name}
+ existed, err := adapter.Engine.Get(&role)
+ if err != nil {
+ panic(err)
+ }
+
+ if existed {
+ return &role
+ } else {
+ return nil
+ }
+}
+
+func GetRole(id string) *Role {
+ owner, name := util.GetOwnerAndNameFromId(id)
+ return getRole(owner, name)
+}
+
+func UpdateRole(id string, role *Role) bool {
+ owner, name := util.GetOwnerAndNameFromId(id)
+ if getRole(owner, name) == nil {
+ return false
+ }
+
+ affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
+ if err != nil {
+ panic(err)
+ }
+
+ return affected != 0
+}
+
+func AddRole(role *Role) bool {
+ affected, err := adapter.Engine.Insert(role)
+ if err != nil {
+ panic(err)
+ }
+
+ return affected != 0
+}
+
+func DeleteRole(role *Role) bool {
+ affected, err := adapter.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{})
+ if err != nil {
+ panic(err)
+ }
+
+ return affected != 0
+}
+
+func (role *Role) GetId() string {
+ return fmt.Sprintf("%s/%s", role.Owner, role.Name)
+}
diff --git a/routers/router.go b/routers/router.go
index 598c51c0..ed0f5386 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -70,6 +70,12 @@ func initAPI() {
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
+ beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
+ beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
+ beego.Router("/api/update-role", &controllers.ApiController{}, "POST:UpdateRole")
+ beego.Router("/api/add-role", &controllers.ApiController{}, "POST:AddRole")
+ beego.Router("/api/delete-role", &controllers.ApiController{}, "POST:DeleteRole")
+
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
diff --git a/web/src/App.js b/web/src/App.js
index f8c9fe9b..7ba49d8b 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -23,6 +23,8 @@ import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
import UserListPage from "./UserListPage";
import UserEditPage from "./UserEditPage";
+import RoleListPage from "./RoleListPage";
+import RoleEditPage from "./RoleEditPage";
import ProviderListPage from "./ProviderListPage";
import ProviderEditPage from "./ProviderEditPage";
import ApplicationListPage from "./ApplicationListPage";
@@ -102,6 +104,8 @@ class App extends Component {
this.setState({ selectedMenuKey: '/organizations' });
} else if (uri.includes('/users')) {
this.setState({ selectedMenuKey: '/users' });
+ } else if (uri.includes('/roles')) {
+ this.setState({ selectedMenuKey: '/roles' });
} else if (uri.includes('/providers')) {
this.setState({ selectedMenuKey: '/providers' });
} else if (uri.includes('/applications')) {
@@ -327,6 +331,13 @@ class App extends Component {
);
+ res.push(
+
+
+ {i18next.t("general:Roles")}
+
+
+ );
res.push(
@@ -436,6 +447,8 @@ class App extends Component {
this.renderLoginIfNotLoggedIn()}/>
this.renderLoginIfNotLoggedIn()}/>
}/>
+ this.renderLoginIfNotLoggedIn()}/>
+ this.renderLoginIfNotLoggedIn()}/>
this.renderLoginIfNotLoggedIn()}/>
this.renderLoginIfNotLoggedIn()}/>
this.renderLoginIfNotLoggedIn()}/>
diff --git a/web/src/RoleEditPage.js b/web/src/RoleEditPage.js
new file mode 100644
index 00000000..a17e4325
--- /dev/null
+++ b/web/src/RoleEditPage.js
@@ -0,0 +1,219 @@
+// Copyright 2021 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from "react";
+import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
+import * as RoleBackend from "./backend/RoleBackend";
+import * as OrganizationBackend from "./backend/OrganizationBackend";
+import * as UserBackend from "./backend/UserBackend";
+import * as Setting from "./Setting";
+import i18next from "i18next";
+
+const { Option } = Select;
+
+class RoleEditPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ classes: props,
+ organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
+ roleName: props.match.params.roleName,
+ role: null,
+ organizations: [],
+ users: [],
+ roles: [],
+ };
+ }
+
+ UNSAFE_componentWillMount() {
+ this.getRole();
+ this.getOrganizations();
+ }
+
+ getRole() {
+ RoleBackend.getRole(this.state.organizationName, this.state.roleName)
+ .then((role) => {
+ this.setState({
+ role: role,
+ });
+
+ this.getUsers(role.owner);
+ this.getRoles(role.owner);
+ });
+ }
+
+ getOrganizations() {
+ OrganizationBackend.getOrganizations("admin")
+ .then((res) => {
+ this.setState({
+ organizations: (res.msg === undefined) ? res : [],
+ });
+ });
+ }
+
+ getUsers(organizationName) {
+ UserBackend.getUsers(organizationName)
+ .then((res) => {
+ this.setState({
+ users: res,
+ });
+ });
+ }
+
+ getRoles(organizationName) {
+ RoleBackend.getRoles(organizationName)
+ .then((res) => {
+ this.setState({
+ roles: res,
+ });
+ });
+ }
+
+ parseRoleField(key, value) {
+ if ([""].includes(key)) {
+ value = Setting.myParseInt(value);
+ }
+ return value;
+ }
+
+ updateRoleField(key, value) {
+ value = this.parseRoleField(key, value);
+
+ let role = this.state.role;
+ role[key] = value;
+ this.setState({
+ role: role,
+ });
+ }
+
+ renderRole() {
+ return (
+
+ {i18next.t("role:Edit Role")}
+
+
+
+ } style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
+
+
+ {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
+
+
+
+
+
+
+
+ {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
+
+
+ {
+ this.updateRoleField('name', e.target.value);
+ }} />
+
+
+
+
+ {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
+
+
+ {
+ this.updateRoleField('displayName', e.target.value);
+ }} />
+
+
+
+
+ {Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
+
+
+
+
+
+
+
+ {Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
+
+
+
+
+
+
+
+ {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
+
+
+ {
+ this.updateRoleField('isEnabled', checked);
+ }} />
+
+
+
+ )
+ }
+
+ submitRoleEdit(willExist) {
+ let role = Setting.deepCopy(this.state.role);
+ RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role)
+ .then((res) => {
+ if (res.msg === "") {
+ Setting.showMessage("success", `Successfully saved`);
+ this.setState({
+ roleName: this.state.role.name,
+ });
+
+ if (willExist) {
+ this.props.history.push(`/roles`);
+ } else {
+ this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`);
+ }
+ } else {
+ Setting.showMessage("error", res.msg);
+ this.updateRoleField('name', this.state.roleName);
+ }
+ })
+ .catch(error => {
+ Setting.showMessage("error", `Failed to connect to server: ${error}`);
+ });
+ }
+
+ render() {
+ return (
+
+ {
+ this.state.role !== null ? this.renderRole() : null
+ }
+
+
+
+
+
+ );
+ }
+}
+
+export default RoleEditPage;
diff --git a/web/src/RoleListPage.js b/web/src/RoleListPage.js
new file mode 100644
index 00000000..f527749f
--- /dev/null
+++ b/web/src/RoleListPage.js
@@ -0,0 +1,222 @@
+// Copyright 2021 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from "react";
+import {Link} from "react-router-dom";
+import {Button, Popconfirm, Switch, Table} from 'antd';
+import moment from "moment";
+import * as Setting from "./Setting";
+import * as RoleBackend from "./backend/RoleBackend";
+import i18next from "i18next";
+import BaseListPage from "./BaseListPage";
+
+class RoleListPage extends BaseListPage {
+ newRole() {
+ const randomName = Setting.getRandomName();
+ return {
+ owner: "built-in",
+ name: `role_${randomName}`,
+ createdTime: moment().format(),
+ displayName: `New Role - ${randomName}`,
+ users: [],
+ roles: [],
+ isEnabled: true,
+ }
+ }
+
+ addRole() {
+ const newRole = this.newRole();
+ RoleBackend.addRole(newRole)
+ .then((res) => {
+ Setting.showMessage("success", `Role added successfully`);
+ this.props.history.push(`/roles/${newRole.owner}/${newRole.name}`);
+ }
+ )
+ .catch(error => {
+ Setting.showMessage("error", `Role failed to add: ${error}`);
+ });
+ }
+
+ deleteRole(i) {
+ RoleBackend.deleteRole(this.state.data[i])
+ .then((res) => {
+ Setting.showMessage("success", `Role deleted successfully`);
+ this.setState({
+ data: Setting.deleteRow(this.state.data, i),
+ pagination: {total: this.state.pagination.total - 1},
+ });
+ }
+ )
+ .catch(error => {
+ Setting.showMessage("error", `Role failed to delete: ${error}`);
+ });
+ }
+
+ renderTable(roles) {
+ const columns = [
+ {
+ title: i18next.t("general:Organization"),
+ dataIndex: 'owner',
+ key: 'owner',
+ width: '120px',
+ sorter: true,
+ ...this.getColumnSearchProps('owner'),
+ render: (text, record, index) => {
+ return (
+
+ {text}
+
+ )
+ }
+ },
+ {
+ title: i18next.t("general:Name"),
+ dataIndex: 'name',
+ key: 'name',
+ width: '150px',
+ fixed: 'left',
+ sorter: true,
+ ...this.getColumnSearchProps('name'),
+ render: (text, record, index) => {
+ return (
+
+ {text}
+
+ )
+ }
+ },
+ {
+ title: i18next.t("general:Created time"),
+ dataIndex: 'createdTime',
+ key: 'createdTime',
+ width: '160px',
+ sorter: true,
+ render: (text, record, index) => {
+ return Setting.getFormattedDate(text);
+ }
+ },
+ {
+ title: i18next.t("general:Display name"),
+ dataIndex: 'displayName',
+ key: 'displayName',
+ width: '200px',
+ sorter: true,
+ ...this.getColumnSearchProps('displayName'),
+ },
+ {
+ title: i18next.t("role:Sub users"),
+ dataIndex: 'users',
+ key: 'users',
+ // width: '100px',
+ sorter: true,
+ ...this.getColumnSearchProps('users'),
+ render: (text, record, index) => {
+ return Setting.getTags(text);
+ }
+ },
+ {
+ title: i18next.t("role:Sub roles"),
+ dataIndex: 'roles',
+ key: 'roles',
+ // width: '100px',
+ sorter: true,
+ ...this.getColumnSearchProps('roles'),
+ render: (text, record, index) => {
+ return Setting.getTags(text);
+ }
+ },
+ {
+ title: i18next.t("general:Is enabled"),
+ dataIndex: 'isEnabled',
+ key: 'isEnabled',
+ width: '120px',
+ sorter: true,
+ render: (text, record, index) => {
+ return (
+
+ )
+ }
+ },
+ {
+ title: i18next.t("general:Action"),
+ dataIndex: '',
+ key: 'op',
+ width: '170px',
+ fixed: (Setting.isMobile()) ? "false" : "right",
+ render: (text, record, index) => {
+ return (
+
+
+
this.deleteRole(index)}
+ >
+
+
+
+ )
+ }
+ },
+ ];
+
+ const paginationProps = {
+ total: this.state.pagination.total,
+ showQuickJumper: true,
+ showSizeChanger: true,
+ showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
+ };
+
+ return (
+
+
(
+
+ {i18next.t("general:Roles")}
+
+
+ )}
+ loading={this.state.loading}
+ onChange={this.handleTableChange}
+ />
+
+ );
+ }
+
+ fetch = (params = {}) => {
+ let field = params.searchedColumn, value = params.searchText;
+ let sortField = params.sortField, sortOrder = params.sortOrder;
+ if (params.type !== undefined && params.type !== null) {
+ field = "type";
+ value = params.type;
+ }
+ this.setState({ loading: true });
+ RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
+ .then((res) => {
+ if (res.status === "ok") {
+ this.setState({
+ loading: false,
+ data: res.data,
+ pagination: {
+ ...params.pagination,
+ total: res.data2,
+ },
+ searchText: params.searchText,
+ searchedColumn: params.searchedColumn,
+ });
+ }
+ });
+ };
+}
+
+export default RoleListPage;
diff --git a/web/src/Setting.js b/web/src/Setting.js
index 372b90b3..1e17d001 100644
--- a/web/src/Setting.js
+++ b/web/src/Setting.js
@@ -590,7 +590,7 @@ export function getNewRowNameForTable(table, rowName) {
}
export function getTagColor(s) {
- return "success";
+ return "processing";
}
export function getTags(tags) {
diff --git a/web/src/backend/RoleBackend.js b/web/src/backend/RoleBackend.js
new file mode 100644
index 00000000..2505f9f9
--- /dev/null
+++ b/web/src/backend/RoleBackend.js
@@ -0,0 +1,56 @@
+// Copyright 2021 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import * as Setting from "../Setting";
+
+export function getRoles(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
+ return fetch(`${Setting.ServerUrl}/api/get-roles?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
+ method: "GET",
+ credentials: "include"
+ }).then(res => res.json());
+}
+
+export function getRole(owner, name) {
+ return fetch(`${Setting.ServerUrl}/api/get-role?id=${owner}/${encodeURIComponent(name)}`, {
+ method: "GET",
+ credentials: "include"
+ }).then(res => res.json());
+}
+
+export function updateRole(owner, name, role) {
+ let newRole = Setting.deepCopy(role);
+ return fetch(`${Setting.ServerUrl}/api/update-role?id=${owner}/${encodeURIComponent(name)}`, {
+ method: 'POST',
+ credentials: 'include',
+ body: JSON.stringify(newRole),
+ }).then(res => res.json());
+}
+
+export function addRole(role) {
+ let newRole = Setting.deepCopy(role);
+ return fetch(`${Setting.ServerUrl}/api/add-role`, {
+ method: 'POST',
+ credentials: 'include',
+ body: JSON.stringify(newRole),
+ }).then(res => res.json());
+}
+
+export function deleteRole(role) {
+ let newRole = Setting.deepCopy(role);
+ return fetch(`${Setting.ServerUrl}/api/delete-role`, {
+ method: 'POST',
+ credentials: 'include',
+ body: JSON.stringify(newRole),
+ }).then(res => res.json());
+}
diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json
index 1d43a3e5..e5f95113 100644
--- a/web/src/locales/de/data.json
+++ b/web/src/locales/de/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index 1176a238..a15d65d7 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json
index 1d43a3e5..e5f95113 100644
--- a/web/src/locales/fr/data.json
+++ b/web/src/locales/fr/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json
index 1d43a3e5..e5f95113 100644
--- a/web/src/locales/ja/data.json
+++ b/web/src/locales/ja/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json
index 1d43a3e5..e5f95113 100644
--- a/web/src/locales/ko/data.json
+++ b/web/src/locales/ko/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json
index 1d43a3e5..e5f95113 100644
--- a/web/src/locales/ru/data.json
+++ b/web/src/locales/ru/data.json
@@ -146,6 +146,7 @@
"Records": "Records",
"Request URI": "Request URI",
"Resources": "Resources",
+ "Roles": "Roles",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Signin URL": "Signin URL",
@@ -325,6 +326,13 @@
"Upload a file...": "Upload a file...",
"User": "User"
},
+ "role": {
+ "Edit Role": "Edit Role",
+ "Sub roles": "Sub roles",
+ "Sub roles - Tooltip": "Sub roles - Tooltip",
+ "Sub users": "Sub users",
+ "Sub users - Tooltip": "Sub users - Tooltip"
+ },
"signup": {
"Accept": "Accept",
"Confirm": "Confirm",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index 598191e9..8c2b1661 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -7,7 +7,7 @@
"Sign Up": "注册"
},
"application": {
- "Edit Application": "修改应用",
+ "Edit Application": "编辑应用",
"Enable code signin": "启用验证码登录",
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
@@ -42,7 +42,7 @@
"Crypto algorithm - Tooltip": "Crypto algorithm - Tooltip",
"Download private key": "下载私钥",
"Download public key": "下载公钥",
- "Edit Cert": "修改证书",
+ "Edit Cert": "编辑证书",
"Expire in years": "有效期(年)",
"Expire in years - Tooltip": "Expire in years - Tooltip",
"Private key": "私钥",
@@ -70,7 +70,7 @@
},
"forget": {
"Account": "账号",
- "Change Password": "修改密码",
+ "Change Password": "编辑密码",
"Choose email or phone": "请选择邮箱或手机号验证",
"Confirm": "验证密码",
"Next Step": "下一步",
@@ -103,7 +103,7 @@
"Display name": "显示名称",
"Display name - Tooltip": "显示名称",
"Down": "下移",
- "Edit": "修改",
+ "Edit": "编辑",
"Email": "电子邮箱",
"Email - Tooltip": "电子邮件:",
"Favicon - Tooltip": "网站的图标",
@@ -146,6 +146,7 @@
"Records": "日志",
"Request URI": "请求URI",
"Resources": "资源",
+ "Roles": "角色",
"Save": "保存",
"Save & Exit": "保存 & 退出",
"Signin URL": "登录URL",
@@ -172,27 +173,27 @@
"ldap": {
"Address": "地址",
"Admin": "管理员",
- "Admin - Tooltip": "LDAP 服务器管理员的 CN 或 ID",
+ "Admin - Tooltip": "LDAP服务器管理员的CN或ID",
"Admin Password": "密码",
- "Admin Password - Tooltip": "LDAP 服务器管理员密码",
+ "Admin Password - Tooltip": "LDAP服务器管理员密码",
"Auto Sync": "自动同步",
- "Auto Sync - Tooltip": "自动同步配置,为 0 时禁用",
- "Base DN": "基本 DN",
- "Base DN - Tooltip": "LDAP 搜索时的基本 DN",
+ "Auto Sync - Tooltip": "自动同步配置,为0时禁用",
+ "Base DN": "基本DN",
+ "Base DN - Tooltip": "LDAP搜索时的基 DN",
"CN": "CN",
- "Edit LDAP": "编辑 LDAP",
+ "Edit LDAP": "编辑LDAP",
"Email": "电子邮件",
- "Group Id": "组 Id",
+ "Group Id": "组ID",
"ID": "ID",
"Last Sync": "最近同步",
"Phone": "电话",
"Server": "服务器",
"Server Host": "域名",
- "Server Host - Tooltip": "LDAP 服务器地址",
- "Server Name": "LDAP 服务器",
- "Server Name - Tooltip": "LDAP 服务器配置显示名称",
+ "Server Host - Tooltip": "LDAP服务器地址",
+ "Server Name": "LDAP服务器",
+ "Server Name - Tooltip": "LDAP服务器配置显示名称",
"Server Port": "端口",
- "Server Port - Tooltip": "LDAP 服务器端口号",
+ "Server Port - Tooltip": "LDAP服务器端口号",
"Sync": "同步",
"The Auto Sync option will sync all users to specify organization": "The Auto Sync option will sync all users to specify organization",
"UidNumber / Uid": "UidNumber / Uid"
@@ -223,7 +224,7 @@
},
"organization": {
"Default avatar": "默认头像",
- "Edit Organization": "修改组织",
+ "Edit Organization": "编辑组织",
"Favicon": "图标",
"Soft deletion": "软删除",
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
@@ -249,7 +250,7 @@
"Copy": "Copy",
"Domain": "域名",
"Domain - Tooltip": "存储节点自定义域名",
- "Edit Provider": "修改提供商",
+ "Edit Provider": "编辑提供商",
"Email Content": "邮件内容",
"Email Content - Tooltip": "邮件内容",
"Email Title": "邮件标题",
@@ -325,6 +326,13 @@
"Upload a file...": "上传文件...",
"User": "用户"
},
+ "role": {
+ "Edit Role": "编辑角色",
+ "Sub roles": "包含角色",
+ "Sub roles - Tooltip": "当前角色所包含的子角色",
+ "Sub users": "包含用户",
+ "Sub users - Tooltip": "当前角色所包含的子用户"
+ },
"signup": {
"Accept": "阅读并接受",
"Confirm": "确认密码",
@@ -364,7 +372,7 @@
"Database - Tooltip": "数据库名称",
"Database type": "数据库类型",
"Database type - Tooltip": "数据库类型",
- "Edit Syncer": "修改同步器",
+ "Edit Syncer": "编辑同步器",
"Is hashed": "是否参与哈希计算",
"Sync interval": "同步间隔",
"Sync interval - Tooltip": "单位为分钟",
@@ -378,7 +386,7 @@
"token": {
"Access token": "Access token",
"Authorization code": "授权码",
- "Edit Token": "修改令牌",
+ "Edit Token": "编辑令牌",
"Expires in": "有效期",
"Scope": "范围",
"Token type": "令牌类型"
@@ -397,7 +405,7 @@
"Code Sent": "验证码已发送",
"Country/Region": "国家/地区",
"Country/Region - Tooltip": "国家/地区",
- "Edit User": "修改用户",
+ "Edit User": "编辑用户",
"Empty input!": "输入为空!",
"Homepage": "个人主页",
"Homepage - Tooltip": "个人主页链接",
@@ -415,7 +423,7 @@
"Link": "绑定",
"Location": "城市",
"Location - Tooltip": "居住地址所在的城市",
- "Modify password...": "修改密码...",
+ "Modify password...": "编辑密码...",
"New Email": "新邮箱",
"New Password": "新密码",
"New phone": "新手机号",
@@ -444,7 +452,7 @@
"webhook": {
"Content type": "Content type",
"Content type - Tooltip": "Content type",
- "Edit Webhook": "修改Webhook",
+ "Edit Webhook": "编辑Webhook",
"Events": "事件",
"Events - Tooltip": "事件",
"Headers": "协议头",