mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
Sysadmin reconstruct settings (#3946)
* sysadmin reconstruct settings page * [system admin] settings: refactored it * fixup & improvement
This commit is contained in:
14
frontend/src/css/system-admin-web-settings.css
Normal file
14
frontend/src/css/system-admin-web-settings.css
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.web-setting-icon-btn {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.web-setting-icon-btn-submit {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.web-setting-icon-btn-cancel {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.web-setting-textarea {
|
||||||
|
min-height: 7rem;
|
||||||
|
}
|
@@ -4,8 +4,7 @@ import { Router } from '@reach/router';
|
|||||||
import { siteRoot } from '../../utils/constants';
|
import { siteRoot } from '../../utils/constants';
|
||||||
import SidePanel from './side-panel';
|
import SidePanel from './side-panel';
|
||||||
import MainPanel from './main-panel';
|
import MainPanel from './main-panel';
|
||||||
import FileScanRecords from './file-scan-records';
|
|
||||||
import WorkWeixinDepartments from './work-weixin-departments';
|
|
||||||
import Info from './info';
|
import Info from './info';
|
||||||
|
|
||||||
import DesktopDevices from './devices/desktop-devices';
|
import DesktopDevices from './devices/desktop-devices';
|
||||||
@@ -17,6 +16,10 @@ import SystemRepo from './repos/system-repo';
|
|||||||
import TrashRepos from './repos/trash-repos';
|
import TrashRepos from './repos/trash-repos';
|
||||||
import DirView from './repos/dir-view';
|
import DirView from './repos/dir-view';
|
||||||
|
|
||||||
|
import WebSettings from './web-settings/web-settings';
|
||||||
|
import FileScanRecords from './file-scan-records';
|
||||||
|
import WorkWeixinDepartments from './work-weixin-departments';
|
||||||
|
|
||||||
import '../../assets/css/fa-solid.css';
|
import '../../assets/css/fa-solid.css';
|
||||||
import '../../assets/css/fa-regular.css';
|
import '../../assets/css/fa-regular.css';
|
||||||
import '../../assets/css/fontawesome.css';
|
import '../../assets/css/fontawesome.css';
|
||||||
@@ -28,7 +31,7 @@ class SysAdmin extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isSidePanelClosed: false,
|
isSidePanelClosed: false,
|
||||||
currentTab: 'file-scan',
|
currentTab: 'file-scan'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +98,14 @@ class SysAdmin extends React.Component {
|
|||||||
<SystemRepo path={siteRoot + 'sys/system-library'} />
|
<SystemRepo path={siteRoot + 'sys/system-library'} />
|
||||||
<TrashRepos path={siteRoot + 'sys/trash-libraries'} />
|
<TrashRepos path={siteRoot + 'sys/trash-libraries'} />
|
||||||
<DirView path={siteRoot + 'sys/libraries/:repoID/*'} />
|
<DirView path={siteRoot + 'sys/libraries/:repoID/*'} />
|
||||||
|
<WebSettings path={siteRoot + 'sys/web-settings'} />
|
||||||
<FileScanRecords
|
<FileScanRecords
|
||||||
path={siteRoot + 'sys/file-scan-records'}
|
path={siteRoot + 'sys/file-scan-records'}
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
tabItemClick={this.tabItemClick}
|
tabItemClick={this.tabItemClick}
|
||||||
/>
|
/>
|
||||||
<WorkWeixinDepartments
|
<WorkWeixinDepartments
|
||||||
path={siteRoot + 'sys/work-weixin/departments'}
|
path={siteRoot + 'sys/work-weixin'}
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
tabItemClick={this.tabItemClick}
|
tabItemClick={this.tabItemClick}
|
||||||
/>
|
/>
|
||||||
|
@@ -65,10 +65,14 @@ class SidePanel extends React.Component {
|
|||||||
}
|
}
|
||||||
{constanceEnabled && canConfigSystem &&
|
{constanceEnabled && canConfigSystem &&
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a className='nav-link ellipsis' href={siteRoot + 'sys/settings/'}>
|
<Link
|
||||||
|
className={`nav-link ellipsis ${this.getActiveClass('web-settings')}`}
|
||||||
|
to={siteRoot + 'sys/web-settings/'}
|
||||||
|
onClick={() => this.props.tabItemClick('web-settings')}
|
||||||
|
>
|
||||||
<span className="sf2-icon-cog2" aria-hidden="true"></span>
|
<span className="sf2-icon-cog2" aria-hidden="true"></span>
|
||||||
<span className="nav-text">{gettext('Settings')}</span>
|
<span className="nav-text">{gettext('Settings')}</span>
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
{canManageLibrary &&
|
{canManageLibrary &&
|
||||||
|
51
frontend/src/pages/sys-admin/web-settings/checkbox-item.js
Normal file
51
frontend/src/pages/sys-admin/web-settings/checkbox-item.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Input } from 'reactstrap';
|
||||||
|
import SettingItemBase from './setting-item-base';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
saveSetting: PropTypes.func.isRequired,
|
||||||
|
keyText: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
|
||||||
|
helpTip: PropTypes.string.isRequired,
|
||||||
|
displayName: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class WebSettingCheckbox extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
inputChecked: this.props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onInputChange = (e) => {
|
||||||
|
const checked = e.target.checked;
|
||||||
|
const valueToNum = checked ? 1 : 0;
|
||||||
|
this.setState({
|
||||||
|
inputChecked: checked
|
||||||
|
});
|
||||||
|
this.props.saveSetting(this.props.keyText, valueToNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { inputChecked } = this.state;
|
||||||
|
const { helpTip, displayName } = this.props;
|
||||||
|
return (
|
||||||
|
<SettingItemBase
|
||||||
|
displayName={displayName}
|
||||||
|
mainContent={
|
||||||
|
<Fragment>
|
||||||
|
<Input className="ml-0" checked={inputChecked} type='checkbox' onChange={this.onInputChange} />
|
||||||
|
<p className="ml-4">{helpTip}</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettingCheckbox.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default WebSettingCheckbox;
|
58
frontend/src/pages/sys-admin/web-settings/file-item.js
Normal file
58
frontend/src/pages/sys-admin/web-settings/file-item.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import SettingItemBase from './setting-item-base';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
postFile: PropTypes.func.isRequired,
|
||||||
|
keyText: PropTypes.string.isRequired,
|
||||||
|
filePath: PropTypes.string.isRequired,
|
||||||
|
helpTip: PropTypes.string.isRequired,
|
||||||
|
fileWidth: PropTypes.number.isRequired,
|
||||||
|
fileHeight: PropTypes.number.isRequired,
|
||||||
|
displayName: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class WebSettingFile extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.fileInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadFile = () => {
|
||||||
|
if (!this.fileInput.current.files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = this.fileInput.current.files[0];
|
||||||
|
this.props.postFile(file, this.props.keyText);
|
||||||
|
}
|
||||||
|
|
||||||
|
openFileInput = () => {
|
||||||
|
this.fileInput.current.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { helpTip, filePath, fileWidth, fileHeight, displayName } = this.props;
|
||||||
|
return (
|
||||||
|
<SettingItemBase
|
||||||
|
displayName={displayName}
|
||||||
|
helpTip={helpTip}
|
||||||
|
mainContent={
|
||||||
|
<img src={filePath + '?t=' + new Date().getTime()} alt={displayName} width={fileWidth} height={fileHeight} className="mb-1" />
|
||||||
|
}
|
||||||
|
extraContent={
|
||||||
|
<Fragment>
|
||||||
|
<Button color="secondary" onClick={this.openFileInput}>{gettext('Change')}</Button>
|
||||||
|
<input className="d-none" type="file" onChange={this.uploadFile} ref={this.fileInput} />
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettingFile.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default WebSettingFile;
|
76
frontend/src/pages/sys-admin/web-settings/input-item.js
Normal file
76
frontend/src/pages/sys-admin/web-settings/input-item.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Input, Button } from 'reactstrap';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import SettingItemBase from './setting-item-base';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
inputType: PropTypes.string,
|
||||||
|
saveSetting: PropTypes.func.isRequired,
|
||||||
|
keyText: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.string,PropTypes.number]),
|
||||||
|
helpTip: PropTypes.string.isRequired,
|
||||||
|
displayName: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class WebSettingInput extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isBtnsShown: false,
|
||||||
|
value: this.props.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBtns = () => {
|
||||||
|
this.setState({isBtnsShown: !this.state.isBtnsShown});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideBtns = (e) => {
|
||||||
|
if (!this.state.isBtnsShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.props.value != this.state.value) {
|
||||||
|
this.setState({value: this.props.value});
|
||||||
|
}
|
||||||
|
this.toggleBtns();
|
||||||
|
}
|
||||||
|
|
||||||
|
onInputChange = (e) => {
|
||||||
|
this.setState({ value: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = (e) => {
|
||||||
|
const value = this.state.value.trim();
|
||||||
|
if (value != this.props.value) {
|
||||||
|
this.props.saveSetting(this.props.keyText, value);
|
||||||
|
}
|
||||||
|
this.toggleBtns();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isBtnsShown, value } = this.state;
|
||||||
|
const { helpTip, displayName, inputType } = this.props;
|
||||||
|
return (
|
||||||
|
<SettingItemBase
|
||||||
|
displayName={displayName}
|
||||||
|
helpTip={helpTip}
|
||||||
|
mainContent={
|
||||||
|
<Input type={inputType || 'text'} className={inputType == 'textarea' ? 'web-setting-textarea' : ''} onChange={this.onInputChange} onFocus={this.toggleBtns} onBlur={this.hideBtns} value={value} />
|
||||||
|
}
|
||||||
|
extraContent={
|
||||||
|
isBtnsShown ?
|
||||||
|
<Fragment>
|
||||||
|
<Button className="sf2-icon-tick web-setting-icon-btn web-setting-icon-btn-submit" onMouseDown={this.onSubmit} title={gettext('Submit')}></Button>
|
||||||
|
<Button className="ml-1 sf2-icon-x2 web-setting-icon-btn web-setting-icon-btn-cancel" title={gettext('Cancel')}></Button>
|
||||||
|
</Fragment> : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSettingInput.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default WebSettingInput;
|
28
frontend/src/pages/sys-admin/web-settings/section.js
Normal file
28
frontend/src/pages/sys-admin/web-settings/section.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
headingText: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class Section extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { headingText, children} = this.props;
|
||||||
|
return (
|
||||||
|
<div className="mb-4">
|
||||||
|
<h4 className="border-bottom font-weight-normal mb-2 pb-1">{headingText}</h4>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default Section;
|
@@ -0,0 +1,41 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Row, Col, Label } from 'reactstrap';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
displayName: PropTypes.string.isRequired,
|
||||||
|
helpTip: PropTypes.string,
|
||||||
|
mainContent: PropTypes.object.isRequired,
|
||||||
|
extraContent: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
class SettingItemBase extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { helpTip, displayName, mainContent, extraContent } = this.props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Row className="my-4">
|
||||||
|
<Col md="3">
|
||||||
|
<Label>{displayName}</Label>
|
||||||
|
</Col>
|
||||||
|
<Col md="5">
|
||||||
|
{mainContent}
|
||||||
|
{helpTip && <p className="small text-secondary">{helpTip}</p>}
|
||||||
|
</Col>
|
||||||
|
<Col md="4">
|
||||||
|
{extraContent}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingItemBase.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SettingItemBase;
|
381
frontend/src/pages/sys-admin/web-settings/web-settings.js
Normal file
381
frontend/src/pages/sys-admin/web-settings/web-settings.js
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { loginUrl, gettext, mediaUrl, logoPath, faviconPath, loginBGPath } from '../../../utils/constants';
|
||||||
|
import Loading from '../../../components/loading';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import MainPanelTopbar from '../main-panel-topbar';
|
||||||
|
import Section from './section';
|
||||||
|
import InputItem from './input-item';
|
||||||
|
import FileItem from './file-item';
|
||||||
|
import CheckboxItem from './checkbox-item';
|
||||||
|
|
||||||
|
import '../../../css/system-admin-web-settings.css';
|
||||||
|
|
||||||
|
class WebSettings extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
errorMsg: '',
|
||||||
|
config_dict: null,
|
||||||
|
logoPath: mediaUrl + logoPath,
|
||||||
|
faviconPath: mediaUrl + faviconPath,
|
||||||
|
loginBGPath: mediaUrl + loginBGPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
seafileAPI.sysAdminGetSysSettingInfo().then((res) => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
config_dict: res.data
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Permission denied')
|
||||||
|
});
|
||||||
|
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Error')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorMsg: gettext('Please check the network.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSetting = (key, value) => {
|
||||||
|
seafileAPI.sysAdminSetSysSettingInfo(key, value).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
config_dict: res.data
|
||||||
|
});
|
||||||
|
toaster.success(gettext('Success'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
postFile = (file, fileType) => {
|
||||||
|
let postFile;
|
||||||
|
if (fileType == 'Logo') {
|
||||||
|
postFile = seafileAPI.sysAdminUpdateLogo(file);
|
||||||
|
} else if (fileType == 'Favicon') {
|
||||||
|
postFile = seafileAPI.sysAdminUpdateFavicon(file);
|
||||||
|
} else if (fileType == 'loginBGImage') {
|
||||||
|
postFile = seafileAPI.sysAdminUpdateLoginBG(file);
|
||||||
|
}
|
||||||
|
postFile.then((res) => {
|
||||||
|
if (fileType == 'Logo') {
|
||||||
|
this.setState({
|
||||||
|
logoPath: res.data.logo_path
|
||||||
|
});
|
||||||
|
} else if (fileType == 'Favicon') {
|
||||||
|
this.setState({
|
||||||
|
faviconPath: res.data.favicon_path
|
||||||
|
});
|
||||||
|
} else if (fileType == 'loginBGImage') {
|
||||||
|
this.setState({
|
||||||
|
loginBGPath: res.data.login_bg_image_path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toaster.success(gettext('Success'));
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, config_dict, logoPath, faviconPath, loginBGPath } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar />
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<div className="cur-view-path">
|
||||||
|
<h3 className="sf-heading">{gettext('Settings')}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="cur-view-content container">
|
||||||
|
{loading && <Loading />}
|
||||||
|
{errorMsg && <p className="error text-center mt-4">{errorMsg}</p>}
|
||||||
|
{(!loading && !errorMsg) && config_dict &&
|
||||||
|
<Fragment>
|
||||||
|
<p className="small text-secondary my-4">{gettext('Note: Settings via web interface are saved in database table (seahub-db/constance_config). They have a higher priority over the settings in config files.')}</p>
|
||||||
|
|
||||||
|
<Section headingText='URL'>
|
||||||
|
<Fragment>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='SERVICE_URL'
|
||||||
|
keyText='SERVICE_URL'
|
||||||
|
value={config_dict['SERVICE_URL']}
|
||||||
|
helpTip={gettext('The URL of the server, like https://seafile.example.com or http://192.168.1.2:8000')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='FILE_SERVER_ROOT'
|
||||||
|
keyText='FILE_SERVER_ROOT'
|
||||||
|
value={config_dict['FILE_SERVER_ROOT']}
|
||||||
|
helpTip={gettext('The internal URL for downloading/uploading files. Users will not be able to download/upload files if this is not set correctly. If you config Seafile behind Nginx/Apache, it should be SERVICE_URL/seafhttp, like https://seafile.example.com/seafhttp .')}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Branding')}>
|
||||||
|
<Fragment>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='SITE_TITLE'
|
||||||
|
keyText='SITE_TITLE'
|
||||||
|
value={config_dict['SITE_TITLE']}
|
||||||
|
helpTip={gettext('Site title shown in a browser tab')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='SITE_NAME'
|
||||||
|
keyText='SITE_NAME'
|
||||||
|
value={config_dict['SITE_NAME']}
|
||||||
|
helpTip={gettext('Site name used in email sending')}
|
||||||
|
/>
|
||||||
|
<FileItem
|
||||||
|
postFile={this.postFile}
|
||||||
|
displayName='Logo'
|
||||||
|
keyText='Logo'
|
||||||
|
filePath={logoPath}
|
||||||
|
fileWidth={256}
|
||||||
|
fileHeight={64}
|
||||||
|
helpTip='logo.png, 256px * 64px'
|
||||||
|
/>
|
||||||
|
<FileItem
|
||||||
|
postFile={this.postFile}
|
||||||
|
displayName='Favicon'
|
||||||
|
keyText='Favicon'
|
||||||
|
filePath={faviconPath}
|
||||||
|
fileWidth={32}
|
||||||
|
fileHeight={32}
|
||||||
|
helpTip='favicon.ico, 32px * 32px'
|
||||||
|
/>
|
||||||
|
<FileItem
|
||||||
|
postFile={this.postFile}
|
||||||
|
displayName={gettext('Login Background Image')}
|
||||||
|
keyText='loginBGImage'
|
||||||
|
filePath={loginBGPath}
|
||||||
|
fileWidth={240}
|
||||||
|
fileHeight={160}
|
||||||
|
helpTip='login-bg.jpg, 2400px * 1600px'
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='ENABLE_BRANDING_CSS'
|
||||||
|
keyText='ENABLE_BRANDING_CSS'
|
||||||
|
value={config_dict['ENABLE_BRANDING_CSS']}
|
||||||
|
helpTip={gettext('Use custom CSS')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
inputType="textarea"
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('Custom CSS')}
|
||||||
|
keyText='CUSTOM_CSS'
|
||||||
|
value={config_dict['CUSTOM_CSS']}
|
||||||
|
helpTip=''
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('User')}>
|
||||||
|
<Fragment>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('allow new registrations')}
|
||||||
|
keyText='ENABLE_SIGNUP'
|
||||||
|
value={config_dict['ENABLE_SIGNUP']}
|
||||||
|
helpTip={gettext('Allow new user registrations. Uncheck this to prevent anyone from creating a new account.')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('activate after registration')}
|
||||||
|
keyText='ACTIVATE_AFTER_REGISTRATION'
|
||||||
|
value={config_dict['ACTIVATE_AFTER_REGISTRATION']}
|
||||||
|
helpTip={gettext('Activate user immediately after registration. If unchecked, a user need to be activated by administrator or via activation email')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('send activation email')}
|
||||||
|
keyText='REGISTRATION_SEND_MAIL'
|
||||||
|
value={config_dict['REGISTRATION_SEND_MAIL']}
|
||||||
|
helpTip={gettext('Send activation Email after user registration.')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('keep sign in')}
|
||||||
|
keyText='LOGIN_REMEMBER_DAYS'
|
||||||
|
value={config_dict['LOGIN_REMEMBER_DAYS']}
|
||||||
|
helpTip={gettext('Number of days that keep user sign in.')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='LOGIN_ATTEMPT_LIMIT'
|
||||||
|
keyText='LOGIN_ATTEMPT_LIMIT'
|
||||||
|
value={config_dict['LOGIN_ATTEMPT_LIMIT']}
|
||||||
|
helpTip={gettext('The maximum number of failed login attempts before showing CAPTCHA.')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='FREEZE_USER_ON_LOGIN_FAILED'
|
||||||
|
keyText='FREEZE_USER_ON_LOGIN_FAILED'
|
||||||
|
value={config_dict['FREEZE_USER_ON_LOGIN_FAILED']}
|
||||||
|
helpTip={gettext('Freeze user account when failed login attempts exceed limit.')}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Groups')}>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='ENABLE_SHARE_TO_ALL_GROUPS'
|
||||||
|
keyText='ENABLE_SHARE_TO_ALL_GROUPS'
|
||||||
|
value={config_dict['ENABLE_SHARE_TO_ALL_GROUPS']}
|
||||||
|
helpTip={gettext('Enable users to share libraries to any groups in the system.')}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Password')}>
|
||||||
|
<Fragment>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='strong password'
|
||||||
|
keyText='USER_STRONG_PASSWORD_REQUIRED'
|
||||||
|
value={config_dict['USER_STRONG_PASSWORD_REQUIRED']}
|
||||||
|
helpTip={gettext('Force user to use a strong password when sign up or change password.')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='force password change'
|
||||||
|
keyText='FORCE_PASSWORD_CHANGE'
|
||||||
|
value={config_dict['FORCE_PASSWORD_CHANGE']}
|
||||||
|
helpTip={gettext('Force user to change password when account is newly added or reset by admin')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('password minimum length')}
|
||||||
|
keyText='USER_PASSWORD_MIN_LENGTH'
|
||||||
|
value={config_dict['USER_PASSWORD_MIN_LENGTH']}
|
||||||
|
helpTip={gettext('The least number of characters an account password should include.')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('password strength level')}
|
||||||
|
keyText='USER_PASSWORD_STRENGTH_LEVEL'
|
||||||
|
value={config_dict['USER_PASSWORD_STRENGTH_LEVEL']}
|
||||||
|
helpTip={gettext('The level(1-4) of an account password\'s strength. For example, \'3\' means password must have at least 3 of the following: num, upper letter, lower letter and other symbols')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='enable two factor authentication'
|
||||||
|
keyText='ENABLE_TWO_FACTOR_AUTH'
|
||||||
|
value={config_dict['ENABLE_TWO_FACTOR_AUTH']}
|
||||||
|
helpTip={gettext('Enable two factor authentication')}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Library')}>
|
||||||
|
<Fragment>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='library history'
|
||||||
|
keyText='ENABLE_REPO_HISTORY_SETTING'
|
||||||
|
value={config_dict['ENABLE_REPO_HISTORY_SETTING']}
|
||||||
|
helpTip={gettext('Allow user to change library history settings')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='encrypted library'
|
||||||
|
keyText='ENABLE_ENCRYPTED_LIBRARY'
|
||||||
|
value={config_dict['ENABLE_ENCRYPTED_LIBRARY']}
|
||||||
|
helpTip={gettext('Allow user to create encrypted libraries')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('library password minimum length')}
|
||||||
|
keyText='REPO_PASSWORD_MIN_LENGTH'
|
||||||
|
value={config_dict['REPO_PASSWORD_MIN_LENGTH']}
|
||||||
|
helpTip={gettext('The least number of characters an encrypted library password should include.')}
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('download/upload link password minimum length')}
|
||||||
|
keyText='SHARE_LINK_PASSWORD_MIN_LENGTH'
|
||||||
|
value={config_dict['SHARE_LINK_PASSWORD_MIN_LENGTH']}
|
||||||
|
helpTip={gettext('The least number of characters a download/upload link password should include.')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='ENABLE_USER_CREATE_ORG_REPO'
|
||||||
|
keyText='ENABLE_USER_CREATE_ORG_REPO'
|
||||||
|
value={config_dict['ENABLE_USER_CREATE_ORG_REPO']}
|
||||||
|
helpTip={gettext('Allow user to add organization libraries. Otherwise, only system admin can add organization libraries.')}
|
||||||
|
/>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='ENABLE_USER_CLEAN_TRASH'
|
||||||
|
keyText='ENABLE_USER_CLEAN_TRASH'
|
||||||
|
value={config_dict['ENABLE_USER_CLEAN_TRASH']}
|
||||||
|
helpTip={gettext('Allow user to clean library trash')}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Online Preview')}>
|
||||||
|
<InputItem
|
||||||
|
inputType="textarea"
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName={gettext('text file extensions')}
|
||||||
|
keyText='TEXT_PREVIEW_EXT'
|
||||||
|
value={config_dict['TEXT_PREVIEW_EXT']}
|
||||||
|
helpTip={gettext('Extensions of text files that can be online previewed, each suffix is separated by a comma.')}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Sync')}>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='DISABLE_SYNC_WITH_ANY_FOLDER'
|
||||||
|
keyText='DISABLE_SYNC_WITH_ANY_FOLDER'
|
||||||
|
value={config_dict['DISABLE_SYNC_WITH_ANY_FOLDER']}
|
||||||
|
helpTip={gettext('If turn on, the desktop clients will not be able to sync a folder outside the default Seafile folder.')}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section headingText={gettext('Terms')}>
|
||||||
|
<CheckboxItem
|
||||||
|
saveSetting={this.saveSetting}
|
||||||
|
displayName='ENABLE_TERMS_AND_CONDITIONS'
|
||||||
|
keyText='ENABLE_TERMS_AND_CONDITIONS'
|
||||||
|
value={config_dict['ENABLE_TERMS_AND_CONDITIONS']}
|
||||||
|
helpTip={gettext('Enable system admin to add Terms and Conditions, and all users will have to accept the terms.')}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebSettings;
|
@@ -17,6 +17,8 @@ export const fileServerRoot = window.app.config.fileServerRoot;
|
|||||||
export const seafileVersion = window.app.config.seafileVersion;
|
export const seafileVersion = window.app.config.seafileVersion;
|
||||||
export const serviceURL = window.app.config.serviceURL;
|
export const serviceURL = window.app.config.serviceURL;
|
||||||
export const appAvatarURL = window.app.config.avatarURL;
|
export const appAvatarURL = window.app.config.avatarURL;
|
||||||
|
export const faviconPath = window.app.config.faviconPath;
|
||||||
|
export const loginBGPath = window.app.config.loginBGPath;
|
||||||
|
|
||||||
//pageOptions
|
//pageOptions
|
||||||
export const seafileCollabServer = window.app.pageOptions.seafileCollabServer;
|
export const seafileCollabServer = window.app.pageOptions.seafileCollabServer;
|
||||||
|
@@ -65,6 +65,7 @@
|
|||||||
.sf2-icon-organization:before { content:"\e010"; }
|
.sf2-icon-organization:before { content:"\e010"; }
|
||||||
.sf2-icon-share:before { content:"\e011"; }
|
.sf2-icon-share:before { content:"\e011"; }
|
||||||
.sf2-icon-star:before { content:"\e012"; }
|
.sf2-icon-star:before { content:"\e012"; }
|
||||||
|
.sf2-icon-wiki:before { content:"\e013"; }
|
||||||
.sf2-icon-wiki-view:before { content:"\e013"; }
|
.sf2-icon-wiki-view:before { content:"\e013"; }
|
||||||
.sf2-icon-history:before { content:"\e014"; }
|
.sf2-icon-history:before { content:"\e014"; }
|
||||||
.sf2-icon-cog1:before { content:"\e015"; }
|
.sf2-icon-cog1:before { content:"\e015"; }
|
||||||
|
@@ -33,6 +33,8 @@
|
|||||||
logoPath: '{{ logo_path }}',
|
logoPath: '{{ logo_path }}',
|
||||||
logoWidth: '{{ logo_width }}',
|
logoWidth: '{{ logo_width }}',
|
||||||
logoHeight: '{{ logo_height }}',
|
logoHeight: '{{ logo_height }}',
|
||||||
|
faviconPath: '{{ favicon_path }}',
|
||||||
|
loginBGPath: '{{ login_bg_path }}',
|
||||||
siteTitle: '{{ site_title }}',
|
siteTitle: '{{ site_title }}',
|
||||||
siteName: '{{ site_name }}',
|
siteName: '{{ site_name }}',
|
||||||
siteRoot: '{{ SITE_ROOT }}',
|
siteRoot: '{{ SITE_ROOT }}',
|
||||||
|
@@ -672,6 +672,7 @@ urlpatterns = [
|
|||||||
url(r'^sys/libraries/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', sysadmin_react_fake_view, name="sys_libraries_template_dirent"),
|
url(r'^sys/libraries/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', sysadmin_react_fake_view, name="sys_libraries_template_dirent"),
|
||||||
|
|
||||||
url(r'^sys/work-weixin/$', sysadmin_react_fake_view, name="sys_work_weixin"),
|
url(r'^sys/work-weixin/$', sysadmin_react_fake_view, name="sys_work_weixin"),
|
||||||
|
url(r'^sys/work-weixin/departments/$', sysadmin_react_fake_view, name="sys_work_weixin_departments"),
|
||||||
|
|
||||||
url(r'^client-login/$', client_token_login, name='client_token_login'),
|
url(r'^client-login/$', client_token_login, name='client_token_login'),
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user