| @@ -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 { | |||
| 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/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/add-file", &controllers.ApiController{}, "POST:AddFile") | |||
| beego.Router("/api/delete-file", &controllers.ApiController{}, "POST:DeleteFile") | |||
| @@ -40,6 +40,8 @@ import i18next from "i18next"; | |||
| import LanguageSelect from "./LanguageSelect"; | |||
| import ChatEditPage from "./ChatEditPage"; | |||
| import ChatListPage from "./ChatListPage"; | |||
| import MessageListPage from "./MessageListPage"; | |||
| import MessageEditPage from "./MessageEditPage"; | |||
| const {Header, Footer} = Layout; | |||
| @@ -92,6 +94,8 @@ class App extends Component { | |||
| this.setState({selectedMenuKey: "/providers"}); | |||
| } else if (uri.includes("/chats")) { | |||
| this.setState({selectedMenuKey: "/chats"}); | |||
| } else if (uri.includes("/messages")) { | |||
| this.setState({selectedMenuKey: "/messages"}); | |||
| } else { | |||
| this.setState({selectedMenuKey: "null"}); | |||
| } | |||
| @@ -331,6 +335,13 @@ class App extends Component { | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| res.push( | |||
| <Menu.Item key="/messages"> | |||
| <Link to="/messages"> | |||
| {i18next.t("general:Messages")} | |||
| </Link> | |||
| </Menu.Item> | |||
| ); | |||
| res.push( | |||
| <Menu.Item key="/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="/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="/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> | |||
| </div> | |||
| ); | |||
| @@ -27,11 +27,13 @@ class ChatEditPage extends React.Component { | |||
| classes: props, | |||
| chatName: props.match.params.chatName, | |||
| chat: null, | |||
| // users: [], | |||
| }; | |||
| } | |||
| UNSAFE_componentWillMount() { | |||
| this.getChat(); | |||
| // this.getUser(); | |||
| } | |||
| 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()); | |||
| } | |||