mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
Add webhook pages.
This commit is contained in:
parent
cbf973882d
commit
0e71e603ac
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Records))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
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-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-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
||||
|
@ -34,6 +34,8 @@ import LdapSyncPage from "./LdapSyncPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
import TokenEditPage from "./TokenEditPage";
|
||||
import RecordListPage from "./RecordListPage";
|
||||
import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
@ -107,6 +109,8 @@ class App extends Component {
|
||||
this.setState({ selectedMenuKey: '/tokens' });
|
||||
} else if (uri.includes('/records')) {
|
||||
this.setState({ selectedMenuKey: '/records' });
|
||||
} else if (uri.includes('/webhooks')) {
|
||||
this.setState({ selectedMenuKey: '/webhooks' });
|
||||
} else if (uri.includes('/signup')) {
|
||||
this.setState({ selectedMenuKey: '/signup' });
|
||||
} else if (uri.includes('/login')) {
|
||||
@ -354,6 +358,13 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/webhooks">
|
||||
<Link to="/webhooks">
|
||||
{i18next.t("general:Webhooks")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/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="/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="/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="/.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.")}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {message, Tooltip} from "antd";
|
||||
import {message, Tag, Tooltip} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import React from "react";
|
||||
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);
|
||||
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());
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user