feat: add message list (#18)
This commit is contained in:
parent
8e6880bdc0
commit
084c36d4af
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2023 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/casbin/casibase/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) GetGlobalMessages() {
|
||||||
|
messages, err := object.GetGlobalMessages()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetMessages() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
|
messages, err := object.GetMessages(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetMessage() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
message, err := object.GetMessage(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) UpdateMessage() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var message object.Message
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err := object.UpdateMessage(id, &message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) AddMessage() {
|
||||||
|
var message object.Message
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err := object.AddMessage(&message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) DeleteMessage() {
|
||||||
|
var message object.Message
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err := object.DeleteMessage(&message)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(success)
|
||||||
|
}
|
|
@ -132,4 +132,9 @@ func (a *Adapter) createTable() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.engine.Sync2(new(Message))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2023 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/casibase/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message 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"`
|
||||||
|
Chat string `xorm:"varchar(100) index" json:"chat"`
|
||||||
|
ReplyTo string `xorm:"varchar(100) index" json:"replyTo"`
|
||||||
|
Author string `xorm:"varchar(100)" json:"author"`
|
||||||
|
Text string `xorm:"mediumtext" json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGlobalMessages() ([]*Message, error) {
|
||||||
|
messages := []*Message{}
|
||||||
|
err := adapter.engine.Asc("owner").Desc("created_time").Find(&messages)
|
||||||
|
if err != nil {
|
||||||
|
return messages, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMessages(owner string) ([]*Message, error) {
|
||||||
|
messages := []*Message{}
|
||||||
|
err := adapter.engine.Desc("created_time").Find(&messages, &Message{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
return messages, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMessage(owner, name string) (*Message, error) {
|
||||||
|
message := Message{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.engine.Get(&message)
|
||||||
|
if err != nil {
|
||||||
|
return &message, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &message, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMessage(id string) (*Message, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getMessage(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateMessage(id string, message *Message) (bool, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
_, err := getMessage(owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if message == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = adapter.engine.ID(core.PK{owner, name}).AllCols().Update(message)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddMessage(message *Message) (bool, error) {
|
||||||
|
affected, err := adapter.engine.Insert(message)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteMessage(message *Message) (bool, error) {
|
||||||
|
affected, err := adapter.engine.ID(core.PK{message.Owner, message.Name}).Delete(&Message{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (message *Message) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", message.Owner, message.Name)
|
||||||
|
}
|
|
@ -81,6 +81,13 @@ func initAPI() {
|
||||||
beego.Router("/api/add-chat", &controllers.ApiController{}, "POST:AddChat")
|
beego.Router("/api/add-chat", &controllers.ApiController{}, "POST:AddChat")
|
||||||
beego.Router("/api/delete-chat", &controllers.ApiController{}, "POST:DeleteChat")
|
beego.Router("/api/delete-chat", &controllers.ApiController{}, "POST:DeleteChat")
|
||||||
|
|
||||||
|
beego.Router("/api/get-global-messages", &controllers.ApiController{}, "GET:GetGlobalMessages")
|
||||||
|
beego.Router("/api/get-messages", &controllers.ApiController{}, "GET:GetMessages")
|
||||||
|
beego.Router("/api/get-message", &controllers.ApiController{}, "GET:GetMessage")
|
||||||
|
beego.Router("/api/update-message", &controllers.ApiController{}, "POST:UpdateMessage")
|
||||||
|
beego.Router("/api/add-message", &controllers.ApiController{}, "POST:AddMessage")
|
||||||
|
beego.Router("/api/delete-message", &controllers.ApiController{}, "POST:DeleteMessage")
|
||||||
|
|
||||||
beego.Router("/api/update-file", &controllers.ApiController{}, "POST:UpdateFile")
|
beego.Router("/api/update-file", &controllers.ApiController{}, "POST:UpdateFile")
|
||||||
beego.Router("/api/add-file", &controllers.ApiController{}, "POST:AddFile")
|
beego.Router("/api/add-file", &controllers.ApiController{}, "POST:AddFile")
|
||||||
beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile")
|
beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile")
|
||||||
|
|
|
@ -40,6 +40,8 @@ import i18next from "i18next";
|
||||||
import LanguageSelect from "./LanguageSelect";
|
import LanguageSelect from "./LanguageSelect";
|
||||||
import ChatEditPage from "./ChatEditPage";
|
import ChatEditPage from "./ChatEditPage";
|
||||||
import ChatListPage from "./ChatListPage";
|
import ChatListPage from "./ChatListPage";
|
||||||
|
import MessageListPage from "./MessageListPage";
|
||||||
|
import MessageEditPage from "./MessageEditPage";
|
||||||
|
|
||||||
const {Header, Footer} = Layout;
|
const {Header, Footer} = Layout;
|
||||||
|
|
||||||
|
@ -92,6 +94,8 @@ class App extends Component {
|
||||||
this.setState({selectedMenuKey: "/providers"});
|
this.setState({selectedMenuKey: "/providers"});
|
||||||
} else if (uri.includes("/chats")) {
|
} else if (uri.includes("/chats")) {
|
||||||
this.setState({selectedMenuKey: "/chats"});
|
this.setState({selectedMenuKey: "/chats"});
|
||||||
|
} else if (uri.includes("/messages")) {
|
||||||
|
this.setState({selectedMenuKey: "/messages"});
|
||||||
} else {
|
} else {
|
||||||
this.setState({selectedMenuKey: "null"});
|
this.setState({selectedMenuKey: "null"});
|
||||||
}
|
}
|
||||||
|
@ -331,6 +335,13 @@ class App extends Component {
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
res.push(
|
||||||
|
<Menu.Item key="/messages">
|
||||||
|
<Link to="/messages">
|
||||||
|
{i18next.t("general:Messages")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/permissions">
|
<Menu.Item key="/permissions">
|
||||||
<a target="_blank" rel="noreferrer" href={Setting.getMyProfileUrl(this.state.account).replace("/account", "/permissions")}>
|
<a target="_blank" rel="noreferrer" href={Setting.getMyProfileUrl(this.state.account).replace("/account", "/permissions")}>
|
||||||
|
@ -418,6 +429,8 @@ class App extends Component {
|
||||||
<Route exact path="/providers/:providerName" render={(props) => this.renderSigninIfNotSignedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers/:providerName" render={(props) => this.renderSigninIfNotSignedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats" render={(props) => this.renderSigninIfNotSignedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats" render={(props) => this.renderSigninIfNotSignedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/chats/:chatName" render={(props) => this.renderSigninIfNotSignedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/chats/:chatName" render={(props) => this.renderSigninIfNotSignedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/messages" render={(props) => this.renderSigninIfNotSignedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/messages/:messageName" render={(props) => this.renderSigninIfNotSignedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,11 +27,13 @@ class ChatEditPage extends React.Component {
|
||||||
classes: props,
|
classes: props,
|
||||||
chatName: props.match.params.chatName,
|
chatName: props.match.params.chatName,
|
||||||
chat: null,
|
chat: null,
|
||||||
|
// users: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getChat();
|
this.getChat();
|
||||||
|
// this.getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
getChat() {
|
getChat() {
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright 2023 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 * as Setting from "./Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import * as MessageBackend from "./backend/MessageBackend";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
import * as ChatBackend from "./backend/ChatBackend";
|
||||||
|
|
||||||
|
class MessageEditPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
messageName: props.match.params.messageName,
|
||||||
|
messages: [],
|
||||||
|
message: null,
|
||||||
|
chats: [],
|
||||||
|
// users: [],
|
||||||
|
chat: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getMessage();
|
||||||
|
this.getMessages();
|
||||||
|
this.getChats();
|
||||||
|
}
|
||||||
|
|
||||||
|
getChats() {
|
||||||
|
ChatBackend.getChats(this.props.account.name)
|
||||||
|
.then((chats) => {
|
||||||
|
if (chats.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
chats: chats.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to get chat: ${chats.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getChat(chatName) {
|
||||||
|
ChatBackend.getChat(this.props.account.name, chatName)
|
||||||
|
.then((chat) => {
|
||||||
|
if (chat.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
chat: chat.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to get chat: ${chat.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessage() {
|
||||||
|
MessageBackend.getMessage(this.props.account.name, this.state.messageName)
|
||||||
|
.then((message) => {
|
||||||
|
if (message.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
message: message.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to get message: ${message.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages() {
|
||||||
|
MessageBackend.getMessages(this.props.account.name)
|
||||||
|
.then((messages) => {
|
||||||
|
if (messages.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
messages: messages.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to get messages: ${messages.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessageField(key, value) {
|
||||||
|
if ([""].includes(key)) {
|
||||||
|
value = Setting.myParseInt(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMessageField(key, value) {
|
||||||
|
value = this.parseMessageField(key, value);
|
||||||
|
|
||||||
|
const message = this.state.message;
|
||||||
|
message[key] = value;
|
||||||
|
this.setState({
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessage() {
|
||||||
|
return (
|
||||||
|
<Card size="small" title={
|
||||||
|
<div>
|
||||||
|
{i18next.t("message:Edit Chat")}
|
||||||
|
<Button type="primary" onClick={this.submitMessageEdit.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} disabled={!Setting.isAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}*/}
|
||||||
|
{/* options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))*/}
|
||||||
|
{/* } />*/}
|
||||||
|
{/* </Col>*/}
|
||||||
|
{/* </Row>*/}
|
||||||
|
<Row style={{marginTop: "10px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{i18next.t("general:Name")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Input
|
||||||
|
value={this.state.message.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
this.updateMessageField("name", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{i18next.t("message:Chat")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Select
|
||||||
|
virtual={false}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
value={this.state.message.chat}
|
||||||
|
onChange={(value) => {
|
||||||
|
this.updateMessageField("chat", value);
|
||||||
|
this.getChat(value);
|
||||||
|
}}
|
||||||
|
options={this.state.chats.map((chat) =>
|
||||||
|
Setting.getOption(chat.name, chat.name)
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{i18next.t("message:Author")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Select
|
||||||
|
virtual={false}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
value={this.state.message.author}
|
||||||
|
onChange={(value) => {
|
||||||
|
this.updateMessageField("author", value);
|
||||||
|
}}
|
||||||
|
options={
|
||||||
|
this.state.chat !== null
|
||||||
|
? this.state.chat.users.map((user) =>
|
||||||
|
Setting.getOption(`${user}`, `${user}`)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{i18next.t("message:replyTo")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Select
|
||||||
|
virtual={false}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
value={this.state.message.replyTo}
|
||||||
|
onChange={(value) => {
|
||||||
|
this.updateMessageField("replyTo", value);
|
||||||
|
}}
|
||||||
|
options={
|
||||||
|
this.state.messages !== null
|
||||||
|
? this.state.messages.map((message) =>
|
||||||
|
Setting.getOption(`${message.name}`, `${message.name}`)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{i18next.t("message:Text")}:
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea
|
||||||
|
rows={10}
|
||||||
|
value={this.state.message.text}
|
||||||
|
onChange={(e) => {
|
||||||
|
this.updateMessageField("text", e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
submitMessageEdit(exitAfterSave) {
|
||||||
|
const message = Setting.deepCopy(this.state.message);
|
||||||
|
MessageBackend.updateMessage(this.state.message.owner, this.state.messageName, message)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
if (res.data) {
|
||||||
|
Setting.showMessage("success", "Successfully saved");
|
||||||
|
this.setState({
|
||||||
|
messageName: this.state.message.name,
|
||||||
|
});
|
||||||
|
if (exitAfterSave) {
|
||||||
|
this.props.history.push(`/messages/${this.state.message.name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", "failed to save: server side failure");
|
||||||
|
this.updateMessageField("name", this.state.messageName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `failed to save: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
Setting.showMessage("error", `failed to save: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{width: "100%"}}>
|
||||||
|
<Col span={1}></Col>
|
||||||
|
<Col span={22}>
|
||||||
|
{this.state.message !== null ? this.renderMessage() : null}
|
||||||
|
</Col>
|
||||||
|
<Col span={1}></Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{margin: 10}}>
|
||||||
|
<Col span={2}></Col>
|
||||||
|
<Col span={18}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
onClick={this.submitMessageEdit.bind(this)}
|
||||||
|
>
|
||||||
|
{i18next.t("general:Save")}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageEditPage;
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Copyright 2023 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, Col, Popconfirm, Row, Table} from "antd";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import * as MessageBackend from "./backend/MessageBackend";
|
||||||
|
import moment from "moment";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
class MessageListPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
classes: props,
|
||||||
|
messages: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
|
this.getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages() {
|
||||||
|
MessageBackend.getMessages(this.props.account.name)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
messages: res.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to get messages: ${res.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newMessage() {
|
||||||
|
const randomName = Setting.getRandomName();
|
||||||
|
return {
|
||||||
|
owner: this.props.account.name,
|
||||||
|
name: `message_${randomName}`,
|
||||||
|
createdTime: moment().format(),
|
||||||
|
// organization: "Message Organization - 1",
|
||||||
|
chat: "",
|
||||||
|
replyTo: "",
|
||||||
|
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||||
|
text: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage() {
|
||||||
|
const newMessage = this.newMessage();
|
||||||
|
MessageBackend.addMessage(newMessage)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Message added successfully");
|
||||||
|
this.setState({
|
||||||
|
messages: Setting.prependRow(this.state.messages, newMessage),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to add Message: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Message failed to add: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMessage(i) {
|
||||||
|
MessageBackend.deleteMessage(this.state.messages[i])
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
Setting.showMessage("success", "Message deleted successfully");
|
||||||
|
this.setState({
|
||||||
|
messages: Setting.deleteRow(this.state.messages, i),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `Failed to delete Message: ${res.msg}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Setting.showMessage("error", `Message failed to delete: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable(messages) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
width: "140px",
|
||||||
|
sorter: (a, b) => a.name.localeCompare(b.name),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/messages/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Created time"),
|
||||||
|
dataIndex: "createdTime",
|
||||||
|
key: "createdTime",
|
||||||
|
width: "150px",
|
||||||
|
sorter: true,
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return Setting.getFormattedDate(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Chat"),
|
||||||
|
dataIndex: "chat",
|
||||||
|
key: "chat",
|
||||||
|
width: "150px",
|
||||||
|
sorter: (a, b) => a.chat.localeCompare(b.chat),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/chat/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Reply to"),
|
||||||
|
dataIndex: "replyTo",
|
||||||
|
key: "replyTo",
|
||||||
|
width: "150px",
|
||||||
|
sorter: (a, b) => a.replyTo.localeCompare(b.replyTo),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/message/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Author"),
|
||||||
|
dataIndex: "author",
|
||||||
|
key: "author",
|
||||||
|
width: "150px",
|
||||||
|
sorter: (a, b) => a.author.localeCompare(b.author),
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Link to={`/member/${text}`}>
|
||||||
|
{text}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("message:Text"),
|
||||||
|
dataIndex: "text",
|
||||||
|
key: "text",
|
||||||
|
width: "200px",
|
||||||
|
sorter: (a, b) => a.text.localeCompare(b.text),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
dataIndex: "action",
|
||||||
|
key: "action",
|
||||||
|
width: "130px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||||
|
type="primary"
|
||||||
|
onClick={() => this.props.history.push(`/messages/${record.name}`)}
|
||||||
|
>
|
||||||
|
{i18next.t("general:Edit")}
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title={`Sure to delete message: ${record.name} ?`}
|
||||||
|
onConfirm={() => this.deleteMessage(index)}
|
||||||
|
okText={i18next.t("general:OK")}
|
||||||
|
cancelText={i18next.t("general:Cancel")}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: "10px"}} type="danger">
|
||||||
|
{i18next.t("general:Delete")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={messages}
|
||||||
|
rowKey="name"
|
||||||
|
size="middle"
|
||||||
|
bordered
|
||||||
|
pagination={{pageSize: 100}}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("message:Messages")}
|
||||||
|
<Button type="primary" size="small" onClick={this.addMessage.bind(this)}>
|
||||||
|
{i18next.t("general:Add")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
loading={messages === null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{width: "100%"}}>
|
||||||
|
<Col span={1}>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
{
|
||||||
|
this.renderTable(this.state.messages)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MessageListPage;
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2023 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 getGlobalMessages() {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-global-messages`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMessages(owner) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-messages?owner=${owner}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMessage(owner, name) {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-message?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMessage(owner, name, message) {
|
||||||
|
const newMessage = Setting.deepCopy(message);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/update-message?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newMessage),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addMessage(message) {
|
||||||
|
const newMessage = Setting.deepCopy(message);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/add-message`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newMessage),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteMessage(message) {
|
||||||
|
const newMessage = Setting.deepCopy(message);
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/delete-message`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(newMessage),
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
Loading…
Reference in New Issue