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": "协议头",