Files
casdoor/web/src/App.js

537 lines
19 KiB
JavaScript
Raw Normal View History

2022-02-13 23:39:27 +08:00
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
2020-10-20 22:37:38 +08:00
//
// 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, Suspense, lazy} from "react";
import "./App.less";
2021-04-29 19:51:03 +08:00
import {Helmet} from "react-helmet";
2020-10-20 22:37:38 +08:00
import * as Setting from "./Setting";
import {setOrgIsTourVisible, setTourLogo} from "./TourConfig";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
2024-03-07 23:21:25 +08:00
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
import {Route, Switch, withRouter} from "react-router-dom";
import CustomGithubCorner from "./common/CustomGithubCorner";
2022-03-05 23:51:55 +08:00
import * as Conf from "./Conf";
2021-02-14 15:40:57 +08:00
2021-02-14 16:59:08 +08:00
import * as Auth from "./auth/Auth";
import EntryPage from "./EntryPage";
2021-02-14 15:40:57 +08:00
import * as AuthBackend from "./auth/AuthBackend";
2021-02-14 14:34:03 +08:00
import AuthCallback from "./auth/AuthCallback";
import SamlCallback from "./auth/SamlCallback";
import i18next from "i18next";
import {withTranslation} from "react-i18next";
const ManagementPage = lazy(() => import("./ManagementPage"));
const {Footer, Content} = Layout;
2020-10-20 22:37:38 +08:00
2023-08-05 17:52:16 +08:00
import {setTwoToneColor} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Cookie from "cookie";
2023-08-05 17:52:16 +08:00
setTwoToneColor("rgb(87,52,211)");
2020-10-20 22:37:38 +08:00
class App extends Component {
constructor(props) {
super(props);
this.setThemeAlgorithm();
let storageThemeAlgorithm = [];
try {
storageThemeAlgorithm = localStorage.getItem("themeAlgorithm") ? JSON.parse(localStorage.getItem("themeAlgorithm")) : ["default"];
} catch {
storageThemeAlgorithm = ["default"];
}
2020-10-20 22:37:38 +08:00
this.state = {
classes: props,
selectedMenuKey: 0,
account: undefined,
accessToken: undefined,
2021-03-26 21:57:41 +08:00
uri: null,
themeAlgorithm: storageThemeAlgorithm,
themeData: Conf.ThemeDefault,
logo: this.getLogo(storageThemeAlgorithm),
requiredEnableMfa: false,
2023-10-28 23:58:51 +08:00
isAiAssistantOpen: false,
application: undefined,
2020-10-20 22:37:38 +08:00
};
2020-10-20 23:14:03 +08:00
Setting.initServerUrl();
2021-02-14 16:59:08 +08:00
Auth.initAuthWithConfig({
serverUrl: Setting.ServerUrl,
2023-07-02 09:15:22 +08:00
appName: Conf.DefaultApplication, // the application used in Casdoor root path: "/"
2021-02-14 16:59:08 +08:00
});
2020-10-20 22:37:38 +08:00
}
2021-03-27 11:38:15 +08:00
UNSAFE_componentWillMount() {
2020-10-20 22:37:38 +08:00
this.updateMenuKey();
this.getAccount();
this.getApplication();
2020-10-20 22:37:38 +08:00
}
componentDidUpdate(prevProps, prevState, snapshot) {
2021-03-26 21:57:41 +08:00
const uri = location.pathname;
if (this.state.uri !== uri) {
this.updateMenuKey();
}
if (this.state.account !== prevState.account) {
const requiredEnableMfa = Setting.isRequiredEnableMfa(this.state.account, this.state.account?.organization);
this.setState({
requiredEnableMfa: requiredEnableMfa,
});
if (requiredEnableMfa === true) {
2023-10-28 23:58:51 +08:00
const mfaType = Setting.getMfaItemsByRules(this.state.account, this.state.account?.organization, [Setting.MfaRuleRequired])
.find((item) => item.rule === Setting.MfaRuleRequired)?.name;
if (mfaType !== undefined) {
this.props.history.push(`/mfa/setup?mfaType=${mfaType}`, {from: "/login"});
}
}
}
2021-03-26 21:57:41 +08:00
}
2020-10-20 22:37:38 +08:00
updateMenuKey() {
const uri = location.pathname;
2021-03-26 21:57:41 +08:00
this.setState({
uri: uri,
});
if (uri === "/" || uri.includes("/shortcuts") || uri.includes("/apps")) {
this.setState({selectedMenuKey: "/home"});
2023-12-31 19:48:58 +08:00
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/groups") || uri.includes("/users") || uri.includes("/invitations")) {
2023-08-05 17:41:35 +08:00
this.setState({selectedMenuKey: "/orgs"});
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
this.setState({selectedMenuKey: "/identity"});
2023-08-12 02:44:38 +08:00
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/auth"});
2023-08-05 17:41:35 +08:00
} else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/logs"});
} else if (uri.includes("/products") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions")) {
this.setState({selectedMenuKey: "/business"});
} else if (uri.includes("/sysinfo") || uri.includes("/syncers") || uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/admin"});
} else if (uri.includes("/signup")) {
this.setState({selectedMenuKey: "/signup"});
} else if (uri.includes("/login")) {
this.setState({selectedMenuKey: "/login"});
} else if (uri.includes("/result")) {
this.setState({selectedMenuKey: "/result"});
2020-10-20 22:37:38 +08:00
} else {
this.setState({selectedMenuKey: -1});
2020-10-20 22:37:38 +08:00
}
}
2022-02-28 21:33:10 +08:00
getAccessTokenParam(params) {
2021-03-28 16:35:59 +08:00
// "/page?access_token=123"
2021-05-16 18:18:55 +08:00
const accessToken = params.get("access_token");
return accessToken === null ? "" : `?accessToken=${accessToken}`;
}
2022-02-28 21:33:10 +08:00
getCredentialParams(params) {
2021-05-16 18:18:55 +08:00
// "/page?username=abc&password=123"
if (params.get("username") === null || params.get("password") === null) {
return "";
}
return `?username=${params.get("username")}&password=${params.get("password")}`;
2021-03-28 16:35:59 +08:00
}
getUrlWithoutQuery() {
2022-02-28 21:33:10 +08:00
return window.location.toString().replace(window.location.search, "");
}
getLanguageParam(params) {
// "/page?language=en"
const language = params.get("language");
if (language !== null) {
Setting.setLanguage(language);
return `language=${language}`;
}
return "";
2021-03-28 16:35:59 +08:00
}
getLogo(themes) {
return Setting.getLogo(themes);
}
setThemeAlgorithm() {
const currentUrl = window.location.href;
const url = new URL(currentUrl);
const themeType = url.searchParams.get("theme");
if (themeType === "dark" || themeType === "default") {
localStorage.setItem("themeAlgorithm", JSON.stringify([themeType]));
}
}
2021-09-14 01:22:13 +08:00
setLanguage(account) {
const language = account?.language;
2023-06-03 10:15:29 +08:00
if (language !== null && language !== "" && language !== i18next.language) {
2021-09-14 01:22:13 +08:00
Setting.setLanguage(language);
}
}
setTheme = (theme, initThemeAlgorithm) => {
this.setState({
themeData: theme,
});
if (initThemeAlgorithm) {
if (localStorage.getItem("themeAlgorithm")) {
let storageThemeAlgorithm = [];
try {
storageThemeAlgorithm = JSON.parse(localStorage.getItem("themeAlgorithm"));
} catch {
storageThemeAlgorithm = ["default"];
}
this.setState({
logo: this.getLogo(storageThemeAlgorithm),
themeAlgorithm: storageThemeAlgorithm,
});
return;
}
this.setState({
logo: this.getLogo(Setting.getAlgorithmNames(theme)),
themeAlgorithm: Setting.getAlgorithmNames(theme),
});
}
};
getApplication() {
const applicationName = localStorage.getItem("applicationName");
if (!applicationName) {
return;
}
ApplicationBackend.getApplication("admin", applicationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res.data,
});
});
}
2020-10-20 22:37:38 +08:00
getAccount() {
2022-02-28 21:33:10 +08:00
const params = new URLSearchParams(this.props.location.search);
let query = this.getAccessTokenParam(params);
2021-05-16 18:18:55 +08:00
if (query === "") {
2022-02-28 21:33:10 +08:00
query = this.getCredentialParams(params);
}
const query2 = this.getLanguageParam(params);
if (query2 !== "") {
const url = window.location.toString().replace(new RegExp(`[?&]${query2}`), "");
window.history.replaceState({}, document.title, url);
2021-05-16 18:18:55 +08:00
}
2022-02-28 21:33:10 +08:00
2021-05-16 18:18:55 +08:00
if (query !== "") {
2021-03-28 16:35:59 +08:00
window.history.replaceState({}, document.title, this.getUrlWithoutQuery());
}
2022-02-28 21:33:10 +08:00
2021-05-16 18:18:55 +08:00
AuthBackend.getAccount(query)
2020-10-20 22:37:38 +08:00
.then((res) => {
2021-04-29 19:51:03 +08:00
let account = null;
let accessToken = null;
2021-04-29 19:51:03 +08:00
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
accessToken = res.data.accessToken;
2021-09-14 01:22:13 +08:00
this.setLanguage(account);
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
setTourLogo(account.organization.logo);
setOrgIsTourVisible(account.organization.enableTour);
2021-05-15 23:34:06 +08:00
} else {
if (res.data !== "Please login first") {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
2021-05-15 23:34:06 +08:00
}
2021-04-29 19:51:03 +08:00
}
2020-10-20 22:37:38 +08:00
this.setState({
2021-04-29 19:51:03 +08:00
account: account,
accessToken: accessToken,
2020-10-20 22:37:38 +08:00
});
});
}
onUpdateAccount(account) {
this.setState({
2022-08-06 23:54:56 +08:00
account: account,
});
}
2020-10-20 22:37:38 +08:00
renderFooter() {
return (
<React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorAccessToken" value={this.state.accessToken} />}
<Footer id="footer" style={
{
textAlign: "center",
}
}>
2023-05-15 16:49:45 +08:00
{
this.state.application?.footerHtml && this.state.application.footerHtml !== "" ?
2023-05-15 16:49:45 +08:00
<React.Fragment>
<div dangerouslySetInnerHTML={{__html: this.state.application.footerHtml}} />
2023-05-15 16:49:45 +08:00
</React.Fragment>
: (
Conf.CustomFooter !== null ? Conf.CustomFooter : (
<React.Fragment>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</React.Fragment>
)
)
2023-05-15 16:49:45 +08:00
}
</Footer>
</React.Fragment>
);
2020-10-20 22:37:38 +08:00
}
2023-10-28 23:58:51 +08:00
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", marginTop: "2px"}} target="_blank" rel="noreferrer" href={`${Conf.AiAssistantUrl}`}>
2023-10-28 23:58:51 +08:00
<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={`${Conf.AiAssistantUrl}/?isRaw=1`} width="100%" height="100%" scrolling="no" frameBorder="no" />
2023-10-28 23:58:51 +08:00
</Drawer>
);
}
2021-02-11 16:43:30 +08:00
isDoorPages() {
return this.isEntryPages() || window.location.pathname.startsWith("/callback");
}
isEntryPages() {
2021-06-09 20:38:46 +08:00
return window.location.pathname.startsWith("/signup") ||
window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/forget") ||
window.location.pathname.startsWith("/prompt") ||
2023-05-05 01:08:56 +08:00
window.location.pathname.startsWith("/result") ||
window.location.pathname.startsWith("/cas") ||
window.location.pathname.startsWith("/select-plan") ||
window.location.pathname.startsWith("/buy-plan") ||
2024-08-26 23:22:53 +08:00
window.location.pathname.startsWith("/qrcode") ||
window.location.pathname.startsWith("/captcha");
2021-02-11 16:43:30 +08:00
}
onClick = ({key}) => {
if (key !== "/swagger" && key !== "/records") {
if (this.state.requiredEnableMfa) {
Setting.showMessage("info", "Please enable MFA first!");
} else {
this.props.history.push(key);
}
}
};
2021-04-29 19:51:03 +08:00
renderPage() {
2021-02-11 16:43:30 +08:00
if (this.isDoorPages()) {
let themeData = this.state.themeData;
if (this.state.organization === undefined) {
const curCookie = Cookie.parse(document.cookie);
if (curCookie["organizationTheme"] && curCookie["organizationTheme"] !== "null") {
themeData = JSON.parse(curCookie["organizationTheme"]);
}
}
2021-02-11 16:43:30 +08:00
return (
<ConfigProvider theme={{
token: {
colorPrimary: themeData.colorPrimary,
borderRadius: themeData.borderRadius,
},
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
}}>
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
<Layout id="parent-area">
<Content style={{display: "flex", justifyContent: "center"}}>
{
this.isEntryPages() ?
<EntryPage
account={this.state.account}
theme={this.state.themeData}
themeAlgorithm={this.state.themeAlgorithm}
updateApplication={(application) => {
this.setState({
application: application,
});
}}
onLoginSuccess={(redirectUrl) => {
window.google?.accounts?.id?.cancel();
if (redirectUrl) {
localStorage.setItem("mfaRedirectUrl", redirectUrl);
}
this.getAccount();
}}
onUpdateAccount={(account) => this.onUpdateAccount(account)}
updataThemeData={this.setTheme}
/> :
<Switch>
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
}
</Content>
{
this.renderFooter()
}
{
this.renderAiAssistant()
}
</Layout>
</StyleProvider>
</ConfigProvider>
);
2021-02-11 16:43:30 +08:00
}
2020-10-20 22:37:38 +08:00
return (
<React.Fragment>
2023-03-19 01:01:39 +08:00
{/* { */}
{/* this.renderBanner() */}
{/* } */}
<FloatButton.BackTop />
2021-08-07 19:52:01 +08:00
<CustomGithubCorner />
2020-10-20 22:37:38 +08:00
{
2024-03-07 23:21:25 +08:00
<Suspense fallback={null}>
<Layout id="parent-area">
<ManagementPage
account={this.state.account}
application={this.state.application}
uri={this.state.uri}
themeData={this.state.themeData}
themeAlgorithm={this.state.themeAlgorithm}
selectedMenuKey={this.state.selectedMenuKey}
requiredEnableMfa={this.state.requiredEnableMfa}
menuVisible={this.state.menuVisible}
logo={this.state.logo}
onChangeTheme={this.setTheme}
onClick = {this.onClick}
onfinish={() => {
this.setState({requiredEnableMfa: false});
}}
openAiAssistant={() => {
this.setState({
isAiAssistantOpen: true,
});
}}
setLogoAndThemeAlgorithm={(nextThemeAlgorithm) => {
this.setState({
themeAlgorithm: nextThemeAlgorithm,
logo: this.getLogo(nextThemeAlgorithm),
});
localStorage.setItem("themeAlgorithm", JSON.stringify(nextThemeAlgorithm));
}}
setLogoutState={() => {
this.setState({
account: null,
});
}}
/>
{
this.renderFooter()
}
{
this.renderAiAssistant()
}
</Layout>
</Suspense>
2020-10-20 22:37:38 +08:00
}
</React.Fragment>
2020-10-20 22:37:38 +08:00
);
}
2021-04-29 19:51:03 +08:00
2023-02-18 18:07:23 +08:00
renderBanner() {
if (!Conf.IsDemoMode) {
return null;
}
2023-02-18 23:39:32 +08:00
const language = Setting.getLanguage();
if (language === "en" || language === "zh") {
return null;
}
2023-02-18 18:07:23 +08:00
return (
<Alert type="info" banner showIcon={false} closable message={
<div style={{textAlign: "center"}}>
<InfoCircleFilled style={{color: "rgb(87,52,211)"}} />
&nbsp;&nbsp;
{i18next.t("general:Found some texts still not translated? Please help us translate at")}
&nbsp;
<a target="_blank" rel="noreferrer" href={"https://crowdin.com/project/casdoor-site"}>
Crowdin
</a>
&nbsp;!&nbsp;🙏
</div>
} />
);
}
2021-04-29 19:51:03 +08:00
render() {
return (
<React.Fragment>
{(this.state.account === undefined || this.state.account === null) ?
2021-04-29 19:51:03 +08:00
<Helmet>
2022-02-13 23:34:29 +08:00
<link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
</Helmet> :
<Helmet>
<title>{this.state.account.organization?.displayName}</title>
<link rel="icon" href={this.state.account.organization?.favicon} />
2021-04-29 19:51:03 +08:00
</Helmet>
}
<ConfigProvider theme={{
token: {
colorPrimary: this.state.themeData.colorPrimary,
colorInfo: this.state.themeData.colorPrimary,
borderRadius: this.state.themeData.borderRadius,
},
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
}}>
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
{
this.renderPage()
}
</StyleProvider>
</ConfigProvider>
2021-04-29 19:51:03 +08:00
</React.Fragment>
);
2021-04-29 19:51:03 +08:00
}
2020-10-20 21:46:44 +08:00
}
export default withRouter(withTranslation()(App));