mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-03 12:30:19 +08:00
feat: use Casdoor to manage Casbin DB table (#1100)
* feat: use Casdoor to manage Casbin DB table Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> * fix: remove trivial codes Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> * chore: go fmt Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> * feat: support role definition Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> * fix: i18n Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> * fix: i18n Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com> Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
This commit is contained in:
94
controllers/casbin_adapter.go
Normal file
94
controllers/casbin_adapter.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) GetCasbinAdapters() {
|
||||||
|
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.GetCasbinAdapters(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
|
||||||
|
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(adapters, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetCasbinAdapter() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
c.Data["json"] = object.GetCasbinAdapter(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateCasbinAdapter() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var casbinAdapter object.CasbinAdapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateCasbinAdapter(id, &casbinAdapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) AddCasbinAdapter() {
|
||||||
|
var casbinAdapter object.CasbinAdapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddCasbinAdapter(&casbinAdapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) DeleteCasbinAdapter() {
|
||||||
|
var casbinAdapter object.CasbinAdapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteCasbinAdapter(&casbinAdapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) SyncPolicies() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
adapter := object.GetCasbinAdapter(id)
|
||||||
|
|
||||||
|
c.Data["json"] = object.SyncPolicies(adapter)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@ -145,6 +145,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(CasbinAdapter))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Provider))
|
err = a.Engine.Sync2(new(Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
217
object/casbin_adapter.go
Normal file
217
object/casbin_adapter.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casbin/casbin/v2"
|
||||||
|
"github.com/casbin/casbin/v2/model"
|
||||||
|
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CasbinAdapter 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"`
|
||||||
|
|
||||||
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
|
Model string `xorm:"varchar(100)" json:"model"`
|
||||||
|
|
||||||
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
|
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||||
|
Database string `xorm:"varchar(100)" json:"database"`
|
||||||
|
Table string `xorm:"varchar(100)" json:"table"`
|
||||||
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
|
||||||
|
Adapter *xormadapter.Adapter `xorm:"-" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCasbinAdapterCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&CasbinAdapter{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCasbinAdapters(owner string) []*CasbinAdapter {
|
||||||
|
adapters := []*CasbinAdapter{}
|
||||||
|
err := adapter.Engine.Where("owner = ?", owner).Find(&adapters)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapters
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationCasbinAdapters(owner string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
|
||||||
|
session := GetSession(owner, page, limit, field, value, sort, order)
|
||||||
|
adapters := []*CasbinAdapter{}
|
||||||
|
err := session.Find(&adapters)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapters
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCasbinAdapter(owner, name string) *CasbinAdapter {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
casbinAdapter := CasbinAdapter{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &casbinAdapter
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCasbinAdapter(id string) *CasbinAdapter {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getCasbinAdapter(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCasbinAdapter(id string, casbinAdapter *CasbinAdapter) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getCasbinAdapter(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
if casbinAdapter.Password == "***" {
|
||||||
|
session.Omit("password")
|
||||||
|
}
|
||||||
|
affected, err := session.Update(casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddCasbinAdapter(casbinAdapter *CasbinAdapter) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(casbinAdapter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteCasbinAdapter(casbinAdapter *CasbinAdapter) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{casbinAdapter.Owner, casbinAdapter.Name}).Delete(&CasbinAdapter{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (casbinAdapter *CasbinAdapter) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", casbinAdapter.Owner, casbinAdapter.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (casbinAdapter *CasbinAdapter) getTable() string {
|
||||||
|
if casbinAdapter.DatabaseType == "mssql" {
|
||||||
|
return fmt.Sprintf("[%s]", casbinAdapter.Table)
|
||||||
|
} else {
|
||||||
|
return casbinAdapter.Table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeReturn(policy []string, i int) string {
|
||||||
|
if len(policy) > i {
|
||||||
|
return policy[i]
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matrixToCasbinRules(pType string, policies [][]string) []*xormadapter.CasbinRule {
|
||||||
|
res := []*xormadapter.CasbinRule{}
|
||||||
|
|
||||||
|
for _, policy := range policies {
|
||||||
|
line := xormadapter.CasbinRule{
|
||||||
|
PType: pType,
|
||||||
|
V0: safeReturn(policy, 0),
|
||||||
|
V1: safeReturn(policy, 1),
|
||||||
|
V2: safeReturn(policy, 2),
|
||||||
|
V3: safeReturn(policy, 3),
|
||||||
|
V4: safeReturn(policy, 4),
|
||||||
|
V5: safeReturn(policy, 5),
|
||||||
|
}
|
||||||
|
res = append(res, &line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncPolicies(casbinAdapter *CasbinAdapter) []*xormadapter.CasbinRule {
|
||||||
|
// init Adapter
|
||||||
|
if casbinAdapter.Adapter == nil {
|
||||||
|
var dataSourceName string
|
||||||
|
if casbinAdapter.DatabaseType == "mssql" {
|
||||||
|
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port, casbinAdapter.Database)
|
||||||
|
} else if casbinAdapter.DatabaseType == "postgres" {
|
||||||
|
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port, casbinAdapter.Database)
|
||||||
|
} else {
|
||||||
|
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", casbinAdapter.User, casbinAdapter.Password, casbinAdapter.Host, casbinAdapter.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isCloudIntranet {
|
||||||
|
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||||
|
}
|
||||||
|
|
||||||
|
casbinAdapter.Adapter, _ = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(casbinAdapter.DatabaseType, dataSourceName, casbinAdapter.Database).Engine, casbinAdapter.getTable(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// init Model
|
||||||
|
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
|
||||||
|
m, err := model.NewModelFromString(modelObj.ModelText)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init Enforcer
|
||||||
|
enforcer, err := casbin.NewEnforcer(m, casbinAdapter.Adapter)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policies := matrixToCasbinRules("p", enforcer.GetPolicy())
|
||||||
|
if strings.Contains(modelObj.ModelText, "[role_definition]") {
|
||||||
|
policies = append(policies, matrixToCasbinRules("g", enforcer.GetGroupingPolicy())...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return policies
|
||||||
|
}
|
@ -97,6 +97,13 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
|
beego.Router("/api/add-model", &controllers.ApiController{}, "POST:AddModel")
|
||||||
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
|
beego.Router("/api/delete-model", &controllers.ApiController{}, "POST:DeleteModel")
|
||||||
|
|
||||||
|
beego.Router("/api/get-adapters", &controllers.ApiController{}, "GET:GetCasbinAdapters")
|
||||||
|
beego.Router("/api/get-adapter", &controllers.ApiController{}, "GET:GetCasbinAdapter")
|
||||||
|
beego.Router("/api/update-adapter", &controllers.ApiController{}, "POST:UpdateCasbinAdapter")
|
||||||
|
beego.Router("/api/add-adapter", &controllers.ApiController{}, "POST:AddCasbinAdapter")
|
||||||
|
beego.Router("/api/delete-adapter", &controllers.ApiController{}, "POST:DeleteCasbinAdapter")
|
||||||
|
beego.Router("/api/sync-policies", &controllers.ApiController{}, "GET:SyncPolicies")
|
||||||
|
|
||||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||||
|
412
web/src/AdapterEditPage.js
Normal file
412
web/src/AdapterEditPage.js
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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, InputNumber, Row, Select, Switch, Table, Tooltip} from "antd";
|
||||||
|
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
|
import {EditOutlined, MinusOutlined} from "@ant-design/icons";
|
||||||
|
require("codemirror/theme/material-darker.css");
|
||||||
|
require("codemirror/mode/javascript/javascript");
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
|
||||||
|
class AdapterEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||||
|
adapterName: props.match.params.adapterName,
|
||||||
|
adapter: null,
|
||||||
|
organizations: [],
|
||||||
|
models: [],
|
||||||
|
policyLists: [],
|
||||||
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getAdapter();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdapter() {
|
||||||
|
AdapterBackend.getAdapter(this.state.organizationName, this.state.adapterName)
|
||||||
|
.then((adapter) => {
|
||||||
|
this.setState({
|
||||||
|
adapter: adapter,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getModels(adapter.owner);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations(this.state.organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getModels(organizationName) {
|
||||||
|
ModelBackend.getModels(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
models: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAdapterField(key, value) {
|
||||||
|
if (["port"].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAdapterField(key, value) {
|
||||||
|
value = this.parseAdapterField(key, value);
|
||||||
|
|
||||||
|
const adapter = this.state.adapter;
|
||||||
|
adapter[key] = value;
|
||||||
|
this.setState({
|
||||||
|
adapter: adapter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
synPolicies() {
|
||||||
|
this.setState({loading: true});
|
||||||
|
AdapterBackend.syncPolicies(this.state.adapter.owner, this.state.adapter.name)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({loading: false, policyLists: res});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({loading: false});
|
||||||
|
Setting.showMessage("error", `Adapter failed to get policies: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(table) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "Rule Type",
|
||||||
|
dataIndex: "PType",
|
||||||
|
key: "PType",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V0",
|
||||||
|
dataIndex: "V0",
|
||||||
|
key: "V0",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V1",
|
||||||
|
dataIndex: "V1",
|
||||||
|
key: "V1",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V2",
|
||||||
|
dataIndex: "V2",
|
||||||
|
key: "V2",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V3",
|
||||||
|
dataIndex: "V3",
|
||||||
|
key: "V3",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V4",
|
||||||
|
dataIndex: "V4",
|
||||||
|
key: "V4",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "V5",
|
||||||
|
dataIndex: "V5",
|
||||||
|
key: "V5",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Option",
|
||||||
|
key: "option",
|
||||||
|
width: "100px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip placement="topLeft" title="Edit">
|
||||||
|
<Button style={{marginRight: "0.5rem"}} icon={<EditOutlined />} size="small" />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title="Delete">
|
||||||
|
<Button icon={<MinusOutlined />} size="small" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
pagination={{
|
||||||
|
defaultPageSize: 10,
|
||||||
|
}}
|
||||||
|
columns={columns} dataSource={table} rowKey="name" size="middle" bordered
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAdapter() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{this.state.mode === "add" ? i18next.t("adapter:New Adapter") : i18next.t("adapter:Edit Adapter")}
|
||||||
|
<Button onClick={() => this.submitAdapterEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitAdapterEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteAdapter()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||||
|
<Row style={{marginTop: "10px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateadapterField("organization", value);})}>
|
||||||
|
{
|
||||||
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.name} onChange={e => {
|
||||||
|
this.updateAdapterField("name", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
|
||||||
|
this.updateAdapterField("type", value);
|
||||||
|
const adapter = this.state.adapter;
|
||||||
|
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
|
||||||
|
this.setState({
|
||||||
|
adapter: adapter,
|
||||||
|
});
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
["Database"]
|
||||||
|
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.host} onChange={e => {
|
||||||
|
this.updateAdapterField("host", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.adapter.port} onChange={value => {
|
||||||
|
this.updateAdapterField("port", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.user} onChange={e => {
|
||||||
|
this.updateAdapterField("user", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.password} onChange={e => {
|
||||||
|
this.updateAdapterField("password", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "mysql", name: "MySQL"},
|
||||||
|
{id: "postgres", name: "PostgreSQL"},
|
||||||
|
{id: "mssql", name: "SQL Server"},
|
||||||
|
{id: "oracle", name: "Oracle"},
|
||||||
|
{id: "sqlite3", name: "Sqlite 3"},
|
||||||
|
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.database} onChange={e => {
|
||||||
|
this.updateAdapterField("database", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.table}
|
||||||
|
disabled={this.state.adapter.type === "Keycloak"} onChange={e => {
|
||||||
|
this.updateAdapterField("table", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.model} onChange={(model => {
|
||||||
|
this.updateAdapterField("model", model);
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<Button type="primary" onClick={() => {this.synPolicies();}}>
|
||||||
|
{i18next.t("adapter:Sync")}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
{
|
||||||
|
this.renderTable(this.state.policyLists)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.adapter.isEnabled} onChange={checked => {
|
||||||
|
this.updateAdapterField("isEnabled", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitAdapterEdit(willExist) {
|
||||||
|
const adapter = Setting.deepCopy(this.state.adapter);
|
||||||
|
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", "Successfully saved");
|
||||||
|
this.setState({
|
||||||
|
adapterName: this.state.adapter.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push("/adapters");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/adapters/${this.state.adapter.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateAdapterField("name", this.state.adapterName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAdapter() {
|
||||||
|
AdapterBackend.deleteAdapter(this.state.adapter)
|
||||||
|
.then(() => {
|
||||||
|
this.props.history.push("/adapters");
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `adapter failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.adapter !== null ? this.renderAdapter() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button size="large" onClick={() => this.submitAdapterEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitAdapterEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteAdapter()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdapterEditPage;
|
261
web/src/AdapterListPage.js
Normal file
261
web/src/AdapterListPage.js
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 AdapterBackend from "./backend/AdapterBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import BaseListPage from "./BaseListPage";
|
||||||
|
|
||||||
|
class AdapterListPage extends BaseListPage {
|
||||||
|
newAdapter() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: "built-in",
|
||||||
|
name: `adapter_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
organization: "built-in",
|
||||||
|
type: "Database",
|
||||||
|
host: "localhost",
|
||||||
|
port: 3306,
|
||||||
|
user: "root",
|
||||||
|
password: "123456",
|
||||||
|
databaseType: "mysql",
|
||||||
|
database: "dbName",
|
||||||
|
table: "tableName",
|
||||||
|
isEnabled: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addAdapter() {
|
||||||
|
const newAdapter = this.newAdapter();
|
||||||
|
AdapterBackend.addAdapter(newAdapter)
|
||||||
|
.then((res) => {
|
||||||
|
this.props.history.push({pathname: `/adapters/${newAdapter.owner}/${newAdapter.name}`, mode: "add"});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Adapter failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAdapter(i) {
|
||||||
|
AdapterBackend.deleteAdapter(this.state.data[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", "Adapter deleted successfully");
|
||||||
|
this.setState({
|
||||||
|
data: Setting.deleteRow(this.state.data, i),
|
||||||
|
pagination: {total: this.state.pagination.total - 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Adapter failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(adapters) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: "organization",
|
||||||
|
key: "organization",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("organization"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/organizations/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "150px",
|
||||||
|
fixed: "left",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("name"),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/adapters/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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("provider:Type"),
|
||||||
|
dataIndex: "type",
|
||||||
|
key: "type",
|
||||||
|
width: "100px",
|
||||||
|
sorter: true,
|
||||||
|
filterMultiple: false,
|
||||||
|
filters: [
|
||||||
|
{text: "Database", value: "Database"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Host"),
|
||||||
|
dataIndex: "host",
|
||||||
|
key: "host",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("host"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("provider:Port"),
|
||||||
|
dataIndex: "port",
|
||||||
|
key: "port",
|
||||||
|
width: "100px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("port"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:User"),
|
||||||
|
dataIndex: "user",
|
||||||
|
key: "user",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("user"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Password"),
|
||||||
|
dataIndex: "password",
|
||||||
|
key: "password",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
...this.getColumnSearchProps("password"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("syncer:Database type"),
|
||||||
|
dataIndex: "databaseType",
|
||||||
|
key: "databaseType",
|
||||||
|
width: "120px",
|
||||||
|
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("syncer:Database"),
|
||||||
|
dataIndex: "database",
|
||||||
|
key: "database",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("syncer:Table"),
|
||||||
|
dataIndex: "table",
|
||||||
|
key: "table",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Is enabled"),
|
||||||
|
dataIndex: "isEnabled",
|
||||||
|
key: "isEnabled",
|
||||||
|
width: "120px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "",
|
||||||
|
key: "op",
|
||||||
|
width: "170px",
|
||||||
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/adapters/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete adapter: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteAdapter(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={adapters} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Adapters")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addAdapter.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={this.state.loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
let field = params.searchedColumn, value = params.searchText;
|
||||||
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
|
if (params.type !== undefined && params.type !== null) {
|
||||||
|
field = "type";
|
||||||
|
value = params.type;
|
||||||
|
}
|
||||||
|
this.setState({loading: true});
|
||||||
|
AdapterBackend.getAdapters("", 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 AdapterListPage;
|
@ -72,6 +72,8 @@ import CasLogout from "./auth/CasLogout";
|
|||||||
import ModelListPage from "./ModelListPage";
|
import ModelListPage from "./ModelListPage";
|
||||||
import ModelEditPage from "./ModelEditPage";
|
import ModelEditPage from "./ModelEditPage";
|
||||||
import SystemInfo from "./SystemInfo";
|
import SystemInfo from "./SystemInfo";
|
||||||
|
import AdapterListPage from "./AdapterListPage";
|
||||||
|
import AdapterEditPage from "./AdapterEditPage";
|
||||||
|
|
||||||
const {Header, Footer} = Layout;
|
const {Header, Footer} = Layout;
|
||||||
|
|
||||||
@ -123,6 +125,8 @@ class App extends Component {
|
|||||||
this.setState({selectedMenuKey: "/permissions"});
|
this.setState({selectedMenuKey: "/permissions"});
|
||||||
} else if (uri.includes("/models")) {
|
} else if (uri.includes("/models")) {
|
||||||
this.setState({selectedMenuKey: "/models"});
|
this.setState({selectedMenuKey: "/models"});
|
||||||
|
} else if (uri.includes("/adapters")) {
|
||||||
|
this.setState({selectedMenuKey: "/adapters"});
|
||||||
} else if (uri.includes("/providers")) {
|
} else if (uri.includes("/providers")) {
|
||||||
this.setState({selectedMenuKey: "/providers"});
|
this.setState({selectedMenuKey: "/providers"});
|
||||||
} else if (uri.includes("/applications")) {
|
} else if (uri.includes("/applications")) {
|
||||||
@ -405,6 +409,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/adapters">
|
||||||
|
<Link to="/adapters">
|
||||||
|
{i18next.t("general:Adapters")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/providers">
|
<Menu.Item key="/providers">
|
||||||
<Link to="/providers">
|
<Link to="/providers">
|
||||||
@ -544,6 +555,8 @@ class App extends Component {
|
|||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||||
|
63
web/src/backend/AdapterBackend.js
Normal file
63
web/src/backend/AdapterBackend.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2022 The Casdoor 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 getAdapters(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-adapters?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAdapter(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-adapter?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAdapter(owner, name, Adapter) {
|
||||||
|
const newAdapter = Setting.deepCopy(Adapter);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/update-adapter?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newAdapter),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addAdapter(Adapter) {
|
||||||
|
const newAdapter = Setting.deepCopy(Adapter);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/add-adapter`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newAdapter),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAdapter(Adapter) {
|
||||||
|
const newAdapter = Setting.deepCopy(Adapter);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/delete-adapter`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newAdapter),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncPolicies(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/sync-policies?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "Mein Konto",
|
"My Account": "Mein Konto",
|
||||||
"Sign Up": "Registrieren"
|
"Sign Up": "Registrieren"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "Aktion",
|
"Action": "Aktion",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "Neu",
|
"Add": "Neu",
|
||||||
"Affiliation URL": "Affiliation-URL",
|
"Affiliation URL": "Affiliation-URL",
|
||||||
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "My Account",
|
"My Account": "My Account",
|
||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Affiliation URL": "Affiliation URL",
|
"Affiliation URL": "Affiliation URL",
|
||||||
"Affiliation URL - Tooltip": "Affiliation URL - Tooltip",
|
"Affiliation URL - Tooltip": "Affiliation URL - Tooltip",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "Mon Compte",
|
"My Account": "Mon Compte",
|
||||||
"Sign Up": "S'inscrire"
|
"Sign Up": "S'inscrire"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "Ajouter",
|
"Add": "Ajouter",
|
||||||
"Affiliation URL": "URL d'affiliation",
|
"Affiliation URL": "URL d'affiliation",
|
||||||
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "マイアカウント",
|
"My Account": "マイアカウント",
|
||||||
"Sign Up": "新規登録"
|
"Sign Up": "新規登録"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "アクション",
|
"Action": "アクション",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "追加",
|
"Add": "追加",
|
||||||
"Affiliation URL": "アフィリエイトURL",
|
"Affiliation URL": "アフィリエイトURL",
|
||||||
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "My Account",
|
"My Account": "My Account",
|
||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Affiliation URL": "Affiliation URL",
|
"Affiliation URL": "Affiliation URL",
|
||||||
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "Мой аккаунт",
|
"My Account": "Мой аккаунт",
|
||||||
"Sign Up": "Регистрация"
|
"Sign Up": "Регистрация"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "Edit Adapter",
|
||||||
|
"New Adapter": "New Adapter",
|
||||||
|
"Policies": "Policies",
|
||||||
|
"Policies - Tooltip": "Policies - Tooltip",
|
||||||
|
"Sync": "Sync"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "Background URL",
|
"Background URL": "Background URL",
|
||||||
"Background URL - Tooltip": "Background URL - Tooltip",
|
"Background URL - Tooltip": "Background URL - Tooltip",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "Действие",
|
"Action": "Действие",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Adapter - Tooltip",
|
"Adapter - Tooltip": "Adapter - Tooltip",
|
||||||
|
"Adapters": "Adapters",
|
||||||
"Add": "Добавить",
|
"Add": "Добавить",
|
||||||
"Affiliation URL": "URL-адрес партнёра",
|
"Affiliation URL": "URL-адрес партнёра",
|
||||||
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
"Affiliation URL - Tooltip": "Unique string-style identifier",
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
"My Account": "我的账户",
|
"My Account": "我的账户",
|
||||||
"Sign Up": "注册"
|
"Sign Up": "注册"
|
||||||
},
|
},
|
||||||
|
"adapter": {
|
||||||
|
"Edit Adapter": "编辑适配器",
|
||||||
|
"New Adapter": "添加适配器",
|
||||||
|
"Policies": "策略",
|
||||||
|
"Policies - Tooltip": "策略",
|
||||||
|
"Sync": "同步"
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"Background URL": "背景图URL",
|
"Background URL": "背景图URL",
|
||||||
"Background URL - Tooltip": "登录页背景图的链接",
|
"Background URL - Tooltip": "登录页背景图的链接",
|
||||||
@ -107,6 +114,7 @@
|
|||||||
"Action": "操作",
|
"Action": "操作",
|
||||||
"Adapter": "适配器",
|
"Adapter": "适配器",
|
||||||
"Adapter - Tooltip": "策略存储的表名",
|
"Adapter - Tooltip": "策略存储的表名",
|
||||||
|
"Adapters": "适配器",
|
||||||
"Add": "添加",
|
"Add": "添加",
|
||||||
"Affiliation URL": "工作单位URL",
|
"Affiliation URL": "工作单位URL",
|
||||||
"Affiliation URL - Tooltip": "工作单位URL",
|
"Affiliation URL - Tooltip": "工作单位URL",
|
||||||
|
Reference in New Issue
Block a user