Add signup table.

This commit is contained in:
Yang Luo 2021-06-16 14:06:41 +08:00
parent 7ea469e876
commit 02b1feb2e5
9 changed files with 431 additions and 159 deletions

View File

@ -32,6 +32,7 @@ type Application struct {
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
Providers []*ProviderItem `xorm:"varchar(10000)" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"`

View File

@ -67,6 +67,7 @@ func initBuiltInApplication() {
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{},
SignupItems: []*SignupItem{},
RedirectUris: []string{},
ExpireInHours: 168,
}

22
object/signup_item.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Rule string `json:"rule"`
}

View File

@ -24,6 +24,7 @@ import LoginPage from "./auth/LoginPage";
import i18next from "i18next";
import UrlTable from "./UrlTable";
import ProviderTable from "./ProviderTable";
import SignupTable from "./SignupTable";
const { Option } = Select;
@ -299,6 +300,18 @@ class ApplicationEditPage extends React.Component {
this.renderPreview()
}
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{i18next.t("application:Signup items")}:
</Col>
<Col span={22} >
<SignupTable
title={i18next.t("application:Signup items")}
table={this.state.application.signupItems}
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}}
/>
</Col>
</Row>
</Card>
)
}

View File

@ -13,8 +13,8 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} from '@ant-design/icons';
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from 'antd';
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd';
import * as Setting from "./Setting";
import i18next from "i18next";
@ -81,11 +81,6 @@ class ProviderTable extends React.Component {
}
</Select>
)
return (
<Input prefix={<LinkOutlined/>} value={text} onChange={e => {
this.updateField(table, index, 'name', e.target.value);
}} />
)
}
},
{

197
web/src/SignupTable.js Normal file
View File

@ -0,0 +1,197 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd';
import * as Setting from "./Setting";
import i18next from "i18next";
const { Option } = Select;
class SignupTable extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
};
}
updateTable(table) {
this.props.onUpdateTable(table);
}
updateField(table, index, key, value) {
table[index][key] = value;
this.updateTable(table);
}
addRow(table) {
let row = {name: "Please select a signup item", visible: true, required: true, rule: "None"};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row);
this.updateTable(table);
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.updateTable(table);
}
upRow(table, i) {
table = Setting.swapRow(table, i - 1, i);
this.updateTable(table);
}
downRow(table, i) {
table = Setting.swapRow(table, i, i + 1);
this.updateTable(table);
}
renderTable(table) {
const columns = [
{
title: i18next.t("provider:Name"),
dataIndex: 'name',
key: 'name',
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: '100%'}}
value={text}
onChange={value => {
this.updateField(table, index, 'name', value);
}} >
{
[
{id: 'Username', name: 'Username'},
{id: 'ID', name: 'ID'},
{id: 'Display name', name: 'Display name'},
{id: 'Affiliation', name: 'Affiliation'},
{id: 'Email', name: 'Email'},
{id: 'Password', name: 'Password'},
{id: 'Confirm', name: 'Confirm'},
{id: 'Phone', name: 'Phone'},
{id: 'Agreement', name: 'Agreement'},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
)
}
},
{
title: i18next.t("provider:visible"),
dataIndex: 'visible',
key: 'visible',
width: '120px',
render: (text, record, index) => {
return (
<Switch checked={text} onChange={checked => {
this.updateField(table, index, 'visible', checked);
if (!checked) {
this.updateField(table, index, 'required', false);
}
}} />
)
}
},
{
title: i18next.t("provider:required"),
dataIndex: 'required',
key: 'required',
width: '120px',
render: (text, record, index) => {
if (!record.visible) {
return null;
}
return (
<Switch checked={text} onChange={checked => {
this.updateField(table, index, 'required', checked);
}} />
)
}
},
{
title: i18next.t("provider:rule"),
dataIndex: 'rule',
key: 'rule',
width: '120px',
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
this.updateField(table, index, 'rule', value);
})}>
{
[
{id: 'None', name: 'None'},
{id: 'Random', name: 'Random'},
{id: 'Incremental', name: 'Incremental'},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
)
}
},
{
title: i18next.t("general:Action"),
key: 'action',
width: '100px',
render: (text, record, index) => {
return (
<div>
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
</Tooltip>
</div>
);
}
},
];
return (
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>
);
}
render() {
return (
<div>
<Row style={{marginTop: '20px'}} >
<Col span={24}>
{
this.renderTable(this.props.table)
}
</Col>
</Row>
</div>
)
}
}
export default SignupTable;

View File

