Compare commits

..

10 Commits

27 changed files with 1660 additions and 1199 deletions

View File

@@ -860,16 +860,16 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
if len(columns) == 0 {
columns = []string{
"owner", "display_name", "avatar", "first_name", "last_name",
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "email_verified",
"location", "address", "addresses", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application", "register_type", "register_source",
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "mfa_items", "last_change_password_time", "managedAccounts", "face_ids", "mfaAccounts",
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "email_verified",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
"spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "mfa_items", "mfa_remember_deadline",
"yammer", "yandex", "zoom", "custom", "need_update_password", "ip_whitelist", "mfa_remember_deadline",
"cart",
}
}

View File

@@ -505,6 +505,160 @@ class ApplicationEditPage extends React.Component {
</React.Fragment>
)}
{this.state.activeMenuKey === "authentication" && (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:Cookie expire"), i18next.t("application:Cookie expire - Tooltip"))} :
</Col>
<Col span={21} >
<InputNumber style={{width: "150px"}} value={this.state.application.cookieExpireInHours || 720} min={1} step={1} precision={0} addonAfter="Hours" onChange={value => {
this.updateApplicationField("cookieExpireInHours", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
</Col>
<Col span={21}>
<PaginateSelect
virtual
style={{width: "100%"}}
allowClear
placeholder={i18next.t("general:Default")}
value={this.state.application.defaultGroup || undefined}
fetchPage={GroupBackend.getGroups}
buildFetchArgs={({page, pageSize, searchText}) => {
const field = searchText ? "name" : "";
return [this.state.owner, false, page, pageSize, field, searchText, "", ""];
}}
reloadKey={this.state.owner}
optionMapper={(group) => Setting.getOption(
<Space>
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
{group.displayName}
</Space>,
`${group.owner}/${group.name}`
)}
filterOption={false}
onChange={(value) => {
this.updateApplicationField("defaultGroup", value || "");
}}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSignUp} onChange={checked => {
this.updateApplicationField("enableSignUp", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Disable signin"), i18next.t("application:Disable signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.disableSignin} onChange={checked => {
this.updateApplicationField("disableSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable exclusive signin"), i18next.t("application:Enable exclusive signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableExclusiveSignin} onChange={checked => {
this.updateApplicationField("enableExclusiveSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
if (!checked) {
this.updateApplicationField("enableAutoSignin", false);
}
this.updateApplicationField("enableSigninSession", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Auto signin"), i18next.t("application:Auto signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableAutoSignin} onChange={checked => {
if (!this.state.application.enableSigninSession && checked) {
Setting.showMessage("error", i18next.t("application:Please enable \"Signin session\" first before enabling \"Auto signin\""));
return;
}
this.updateApplicationField("enableAutoSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable Email linking"), i18next.t("application:Enable Email linking - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableLinkWithEmail} onChange={checked => {
this.updateApplicationField("enableLinkWithEmail", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.signupUrl} onChange={e => {
this.updateApplicationField("signupUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.signinUrl} onChange={e => {
this.updateApplicationField("signinUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.forgetUrl} onChange={e => {
this.updateApplicationField("forgetUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.affiliationUrl} onChange={e => {
this.updateApplicationField("affiliationUrl", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
)}
{this.state.activeMenuKey === "oidc-oauth" && (
<React.Fragment>
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
@@ -657,157 +811,11 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:Cookie expire"), i18next.t("application:Cookie expire - Tooltip"))} :
</Col>
<Col span={21} >
<InputNumber style={{width: "150px"}} value={this.state.application.cookieExpireInHours || 720} min={1} step={1} precision={0} addonAfter="Hours" onChange={value => {
this.updateApplicationField("cookieExpireInHours", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
</Col>
<Col span={21}>
<PaginateSelect
virtual
style={{width: "100%"}}
allowClear
placeholder={i18next.t("general:Default")}
value={this.state.application.defaultGroup || undefined}
fetchPage={GroupBackend.getGroups}
buildFetchArgs={({page, pageSize, searchText}) => {
const field = searchText ? "name" : "";
return [this.state.owner, false, page, pageSize, field, searchText, "", ""];
}}
reloadKey={this.state.owner}
optionMapper={(group) => Setting.getOption(
<Space>
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
{group.displayName}
</Space>,
`${group.owner}/${group.name}`
)}
filterOption={false}
onChange={(value) => {
this.updateApplicationField("defaultGroup", value || "");
}}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSignUp} onChange={checked => {
this.updateApplicationField("enableSignUp", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Disable signin"), i18next.t("application:Disable signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.disableSignin} onChange={checked => {
this.updateApplicationField("disableSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable exclusive signin"), i18next.t("application:Enable exclusive signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableExclusiveSignin} onChange={checked => {
this.updateApplicationField("enableExclusiveSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
if (!checked) {
this.updateApplicationField("enableAutoSignin", false);
}
this.updateApplicationField("enableSigninSession", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Auto signin"), i18next.t("application:Auto signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableAutoSignin} onChange={checked => {
if (!this.state.application.enableSigninSession && checked) {
Setting.showMessage("error", i18next.t("application:Please enable \"Signin session\" first before enabling \"Auto signin\""));
return;
}
this.updateApplicationField("enableAutoSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable Email linking"), i18next.t("application:Enable Email linking - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableLinkWithEmail} onChange={checked => {
this.updateApplicationField("enableLinkWithEmail", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.signupUrl} onChange={e => {
this.updateApplicationField("signupUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.signinUrl} onChange={e => {
this.updateApplicationField("signinUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.forgetUrl} onChange={e => {
this.updateApplicationField("forgetUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input prefix={<LinkOutlined />} value={this.state.application.affiliationUrl} onChange={e => {
this.updateApplicationField("affiliationUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
</React.Fragment>
)}
{this.state.activeMenuKey === "saml" && (
<React.Fragment>
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:SAML reply URL"), i18next.t("application:Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip"))} :
</Col>
@@ -1452,7 +1460,7 @@ class ApplicationEditPage extends React.Component {
<Layout style={{background: "inherit", height: "100%", overflow: "auto"}}>
{
this.state.menuMode === "horizontal" || !this.state.menuMode ? (
<Header style={{background: "inherit", padding: "0px", position: "sticky", top: 0}}>
<Header style={{background: "inherit", padding: "0px", position: "sticky", top: 0, height: 38, minHeight: 38}}>
<div className="demo-logo" />
<Tabs
onChange={(key) => {
@@ -1461,9 +1469,12 @@ class ApplicationEditPage extends React.Component {
}}
type="card"
activeKey={this.state.activeMenuKey}
tabBarStyle={{marginBottom: 0}}
items={[
{label: i18next.t("application:Basic"), key: "basic"},
{label: i18next.t("application:Authentication"), key: "authentication"},
{label: "OIDC/OAuth", key: "oidc-oauth"},
{label: "SAML", key: "saml"},
{label: i18next.t("application:Providers"), key: "providers"},
{label: i18next.t("application:UI Customization"), key: "ui-customization"},
{label: i18next.t("application:Security"), key: "security"},
@@ -1488,6 +1499,8 @@ class ApplicationEditPage extends React.Component {
>
<Menu.Item key="basic">{i18next.t("application:Basic")}</Menu.Item>
<Menu.Item key="authentication">{i18next.t("application:Authentication")}</Menu.Item>
<Menu.Item key="oidc-oauth">OIDC/OAuth</Menu.Item>
<Menu.Item key="saml">SAML</Menu.Item>
<Menu.Item key="providers">{i18next.t("application:Providers")}</Menu.Item>
<Menu.Item key="ui-customization">{i18next.t("application:UI Customization")}</Menu.Item>
<Menu.Item key="security">{i18next.t("application:Security")}</Menu.Item>

View File

@@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Name", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Display name", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "First name", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Last name", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Avatar", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "User type", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Password", visible: true, viewRule: "Self", modifyRule: "Self"},
@@ -66,6 +68,7 @@ class OrganizationListPage extends BaseListPage {
{name: "Country/Region", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Location", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Address", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Addresses", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Affiliation", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Title", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "ID card type", visible: true, viewRule: "Public", modifyRule: "Self"},
@@ -86,6 +89,8 @@ class OrganizationListPage extends BaseListPage {
{name: "Balance", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Balance credit", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Balance currency", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Cart", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Transactions", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Register type", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Register source", visible: true, viewRule: "Public", modifyRule: "Admin"},
@@ -99,10 +104,15 @@ class OrganizationListPage extends BaseListPage {
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{name: "Multi-factor authentication", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "MFA items", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "WebAuthn credentials", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Last change password time", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Managed accounts", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Face ID", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "MFA accounts", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Need update password", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "IP whitelist", visible: true, viewRule: "Admin", modifyRule: "Admin"},
],
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -137,7 +137,7 @@ class RecordListPage extends BaseListPage {
title: i18next.t("record:Status code"),
dataIndex: "statusCode",
key: "statusCode",
width: "120px",
width: "140px",
sorter: true,
...this.getColumnSearchProps("statusCode"),
},

View File

@@ -609,13 +609,20 @@ class UserEditPage extends React.Component {
);
} else if (accountItem.name === "Addresses") {
return (
<AddressTable
title={i18next.t("user:Addresses")}
table={this.state.user.addresses}
onUpdateTable={(value) => {
this.updateUserField("addresses", value);
}}
/>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Addresses"), i18next.t("user:Addresses"))} :
</Col>
<Col span={22} >
<AddressTable
title={i18next.t("user:Addresses")}
table={this.state.user.addresses}
onUpdateTable={(value) => {
this.updateUserField("addresses", value);
}}
/>
</Col>
</Row>
);
} else if (accountItem.name === "Affiliation") {
return (
@@ -880,7 +887,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Transactions"), i18next.t("general:Transactions"))} :
</Col>
<Col span={22}>
<TransactionTable transactions={this.state.transactions} hideTag={true} />
<TransactionTable title={i18next.t("general:Transactions")} transactions={this.state.transactions} hideTag={true} />
</Col>
</Row>
);
@@ -1130,15 +1137,21 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("mfa:Multi-factor authentication"), i18next.t("mfa:Multi-factor authentication - Tooltip "))} :
</Col>
<Col span={22} >
<Card size="small" title={i18next.t("mfa:Multi-factor methods")}
extra={this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
<PopconfirmModal
text={i18next.t("general:Disable")}
title={i18next.t("general:Sure to disable") + "?"}
onConfirm={() => this.deleteMfa()}
/> : null
}>
<Card size="small" title={
<div>
{i18next.t("mfa:Multi-factor methods")}&nbsp;&nbsp;&nbsp;&nbsp;
{this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
<PopconfirmModal
text={i18next.t("general:Disable")}
title={i18next.t("general:Sure to disable") + "?"}
onConfirm={() => this.deleteMfa()}
size="small"
/> : null
}
</div>
}>
<List
size="small"
rowKey="mfaType"
itemLayout="horizontal"
dataSource={this.state.multiFactorAuths}

View File

@@ -402,24 +402,27 @@ export function getProviderUrl(provider) {
}
}
export function getProviderLogoWidget(provider) {
export function getProviderLogoWidget(provider, options = {}) {
if (provider === undefined) {
return null;
}
const url = getProviderUrl(provider);
if (url !== "") {
const disableLink = options.disableLink === true;
const imgEl = <img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />;
if (url !== "" && !disableLink) {
return (
<Tooltip title={provider.type}>
<a target="_blank" rel="noreferrer" href={getProviderUrl(provider)}>
<img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
{imgEl}
</a>
</Tooltip>
);
} else {
return (
<Tooltip title={provider.type}>
<img width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
{imgEl}
</Tooltip>
);
}

View File

@@ -23,24 +23,24 @@ class RegionSelect extends React.Component {
super(props);
this.state = {
classes: props,
value: "",
};
}
onChange(e) {
this.props.onChange(e);
this.setState({value: e});
}
render() {
const value = this.props.value !== undefined && this.props.value !== "" ? this.props.value : (this.props.defaultValue !== undefined && this.props.defaultValue !== "" ? this.props.defaultValue : undefined);
return (
<Select virtual={false}
size={this.props.size}
showSearch
optionFilterProp="label"
style={{width: "100%"}}
defaultValue={this.props.defaultValue || undefined}
value={value}
placeholder="Please select country/region"
onChange={(value => {this.onChange(value);})}
onChange={(val) => {this.onChange(val);}}
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
filterSort={(optionA, optionB) =>
(optionA?.label ?? "").toLowerCase().localeCompare((optionB?.label ?? "").toLowerCase())

View File

@@ -268,7 +268,7 @@
"Admin": "管理工具",
"Affiliation URL": "工作单位URL",
"Affiliation URL - Tooltip": "工作单位的官网URL",
"All": "全部允许",
"All": "全部",
"Application": "应用",
"Application - Tooltip": "可以访问的应用",
"Applications": "应用",

View File

@@ -0,0 +1,44 @@
// Copyright 2026 The Casdoor 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 {Col, Row} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import {CaptchaPreview} from "../common/CaptchaPreview";
export function renderCaptchaProviderFields(provider, providerName) {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
<Col span={22} >
<CaptchaPreview
owner={provider.owner}
name={provider.name}
provider={provider}
providerName={providerName}
captchaType={provider.type}
subType={provider.subType}
clientId={provider.clientId}
clientSecret={provider.clientSecret}
clientId2={provider.clientId2}
clientSecret2={provider.clientSecret2}
providerUrl={provider.providerUrl}
/>
</Col>
</Row>
);
}

View File

@@ -0,0 +1,255 @@
// Copyright 2026 The Casdoor 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, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as ProviderEditTestEmail from "../common/TestEmailWidget";
import Editor from "../common/Editor";
import HttpHeaderTable from "../table/HttpHeaderTable";
const {Option} = Select;
export function renderEmailProviderFields(provider, updateProviderField, renderEmailMappingInput, account) {
return (
<React.Fragment>
{
["Custom HTTP Email", "SendGrid"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.host} onChange={e => {
updateProviderField("host", e.target.value);
}} />
</Col>
</Row>
{["Azure ACS", "SendGrid"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={provider.port} onChange={value => {
updateProviderField("port", value);
}} />
</Col>
</Row>
)}
{["Azure ACS", "SendGrid"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SSL mode"), i18next.t("provider:SSL mode - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "200px"}} value={provider.sslMode || "Auto"} onChange={value => {
updateProviderField("sslMode", value);
}}>
<Option value="Auto">{i18next.t("general:Auto")}</Option>
<Option value="Enable">{i18next.t("general:Enable")}</Option>
<Option value="Disable">{i18next.t("general:Disable")}</Option>
</Select>
</Col>
</Row>
)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Enable proxy"), i18next.t("provider:Enable proxy - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={provider.enableProxy} onChange={checked => {
updateProviderField("enableProxy", checked);
}} />
</Col>
</Row>
{
provider.type === "Custom HTTP Email" ? (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
updateProviderField("method", value);
}}>
{
[
{id: "GET", name: "GET"},
{id: "POST", name: "POST"},
{id: "PUT", name: "PUT"},
{id: "DELETE", name: "DELETE"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>
{
provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : provider.issuerUrl} onChange={value => {
updateProviderField("issuerUrl", value);
}}>
{
[
{id: "application/json", name: "application/json"},
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
</Col>
<Col span={22} >
<HttpHeaderTable httpHeaders={provider.httpHeaders} onUpdateTable={(value) => {updateProviderField("httpHeaders", value);}} />
</Col>
</Row>
{provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
</Col>
<Col span={22}>
{renderEmailMappingInput()}
</Col>
</Row> : null}
</React.Fragment>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email title"), i18next.t("provider:Email title - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.title} onChange={e => {
updateProviderField("title", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes. <reset-link>Or click %link to reset</reset-link>")} >
{i18next.t("general:Reset to Default")} (Text)
</Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >
{i18next.t("general:Reset to Default")} (HTML)
</Button>
</Row>
<Row>
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{height: "300px", margin: "10px"}}>
<Editor
value={provider.content}
fillHeight
dark
lang="html"
onChange={value => {
updateProviderField("content", value);
}}
/>
</div>
</Col>
<Col span={1} />
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{margin: "10px"}}>
<div dangerouslySetInnerHTML={{__html: provider.content.replace("%s", "123456").replace("%{user.friendlyName}", Setting.getFriendlyUserName(account))}} />
</div>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(`${i18next.t("provider:Email content")}-${i18next.t("general:Invitations")}`, i18next.t("provider:Email content - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => updateProviderField("metadata", "You have invited to join Casdoor. Here is your invitation code: %s, please enter in 5 minutes. Or click %link to signup")} >
{i18next.t("general:Reset to Default")} (Text)
</Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => updateProviderField("metadata", Setting.getDefaultInvitationHtmlEmailContent())} >
{i18next.t("general:Reset to Default")} (HTML)
</Button>
</Row>
<Row>
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{height: "300px", margin: "10px"}}>
<Editor
value={provider.metadata}
fillHeight
dark
lang="html"
onChange={value => {
updateProviderField("metadata", value);
}}
/>
</div>
</Col>
<Col span={1} />
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{margin: "10px"}}>
<div dangerouslySetInnerHTML={{__html: provider.metadata.replace("%code", "123456").replace("%s", "123456")}} />
</div>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
</Col>
<Col span={4}>
<Input value={provider.receiver} placeholder={i18next.t("user:Input your email")}
onChange={e => {
updateProviderField("receiver", e.target.value);
}} />
</Col>
{["Azure ACS", "SendGrid"].includes(provider.type) ? null : (
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(provider)} >
{i18next.t("provider:Test SMTP Connection")}
</Button>
)}
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidEmail(provider.receiver)}
onClick={() => ProviderEditTestEmail.sendTestEmail(provider, provider.receiver)} >
{i18next.t("provider:Send Testing Email")}
</Button>
</Row>
</React.Fragment>
);
}

View File

@@ -0,0 +1,48 @@
// Copyright 2026 The Casdoor 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 {Col, Input, Row} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
export function renderFaceIdProviderFields(provider, updateProviderField) {
return (
<>
{["Alibaba Cloud Facebody"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.intranetEndpoint} onChange={e => {
updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
</>
);
}

View File

@@ -0,0 +1,34 @@
// Copyright 2026 The Casdoor 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 {Col, Input, Row} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
export function renderIDVerificationProviderFields(provider, updateProviderField) {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
);
}

View File

@@ -0,0 +1,56 @@
// Copyright 2026 The Casdoor 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 {Col, Input, InputNumber, Row} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
export function renderMfaProviderFields(provider, updateProviderField) {
return (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.host} placeholder="10.10.10.10" onChange={e => {
updateProviderField("host", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={provider.port} onChange={value => {
updateProviderField("port", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:RADIUS Shared Secret - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.clientSecret} placeholder="Shared secret" onChange={e => {
updateProviderField("clientSecret", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
);
}

View File

@@ -0,0 +1,103 @@
// Copyright 2026 The Casdoor 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, Col, Input, Row, Select} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as ProviderNotification from "../common/TestNotificationWidget";
const {Option} = Select;
const {TextArea} = Input;
export function renderNotificationProviderFields(provider, updateProviderField, getReceiverRow) {
return (
<React.Fragment>
{["CUCloud"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(provider.type) ?
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.regionId} onChange={e => {
updateProviderField("regionId", e.target.value);
}} />
</Col>
</Row>
) : null}
{["Custom HTTP"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
updateProviderField("method", value);
}}>
{
[
{id: "GET", name: "GET"},
{id: "POST", name: "POST"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>
) : null}
{["Custom HTTP", "CUCloud"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.title} onChange={e => {
updateProviderField("title", e.target.value);
}} />
</Col>
</Row>
) : null}
{["Google Chat", "CUCloud"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={provider.metadata} onChange={e => {
updateProviderField("metadata", e.target.value);
}} />
</Col>
</Row>
) : null}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Content"), i18next.t("provider:Content - Tooltip"))} :
</Col>
<Col span={22} >
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={provider.content} onChange={e => {
updateProviderField("content", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
{getReceiverRow(provider)}
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
onClick={() => ProviderNotification.sendTestNotification(provider)} >
{i18next.t("provider:Send Testing Notification")}
</Button>
</Row>
</React.Fragment>
);
}

View File

@@ -0,0 +1,218 @@
// Copyright 2026 The Casdoor 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 {Col, Input, Radio, Row, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
const {TextArea} = Input;
export function renderOAuthProviderFields(provider, updateProviderField, renderUserMappingInput) {
const getDomainLabel = provider => {
switch (provider.category) {
case "OAuth":
if (provider.type === "AzureAD" || provider.type === "AzureADB2C") {
return Setting.getLabel(i18next.t("provider:Tenant ID"), i18next.t("provider:Tenant ID - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"));
}
};
return (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={provider.emailRegex} onChange={e => {
updateProviderField("emailRegex", e.target.value);
}} />
</Col>
</Row>
{
provider.type !== "WeChat" ? null : (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Use WeChat Media Platform in PC"), i18next.t("provider:Use WeChat Media Platform in PC - Tooltip"))} :
</Col>
<Col span={1} >
<Switch disabled={!provider.clientId} checked={provider.disableSsl} onChange={checked => {
updateProviderField("disableSsl", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("token:Access token"), i18next.t("token:Access token - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.content} disabled={!provider.disableSsl || !provider.clientId2} onChange={e => {
updateProviderField("content", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Follow-up action"), i18next.t("provider:Follow-up action - Tooltip"))} :
</Col>
<Col>
<Radio.Group value={provider.signName}
disabled={!provider.disableSsl || !provider.clientId || !provider.clientId2}
buttonStyle="solid"
onChange={e => {
updateProviderField("signName", e.target.value);
}}>
<Radio.Button value="open">{i18next.t("provider:Use WeChat Open Platform to login")}</Radio.Button>
<Radio.Button value="media">{i18next.t("provider:Use WeChat Media Platform to login")}</Radio.Button>
</Radio.Group>
</Col>
</Row>
</React.Fragment>
)
}
{
provider.type !== "ADFS" && provider.type !== "AzureAD"
&& provider.type !== "AzureADB2C" && (provider.type !== "Casdoor" && provider.category !== "Storage")
&& provider.type !== "Okta" && provider.type !== "Nextcloud" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{getDomainLabel(provider)} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.domain} onChange={e => {
updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
)
}
{
provider.type !== "Google" && provider.type !== "Lark" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{provider.type === "Google" ?
Setting.getLabel(i18next.t("provider:Get phone number"), i18next.t("provider:Get phone number - Tooltip"))
: Setting.getLabel(i18next.t("provider:Use global endpoint"), i18next.t("provider:Use global endpoint - Tooltip"))} :
</Col>
<Col span={1} >
<Switch disabled={!provider.clientId} checked={provider.disableSsl} onChange={checked => {
updateProviderField("disableSsl", checked);
}} />
</Col>
</Row>
)
}
{
provider.type.startsWith("Custom") ? (
<React.Fragment>
<Col>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={provider.customAuthUrl} onChange={e => {
updateProviderField("customAuthUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={provider.customTokenUrl} onChange={e => {
updateProviderField("customTokenUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={provider.scopes} onChange={e => {
updateProviderField("scopes", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={provider.customUserInfoUrl} onChange={e => {
updateProviderField("customUserInfoUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Enable PKCE"), i18next.t("provider:Enable PKCE - Tooltip"))} :
</Col>
<Col span={22} >
<Switch checked={provider.enablePkce} onChange={checked => {
updateProviderField("enablePkce", checked);
}} />
</Col>
</Row>
</Col>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:User mapping"), i18next.t("provider:User mapping - Tooltip"))} :
</Col>
<Col span={22} >
{renderUserMappingInput()}
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined />} value={provider.customLogo} onChange={e => {
updateProviderField("customLogo", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<a target="_blank" rel="noreferrer" href={provider.customLogo}>
<img src={provider.customLogo} alt={provider.customLogo} height={90} style={{marginBottom: "20px"}} />
</a>
</Col>
</Row>
</Col>
</Row>
</React.Fragment>
) : null
}
</React.Fragment>
);
}

View File

@@ -0,0 +1,72 @@
// Copyright 2026 The Casdoor 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 {Col, Input, Row, Select} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import {LinkOutlined} from "@ant-design/icons";
const {Option} = Select;
export function renderPaymentProviderFields(provider, updateProviderField, certs) {
return (
<React.Fragment>
{
(provider.type === "Alipay" || provider.type === "WeChat Pay" || provider.type === "Casdoor") ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.cert} onChange={(value => {updateProviderField("cert", value);})}>
{
certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
}
</Select>
</Col>
</Row>
) : null
}
{
(provider.type === "Alipay") ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Root cert"), i18next.t("general:Root cert - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.metadata} onChange={(value => {updateProviderField("metadata", value);})}>
{
certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
}
</Select>
</Col>
</Row>
) : null
}
{(provider.type === "GC" || provider.type === "FastSpring") ? (
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22}>
<Input prefix={<LinkOutlined />} value={provider.host} onChange={e => {
updateProviderField("host", e.target.value);
}} />
</Col>
</Row>
) : null}
</React.Fragment>
);
}

View File

@@ -0,0 +1,133 @@
// Copyright 2026 The Casdoor 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, Col, Input, Row, Switch} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import {authConfig} from "../auth/Auth";
import copy from "copy-to-clipboard";
const {TextArea} = Input;
export function renderSamlProviderFields(provider, updateProviderField, metadataConfig) {
const {requestUrl, setRequestUrl, metadataLoading, fetchSamlMetadata, parseSamlMetadata} = metadataConfig;
return (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Sign request"), i18next.t("provider:Sign request - Tooltip"))} :
</Col>
<Col span={22} >
<Switch checked={provider.enableSignAuthnRequest} onChange={checked => {
updateProviderField("enableSignAuthnRequest", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata url"), i18next.t("provider:Metadata url - Tooltip"))} :
</Col>
<Col span={6} >
<Input value={requestUrl} onChange={e => {
setRequestUrl(e.target.value);
}} />
</Col>
<Col span={16} >
<Button style={{marginLeft: "10px"}} type="primary" loading={metadataLoading} onClick={() => {fetchSamlMetadata();}}>{i18next.t("general:Request")}</Button>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={provider.metadata} onChange={e => {
updateProviderField("metadata", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={2} />
<Col span={2}>
<Button type="primary" onClick={() => {parseSamlMetadata();}}>
{i18next.t("provider:Parse")}
</Button>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:SAML 2.0 Endpoint (HTTP)"))} :
</Col>
<Col span={22} >
<Input value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
</Col>
<Col span={22} >
<Input value={provider.idP} onChange={e => {
updateProviderField("idP", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Issuer URL"), i18next.t("provider:Issuer URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.issuerUrl} onChange={e => {
updateProviderField("issuerUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SP ACS URL"), i18next.t("provider:SP ACS URL - Tooltip"))} :
</Col>
<Col span={21} >
<Input value={`${authConfig.serverUrl}/api/acs`} readOnly="readonly" />
</Col>
<Col span={1}>
<Button type="primary" onClick={() => {
copy(`${authConfig.serverUrl}/api/acs`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("general:Copy")}
</Button>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SP Entity ID"), i18next.t("provider:SP Entity ID - Tooltip"))} :
</Col>
<Col span={21} >
<Input value={`${authConfig.serverUrl}/api/acs`} readOnly="readonly" />
</Col>
<Col span={1}>
<Button type="primary" onClick={() => {
copy(`${authConfig.serverUrl}/api/acs`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("general:Copy")}
</Button>
</Col>
</Row>
</React.Fragment>
);
}

View File

@@ -0,0 +1,182 @@
// Copyright 2026 The Casdoor 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, Col, Input, Row, Select, Switch} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as ProviderEditTestSms from "../common/TestSmsWidget";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
import HttpHeaderTable from "../table/HttpHeaderTable";
import {LinkOutlined} from "@ant-design/icons";
const {Option} = Select;
const SMS_PROVIDERS_WITHOUT_SIGN_NAME = ["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Msg91 SMS", "Infobip SMS"];
const SMS_PROVIDERS_WITHOUT_TEMPLATE_CODE = ["Infobip SMS", "Custom HTTP SMS"];
export function renderSmsProviderFields(provider, updateProviderField, renderSmsMappingInput, account) {
return (
<React.Fragment>
{SMS_PROVIDERS_WITHOUT_SIGN_NAME.includes(provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.signName} onChange={e => {
updateProviderField("signName", e.target.value);
}} />
</Col>
</Row>
)
}
{SMS_PROVIDERS_WITHOUT_TEMPLATE_CODE.includes(provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.templateCode} onChange={e => {
updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
)
}
{
provider.type === "Custom HTTP SMS" ? (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.method} onChange={value => {
updateProviderField("method", value);
}}>
{
[
{id: "GET", name: "GET"},
{id: "POST", name: "POST"},
{id: "PUT", name: "PUT"},
{id: "DELETE", name: "DELETE"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>
{
provider.method !== "GET" ? (<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={provider.issuerUrl === "" ? "application/x-www-form-urlencoded" : provider.issuerUrl} onChange={value => {
updateProviderField("issuerUrl", value);
}}>
{
[
{id: "application/json", name: "application/json"},
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP header"), i18next.t("provider:HTTP header - Tooltip"))} :
</Col>
<Col span={22} >
<HttpHeaderTable httpHeaders={provider.httpHeaders} onUpdateTable={(value) => {updateProviderField("httpHeaders", value);}} />
</Col>
</Row>
{provider.method !== "GET" ? <Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:HTTP body mapping"), i18next.t("provider:HTTP body mapping - Tooltip"))} :
</Col>
<Col span={22}>
{renderSmsMappingInput()}
</Col>
</Row> : null}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.title} onChange={e => {
updateProviderField("title", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Enable proxy"), i18next.t("provider:Enable proxy - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={provider.enableProxy} onChange={checked => {
updateProviderField("enableProxy", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
</Col>
<Col span={4} >
<Input.Group compact>
<CountryCodeSelect
style={{width: "90px"}}
initValue={provider.content}
onChange={(value) => {
updateProviderField("content", value);
}}
countryCodes={account.organization.countryCodes}
/>
<Input value={provider.receiver}
style={{width: "150px"}}
placeholder = {i18next.t("user:Input your phone number")}
onChange={e => {
updateProviderField("receiver", e.target.value);
}} />
</Input.Group>
</Col>
<Col span={2} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidPhone(provider.receiver) || (provider.type === "Custom HTTP SMS" && provider.endpoint === "")}
onClick={() => ProviderEditTestSms.sendTestSms(provider, "+" + Setting.getCountryCode(provider.content) + provider.receiver)} >
{i18next.t("provider:Send Testing SMS")}
</Button>
</Col>
</Row>
</React.Fragment>
);
}

View File

@@ -0,0 +1,112 @@
// Copyright 2026 The Casdoor 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 {Col, Input, Row} from "antd";
import {LinkOutlined} from "@ant-design/icons";
import * as Setting from "../Setting";
import i18next from "i18next";
export function renderStorageProviderFields(provider, updateProviderField) {
return (
<React.Fragment>
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.intranetEndpoint} onChange={e => {
updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.endpoint} onChange={e => {
updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(provider.type) ?
Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))
: Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.bucket} onChange={e => {
updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.pathPrefix} onChange={e => {
updateProviderField("pathPrefix", e.target.value);
}} />
</Col>
</Row>
{["Synology", "Casdoor"].includes(provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={provider.domain} disabled={provider.type === "Local File System"} onChange={e => {
updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
)}
{["Casdoor"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.content} onChange={e => {
updateProviderField("content", e.target.value);
}} />
</Col>
</Row>
) : null}
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO"].includes(provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(provider.type) ?
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={provider.regionId} onChange={e => {
updateProviderField("regionId", e.target.value);
}} />
</Col>
</Row>
) : null}
</React.Fragment>
);
}

View File

@@ -0,0 +1,48 @@
// Copyright 2026 The Casdoor 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 {Checkbox, Col, Row} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import * as Web3Auth from "../auth/Web3Auth";
export function renderWeb3ProviderFields(provider, updateProviderField) {
const getWalletValue = () => {
try {
return JSON.parse(provider.metadata);
} catch {
return ["injected"];
}
};
return (
provider.type === "Web3Onboard" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Wallets"), i18next.t("provider:Wallets - Tooltip"))} :
</Col>
<Col span={22}>
<Checkbox.Group
options={Web3Auth.getWeb3OnboardWalletsOptions()}
value={getWalletValue()}
onChange={options => {
updateProviderField("metadata", JSON.stringify(options));
}}
/>
</Col>
</Row>
) : null
);
}

View File

@@ -14,12 +14,16 @@
import React from "react";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Table, Tooltip} from "antd";
import {AutoComplete, Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
import RegionSelect from "../common/select/RegionSelect";
const {Option} = Select;
const TAG_OPTIONS = [
{value: "Home", label: "Home"},
{value: "Work", label: "Work"},
{value: "Other", label: "Other"},
];
class AddressTable extends React.Component {
constructor(props) {
@@ -86,16 +90,20 @@ class AddressTable extends React.Component {
key: "tag",
width: "100px",
render: (text, record, index) => {
const tagOptions = TAG_OPTIONS.map(opt => ({...opt, label: opt.value === "Home" ? i18next.t("general:Home") : opt.value === "Work" ? i18next.t("user:Work") : i18next.t("user:Other")}));
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
<AutoComplete
size="small"
style={{width: "100%"}}
value={text || ""}
options={tagOptions}
onChange={value => {
this.updateField(table, index, "tag", value);
}} >
<Option value="Home">{i18next.t("general:Home")}</Option>
<Option value="Work">{i18next.t("user:Work")}</Option>
<Option value="Other">{i18next.t("user:Other")}</Option>
</Select>
}}
onSelect={value => {
this.updateField(table, index, "tag", value);
}}
/>
);
},
},
@@ -106,7 +114,7 @@ class AddressTable extends React.Component {
width: "150px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "line1", e.target.value);
}} />
);
@@ -119,7 +127,7 @@ class AddressTable extends React.Component {
width: "150px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "line2", e.target.value);
}} />
);
@@ -132,7 +140,7 @@ class AddressTable extends React.Component {
width: "120px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "city", e.target.value);
}} />
);
@@ -145,7 +153,7 @@ class AddressTable extends React.Component {
width: "100px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "state", e.target.value);
}} />
);
@@ -158,7 +166,7 @@ class AddressTable extends React.Component {
width: "100px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "zipCode", e.target.value);
}} />
);
@@ -172,6 +180,7 @@ class AddressTable extends React.Component {
render: (text, record, index) => {
return (
<RegionSelect
size="small"
value={text}
onChange={value => {
this.updateField(table, index, "region", value);

View File

@@ -86,7 +86,7 @@ class ManagedAccountTable extends React.Component {
render: (text, record, index) => {
const items = this.props.applications;
return (
<Select virtual={false} style={{width: "100%"}}
<Select virtual={false} size="small" style={{width: "100%"}}
value={text}
onChange={value => {
this.updateField(table, index, "application", value);
@@ -105,7 +105,7 @@ class ManagedAccountTable extends React.Component {
// width: "420px",
render: (text, record, index) => {
return (
<Input prefix={<LinkOutlined />} value={text} onChange={e => {
<Input size="small" prefix={<LinkOutlined />} value={text} onChange={e => {
this.updateField(table, index, "signinUrl", e.target.value);
}} />
);
@@ -118,7 +118,7 @@ class ManagedAccountTable extends React.Component {
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "username", e.target.value);
}} />
);
@@ -131,7 +131,7 @@ class ManagedAccountTable extends React.Component {
width: "200px",
render: (text, record, index) => {
return (
<Input.Password value={text} onChange={e => {
<Input.Password size="small" value={text} onChange={e => {
this.updateField(table, index, "password", e.target.value);
}} />
);

View File

@@ -86,7 +86,7 @@ class MfaAccountTable extends React.Component {
width: "400px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "accountName", e.target.value);
}} />
);
@@ -99,7 +99,7 @@ class MfaAccountTable extends React.Component {
width: "300px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "issuer", e.target.value);
}} />
);
@@ -111,7 +111,7 @@ class MfaAccountTable extends React.Component {
key: "origin",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
<Input size="small" value={text} onChange={e => {
this.updateField(table, index, "origin", e.target.value);
}} />
);
@@ -123,7 +123,7 @@ class MfaAccountTable extends React.Component {
key: "secretKey",
render: (text, record, index) => {
return (
<Input.Password value={text} onChange={e => {
<Input.Password size="small" value={text} onChange={e => {
this.updateField(table, index, "secretKey", e.target.value);
}} />
);

View File

@@ -84,7 +84,7 @@ class MfaTable extends React.Component {
key: "name",
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: "100%"}}
<Select virtual={false} size="small" style={{width: "100%"}}
value={text}
onChange={value => {
this.updateField(table, index, "name", value);
@@ -103,7 +103,7 @@ class MfaTable extends React.Component {
width: "100px",
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: "100%"}}
<Select virtual={false} size="small" style={{width: "100%"}}
value={text}
defaultValue="Optional"
options={RuleItems.map((item) =>

View File

@@ -110,7 +110,17 @@ class ProviderTable extends React.Component {
width: "100px",
render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return provider?.category;
const owner = provider?.owner || this.getUserOrganization()?.name;
const editUrl = provider && owner && provider.name ? `/providers/${owner}/${provider.name}` : null;
const categoryText = provider?.category;
if (editUrl && categoryText) {
return (
<a href={editUrl} target="_blank" rel="noopener noreferrer">
{categoryText}
</a>
);
}
return categoryText;
},
},
{
@@ -120,7 +130,17 @@ class ProviderTable extends React.Component {
width: "80px",
render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return Provider.getProviderLogoWidget(provider);
const owner = provider?.owner || this.getUserOrganization()?.name;
const editUrl = provider && owner && provider.name ? `/providers/${owner}/${provider.name}` : null;
const typeWidget = Provider.getProviderLogoWidget(provider, {disableLink: !!editUrl});
if (editUrl && typeWidget) {
return (
<a href={editUrl} target="_blank" rel="noopener noreferrer">
{typeWidget}
</a>
);
}
return typeWidget;
},
},
{

View File

@@ -131,13 +131,18 @@ class TransactionTable extends React.Component {
columns={columns}
dataSource={this.props.transactions}
rowKey={(record) => `${record.owner}/${record.name}`}
size="small"
size="middle"
bordered
pagination={{
pageSize: 10,
showSizeChanger: true,
pageSizeOptions: ["10", "20", "50", "100"],
}}
title={this.props.title ? () => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
</div>
) : undefined}
/>
);
}