mirror of https://github.com/casbin/casnode.git
438 lines
14 KiB
JavaScript
438 lines
14 KiB
JavaScript
// 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 * as AccountBackend from "./backend/AccountBackend";
|
|
import * as NodeBackend from "./backend/NodeBackend";
|
|
import * as Setting from "./Setting";
|
|
import * as Conf from "./Conf";
|
|
import {Link, withRouter} from "react-router-dom";
|
|
import i18next from "i18next";
|
|
import {ServerUrl} from "./Setting";
|
|
|
|
class Header extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
classes: props,
|
|
searchValue: "",
|
|
searchResShow: false,
|
|
nodes: [],
|
|
matchNodes: [],
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.getNodes();
|
|
}
|
|
|
|
getMatchNodes(nodes, curSearchVal, matchNodes) {
|
|
if (!curSearchVal || !nodes) {
|
|
return;
|
|
}
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const name = nodes[i].name;
|
|
const id = nodes[i].id;
|
|
if (name.indexOf(curSearchVal) > -1 || id.indexOf(curSearchVal) > -1) {
|
|
matchNodes.push(nodes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
onSearchValueChange(e) {
|
|
const nodes = this.state.nodes;
|
|
const curSearchVal = e.target.value;
|
|
const matchNodes = [];
|
|
this.getMatchNodes(nodes, curSearchVal, matchNodes);
|
|
this.setState({
|
|
searchValue: curSearchVal,
|
|
matchNodes: matchNodes,
|
|
searchResShow: true,
|
|
});
|
|
}
|
|
|
|
addSearchValue() {
|
|
this.setState({
|
|
searchValue: `${Conf.Domain}/t ` + this.state.searchValue,
|
|
});
|
|
}
|
|
|
|
onKeyup(e) {
|
|
if (e.keyCode === 13) {
|
|
this.props.history.push(`/search?keyword=${this.state.searchValue}`);
|
|
this.setState({
|
|
searchResShow: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
signout() {
|
|
if (!window.confirm(i18next.t("signout:Are you sure to log out?"))) {
|
|
return;
|
|
}
|
|
|
|
AccountBackend.signout().then((res) => {
|
|
if (res.status === "ok") {
|
|
this.props.onSignout();
|
|
// this.props.history.push("/");
|
|
window.location.href = "/";
|
|
} else {
|
|
// this.props.history.push("/");
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
}
|
|
|
|
renderItem() {
|
|
const isSignedIn = this.props.account !== undefined && this.props.account !== null;
|
|
const username = this.props.account?.name;
|
|
|
|
if (!isSignedIn) {
|
|
return (
|
|
<div className="tools">
|
|
<Link to="/" className="top">
|
|
{i18next.t("general:Home")}
|
|
</Link>
|
|
|
|
<a href={`${ServerUrl}/swagger`} className="top">
|
|
{i18next.t("general:Swagger")}
|
|
</a>
|
|
|
|
<a href={Setting.getSignupUrl()} className="top">
|
|
{i18next.t("general:Sign Up")}
|
|
</a>
|
|
|
|
<a href={Setting.getSigninUrl()} className="top">
|
|
{i18next.t("general:Sign In")}
|
|
</a>
|
|
</div>
|
|
);
|
|
} else {
|
|
return (
|
|
<div className="tools">
|
|
<Link to="/" className="top">
|
|
{i18next.t("general:Home")}
|
|
</Link>
|
|
|
|
<a href={`${ServerUrl}/swagger`} className="top">
|
|
{i18next.t("general:Swagger")}
|
|
</a>
|
|
|
|
<Link to={`/member/${username}`} className="top">
|
|
{username}
|
|
</Link>
|
|
|
|
<Link to="/notes" className="top">
|
|
{i18next.t("general:Note")}
|
|
</Link>
|
|
|
|
<Link to="/t" className="top">
|
|
{i18next.t("general:Timeline")}
|
|
</Link>
|
|
|
|
<a className="top" href={Setting.getMyProfileUrl(this.props.account)} rel="noreferrer">
|
|
{i18next.t("general:Setting")}
|
|
</a>
|
|
{/* <Link to="/settings" className="top">*/}
|
|
{/* {i18next.t("general:Setting")}*/}
|
|
{/* </Link>*/}
|
|
|
|
{this.props.account?.isAdmin ? (
|
|
<span>
|
|
<Link to="/admin" className="top">
|
|
{i18next.t("general:Admin")}
|
|
</Link>
|
|
|
|
</span>
|
|
) : null}
|
|
<a href="#;" onClick={this.signout.bind(this)} className="top">
|
|
{i18next.t("general:Sign Out")}
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
renderMobileHeader() {
|
|
const isSignedIn = this.props.account !== undefined && this.props.account !== null;
|
|
const menuStyle = this.props.showMenu
|
|
? {
|
|
"--show-dropdown": "block",
|
|
}
|
|
: null;
|
|
|
|
if (!isSignedIn) {
|
|
return (
|
|
<div id="Top">
|
|
<div className="content">
|
|
<div style={{paddingTop: "6px"}}>
|
|
<table cellPadding="0" cellSpacing="0" border="0" width="100%">
|
|
<tbody>
|
|
<tr>
|
|
<td width="5" align="left"></td>
|
|
<td width="80" align="left" style={{paddingTop: "4px"}}>
|
|
<Link to="/" name="top">
|
|
<div id="logoMobile"></div>
|
|
</Link>
|
|
</td>
|
|
<td width="auto" align="right" style={{paddingTop: "2px"}}>
|
|
<Link to="/" className="top">
|
|
{i18next.t("general:Home")}
|
|
</Link>
|
|
|
|
<a href={Setting.getSignupUrl()} className="top">
|
|
{i18next.t("general:Sign Up")}
|
|
</a>
|
|
|
|
<a href={Setting.getSigninUrl()} className="top">
|
|
{i18next.t("general:Sign In")}
|
|
</a>
|
|
</td>
|
|
<td width="10" align="left"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} else {
|
|
return (
|
|
<header className="site-header">
|
|
<div className="site-header-logo">
|
|
<div
|
|
id="logoMobile"
|
|
style={{
|
|
backgroundImage: `url(${Conf.FrontConfig.logoImage})`,
|
|
}}
|
|
onClick={() => this.props.history.push("/")}
|
|
/>
|
|
</div>
|
|
<div className="site-header-menu">
|
|
{this.renderSearch()}
|
|
<button id="menu-entry" onClick={() => this.changeShowMenuStatus()}>
|
|
<img
|
|
src={this.props.account?.avatar !== "" ? this.props.account?.avatar : Setting.getUserAvatar(this.props.account?.name)}
|
|
width={24}
|
|
border={0}
|
|
style={{borderRadius: "32px", verticalAlign: "middle"}}
|
|
height="32"
|
|
align="absmiddle"
|
|
alt={this.props.account?.name}
|
|
onError={(event) => {
|
|
event.target.onerror = "";
|
|
event.target.src = Conf.AvatarErrorUrl;
|
|
return true;
|
|
}}
|
|
/>
|
|
</button>
|
|
<div id="user-menu" style={menuStyle}>
|
|
<div>
|
|
<Link to={`/member/${this.props.account?.name}`} className="top">
|
|
{i18next.t("general:Homepage")}
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
<Link to="/my/nodes" className="top">
|
|
{i18next.t("bar:Nodes")}
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
<Link to="/my/topics" className="top">
|
|
{i18next.t("bar:Topics")}
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
<a className="top" href={Setting.getMyProfileUrl(this.props.account)} rel="noreferrer">
|
|
{i18next.t("general:Setting")}
|
|
</a>
|
|
{/* <Link to="/settings" className="top">*/}
|
|
{/* {i18next.t("general:Setting")}*/}
|
|
{/* </Link>*/}
|
|
</div>
|
|
<div>
|
|
<Link to="/admin" className="top">
|
|
{i18next.t("general:Admin")}
|
|
</Link>
|
|
</div>
|
|
<div className="menu_sep"></div>
|
|
<div>
|
|
<a target="_blank" className="top" href={Setting.getMyResourcesUrl(this.props.account)} rel="noreferrer">
|
|
<img src={Setting.getStatic("/img/neue_image.png")} height="14" border="0" align="absmiddle" /> {i18next.t("bar:File library")}
|
|
</a>
|
|
</div>
|
|
<div>
|
|
<Link to="/notes" className="top">
|
|
<img src={Setting.getStatic("/img/neue_notepad.png")} height="14" border="0" align="absmiddle" /> {i18next.t("general:Note")}
|
|
</Link>
|
|
</div>
|
|
<div>
|
|
<Link to="/t" className="top">
|
|
<img src={Setting.getStatic("/img/neue_comment.png")} height="14" border="0" align="absmiddle" /> {i18next.t("general:Timeline")}
|
|
</Link>
|
|
</div>
|
|
<div className="menu_sep"></div>
|
|
<div>
|
|
<Link to="/select/language" className="top">
|
|
{i18next.t("general:Language")}
|
|
</Link>
|
|
</div>
|
|
<div className="menu_sep"></div>
|
|
<div>
|
|
<div className="top" onClick={Setting.toggleThemeMode}>
|
|
<img src={Setting.getThemeBtnUrl()} align="absmiddle" height="10" alt="Light" style={{verticalAlign: "middle"}} />
|
|
</div>
|
|
</div>
|
|
<div className="menu_sep"></div>
|
|
<div style={{padding: "10px"}}>
|
|
<div className="member-activity-bar">
|
|
<div className="member-activity-start" style={{width: "5%"}}></div>
|
|
</div>
|
|
</div>
|
|
<div className="menu_sep"></div>
|
|
<div>
|
|
<Link to="/signout" className="top">
|
|
{i18next.t("general:Sign Out")}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|
|
}
|
|
|
|
renderSearchEngine() {
|
|
let searchUrl;
|
|
switch (Conf.DefaultSearchSite) {
|
|
case "google":
|
|
case "Google":
|
|
searchUrl = `https://www.google.com/search?q=site:${Conf.Domain}/t ${this.state.searchValue}`;
|
|
break;
|
|
case "bing":
|
|
case "Bing":
|
|
searchUrl = `https://cn.bing.com/search?q=site:${Conf.Domain}/t ${this.state.searchValue}`;
|
|
break;
|
|
case "baidu":
|
|
case "Baidu":
|
|
searchUrl = `https://www.baidu.com/s?q6=${Conf.Domain}&q3=${this.state.searchValue}`;
|
|
break;
|
|
default:
|
|
searchUrl = "/search?keyword=" + this.state.searchValue;
|
|
}
|
|
|
|
return (
|
|
<div className="cell">
|
|
<a className="search-item" href={searchUrl} target="blank">
|
|
{`${i18next.t("search:Click here to search in ")}${Conf.DefaultSearchSite}`}
|
|
</a>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
renderSearch() {
|
|
if (Setting.PcBrowser) {
|
|
return (
|
|
<div id="Search">
|
|
<div id="search-container" className="">
|
|
<input
|
|
type="text"
|
|
maxLength="40"
|
|
name="q"
|
|
id="search"
|
|
autoComplete={"off"}
|
|
value={this.state.searchValue}
|
|
onKeyUp={(event) => this.onKeyup(event)}
|
|
onChange={(event) => this.onSearchValueChange(event)}
|
|
onFocus={() => {
|
|
this.setState({
|
|
searchResShow: true,
|
|
});
|
|
}}
|
|
onBlur={() => {
|
|
setTimeout(() => {
|
|
this.setState({
|
|
searchResShow: false,
|
|
});
|
|
}, 200);
|
|
}}
|
|
/>
|
|
{this.state.searchResShow && this.state.searchValue ? (
|
|
<div id="search-result" className="box" style={{display: "block"}}>
|
|
<div className="cell">{i18next.t("search:Press Enter to search in site.")}</div>
|
|
{this.state.matchNodes.length !== 0 ? (
|
|
<div className="cell">
|
|
<span className="fade">节点 / Nodes</span>
|
|
{this.state.matchNodes.map((val) => {
|
|
// TODO: maybe weshould add `active` iterm
|
|
return (
|
|
<a className="search-item" href={`/go/${val.id}`} key={val.id}>
|
|
{val.name} / {val.id}
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
) : null}
|
|
{this.renderSearchEngine()}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
changeShowMenuStatus() {
|
|
this.props.changeMenuStatus(!this.props.showMenu);
|
|
}
|
|
|
|
getNodes() {
|
|
if (this.state.account === null) {
|
|
return;
|
|
}
|
|
|
|
NodeBackend.getNodes().then((res) => {
|
|
this.setState({
|
|
nodes: res,
|
|
});
|
|
});
|
|
}
|
|
|
|
render() {
|
|
if (!Setting.PcBrowser) {
|
|
return this.renderMobileHeader();
|
|
}
|
|
return (
|
|
<div id="Top">
|
|
<div className="content">
|
|
<div className="site-nav">
|
|
<Link to="/" name="top" style={{fontSize: 0}} title={Conf.FrontConfig.signinBoxSpan}>
|
|
<div
|
|
id="logo"
|
|
style={{
|
|
backgroundImage: `url(${Conf.FrontConfig.logoImage})`,
|
|
}}
|
|
/>
|
|
</Link>
|
|
{this.renderSearch()}
|
|
{this.renderItem()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default withRouter(Header);
|