Compare commits

...

7 Commits

Author SHA1 Message Date
e4cf244cf8 fix: theme will fully restore after page reload (#2743)
* fix: theme will set to default after flush

* fix: use consume theme to ensure EntryPage will always use default themeAlgorithm

* fix: fix logo render, add try catch to handle
potential err cause by JSON.parse
2024-02-25 00:05:13 +08:00
f5a6415e57 feat: improve dark theme UI (#2742) 2024-02-24 20:11:42 +08:00
13e871043c fix: fix theme switch bug (#2741) 2024-02-24 16:56:12 +08:00
a8699d0b87 feat: use React routing to remove spin between signup and signin pages (#2740)
* fix: Regarding the color of loading

* fix: use goToLinkSoft and use same code format with result and forget psw

* fix: update signup url
2024-02-24 12:59:09 +08:00
6621d693de feat: revert "feat: use i18next-resources-to-backend to lazy load i18n" (#2739)
This reverts commit dc3131c683.
2024-02-23 23:38:49 +08:00
dc3131c683 feat: use i18next-resources-to-backend to lazy load i18n (#2738)
* feat: use i18next-resources-to-backend to lazy load i18n file

* feat: change source in yarn.lock
2024-02-23 22:35:59 +08:00
042a8d0ad6 feat: add rule for SMS and Email provider (#2733)
* add phonecoderule

* feat:add phone code rule

* feat: add email rule

* fix: merge
2024-02-23 00:09:37 +08:00
12 changed files with 143 additions and 70 deletions

View File

@ -164,7 +164,7 @@ func (c *ApiController) SendVerificationCode() {
c.SetSession(MfaDestSession, vform.Dest)
}
provider, err = application.GetEmailProvider()
provider, err = application.GetEmailProvider(vform.Method)
if err != nil {
c.ResponseError(err.Error())
return
@ -210,7 +210,7 @@ func (c *ApiController) SendVerificationCode() {
vform.CountryCode = mfaProps.CountryCode
}
provider, err = application.GetSmsProvider()
provider, err = application.GetSmsProvider(vform.Method)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -38,12 +38,38 @@ func (application *Application) GetProviderByCategory(category string) (*Provide
return nil, nil
}
func (application *Application) GetEmailProvider() (*Provider, error) {
return application.GetProviderByCategory("Email")
func (application *Application) GetProviderByCategoryAndRule(category string, method string) (*Provider, error) {
providers, err := GetProviders(application.Organization)
if err != nil {
return nil, err
}
m := map[string]*Provider{}
for _, provider := range providers {
if provider.Category != category {
continue
}
m[provider.Name] = provider
}
for _, providerItem := range application.Providers {
if providerItem.Rule == method || providerItem.Rule == "all" {
if provider, ok := m[providerItem.Name]; ok {
return provider, nil
}
}
}
return nil, nil
}
func (application *Application) GetSmsProvider() (*Provider, error) {
return application.GetProviderByCategory("SMS")
func (application *Application) GetEmailProvider(method string) (*Provider, error) {
return application.GetProviderByCategoryAndRule("Email", method)
}
func (application *Application) GetSmsProvider(method string) (*Provider, error) {
return application.GetProviderByCategoryAndRule("SMS", method)
}
func (application *Application) GetStorageProvider() (*Provider, error) {

View File

@ -102,19 +102,24 @@ setTwoToneColor("rgb(87,52,211)");
class App extends Component {
constructor(props) {
super(props);
let storageThemeAlgorithm = [];
try {
storageThemeAlgorithm = localStorage.getItem("themeAlgorithm") ? JSON.parse(localStorage.getItem("themeAlgorithm")) : ["default"];
} catch {
storageThemeAlgorithm = ["default"];
}
this.state = {
classes: props,
selectedMenuKey: 0,
account: undefined,
uri: null,
menuVisible: false,
themeAlgorithm: ["default"],
themeAlgorithm: storageThemeAlgorithm,
themeData: Conf.ThemeDefault,
logo: this.getLogo(Setting.getAlgorithmNames(Conf.ThemeDefault)),
logo: this.getLogo(storageThemeAlgorithm),
requiredEnableMfa: false,
isAiAssistantOpen: false,
};
Setting.initServerUrl();
Auth.initAuthWithConfig({
serverUrl: Setting.ServerUrl,
@ -228,6 +233,19 @@ class App extends Component {
});
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),
@ -385,6 +403,7 @@ class App extends Component {
themeAlgorithm: nextThemeAlgorithm,
logo: this.getLogo(nextThemeAlgorithm),
});
localStorage.setItem("themeAlgorithm", JSON.stringify(nextThemeAlgorithm));
}} />
<LanguageSelect languages={this.state.account.organization.languages} />
<Tooltip title="Click to open AI assitant">
@ -393,7 +412,7 @@ class App extends Component {
isAiAssistantOpen: true,
});
}}>
<DeploymentUnitOutlined style={{fontSize: "24px", color: "rgb(77,77,77)"}} />
<DeploymentUnitOutlined style={{fontSize: "24px"}} />
</div>
</Tooltip>
<OpenTour />
@ -420,7 +439,10 @@ class App extends Component {
return [];
}
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone />, [
const textColor = this.state.themeAlgorithm.includes("dark") ? "white" : "black";
const twoToneColor = this.state.themeData.colorPrimary;
res.push(Setting.getItem(<Link style={{color: textColor}} to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
Setting.getItem(<Link to="/apps">{i18next.t("general:Apps")}</Link>, "/apps"),
@ -437,21 +459,21 @@ class App extends Component {
</a>, "#"));
}
res.push(Setting.getItem(<Link style={{color: "black"}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
Setting.getItem(<Link to="/invitations">{i18next.t("general:Invitations")}</Link>, "/invitations"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
@ -465,14 +487,14 @@ class App extends Component {
}
})));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
Conf.CasvisorUrl ? Setting.getItem(<a target="_blank" rel="noreferrer" href={Conf.CasvisorUrl}>{i18next.t("general:Records")}</a>, "/records")
: Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>, "/payments"),
Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>, "/plans"),
@ -481,13 +503,13 @@ class App extends Component {
]));
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
} else {
res.push(Setting.getItem(<Link style={{color: "black"}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
res.push(Setting.getItem(<Link style={{color: textColor}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks")]));
}
@ -630,7 +652,7 @@ class App extends Component {
items={this.getMenuItems()}
mode={"horizontal"}
selectedKeys={[this.state.selectedMenuKey]}
style={{position: "absolute", left: "145px", right: menuStyleRight}}
style={{position: "absolute", left: "145px", right: menuStyleRight, backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}
/>
)}
{
@ -729,37 +751,43 @@ class App extends Component {
renderPage() {
if (this.isDoorPages()) {
return (
<Layout id="parent-area">
<Content style={{display: "flex", justifyContent: "center"}}>
{
this.isEntryPages() ?
<EntryPage
account={this.state.account}
theme={this.state.themeData}
onLoginSuccess={(redirectUrl) => {
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>
<ConfigProvider theme={{
algorithm: Setting.getAlgorithm(["default"]),
}}>
<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}
onLoginSuccess={(redirectUrl) => {
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>
);
}

View File

@ -54,7 +54,7 @@ img {
cursor: pointer;
&:hover {
background-color: #f5f5f5 !important;
background-color: #f5f5f5a5 !important;
}
}
@ -67,8 +67,7 @@ img {
cursor: pointer;
&:hover {
background-color: #f5f5f5;
color: black;
background-color: #f5f5f5a5;
}
}

View File

@ -69,7 +69,7 @@ export function getThemeData(organization, application) {
}
export function getAlgorithm(themeAlgorithmNames) {
return themeAlgorithmNames.map((algorithmName) => {
return themeAlgorithmNames.sort().reverse().map((algorithmName) => {
if (algorithmName === "dark") {
return theme.darkAlgorithm;
}
@ -1165,7 +1165,7 @@ export function getLoginLink(application) {
if (application === null) {
url = null;
} else if (window.location.pathname.includes("/signup/oauth/authorize")) {
url = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
url = window.location.pathname.replace("/signup/oauth/authorize", "/login/oauth/authorize");
} else if (authConfig.appName === application.name) {
url = "/login";
} else if (application.signinUrl === "") {
@ -1173,7 +1173,7 @@ export function getLoginLink(application) {
} else {
url = application.signinUrl;
}
return url;
return url + window.location.search;
}
export function redirectToLoginPage(application, history) {
@ -1216,7 +1216,7 @@ export function renderSignupLink(application, text) {
if (application === null) {
url = null;
} else if (window.location.pathname.includes("/login/oauth/authorize")) {
url = window.location.href.replace("/login/oauth/authorize", "/signup/oauth/authorize");
url = window.location.pathname.replace("/login/oauth/authorize", "/signup/oauth/authorize");
} else if (authConfig.appName === application.name) {
url = "/signup";
} else {
@ -1228,10 +1228,10 @@ export function renderSignupLink(application, text) {
}
const storeSigninUrl = () => {
sessionStorage.setItem("signinUrl", window.location.href);
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
};
return renderLink(url, text, storeSigninUrl);
return renderLink(url + window.location.search, text, storeSigninUrl);
}
export function renderForgetLink(application, text) {
@ -1249,7 +1249,7 @@ export function renderForgetLink(application, text) {
}
const storeSigninUrl = () => {
sessionStorage.setItem("signinUrl", window.location.href);
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
};
return renderLink(url, text, storeSigninUrl);

View File

@ -156,7 +156,7 @@ class ForgetPage extends React.Component {
if (res.status === "ok") {
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
Setting.goToLinkSoft(linkInStorage);
} else {
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
}

View File

@ -92,7 +92,7 @@ class ResultPage extends React.Component {
<Button type="primary" key="login" onClick={() => {
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
Setting.goToLinkSoft(this, linkInStorage);
} else {
Setting.redirectToLoginPage(application, this.props.history);
}

View File

@ -87,8 +87,8 @@ class SignupPage extends React.Component {
componentDidMount() {
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null) {
const signinUrl = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
sessionStorage.setItem("signinUrl", signinUrl);
const signinUrl = window.location.pathname.replace("/signup/oauth/authorize", "/login/oauth/authorize");
sessionStorage.setItem("signinUrl", signinUrl + window.location.search);
}
if (this.getApplicationObj() === undefined) {
@ -639,7 +639,7 @@ class SignupPage extends React.Component {
<a onClick={() => {
const linkInStorage = sessionStorage.getItem("signinUrl");
if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage);
Setting.goToLinkSoft(this, linkInStorage);
} else {
Setting.redirectToLoginPage(application, this.props.history);
}

View File

@ -36,7 +36,7 @@ class OpenTour extends React.Component {
this.canTour() ?
<Tooltip title="Click to open tour">
<div className="select-box" style={{display: Setting.isMobile() ? "none" : null, ...this.props.style}} onClick={() => TourConfig.setIsTourVisible(true)} >
<QuestionCircleOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
<QuestionCircleOutlined style={{fontSize: "24px"}} />
</div>
</Tooltip>
:

View File

@ -56,7 +56,7 @@ class LanguageSelect extends React.Component {
return (
<Dropdown menu={{items: languageItems, onClick}} >
<div className="select-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} >
<GlobalOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
<GlobalOutlined style={{fontSize: "24px"}} />
</div>
</Dropdown>
);

View File

@ -21,9 +21,9 @@ import {CheckOutlined} from "@ant-design/icons";
import {CompactTheme, DarkTheme, Light} from "antd-token-previewer/es/icons";
export const Themes = [
{label: "Default", key: "default", icon: <Light style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Default")
{label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Dark")
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Compact")
{label: "Default", key: "default", icon: <Light style={{fontSize: "24px"}} />}, // i18next.t("theme:Default")
{label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px"}} />}, // i18next.t("theme:Dark")
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px"}} />}, // i18next.t("theme:Compact")
];
function getIcon(themeKey) {

View File

@ -223,6 +223,26 @@ class ProviderTable extends React.Component {
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
</Select>
);
} else if (record.provider?.category === "SMS" || record.provider?.category === "Email") {
if (text === "None") {
text = "all";
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
defaultValue="all"
onChange={value => {
this.updateField(table, index, "rule", value);
}}>
<Option key="all" value="all">{"All"}</Option>
<Option key="signup" value="signup">{"Signup"}</Option>
<Option key="login" value="login">{"Login"}</Option>
<Option key="forget" value="forget">{"Forget Password"}</Option>
<Option key="reset" value="reset">{"Reset Password"}</Option>
<Option key="mfaSetup" value="mfaSetup">{"Set MFA"}</Option>
<Option key="mfaAuth" value="mfaAuth">{"MFA Auth"}</Option>
</Select>
);
} else {
return null;
}