diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-sys-notification-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-sys-notification-dialog.js new file mode 100644 index 0000000000..066f8931f0 --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-add-sys-notification-dialog.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input } from 'reactstrap'; +import { gettext } from '../../../utils/constants'; + +const propTypes = { + toggle: PropTypes.func.isRequired, + addNotification: PropTypes.func.isRequired +}; + +class SysAdminAddSysNotificationDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: '', + isSubmitBtnActive: false + }; + } + + handleChange = (e) => { + const value = e.target.value; + if (!value.trim()) { + this.setState({isSubmitBtnActive: false}); + } else { + this.setState({isSubmitBtnActive: true}); + } + + this.setState({value: value}); + } + + handleSubmit = () => { + this.toggle(); + this.props.addNotification(this.state.value.trim()); + } + + toggle = () => { + this.props.toggle(); + } + + render() { + return ( + + {gettext('Add new notification')} + +
+ + + +
+
+ + + + +
+ ); + } +} + +SysAdminAddSysNotificationDialog.propTypes = propTypes; + +export default SysAdminAddSysNotificationDialog; diff --git a/frontend/src/pages/sys-admin/index.js b/frontend/src/pages/sys-admin/index.js index 1332d01c34..e0968faeab 100644 --- a/frontend/src/pages/sys-admin/index.js +++ b/frontend/src/pages/sys-admin/index.js @@ -17,6 +17,7 @@ import TrashRepos from './repos/trash-repos'; import DirView from './repos/dir-view'; import WebSettings from './web-settings/web-settings'; +import Notifications from './notifications/notifications'; import FileScanRecords from './file-scan-records'; import WorkWeixinDepartments from './work-weixin-departments'; @@ -99,6 +100,7 @@ class SysAdmin extends React.Component { + { + this.setState({isItemFreezed: true}); + } + + onUnfreezedItem = () => { + this.setState({isItemFreezed: false}); + } + + render() { + const { loading, errorMsg, items } = this.props; + if (loading) { + return ; + } else if (errorMsg) { + return

{errorMsg}

; + } else { + const emptyTip = ( + +

{gettext('No notifications')}

+
+ ); + const table = ( + + + + + + + + + {items.map((item, index) => { + return (); + })} + +
{gettext('Notification Detail')}{/*Operations*/}
+ ); + return items.length ? table : emptyTip; + } + } +} + +class Item extends Component { + + constructor(props) { + super(props); + this.state = { + isOpIconShown: false, + highlight: false, + isDeleteDialogOpen: false + }; + } + + handleMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: true, + highlight: true + }); + } + } + + handleMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + isOpIconShown: false, + highlight: false + }); + } + } + + onUnfreezedItem = () => { + this.setState({ + highlight: false, + isOpIconShow: false + }); + this.props.onUnfreezedItem(); + } + + toggleDeleteDialog = (e) => { + if (e) { + e.preventDefault(); + } + this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen}); + } + + deleteNotification = () => { + this.props.deleteNotification(this.props.item.id); + this.toggleDeleteDialog(); + } + + setToCurrent = () => { + this.props.setToCurrent(this.props.item.id); + } + + onMenuItemClick = (operation) => { + switch(operation) { + case 'Set to current': + this.setToCurrent(); + break; + case 'Delete': + this.toggleDeleteDialog(); + break; + } + } + + render() { + const { item } = this.props; + const { isOpIconShown, isDeleteDialogOpen } = this.state; + + return ( + + + + {item.msg} + {item.is_current && + {gettext('(current notification)')} + } + + + {isOpIconShown && + + } + + + {isDeleteDialogOpen && + + } + + ); + } +} + +class Notifications extends Component { + + constructor(props) { + super(props); + this.state = { + loading: true, + errorMsg: '', + notificationList: [], + isAddNotificationDialogOpen: false + }; + } + + componentDidMount () { + seafileAPI.sysAdminListAllSysNotifications().then((res) => { + this.setState({ + loading: false, + notificationList: res.data.notifications + }); + }).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.') + }); + } + }); + } + + toggleAddNotificationDialog = () => { + this.setState({isAddNotificationDialogOpen: !this.state.isAddNotificationDialogOpen}); + } + + addNotification = (msg) => { + seafileAPI.sysAdminAddSysNotification(msg).then(res => { + let notificationList = this.state.notificationList; + notificationList.unshift(res.data.notification); + this.setState({notificationList: notificationList}); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + deleteNotification = (id) => { + seafileAPI.sysAdminDeleteSysNotification(id).then(res => { + let notificationList = this.state.notificationList.filter(item => { + return item.id != id; + }); + this.setState({notificationList: notificationList}); + toaster.success(gettext('Successfully deleted 1 item.')); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + setToCurrent = (id) => { + seafileAPI.sysAdminSetSysNotificationToCurrent(id).then(res => { + let notificationList = this.state.notificationList.map(item => { + if (item.id == id) { + item.is_current = true; + } else { + item.is_current = false; + } + return item; + }); + this.setState({notificationList: notificationList}); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + render() { + const { isAddNotificationDialogOpen } = this.state; + return ( + + + + +
+
+
+

{gettext('All Notifications')}

+
+
+ +
+
+
+ {isAddNotificationDialogOpen && + + } +
+ ); + } +} + +export default Notifications; diff --git a/frontend/src/pages/sys-admin/notifications/op-menu.js b/frontend/src/pages/sys-admin/notifications/op-menu.js new file mode 100644 index 0000000000..557f073bea --- /dev/null +++ b/frontend/src/pages/sys-admin/notifications/op-menu.js @@ -0,0 +1,90 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; +import { gettext } from '../../../utils/constants'; +import { Utils } from '../../../utils/utils'; + +const propTypes = { + item: PropTypes.object.isRequired, + onFreezedItem: PropTypes.func.isRequired, + onUnfreezedItem: PropTypes.func.isRequired, + onMenuItemClick: PropTypes.func.isRequired, +}; + +class OpMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemMenuShow: false + }; + } + + onMenuItemClick = (e) => { + let operation = Utils.getEventData(e, 'op'); + this.props.onMenuItemClick(operation); + } + + onDropdownToggleClick = (e) => { + this.toggleOperationMenu(e); + } + + toggleOperationMenu = (e) => { + this.setState( + {isItemMenuShow: !this.state.isItemMenuShow}, + () => { + if (this.state.isItemMenuShow) { + this.props.onFreezedItem(); + } else { + this.props.onUnfreezedItem(); + } + } + ); + } + + translateOperations = (item) => { + let translateResult = ''; + switch(item) { + case 'Set to current': + translateResult = gettext('Set to current'); + break; + case 'Delete': + translateResult = gettext('Delete'); + break; + default: + break; + } + + return translateResult; + } + + render() { + const { item } = this.props; + let operations = []; + if (!item.is_current) { + operations.push('Set to current'); + } + operations.push('Delete'); + + return ( + + + + {operations.map((item, index )=> { + return ({this.translateOperations(item)}); + })} + + + ); + } +} + +OpMenu.propTypes = propTypes; + +export default OpMenu; diff --git a/frontend/src/pages/sys-admin/side-panel.js b/frontend/src/pages/sys-admin/side-panel.js index 015193638e..3646da7d31 100644 --- a/frontend/src/pages/sys-admin/side-panel.js +++ b/frontend/src/pages/sys-admin/side-panel.js @@ -129,10 +129,14 @@ class SidePanel extends React.Component { } {isDefaultAdmin &&
  • - + this.props.tabItemClick('notifications')} + > {gettext('Notifications')} - +
  • } {isDefaultAdmin && diff --git a/seahub/urls.py b/seahub/urls.py index bf4b4a7c18..9ddc354acf 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -669,6 +669,7 @@ urlpatterns = [ url(r'^sys/desktop-devices/$', sysadmin_react_fake_view, name="sys_desktop_devices"), url(r'^sys/mobile-devices/$', sysadmin_react_fake_view, name="sys_mobile_devices"), url(r'^sys/device-errors/$', sysadmin_react_fake_view, name="sys_device_errors"), + url(r'^sys/notifications/$', sysadmin_react_fake_view, name="sys_notifications"), url(r'^sys/web-settings/$', sysadmin_react_fake_view, name="sys_web_settings"), url(r'^sys/all-libraries/$', sysadmin_react_fake_view, name="sys_all_libraries"), url(r'^sys/system-library/$', sysadmin_react_fake_view, name="sys_system_library"),