mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 05:10:19 +08:00
@ -35,6 +35,8 @@ import Face from "./auth/Face";
|
||||
import LoginPage from "./auth/LoginPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from './SelectLanguageBox';
|
||||
import i18next from 'i18next';
|
||||
|
||||
const { Header, Footer } = Layout;
|
||||
|
||||
@ -56,6 +58,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
Setting.setLanguage();
|
||||
this.updateMenuKey();
|
||||
this.getAccount();
|
||||
}
|
||||
@ -127,11 +130,11 @@ class App extends Component {
|
||||
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
|
||||
<Menu.Item key="201">
|
||||
<SettingOutlined />
|
||||
My Account
|
||||
{i18next.t("account:My Account")}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="202">
|
||||
<LogoutOutlined />
|
||||
Logout
|
||||
{i18next.t("account:Logout")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
@ -162,14 +165,14 @@ class App extends Component {
|
||||
res.push(
|
||||
<Menu.Item key="100" style={{float: 'right', marginRight: '20px'}}>
|
||||
<Link to="/register">
|
||||
Register
|
||||
{i18next.t("account:Register")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="101" style={{float: 'right'}}>
|
||||
<Link to="/login">
|
||||
Login
|
||||
{i18next.t("account:Login")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
@ -190,7 +193,7 @@ class App extends Component {
|
||||
res.push(
|
||||
<Menu.Item key="0">
|
||||
<Link to="/">
|
||||
Home
|
||||
{i18next.t("general:Home")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
@ -199,33 +202,32 @@ class App extends Component {
|
||||
res.push(
|
||||
<Menu.Item key="1">
|
||||
<Link to="/organizations">
|
||||
Organizations
|
||||
{i18next.t("general:Organizations")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="2">
|
||||
<Link to="/users">
|
||||
Users
|
||||
{i18next.t("general:Users")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="3">
|
||||
<Link to="/providers">
|
||||
Providers
|
||||
{i18next.t("general:Providers")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="4">
|
||||
<Link to="/applications">
|
||||
Applications
|
||||
{i18next.t("general:Applications")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -305,6 +307,7 @@ class App extends Component {
|
||||
textAlign: 'center',
|
||||
}
|
||||
}>
|
||||
<SelectLanguageBox/>
|
||||
Made with <span style={{color: 'rgb(255, 255, 255)'}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casbin.org">Casbin</a>
|
||||
</Footer>
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ import * as Setting from "./Setting";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import Face from "./auth/Face";
|
||||
import i18next from "i18next";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -89,13 +90,13 @@ class ApplicationEditPage extends React.Component {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
Edit Application
|
||||
<Button type="primary" onClick={this.submitApplicationEdit.bind(this)}>Save</Button>
|
||||
{i18next.t("application:Edit Application")}
|
||||
<Button type="primary" onClick={this.submitApplicationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</div>
|
||||
} style={{marginLeft: '5px'}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Name:
|
||||
{i18next.t("general:Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.name} onChange={e => {
|
||||
@ -105,7 +106,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Display Name:
|
||||
{i18next.t("general:Display Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.displayName} onChange={e => {
|
||||
@ -130,7 +131,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={1}>
|
||||
Preview:
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<a target="_blank" href={this.state.application.logo}>
|
||||
@ -142,7 +143,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Organization:
|
||||
{i18next.t("general:Organization")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField('organization', value);})}>
|
||||
@ -154,7 +155,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Enable Password:
|
||||
{i18next.t("application:Enable Password")}:
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enablePassword} onChange={checked => {
|
||||
@ -164,7 +165,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Providers:
|
||||
{i18next.t("general:Providers")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select mode="tags" style={{width: '100%'}}
|
||||
@ -180,7 +181,7 @@ class ApplicationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Face Preview:
|
||||
{i18next.t("application:Face Preview")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<a style={{marginBottom: '10px'}} target="_blank" href={`/doors/${this.state.application.name}`}>
|
||||
@ -237,7 +238,7 @@ class ApplicationEditPage extends React.Component {
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large" onClick={this.submitApplicationEdit.bind(this)}>Save</Button>
|
||||
<Button type="primary" size="large" onClick={this.submitApplicationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class ApplicationListPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -85,7 +86,7 @@ class ApplicationListPage extends React.Component {
|
||||
renderTable(applications) {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '150px',
|
||||
@ -99,7 +100,7 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Created Time',
|
||||
title: i18next.t("general:Created Time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
@ -109,7 +110,7 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Display Name',
|
||||
title: i18next.t("general:Display Name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
@ -129,7 +130,7 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Organization',
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: 'organization',
|
||||
key: 'organization',
|
||||
width: '200px',
|
||||
@ -143,7 +144,7 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Providers',
|
||||
title: i18next.t("general:Providers"),
|
||||
dataIndex: 'providers',
|
||||
key: 'providers',
|
||||
width: '200px',
|
||||
@ -157,19 +158,19 @@ class ApplicationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>Edit</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete application: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteApplication(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -182,8 +183,8 @@ class ApplicationListPage extends React.Component {
|
||||
<Table columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
Applications
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>Add</Button>
|
||||
{i18next.t("general:Applications")}
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={applications === null}
|
||||
|
@ -16,6 +16,7 @@ import React from "react";
|
||||
import {Button, Card, Col, Input, Row} from 'antd';
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class OrganizationEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -61,13 +62,13 @@ class OrganizationEditPage extends React.Component {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
Edit Organization
|
||||
<Button type="primary" onClick={this.submitOrganizationEdit.bind(this)}>Save</Button>
|
||||
{i18next.t("organization:Edit Organization")}
|
||||
<Button type="primary" onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</div>
|
||||
} style={{marginLeft: '5px'}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Name:
|
||||
{i18next.t("general:Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.name} onChange={e => {
|
||||
@ -77,7 +78,7 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Display Name:
|
||||
{i18next.t("general:Display Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.displayName} onChange={e => {
|
||||
@ -87,7 +88,7 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Website URL:
|
||||
{i18next.t("organization:Website URL")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.websiteUrl} onChange={e => {
|
||||
@ -137,7 +138,7 @@ class OrganizationEditPage extends React.Component {
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large" onClick={this.submitOrganizationEdit.bind(this)}>Save</Button>
|
||||
<Button type="primary" size="large" onClick={this.submitOrganizationEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class OrganizationListPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -83,7 +84,7 @@ class OrganizationListPage extends React.Component {
|
||||
renderTable(organizations) {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
@ -97,7 +98,7 @@ class OrganizationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Created Time',
|
||||
title: i18next.t("general:Created Time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
@ -107,14 +108,14 @@ class OrganizationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Display Name',
|
||||
title: i18next.t("general:Display Name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
},
|
||||
{
|
||||
title: 'Website URL',
|
||||
title: i18next.t("organization:Website URL"),
|
||||
dataIndex: 'websiteUrl',
|
||||
key: 'websiteUrl',
|
||||
width: '300px',
|
||||
@ -128,19 +129,19 @@ class OrganizationListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}`)}>Edit</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete organization: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteOrganization(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -153,8 +154,8 @@ class OrganizationListPage extends React.Component {
|
||||
<Table columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
Organizations
|
||||
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>Add</Button>
|
||||
{i18next.t("general:Organizations")}
|
||||
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={organizations === null}
|
||||
|
@ -16,6 +16,7 @@ import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select} from 'antd';
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -63,13 +64,13 @@ class ProviderEditPage extends React.Component {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
Edit Provider
|
||||
<Button type="primary" onClick={this.submitProviderEdit.bind(this)}>Save</Button>
|
||||
{i18next.t("provider:Edit Provider")}
|
||||
<Button type="primary" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</div>
|
||||
} style={{marginLeft: '5px'}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Name:
|
||||
{i18next.t("general:Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.name} onChange={e => {
|
||||
@ -79,7 +80,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Display Name:
|
||||
{i18next.t("general:Display Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.displayName} onChange={e => {
|
||||
@ -89,7 +90,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Type:
|
||||
{i18next.t("provider:Type")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {this.updateProviderField('type', value);})}>
|
||||
@ -106,7 +107,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Client ID:
|
||||
{i18next.t("provider:Client ID")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId} onChange={e => {
|
||||
@ -116,7 +117,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Client Secret:
|
||||
{i18next.t("provider:Client Secret")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret} onChange={e => {
|
||||
@ -126,7 +127,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Provider URL:
|
||||
{i18next.t("provider:Provider URL")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.providerUrl} onChange={e => {
|
||||
@ -176,7 +177,7 @@ class ProviderEditPage extends React.Component {
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large" onClick={this.submitProviderEdit.bind(this)}>Save</Button>
|
||||
<Button type="primary" size="large" onClick={this.submitProviderEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -19,6 +19,7 @@ import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Provider from "./auth/Provider";
|
||||
import i18next from "i18next";
|
||||
|
||||
class ProviderListPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -87,7 +88,7 @@ class ProviderListPage extends React.Component {
|
||||
renderTable(providers) {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '120px',
|
||||
@ -101,7 +102,7 @@ class ProviderListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Created Time',
|
||||
title: i18next.t("general:Created Time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
@ -111,14 +112,14 @@ class ProviderListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Display Name',
|
||||
title: i18next.t("general:Display Name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
title: i18next.t("provider:Type"),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: '80px',
|
||||
@ -130,7 +131,7 @@ class ProviderListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Client Id',
|
||||
title: i18next.t("provider:Client ID"),
|
||||
dataIndex: 'clientId',
|
||||
key: 'clientId',
|
||||
width: '150px',
|
||||
@ -144,7 +145,7 @@ class ProviderListPage extends React.Component {
|
||||
// sorter: (a, b) => a.clientSecret.localeCompare(b.clientSecret),
|
||||
// },
|
||||
{
|
||||
title: 'Provider Url',
|
||||
title: i18next.t("provider:Provider URL"),
|
||||
dataIndex: 'providerUrl',
|
||||
key: 'providerUrl',
|
||||
width: '150px',
|
||||
@ -160,19 +161,19 @@ class ProviderListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>Edit</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete provider: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteProvider(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -185,8 +186,8 @@ class ProviderListPage extends React.Component {
|
||||
<Table columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
Providers
|
||||
<Button type="primary" size="small" onClick={this.addProvider.bind(this)}>Add</Button>
|
||||
{i18next.t("general:Providers")}
|
||||
<Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={providers === null}
|
||||
|
38
web/src/SelectLanguageBox.js
Normal file
38
web/src/SelectLanguageBox.js
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
|
||||
class SelectLanguageBox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div align="center">
|
||||
<div class="box" style={{width: "600px"}}>
|
||||
<a href="javascript:void(0)" onClick={() => Setting.changeLanguage("en")} class="lang-selector">English</a>/
|
||||
<a href="javascript:void(0)" onClick={() => Setting.changeLanguage("zh")} class="lang-selector">简体中文</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectLanguageBox;
|
@ -15,6 +15,8 @@
|
||||
import {message} from "antd";
|
||||
import React from "react";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
import i18next from "i18next";
|
||||
|
||||
export let ServerUrl = "";
|
||||
|
||||
@ -135,3 +137,17 @@ export function getAvatarColor(s) {
|
||||
}
|
||||
return colorList[random % 4];
|
||||
}
|
||||
|
||||
export function setLanguage() {
|
||||
let language = localStorage.getItem('language');
|
||||
if (language === undefined) {
|
||||
language = "en"
|
||||
}
|
||||
i18next.changeLanguage(language)
|
||||
}
|
||||
|
||||
export function changeLanguage(language) {
|
||||
localStorage.setItem("language", language)
|
||||
i18next.changeLanguage(language)
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import * as UserBackend from "./backend/UserBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -77,13 +78,13 @@ class UserEditPage extends React.Component {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
Edit User
|
||||
<Button type="primary" onClick={this.submitUserEdit.bind(this)}>Save</Button>
|
||||
{i18next.t("user:Edit User")}
|
||||
<Button type="primary" onClick={this.submitUserEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</div>
|
||||
} style={{marginLeft: '5px'}} type="inner">
|
||||
<Row style={{marginTop: '10px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Organization:
|
||||
{i18next.t("general:Organization")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}>
|
||||
@ -103,7 +104,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Name:
|
||||
{i18next.t("general:Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.name} onChange={e => {
|
||||
@ -113,7 +114,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Display Name:
|
||||
{i18next.t("general:Display Name")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.displayName} onChange={e => {
|
||||
@ -123,7 +124,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Avatar:
|
||||
{i18next.t("general:Avatar")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
@ -138,7 +139,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={1}>
|
||||
Preview:
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col span={23} >
|
||||
<a target="_blank" href={this.state.user.avatar}>
|
||||
@ -150,7 +151,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Password Type:
|
||||
{i18next.t("general:Password Type")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.passwordType} onChange={e => {
|
||||
@ -160,7 +161,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Password:
|
||||
{i18next.t("general:Password")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.password} onChange={e => {
|
||||
@ -170,7 +171,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Email:
|
||||
{i18next.t("general:Email")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.email} onChange={e => {
|
||||
@ -180,7 +181,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Phone:
|
||||
{i18next.t("general:Phone")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.phone} onChange={e => {
|
||||
@ -190,7 +191,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Affiliation:
|
||||
{i18next.t("user:Affiliation")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.affiliation} onChange={e => {
|
||||
@ -200,7 +201,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Tag:
|
||||
{i18next.t("user:Tag")}:
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.tag} onChange={e => {
|
||||
@ -221,7 +222,7 @@ class UserEditPage extends React.Component {
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Is Admin:
|
||||
{i18next.t("user:Is Admin")}:
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.user.isAdmin} onChange={checked => {
|
||||
@ -231,7 +232,7 @@ class UserEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
Is Global Admin:
|
||||
{i18next.t("user:Is Global Admin")}:
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
|
||||
@ -289,7 +290,7 @@ class UserEditPage extends React.Component {
|
||||
<Col span={2}>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<Button type="primary" size="large" onClick={this.submitUserEdit.bind(this)}>Save</Button>
|
||||
<Button type="primary" size="large" onClick={this.submitUserEdit.bind(this)}>{i18next.t("general:Save")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ import {Button, Col, Popconfirm, Row, Switch, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class UserListPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -90,7 +91,7 @@ class UserListPage extends React.Component {
|
||||
renderTable(users) {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Organization',
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: 'owner',
|
||||
key: 'owner',
|
||||
width: '120px',
|
||||
@ -104,7 +105,7 @@ class UserListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '100px',
|
||||
@ -118,7 +119,7 @@ class UserListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Created Time',
|
||||
title: i18next.t("general:Created Time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
@ -142,14 +143,14 @@ class UserListPage extends React.Component {
|
||||
// sorter: (a, b) => a.password.localeCompare(b.password),
|
||||
// },
|
||||
{
|
||||
title: 'Display Name',
|
||||
title: i18next.t("general:Display Name"),
|
||||
dataIndex: 'displayName',
|
||||
key: 'displayName',
|
||||
// width: '100px',
|
||||
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
|
||||
},
|
||||
{
|
||||
title: 'Avatar',
|
||||
title: i18next.t("general:Avatar"),
|
||||
dataIndex: 'avatar',
|
||||
key: 'avatar',
|
||||
width: '100px',
|
||||
@ -162,7 +163,7 @@ class UserListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
title: i18next.t("general:Email"),
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
width: '160px',
|
||||
@ -183,21 +184,21 @@ class UserListPage extends React.Component {
|
||||
// sorter: (a, b) => a.phone.localeCompare(b.phone),
|
||||
// },
|
||||
{
|
||||
title: 'Affiliation',
|
||||
title: i18next.t("user:Affiliation"),
|
||||
dataIndex: 'affiliation',
|
||||
key: 'affiliation',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.affiliation.localeCompare(b.affiliation),
|
||||
},
|
||||
{
|
||||
title: 'Tag',
|
||||
title: i18next.t("user:Tag"),
|
||||
dataIndex: 'tag',
|
||||
key: 'tag',
|
||||
width: '100px',
|
||||
sorter: (a, b) => a.tag.localeCompare(b.tag),
|
||||
},
|
||||
{
|
||||
title: 'Is Admin',
|
||||
title: i18next.t("user:Is Admin"),
|
||||
dataIndex: 'isAdmin',
|
||||
key: 'isAdmin',
|
||||
width: '120px',
|
||||
@ -209,7 +210,7 @@ class UserListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Is Global Admin',
|
||||
title: i18next.t("user:Is Global Admin"),
|
||||
dataIndex: 'isGlobalAdmin',
|
||||
key: 'isGlobalAdmin',
|
||||
width: '120px',
|
||||
@ -221,19 +222,19 @@ class UserListPage extends React.Component {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>Edit</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete user: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteUser(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -246,8 +247,8 @@ class UserListPage extends React.Component {
|
||||
<Table columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
Users
|
||||
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>Add</Button>
|
||||
{i18next.t("general:Users")}
|
||||
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
|
@ -16,6 +16,7 @@ import React from "react";
|
||||
import {Card, Col, Row} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import SingleCard from "./SingleCard";
|
||||
import i18next from "i18next";
|
||||
|
||||
class HomePage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -29,10 +30,10 @@ class HomePage extends React.Component {
|
||||
let items = [];
|
||||
if (Setting.isAdminUser(this.props.account)) {
|
||||
items = [
|
||||
{link: "/organizations", name: "Organizations", organizer: "User containers"},
|
||||
{link: "/users", name: "Users", organizer: "Users under all organizations"},
|
||||
{link: "/providers", name: "Providers", organizer: "OAuth providers"},
|
||||
{link: "/applications", name: "Applications", organizer: "Applications that requires authentication"},
|
||||
{link: "/organizations", name: i18next.t("general:Organizations"), organizer: i18next.t("general:User containers")},
|
||||
{link: "/users", name: i18next.t("general:Users"), organizer: i18next.t("general:Users under all organizations")},
|
||||
{link: "/providers", name: i18next.t("general:Providers"), organizer: i18next.t("general:OAuth providers")},
|
||||
{link: "/applications", name: i18next.t("general:Applications"), organizer: i18next.t("general:Applications that requires authentication")},
|
||||
];
|
||||
}
|
||||
|
||||
|
39
web/src/i18n.js
Normal file
39
web/src/i18n.js
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 i18n from 'i18next'
|
||||
import zh from './locales/zh.json'
|
||||
import en from './locales/en.json'
|
||||
|
||||
const resources = {
|
||||
en: en,
|
||||
zh: zh
|
||||
};
|
||||
|
||||
i18n
|
||||
.init({
|
||||
lng: "en",
|
||||
|
||||
resources: resources,
|
||||
|
||||
keySeparator: false,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
|
||||
saveMissing: true,
|
||||
})
|
||||
|
||||
export default i18n;
|
64
web/src/locales/en.json
Normal file
64
web/src/locales/en.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"general":
|
||||
{
|
||||
"Home": "Home",
|
||||
"Organizations": "Organizations",
|
||||
"Providers": "Providers",
|
||||
"Users": "Users",
|
||||
"Applications": "Applications",
|
||||
"Save": "Save",
|
||||
"Add": "Add",
|
||||
"Action": "Action",
|
||||
"Edit": "Edit",
|
||||
"Delete": "Delete",
|
||||
"Organization": "Organization",
|
||||
"Created Time": "Created Time",
|
||||
"Name": "Name",
|
||||
"Display Name": "Display Name",
|
||||
"Avatar": "Avatar",
|
||||
"Preview": "Preview",
|
||||
"Password Type": "Password Type",
|
||||
"Password": "Password",
|
||||
"Email": "Email",
|
||||
"Phone": "Phone",
|
||||
"Logo": "Logo",
|
||||
"User containers": "User containers",
|
||||
"Users under all organizations": "Users under all organizations",
|
||||
"OAuth providers": "OAuth providers",
|
||||
"Applications that requires authentication": "Applications that requires authentication"
|
||||
},
|
||||
"account":
|
||||
{
|
||||
"My Account": "My Account",
|
||||
"Login": "Login",
|
||||
"Logout": "Logout",
|
||||
"Register": "Register"
|
||||
},
|
||||
"organization":
|
||||
{
|
||||
"Edit Organization": "Edit Organization",
|
||||
"Website URL": "Website URL"
|
||||
},
|
||||
"provider":
|
||||
{
|
||||
"Type": "Type",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"Provider URL": "Provider URL",
|
||||
"Edit Provider": "Edit Provider"
|
||||
},
|
||||
"user":
|
||||
{
|
||||
"Edit User": "Edit User",
|
||||
"Affiliation": "Affiliation",
|
||||
"Tag": "Tag",
|
||||
"Is Admin": "Is Admin",
|
||||
"Is Global Admin": "Is Global Admin"
|
||||
},
|
||||
"application":
|
||||
{
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable Password": "Enable Password",
|
||||
"Face Preview": "Face Preview"
|
||||
}
|
||||
}
|
66
web/src/locales/zh.json
Normal file
66
web/src/locales/zh.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"general":
|
||||
{
|
||||
"Home": "主页",
|
||||
"Organizations": "组织",
|
||||
"Providers": "第三方",
|
||||
"Users": "用户",
|
||||
"Applications": "应用",
|
||||
"Save": "保存",
|
||||
"Add": "添加",
|
||||
"Action": "行为",
|
||||
"Edit": "修改",
|
||||
"Delete": "删除",
|
||||
"Organization": "组织",
|
||||
"Created Time": "创建时间",
|
||||
"Name": "名字",
|
||||
"Display Name": "展示的名字",
|
||||
"Avatar": "头像",
|
||||
"Preview": "预览",
|
||||
"Password Type": "密码类型",
|
||||
"Password": "密码",
|
||||
"Email": "电子邮件",
|
||||
"Phone": "手机",
|
||||
"Logo": "Logo",
|
||||
"User containers": "用户容器",
|
||||
"Users under all organizations": "所有组织里的用户",
|
||||
"OAuth providers": "OAuth提供方",
|
||||
"Applications that requires authentication": "需要鉴权的应用"
|
||||
},
|
||||
"account":
|
||||
{
|
||||
"My Account": "我的账户",
|
||||
"Login": "登录",
|
||||
"Logout": "登出",
|
||||
"Register": "注册"
|
||||
},
|
||||
"organization":
|
||||
{
|
||||
"Edit Organization": "修改组织",
|
||||
"Website URL": "网页地址"
|
||||
},
|
||||
"provider":
|
||||
{
|
||||
"Name": "名字",
|
||||
"Display Name": "展示的名字",
|
||||
"Type": "类型",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"Provider URL": "第三方URL",
|
||||
"Edit Provider": "修改第三方"
|
||||
},
|
||||
"user":
|
||||
{
|
||||
"Edit User": "修改用户",
|
||||
"Affiliation": "联盟",
|
||||
"Tag": "标签",
|
||||
"Is Admin": "是管理员",
|
||||
"Is Global Admin": "是全局管理员"
|
||||
},
|
||||
"application":
|
||||
{
|
||||
"Edit Application": "修改应用",
|
||||
"Enable Password": "开启密码",
|
||||
"Face Preview": "图标预览"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user