@ -126,63 +126,13 @@ class SignupPage extends React.Component {
this.form.current.scrollToField(errorFields[0].name);
}
renderForm(application) {
if (!application.enableSignUp) {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
extra={[
<Link onClick={() => {
Setting.goToLogin(this, application);
}}>
<Button type="primary" key="signin">
Sign In
</Button>
</Link>
]}
>
</Result>
)
renderFormItem(application, signupItem) {
if (!signupItem.visible) {
return null;
}
if (signupItem.name === "Username") {
return (
<Form
{...formItemLayout}
ref={this.form}
name="signup"
onFinish={(values) => this.onFinish(values)}
onFinishFailed={(errorInfo) => this.onFinishFailed(errorInfo.values, errorInfo.errorFields, errorInfo.outOfDate)}
initialValues={{
application: application.name,
organization: application.organization,
}}
style={{width: !Setting.isMobile() ? "400px" : "250px"}}
size="large"
>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="application"
rules={[
{
required: true,
message: 'Please input your application!',
},
]}
>
</Form.Item>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: 'Please input your organization!',
},
]}
>
</Form.Item>
<Form.Item
name="username"
label={i18next.t("signup:Username")}
@ -196,6 +146,9 @@ class SignupPage extends React.Component {
>
<Input />
</Form.Item>
)
} else if (signupItem.name === "Display name") {
return (
<Form.Item
name="name"
label={i18next.t("general:Display name")}
@ -209,6 +162,9 @@ class SignupPage extends React.Component {
>
<Input />
</Form.Item>
)
} else if (signupItem.name === "Affiliation") {
return (
<Form.Item
name="affiliation"
label={i18next.t("user:Affiliation")}
@ -222,6 +178,10 @@ class SignupPage extends React.Component {
>
<Input />
</Form.Item>
)
} else if (signupItem.name === "Email") {
return (
<React.Fragment>
<Form.Item
name="email"
label={i18next.t("general:Email")}
@ -253,6 +213,11 @@ class SignupPage extends React.Component {
coolDownTime={60}
/>
</Form.Item>
</React.Fragment>
)
} else if (signupItem.name === "Password") {
return (
<React.Fragment>
<Form.Item
name="password"
label={i18next.t("general:Password")}
@ -289,6 +254,11 @@ class SignupPage extends React.Component {
>
<Input.Password />
</Form.Item>
</React.Fragment>
)
} else if (signupItem.name === "Phone") {
return (
<React.Fragment>
<Form.Item
name="phone"
label={i18next.t("general:Phone")}
@ -324,7 +294,15 @@ class SignupPage extends React.Component {
coolDownTime={60}
/>
</Form.Item>
<Form.Item name="agreement" valuePropName="checked" {...tailFormItemLayout}>
</React.Fragment>
)
} else if (signupItem.name === "Agreement") {
return (
<Form.Item
name="agreement"
valuePropName="checked"
{...tailFormItemLayout}
>
<Checkbox>
{i18next.t("signup:Accept")}&nbsp;
<Link to={"/agreement"}>
@ -332,6 +310,69 @@ class SignupPage extends React.Component {
</Link>
</Checkbox>
</Form.Item>
)
}
}
renderForm(application) {
if (!application.enableSignUp) {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
extra={[
<Link onClick={() => {
Setting.goToLogin(this, application);
}}>
<Button type="primary" key="signin">
Sign In
</Button>
</Link>
]}
>
</Result>
)
}
return (
<Form
{...formItemLayout}
ref={this.form}
name="signup"
onFinish={(values) => this.onFinish(values)}
onFinishFailed={(errorInfo) => this.onFinishFailed(errorInfo.values, errorInfo.errorFields, errorInfo.outOfDate)}
initialValues={{
application: application.name,
organization: application.organization,
}}
style={{width: !Setting.isMobile() ? "400px" : "250px"}}
size="large"
>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="application"
rules={[
{
required: true,
message: 'Please input your application!',
},
]}
>
</Form.Item>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: 'Please input your organization!',
},
]}
>
</Form.Item>
{
application.signupItems.map(signupItem => this.renderFormItem(application, signupItem))
}
<Form.Item {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
{i18next.t("account:Sign Up")}

View File

@ -177,7 +177,8 @@
"Test signup page..": "Test signup page..",
"Test signin page..": "Test signin page..",
"Redirect URL": "Redirect URL",
"Redirect URLs": "Redirect URLs"
"Redirect URLs": "Redirect URLs",
"Signup items": "Signup items"
},
"forget":
{

View File

@ -177,7 +177,8 @@
"Test signup page..": "测试注册页面..",
"Test signin page..": "测试登录页面..",
"Redirect URL": "回调URL",
"Redirect URLs": "回调URLs"
"Redirect URLs": "回调URLs",
"Signup items": "注册项"
},
"forget":
{