| // 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, {Component} from "react"; |
| import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom"; |
| import {Avatar, BackTop, Drawer, Dropdown, Layout, Menu, Tooltip} from "antd"; |
| import {DeploymentUnitOutlined, DownOutlined, GithubOutlined, LogoutOutlined, SettingOutlined, ShareAltOutlined} from "@ant-design/icons"; |
| import "./App.less"; |
| import * as Setting from "./Setting"; |
| import * as AccountBackend from "./backend/AccountBackend"; |
| import AuthCallback from "./AuthCallback"; |
| import * as Conf from "./Conf"; |
| import HomePage from "./HomePage"; |
| import SiteListPage from "./SiteListPage"; |
| import SiteEditPage from "./SiteEditPage"; |
| import CertListPage from "./CertListPage"; |
| import CertEditPage from "./CertEditPage"; |
| import RuleListPage from "./RuleListPage"; |
| import RuleEditPage from "./RuleEditPage"; |
| import SigninPage from "./SigninPage"; |
| import RecordListPage from "./RecordListPage"; |
| import RecordEditPage from "./RecordEditPage"; |
| import i18next from "i18next"; |
| import DashboardPage from "./DashboardPage"; |
| import LanguageSelect from "./LanguageSelect"; |
| import {withTranslation} from "react-i18next"; |
| // import SelectLanguageBox from "./SelectLanguageBox"; |
| |
| const {Header, Footer} = Layout; |
| |
| class App extends Component { |
| constructor(props) { |
| super(props); |
| this.state = { |
| classes: props, |
| selectedMenuKey: 0, |
| account: undefined, |
| uri: null, |
| isAiAssistantOpen: false, |
| }; |
| |
| Setting.initServerUrl(); |
| Setting.initCasdoorSdk(Conf.AuthConfig); |
| } |
| |
| UNSAFE_componentWillMount() { |
| this.updateMenuKey(); |
| this.getAccount(); |
| } |
| |
| componentDidUpdate() { |
| // eslint-disable-next-line no-restricted-globals |
| const uri = location.pathname; |
| if (this.state.uri !== uri) { |
| this.updateMenuKey(); |
| } |
| } |
| |
| updateMenuKey() { |
| // eslint-disable-next-line no-restricted-globals |
| const uri = location.pathname; |
| this.setState({ |
| uri: uri, |
| }); |
| if (uri === "/") { |
| this.setState({selectedMenuKey: "/"}); |
| } else if (uri.includes("/dashboard")) { |
| this.setState({selectedMenuKey: "/dashboard"}); |
| } else if (uri.includes("/sites")) { |
| this.setState({selectedMenuKey: "/sites"}); |
| } else if (uri.includes("/certs")) { |
| this.setState({selectedMenuKey: "/certs"}); |
| } else if (uri.includes("/records")) { |
| this.setState({selectedMenuKey: "/records"}); |
| } else if (uri.includes("/rules")) { |
| this.setState({selectedMenuKey: "/rules"}); |
| } else { |
| this.setState({selectedMenuKey: "null"}); |
| } |
| } |
| |
| onUpdateAccount(account) { |
| this.setState({ |
| account: account, |
| }); |
| } |
| |
| setLanguage(account) { |
| // let language = account?.language; |
| const language = localStorage.getItem("language"); |
| if (language !== "" && language !== i18next.language) { |
| Setting.setLanguage(language); |
| } |
| } |
| |
| openAiAssistant = () => { |
| this.setState({ |
| isAiAssistantOpen: true, |
| }); |
| }; |
| |
| getAccount() { |
| AccountBackend.getAccount() |
| .then((res) => { |
| const account = res.data; |
| if (account !== null) { |
| this.setLanguage(account); |
| account.hostname = res.data2; |
| } |
| |
| this.setState({ |
| account: account, |
| }); |
| }); |
| } |
| |
| signout() { |
| AccountBackend.signout() |
| .then((res) => { |
| if (res.status === "ok") { |
| this.setState({ |
| account: null, |
| }); |
| |
| Setting.showMessage("success", "Successfully signed out, redirected to homepage"); |
| Setting.goToLink("/"); |
| // this.props.history.push("/"); |
| } else { |
| Setting.showMessage("error", `Signout failed: ${res.msg}`); |
| } |
| }); |
| } |
| |
| handleRightDropdownClick(e) { |
| if (e.key === "/account") { |
| Setting.openLink(Setting.getMyProfileUrl(this.state.account)); |
| } else if (e.key === "/logout") { |
| this.signout(); |
| } |
| } |
| |
| renderAvatar() { |
| if (this.state.account.avatar === "") { |
| return ( |
| <Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large"> |
| {Setting.getShortName(this.state.account.name)} |
| </Avatar> |
| ); |
| } else { |
| return ( |
| <Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large"> |
| {Setting.getShortName(this.state.account.name)} |
| </Avatar> |
| ); |
| } |
| } |
| |
| renderRightDropdown() { |
| const menu = ( |
| <Menu onClick={this.handleRightDropdownClick.bind(this)}> |
| <Menu.Item key="/account" icon={<SettingOutlined />}> |
| {i18next.t("account:My Account")} |
| </Menu.Item> |
| <Menu.Item key="/logout" icon={<LogoutOutlined />}> |
| {i18next.t("account:Sign Out")} |
| </Menu.Item> |
| </Menu> |
| ); |
| |
| return ( |
| <Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown"> |
| <div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}> |
| |
| |
| { |
| this.renderAvatar() |
| } |
| |
| |
| {Setting.isMobile() ? null : Setting.getShortName(this.state.account.displayName)} <DownOutlined /> |
| |
| |
| |
| </div> |
| </Dropdown> |
| ); |
| } |
| |
| renderAccount() { |
| const res = []; |
| |
| if (this.state.account === undefined) { |
| return null; |
| } else if (this.state.account === null) { |
| res.push( |
| <Menu.Item key="/signup" style={{float: "right", marginRight: "20px"}}> |
| <a href={Setting.getSignupUrl()}> |
| {i18next.t("account:Sign Up")} |
| </a> |
| </Menu.Item> |
| ); |
| res.push( |
| <Menu.Item key="/signin" style={{float: "right"}}> |
| <a href={Setting.getSigninUrl()}> |
| {i18next.t("account:Sign In")} |
| </a> |
| </Menu.Item> |
| ); |
| res.push( |
| <Menu.Item key="/" style={{float: "right"}}> |
| <a href="/"> |
| {i18next.t("general:Home")} |
| </a> |
| </Menu.Item> |
| ); |
| return ( |
| <Menu> |
| { |
| res |
| } |
| </Menu> |
| ); |
| } else { |
| res.push( |
| <div style={{float: "right", display: "flex", alignItems: "center"}}> |
| <Tooltip title="Click to open AI assitant" style={{marginRight: "50px", marginTop: "10px"}} > |
| <div className="select-box" onClick={this.openAiAssistant}> |
| <DeploymentUnitOutlined style={{fontSize: "24px"}} /> |
| </div> |
| </Tooltip> |
| <LanguageSelect /> |
| {this.renderRightDropdown()} |
| </div> |
| ); |
| return ( |
| <div style={{margin: "0px", padding: "0px"}}> |
| { |
| res |
| } |
| </div> |
| ); |
| } |
| } |
| |
| renderMenu() { |
| const res = []; |
| |
| if (this.state.account === null || this.state.account === undefined) { |
| return []; |
| } |
| |
| res.push( |
| <Menu.Item key="/"> |
| <a href="/"> |
| {i18next.t("general:Home")} |
| </a> |
| {/* <Link to="/">*/} |
| {/* Home*/} |
| {/* </Link>*/} |
| </Menu.Item> |
| ); |
| |
| res.push( |
| <Menu.Item key="/dashboard"> |
| <Link to="/dashboard"> |
| {i18next.t("general:Dashboard")} |
| </Link> |
| </Menu.Item> |
| ); |
| res.push( |
| <Menu.Item key="/sites"> |
| <Link to="/sites"> |
| {i18next.t("general:Sites")} |
| </Link> |
| </Menu.Item> |
| ); |
| res.push( |
| <Menu.Item key="/certs"> |
| <Link to="/certs"> |
| {i18next.t("general:Certs")} |
| </Link> |
| </Menu.Item> |
| ); |
| |
| res.push( |
| <Menu.Item key="/records"> |
| <Link to="/records"> |
| {i18next.t("general:Records")} |
| </Link> |
| </Menu.Item> |
| ); |
| |
| res.push( |
| <Menu.Item key="/rules"> |
| <Link to="/rules"> |
| {i18next.t("general:Rules")} |
| </Link> |
| </Menu.Item> |
| ); |
| return res; |
| } |
| |
| renderHomeIfSignedIn(component) { |
| if (this.state.account !== null && this.state.account !== undefined) { |
| return <Redirect to="/" />; |
| } else { |
| return component; |
| } |
| } |
| |
| renderSigninIfNotSignedIn(component) { |
| if (this.state.account === null) { |
| sessionStorage.setItem("from", window.location.pathname); |
| return <Redirect to="/signin" />; |
| } else if (this.state.account === undefined) { |
| return null; |
| } else { |
| return component; |
| } |
| } |
| |
| renderContent() { |
| return ( |
| <div> |
| <Header style={{padding: "0", marginBottom: "3px", backgroundColor: "white", width: "100%"}} mode={"horizontal"}> |
| { |
| Setting.isMobile() ? null : ( |
| <Link to={"/"}> |
| <div className="logo" /> |
| </Link> |
| ) |
| } |
| <Menu |
| // theme="dark" |
| mode={"horizontal"} |
| selectedKeys={[`${this.state.selectedMenuKey}`]} |
| style={{lineHeight: "64px", position: "absolute", left: 138, right: "300px"}} |
| > |
| { |
| this.renderMenu() |
| } |
| {/* <SelectLanguageBox /> */} |
| </Menu> |
| { |
| this.renderAccount() |
| } |
| </Header> |
| <Switch> |
| <Route exact path="/callback" component={AuthCallback} /> |
| <Route exact path="/home" render={(props) => <HomePage account={this.state.account} {...props} />} /> |
| <Route exact path="/" render={(props) => <Redirect to="/sites" />} /> |
| <Route exact path="/signin" render={(props) => this.renderHomeIfSignedIn(<SigninPage {...props} />)} /> |
| <Route exact path="/sites" render={(props) => this.renderSigninIfNotSignedIn(<SiteListPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/sites/:owner/:siteName" render={(props) => this.renderSigninIfNotSignedIn(<SiteEditPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/certs" render={(props) => this.renderSigninIfNotSignedIn(<CertListPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/certs/:owner/:certName" render={(props) => this.renderSigninIfNotSignedIn(<CertEditPage account={this.state.account} {...props} />)} /> |
| |
| <Route exact path="/records" render={(props) => this.renderSigninIfNotSignedIn(<RecordListPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/records/:owner/:id" render={(props) => this.renderSigninIfNotSignedIn(<RecordEditPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/rules" render={(props) => this.renderSigninIfNotSignedIn(<RuleListPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/rules/:owner/:ruleName" render={(props) => this.renderSigninIfNotSignedIn(<RuleEditPage account={this.state.account} {...props} />)} /> |
| <Route exact path="/dashboard" render={(props) => this.renderSigninIfNotSignedIn(<DashboardPage account={this.state.account} {...props} />)} /> |
| </Switch> |
| </div> |
| ); |
| } |
| |
| renderFooter() { |
| // How to keep your footer where it belongs ? |
| // https://www.freecodecamp.org/news/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ |
| |
| return ( |
| <Footer id="footer" style={ |
| { |
| borderTop: "1px solid #e8e8e8", |
| backgroundColor: "white", |
| textAlign: "center", |
| } |
| }> |
| Powered by <a target="_blank" href="https://github.com/casbin/caswaf" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casbin_logo_1024x256.png`} /></a> |
| </Footer> |
| ); |
| } |
| |
| renderAiAssistant() { |
| return ( |
| <Drawer |
| title={ |
| <React.Fragment> |
| <Tooltip title="Want to deploy your own AI assistant? Click to learn more!"> |
| <a target="_blank" rel="noreferrer" href={"https://casdoor.com"}> |
| <img style={{width: "20px", marginRight: "10px", marginBottom: "2px"}} alt="help" src="https://casbin.org/img/casbin.svg" /> |
| AI Assistant |
| </a> |
| </Tooltip> |
| <a className="custom-link" style={{float: "right", marginRight: "35px", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://ai.casbin.com"}> |
| <ShareAltOutlined className="custom-link" style={{fontSize: "20px", color: "rgb(140,140,140)"}} /> |
| </a> |
| <a className="custom-link" style={{float: "right", marginRight: "30px", marginTop: "2px"}} target="_blank" rel="noreferrer" href={"https://github.com/casibase/casibase"}> |
| <GithubOutlined className="custom-link" style={{fontSize: "20px", color: "rgb(140,140,140)"}} /> |
| </a> |
| </React.Fragment> |
| } |
| placement="right" |
| width={500} |
| mask={false} |
| onClose={() => { |
| this.setState({ |
| isAiAssistantOpen: false, |
| }); |
| }} |
| visible={this.state.isAiAssistantOpen} |
| > |
| <iframe id="iframeHelper" title={"iframeHelper"} src={"https://ai.casbin.com/?isRaw=1"} width="100%" height="100%" scrolling="no" frameBorder="no" /> |
| </Drawer> |
| ); |
| } |
| |
| render() { |
| return ( |
| <div id="parent-area"> |
| <BackTop /> |
| <div id="content-wrap"> |
| { |
| this.renderContent() |
| } |
| </div> |
| { |
| this.renderFooter() |
| } |
| { |
| this.renderAiAssistant() |
| } |
| </div> |
| ); |
| } |
| } |
| |
| export default withRouter(withTranslation()(App)); |