1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-20 02:48:51 +00:00

org admin update name/logo (#5385)

This commit is contained in:
lian
2023-03-07 18:11:33 +08:00
committed by GitHub
parent 8d05178828
commit 0f8542e894
18 changed files with 615 additions and 43 deletions

View File

@@ -12,6 +12,7 @@ import OrgStatisticReport from './statistic/statistic-reports';
import OrgDesktopDevices from './devices/desktop-devices.js';
import OrgMobileDevices from './devices/mobile-devices.js';
import OrgDevicesErrors from './devices/devices-errors.js';
import WebSettings from './web-settings/web-settings';
import OrgUsers from './org-users-users';
import OrgUsersSearchUsers from './org-users-search-users';
import OrgAdmins from './org-users-admins';
@@ -93,6 +94,7 @@ class Org extends React.Component {
<OrgDesktopDevices path={siteRoot + 'org/deviceadmin/desktop-devices/'} />
<OrgMobileDevices path={siteRoot + 'org/deviceadmin/mobile-devices/'} />
<OrgDevicesErrors path={siteRoot + 'org/deviceadmin/devices-errors/'} />
<WebSettings path={siteRoot + 'org/web-settings'} />
<OrgUsers path={siteRoot + 'org/useradmin'} />
<OrgUsersSearchUsers path={siteRoot + 'org/useradmin/search-users'} />
<OrgAdmins path={siteRoot + 'org/useradmin/admins/'} />

View File

@@ -50,6 +50,12 @@ class SidePanel extends React.Component {
<span className="nav-text">{gettext('Devices')}</span>
</Link>
</li>
<li className="nav-item">
<Link className={`nav-link ellipsis ${this.getActiveClass('web-settings')}`} to={siteRoot + 'org/web-settings/'} onClick={() => this.tabItemClick('web-settings')} >
<span className="sf2-icon-cog2"></span>
<span className="nav-text">{gettext('Settings')}</span>
</Link>
</li>
<li className="nav-item">
<Link className={`nav-link ellipsis ${this.getActiveClass('repoadmin')}`} to={siteRoot + 'org/repoadmin/'} onClick={() => this.tabItemClick('repoadmin')} >
<span className="sf2-icon-library"></span>

View 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;

View 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;

View File

@@ -0,0 +1,79 @@
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,
disabled: PropTypes.bool.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, disabled } = this.props;
return (
<SettingItemBase
displayName={displayName}
helpTip={helpTip}
mainContent={
disabled ?
<Input type={inputType || 'text'} className={inputType == 'textarea' ? 'web-setting-textarea' : ''} value={value} disabled /> :
<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;

View 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;

View File

@@ -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 className="web-setting-label">{displayName}</Label>
</Col>
<Col md="5">
{mainContent}
{helpTip && <p className="small text-secondary mt-1">{helpTip}</p>}
</Col>
<Col md="4">
{extraContent}
</Col>
</Row>
</Fragment>
);
}
}
SettingItemBase.propTypes = propTypes;
export default SettingItemBase;

View File

@@ -0,0 +1,114 @@
import React, { Component, Fragment } from 'react';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, isPro, mediaUrl, logoPath, orgID, orgEnableAdminCustomLogo, orgEnableAdminCustomName } 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,
};
}
componentDidMount () {
seafileAPI.orgAdminGetOrgInfo().then((res) => {
this.setState({
loading: false,
config_dict: res.data
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
updateName= (key, newOrgName) => {
seafileAPI.orgAdminUpdateName(orgID, newOrgName).then((res) => {
this.setState({
config_dict: res.data
});
toaster.success(gettext('Success'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
updateLogo = (file) => {
seafileAPI.orgAdminUpdateLogo(orgID, file).then((res) => {
this.setState({
logoPath: mediaUrl + res.data.logo_path
});
toaster.success(gettext('Success'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg, config_dict, logoPath } = this.state;
return (
<Fragment>
<MainPanelTopbar {...this.props} />
<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 mw-100">
{loading && <Loading />}
{errorMsg && <p className="error text-center mt-4">{errorMsg}</p>}
{(!loading && !errorMsg) && config_dict &&
<Fragment>
<p className="small text-secondary my-4"></p>
<Section headingText={gettext('Info')}>
<Fragment>
<InputItem
saveSetting={this.updateName}
displayName={gettext('Team name')}
keyText='orgName'
value={config_dict['org_name']}
helpTip={''}
disabled={!orgEnableAdminCustomName}
/>
{ orgEnableAdminCustomLogo && <FileItem
postFile={this.updateLogo}
displayName='Logo'
keyText='Logo'
filePath={logoPath}
fileWidth={256}
fileHeight={64}
helpTip='logo.png, 256px * 64px'
/>
}
</Fragment>
</Section>
</Fragment>
}
</div>
</div>
</div>
</Fragment>
);
}
}
export default WebSettings;

View File

@@ -133,8 +133,11 @@ export const filePermission = window.draft ? window.draft.config.perm : '';
// org admin
export const orgID = window.org ? window.org.pageOptions.orgID : '';
export const orgName = window.org ? window.org.pageOptions.orgName : '';
export const invitationLink = window.org ? window.org.pageOptions.invitationLink : '';
export const orgMemberQuotaEnabled = window.org ? window.org.pageOptions.orgMemberQuotaEnabled : '';
export const orgEnableAdminCustomLogo = window.org ? window.org.pageOptions.orgEnableAdminCustomLogo === 'True' : false;
export const orgEnableAdminCustomName = window.org ? window.org.pageOptions.orgEnableAdminCustomName === 'True' : false;
// sys admin
export const constanceEnabled = window.sysadmin ? window.sysadmin.pageOptions.constance_enabled : '';