mirror of
https://github.com/casdoor/casdoor.git
synced 2025-09-07 02:20:28 +08:00
Add webhook pages.
This commit is contained in:
107
controllers/webhook.go
Normal file
107
controllers/webhook.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetWebhooks
|
||||||
|
// @Title GetWebhooks
|
||||||
|
// @Description get webhooks
|
||||||
|
// @Param owner query string true "The owner of webhooks"
|
||||||
|
// @Success 200 {array} object.Webhook The Response object
|
||||||
|
// @router /get-webhooks [get]
|
||||||
|
func (c *ApiController) GetWebhooks() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetWebhooks(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner)))
|
||||||
|
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit)
|
||||||
|
c.ResponseOk(webhooks, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetWebhook
|
||||||
|
// @Description get webhook
|
||||||
|
// @Param id query string true "The id of the webhook"
|
||||||
|
// @Success 200 {object} object.Webhook The Response object
|
||||||
|
// @router /get-webhook [get]
|
||||||
|
func (c *ApiController) GetWebhook() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetWebhook(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateWebhook
|
||||||
|
// @Description update webhook
|
||||||
|
// @Param id query string true "The id of the webhook"
|
||||||
|
// @Param body body object.Webhook true "The details of the webhook"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-webhook [post]
|
||||||
|
func (c *ApiController) UpdateWebhook() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var webhook object.Webhook
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateWebhook(id, &webhook))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddWebhook
|
||||||
|
// @Description add webhook
|
||||||
|
// @Param body body object.Webhook true "The details of the webhook"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-webhook [post]
|
||||||
|
func (c *ApiController) AddWebhook() {
|
||||||
|
var webhook object.Webhook
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddWebhook(&webhook))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteWebhook
|
||||||
|
// @Description delete webhook
|
||||||
|
// @Param body body object.Webhook true "The details of the webhook"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-webhook [post]
|
||||||
|
func (c *ApiController) DeleteWebhook() {
|
||||||
|
var webhook object.Webhook
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteWebhook(&webhook))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
@@ -138,11 +138,17 @@ func (a *Adapter) createTable() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Records))
|
err = a.Engine.Sync2(new(Records))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Webhook))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Ldap))
|
err = a.Engine.Sync2(new(Ldap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
122
object/webhook.go
Normal file
122
object/webhook.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// 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 Webhook 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"`
|
||||||
|
|
||||||
|
Url string `xorm:"varchar(100)" json:"url"`
|
||||||
|
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||||
|
Events []string `xorm:"varchar(100)" json:"events"`
|
||||||
|
|
||||||
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWebhookCount(owner string) int {
|
||||||
|
count, err := adapter.Engine.Count(&Webhook{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWebhooks(owner string) []*Webhook {
|
||||||
|
webhooks := []*Webhook{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return webhooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationWebhooks(owner string, offset, limit int) []*Webhook {
|
||||||
|
webhooks := []*Webhook{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&webhooks, &Webhook{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return webhooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWebhook(owner string, name string) *Webhook {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook := Webhook{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &webhook
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWebhook(id string) *Webhook {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getWebhook(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateWebhook(id string, webhook *Webhook) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getWebhook(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddWebhook(webhook *Webhook) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(webhook)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteWebhook(webhook *Webhook) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{webhook.Owner, webhook.Name}).Delete(&Webhook{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Webhook) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
|
}
|
@@ -106,6 +106,12 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||||
|
|
||||||
|
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
|
||||||
|
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
|
||||||
|
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")
|
||||||
|
beego.Router("/api/add-webhook", &controllers.ApiController{}, "POST:AddWebhook")
|
||||||
|
beego.Router("/api/delete-webhook", &controllers.ApiController{}, "POST:DeleteWebhook")
|
||||||
|
|
||||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||||
|
|
||||||
|
@@ -34,6 +34,8 @@ import LdapSyncPage from "./LdapSyncPage";
|
|||||||
import TokenListPage from "./TokenListPage";
|
import TokenListPage from "./TokenListPage";
|
||||||
import TokenEditPage from "./TokenEditPage";
|
import TokenEditPage from "./TokenEditPage";
|
||||||
import RecordListPage from "./RecordListPage";
|
import RecordListPage from "./RecordListPage";
|
||||||
|
import WebhookListPage from "./WebhookListPage";
|
||||||
|
import WebhookEditPage from "./WebhookEditPage";
|
||||||
import AccountPage from "./account/AccountPage";
|
import AccountPage from "./account/AccountPage";
|
||||||
import HomePage from "./basic/HomePage";
|
import HomePage from "./basic/HomePage";
|
||||||
import CustomGithubCorner from "./CustomGithubCorner";
|
import CustomGithubCorner from "./CustomGithubCorner";
|
||||||
@@ -107,6 +109,8 @@ class App extends Component {
|
|||||||
this.setState({ selectedMenuKey: '/tokens' });
|
this.setState({ selectedMenuKey: '/tokens' });
|
||||||
} else if (uri.includes('/records')) {
|
} else if (uri.includes('/records')) {
|
||||||
this.setState({ selectedMenuKey: '/records' });
|
this.setState({ selectedMenuKey: '/records' });
|
||||||
|
} else if (uri.includes('/webhooks')) {
|
||||||
|
this.setState({ selectedMenuKey: '/webhooks' });
|
||||||
} else if (uri.includes('/signup')) {
|
} else if (uri.includes('/signup')) {
|
||||||
this.setState({ selectedMenuKey: '/signup' });
|
this.setState({ selectedMenuKey: '/signup' });
|
||||||
} else if (uri.includes('/login')) {
|
} else if (uri.includes('/login')) {
|
||||||
@@ -354,6 +358,13 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/webhooks">
|
||||||
|
<Link to="/webhooks">
|
||||||
|
{i18next.t("general:Webhooks")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/swagger">
|
<Menu.Item key="/swagger">
|
||||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||||
@@ -414,6 +425,8 @@ class App extends Component {
|
|||||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)}/>
|
||||||
|
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import {message, Tooltip} from "antd";
|
import {message, Tag, Tooltip} from "antd";
|
||||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||||
@@ -511,3 +511,19 @@ export function getDeduplicatedArray(array, filterArray, key) {
|
|||||||
const res = array.filter(item => filterArray.filter(filterItem => filterItem[key] === item[key]).length === 0);
|
const res = array.filter(item => filterArray.filter(filterItem => filterItem[key] === item[key]).length === 0);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTagColor(s) {
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTags(tags) {
|
||||||
|
let res = [];
|
||||||
|
tags.forEach((tag, i) => {
|
||||||
|
res.push(
|
||||||
|
<Tag color={getTagColor(tag)}>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
191
web/src/WebhookEditPage.js
Normal file
191
web/src/WebhookEditPage.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
// 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} from 'antd';
|
||||||
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
|
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||||
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
class WebhookEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
webhookName: props.match.params.webhookName,
|
||||||
|
webhook: null,
|
||||||
|
organizations: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getWebhook();
|
||||||
|
this.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhook() {
|
||||||
|
WebhookBackend.getWebhook("admin", this.state.webhookName)
|
||||||
|
.then((webhook) => {
|
||||||
|
this.setState({
|
||||||
|
webhook: webhook,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrganizations() {
|
||||||
|
OrganizationBackend.getOrganizations("admin")
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
organizations: (res.msg === undefined) ? res : [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseWebhookField(key, value) {
|
||||||
|
if (["port"].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWebhookField(key, value) {
|
||||||
|
value = this.parseWebhookField(key, value);
|
||||||
|
|
||||||
|
let webhook = this.state.webhook;
|
||||||
|
webhook[key] = value;
|
||||||
|
this.setState({
|
||||||
|
webhook: webhook,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWebhook() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{i18next.t("webhook:Edit Webhook")}
|
||||||
|
<Button type="primary" onClick={this.submitWebhookEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||||
|
</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.webhook.organization} onChange={(value => {this.updateWebhookField('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.webhook.name} onChange={e => {
|
||||||
|
this.updateWebhookField('name', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:URL"), i18next.t("webhook:URL - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input prefix={<LinkOutlined/>} value={this.state.webhook.url} onChange={e => {
|
||||||
|
this.updateWebhookField('url', e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: '100%'}} value={this.state.webhook.contentType} onChange={(value => {this.updateWebhookField('contentType', value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: 'application/json', name: 'application/json'},
|
||||||
|
{id: 'application/x-www-form-urlencoded', name: 'application/x-www-form-urlencoded'},
|
||||||
|
].map((contentType, index) => <Option key={index} value={contentType.id}>{contentType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: '20px'}} >
|
||||||
|
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:Events"), i18next.t("webhook:Events - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" style={{width: '100%'}}
|
||||||
|
value={this.state.webhook.events}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateWebhookField('events', value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
(
|
||||||
|
["signup", "login", "logout", "update-user"].map((option, index) => {
|
||||||
|
return (
|
||||||
|
<Option key={option} value={option}>{option}</Option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submitWebhookEdit() {
|
||||||
|
let webhook = Setting.deepCopy(this.state.webhook);
|
||||||
|
WebhookBackend.updateWebhook(this.state.webhook.owner, this.state.webhookName, webhook)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.msg === "") {
|
||||||
|
Setting.showMessage("success", `Successfully saved`);
|
||||||
|
this.setState({
|
||||||
|
webhookName: this.state.webhook.name,
|
||||||
|
});
|
||||||
|
this.props.history.push(`/webhooks/${this.state.webhook.name}`);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", res.msg);
|
||||||
|
this.updateWebhookField('name', this.state.webhookName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.webhook !== null ? this.renderWebhook() : null
|
||||||
|
}
|
||||||
|
<div style={{marginTop: '20px', marginLeft: '40px'}}>
|
||||||
|
<Button type="primary" size="large" onClick={this.submitWebhookEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebhookEditPage;
|
224
web/src/WebhookListPage.js
Normal file
224
web/src/WebhookListPage.js
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// 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, Table} from 'antd';
|
||||||
|
import moment from "moment";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class WebhookListPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
webhooks: null,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getWebhooks(1, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhooks(page, pageSize) {
|
||||||
|
WebhookBackend.getWebhooks("admin", page, pageSize)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
webhooks: res.data,
|
||||||
|
total: res.data2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newWebhook() {
|
||||||
|
var randomName = Math.random().toString(36).slice(-6)
|
||||||
|
return {
|
||||||
|
owner: "admin", // this.props.account.webhookname,
|
||||||
|
name: `webhook_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
url: "https://example.com/callback",
|
||||||
|
contentType: "application/json",
|
||||||
|
events: [],
|
||||||
|
organization: "built-in",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addWebhook() {
|
||||||
|
const newWebhook = this.newWebhook();
|
||||||
|
WebhookBackend.addWebhook(newWebhook)
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Webhook added successfully`);
|
||||||
|
this.setState({
|
||||||
|
webhooks: Setting.prependRow(this.state.webhooks, newWebhook),
|
||||||
|
total: this.state.total + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Webhook failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteWebhook(i) {
|
||||||
|
WebhookBackend.deleteWebhook(this.state.webhooks[i])
|
||||||
|
.then((res) => {
|
||||||
|
Setting.showMessage("success", `Webhook deleted successfully`);
|
||||||
|
this.setState({
|
||||||
|
webhooks: Setting.deleteRow(this.state.webhooks, i),
|
||||||
|
total: this.state.total - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Webhook failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(webhooks) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Organization"),
|
||||||
|
dataIndex: 'organization',
|
||||||
|
key: 'organization',
|
||||||
|
width: '80px',
|
||||||
|
sorter: (a, b) => a.organization.localeCompare(b.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: (a, b) => a.name.localeCompare(b.name),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/webhooks/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: 'createdTime',
|
||||||
|
key: 'createdTime',
|
||||||
|
width: '180px',
|
||||||
|
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("webhook:URL"),
|
||||||
|
dataIndex: 'url',
|
||||||
|
key: 'url',
|
||||||
|
width: '300px',
|
||||||
|
sorter: (a, b) => a.url.localeCompare(b.url),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<a target="_blank" rel="noreferrer" href={text}>
|
||||||
|
{
|
||||||
|
Setting.getShortText(text)
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("webhook:Content type"),
|
||||||
|
dataIndex: 'contentType',
|
||||||
|
key: 'contentType',
|
||||||
|
width: '150px',
|
||||||
|
sorter: (a, b) => a.contentType.localeCompare(b.contentType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("webhook:Events"),
|
||||||
|
dataIndex: 'events',
|
||||||
|
key: 'events',
|
||||||
|
// width: '100px',
|
||||||
|
sorter: (a, b) => a.events.localeCompare(b.events),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getTags(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(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete webhook: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteWebhook(index)}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
total: this.state.total,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total),
|
||||||
|
onChange: (page, pageSize) => this.getWebhooks(page, pageSize),
|
||||||
|
onShowSizeChange: (current, size) => this.getWebhooks(current, size),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={webhooks} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("general:Webhooks")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addWebhook.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={webhooks === null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.renderTable(this.state.webhooks)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebhookListPage;
|
56
web/src/backend/WebhookBackend.js
Normal file
56
web/src/backend/WebhookBackend.js
Normal file
@@ -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 getWebhooks(owner, page = "", pageSize = "") {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-webhooks?owner=${owner}&p=${page}&pageSize=${pageSize}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebhook(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-webhook?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include"
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateWebhook(owner, name, webhook) {
|
||||||
|
let newWebhook = Setting.deepCopy(webhook);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/update-webhook?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newWebhook),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addWebhook(webhook) {
|
||||||
|
let newWebhook = Setting.deepCopy(webhook);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/add-webhook`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newWebhook),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteWebhook(webhook) {
|
||||||
|
let newWebhook = Setting.deepCopy(webhook);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/delete-webhook`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(newWebhook),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
Reference in New Issue
Block a user