diff --git a/frontend/src/components/common/account.js b/frontend/src/components/common/account.js index 065bf1ee63..cf90c67752 100644 --- a/frontend/src/components/common/account.js +++ b/frontend/src/components/common/account.js @@ -77,6 +77,7 @@ class Account extends Component { quotaUsage: Utils.bytesToSize(resp.data.usage), quotaTotal: Utils.bytesToSize(resp.data.total), isStaff: resp.data.is_staff, + isInstAdmin: resp.data.is_inst_admin, isOrgStaff: resp.data.is_org_staff === 1 ? true : false, showInfo: !this.state.showInfo, }); @@ -91,29 +92,47 @@ class Account extends Component { } renderMenu = () => { - if (this.state.isStaff && this.props.isAdminPanel) { - return ( - {gettext('Exit Admin Panel')} - ); + let data; + const { isStaff, isOrgStaff, isInstAdmin } = this.state; + + if (this.props.isAdminPanel) { + if (isStaff) { + data = { + url: siteRoot, + text: gettext('Exit System Admin') + }; + } else if (isOrgStaff) { + data = { + url: siteRoot, + text: gettext('Exit Organization Admin') + }; + } else if (isInstAdmin) { + data = { + url: siteRoot, + text: gettext('Exit Institution Admin') + }; + } + + } else { + if (isStaff) { + data = { + url: `${siteRoot}sys/useradmin/`, + text: gettext('System Admin') + }; + } else if (isOrgStaff) { + data = { + url: `${siteRoot}org/useradmin/`, + text: gettext('Organization Admin') + }; + } else if (isInstAdmin) { + data = { + url: `${siteRoot}inst/useradmin/`, + text: gettext('Institution Admin') + }; + } } - if (this.state.isOrgStaff && this.props.isAdminPanel) { - return ( - {gettext('Exit Organization Admin')} - ); - } - - if (this.state.isStaff) { - return ( - {gettext('System Admin')} - ); - } - - if (this.state.isOrgStaff) { - return ( - {gettext('Organization Admin')} - ); - } + return data && {data.text}; } renderAvatar = () => { diff --git a/frontend/src/components/main-side-nav.js b/frontend/src/components/main-side-nav.js index 4dfea55b9d..a080465f0a 100644 --- a/frontend/src/components/main-side-nav.js +++ b/frontend/src/components/main-side-nav.js @@ -8,7 +8,7 @@ import { Utils } from '../utils/utils'; import toaster from './toast'; import Group from '../models/group'; -import { canViewOrg, isDocs, isPro } from '../utils/constants'; +import { canViewOrg, isDocs, isPro, customNavItems } from '../utils/constants'; const propTypes = { currentTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, @@ -164,6 +164,21 @@ class MainSideNav extends React.Component { ); } + renderCustomNavItems() { + return ( + customNavItems.map((item, idx) => { + return ( +
  • + + + {item.desc} + +
  • + ); + }) + ); + } + render() { let showActivity = isDocs || isPro; return ( @@ -261,6 +276,7 @@ class MainSideNav extends React.Component { {this.renderSharedAdmin()} + {customNavItems && this.renderCustomNavItems()} diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index ede75f065f..6a66be5c13 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -54,6 +54,7 @@ export const repoPasswordMinLength = window.app.pageOptions.repoPasswordMinLengt export const canAddPublicRepo = window.app.pageOptions.canAddPublicRepo; export const canInvitePeople = window.app.pageOptions.canInvitePeople; export const canLockUnlockFile = window.app.pageOptions.canLockUnlockFile; +export const customNavItems = window.app.pageOptions.customNavItems; export const curNoteMsg = window.app.pageOptions.curNoteMsg; export const curNoteID = window.app.pageOptions.curNoteID; diff --git a/media/office-template/empty.docx b/media/office-template/empty.docx index 939e7d7f9b..f5ea6976e7 100644 Binary files a/media/office-template/empty.docx and b/media/office-template/empty.docx differ diff --git a/seahub/api2/endpoints/repos.py b/seahub/api2/endpoints/repos.py index 801502376f..78fc15707f 100644 --- a/seahub/api2/endpoints/repos.py +++ b/seahub/api2/endpoints/repos.py @@ -351,8 +351,17 @@ class RepoView(APIView): repo = seafile_api.get_repo(repo_id) if not repo: - error_msg = 'Library %s not found.' % repo_id - return api_error(status.HTTP_404_NOT_FOUND, error_msg) + # for case of `seafile-data` has been damaged + # no `repo object` will be returned from seafile api + # delete the database record anyway + try: + seafile_api.remove_repo(repo_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) # check permission username = request.user.username diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 1ab9930542..9da3011211 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -329,6 +329,9 @@ class AccountInfo(APIView): info['institution'] = p.institution if p and p.institution else "" info['is_staff'] = request.user.is_staff + if getattr(settings, 'MULTI_INSTITUTION', False): + info['is_inst_admin'] = request.user.inst_admin + interval = UserOptions.objects.get_file_updates_email_interval(email) info['email_notification_interval'] = 0 if interval is None else interval return info diff --git a/seahub/institutions/middleware.py b/seahub/institutions/middleware.py index 6a609e6f46..b00c76c71a 100644 --- a/seahub/institutions/middleware.py +++ b/seahub/institutions/middleware.py @@ -16,6 +16,7 @@ class InstitutionMiddleware(object): try: inst_admin = InstitutionAdmin.objects.get(user=username) except InstitutionAdmin.DoesNotExist: + request.user.inst_admin = False return None request.user.institution = inst_admin.institution diff --git a/seahub/notifications/management/commands/send_file_updates.py b/seahub/notifications/management/commands/send_file_updates.py index 65ba0614fa..c8be42b028 100644 --- a/seahub/notifications/management/commands/send_file_updates.py +++ b/seahub/notifications/management/commands/send_file_updates.py @@ -63,8 +63,10 @@ class Command(BaseCommand): def handle(self, *args, **options): logger.debug('Start sending file updates emails...') + self.stdout.write('[%s] Start sending file updates emails...' % str(datetime.now())) self.do_action() logger.debug('Finish sending file updates emails.\n') + self.stdout.write('[%s] Finish sending file updates emails.\n\n' % str(datetime.now())) def get_avatar(self, username, default_size=32): img_tag = avatar(username, default_size) @@ -176,9 +178,6 @@ class Command(BaseCommand): return (op, details) def do_action(self): - today = datetime.utcnow().replace(hour=0).replace(minute=0).replace( - second=0).replace(microsecond=0) - emails = [] user_file_updates_email_intervals = [] for ele in UserOptions.objects.filter( @@ -190,6 +189,7 @@ class Command(BaseCommand): emails.append(ele.email) except Exception as e: logger.error(e) + self.stderr.write('[%s]: %s' % (str(datetime.now()), e)) continue user_last_emailed_time_dict = {} @@ -201,6 +201,7 @@ class Command(BaseCommand): ele.option_val, "%Y-%m-%d %H:%M:%S") except Exception as e: logger.error(e) + self.stderr.write('[%s]: %s' % (str(datetime.now()), e)) continue for (username, interval_val) in user_file_updates_email_intervals: @@ -212,14 +213,18 @@ class Command(BaseCommand): translation.activate(user_language) logger.debug('Set language code to %s for user: %s' % ( user_language, username)) - self.stdout.write('[%s] Set language code to %s' % ( - str(datetime.now()), user_language)) + self.stdout.write('[%s] Set language code to %s for user: %s' % ( + str(datetime.now()), user_language, username)) - # get last_emailed_time if any, defaults to today - last_emailed_time = user_last_emailed_time_dict.get(username, today) + # get last_emailed_time if any, defaults to today 00:00:00.0 + last_emailed_time = user_last_emailed_time_dict.get(username, None) now = datetime.utcnow().replace(microsecond=0) - if (now - last_emailed_time).seconds < interval_val: - continue + if not last_emailed_time: + last_emailed_time = datetime.utcnow().replace(hour=0).replace( + minute=0).replace(second=0).replace(microsecond=0) + else: + if (now - last_emailed_time).total_seconds() < interval_val: + continue # get file updates(from: last_emailed_time, to: now) for repos # user can access @@ -245,6 +250,9 @@ class Command(BaseCommand): logger.error('Failed to format mail content for user: %s' % username) logger.error(e, exc_info=True) + self.stderr.write('[%s] Failed to format mail content for user: %s' % + (str(datetime.now()), username)) + self.stderr.write('[%s]: %s' % (str(datetime.now()), e)) continue nickname = email2nickname(username) @@ -263,11 +271,13 @@ class Command(BaseCommand): # set new last_emailed_time UserOptions.objects.set_file_updates_last_emailed_time( username, now) + self.stdout.write('[%s] Successful to send email to %s' % + (str(datetime.now()), contact_email)) except Exception as e: logger.error('Failed to send email to %s, error detail: %s' % (contact_email, e)) self.stderr.write('[%s] Failed to send email to %s, error ' - 'detail: %s' % (str(now), contact_email, e)) + 'detail: %s' % (str(datetime.now()), contact_email, e)) finally: # reset lang translation.activate(cur_language) diff --git a/seahub/settings.py b/seahub/settings.py index 6c209c2a4a..904064a669 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -902,3 +902,14 @@ if ENABLE_REMOTE_USER_AUTHENTICATION: if ENABLE_OAUTH or ENABLE_WORK_WEIXIN: AUTHENTICATION_BACKENDS += ('seahub.oauth.backends.OauthRemoteUserBackend',) + +##################### +# Custom Nav Items # +##################### +# an example: +# CUSTOM_NAV_ITEMS = [ +# {'icon': 'sf2-icon-star', +# 'desc': 'test custom name', +# 'link': 'http://127.0.0.1:8000/shared-libs/', +# }, +# ] diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html index aac4ad9c4b..60825012f3 100644 --- a/seahub/templates/base_for_react.html +++ b/seahub/templates/base_for_react.html @@ -91,9 +91,10 @@ repoPasswordMinLength: {{repo_password_min_length}}, canAddPublicRepo: {% if can_add_public_repo %} true {% else %} false {% endif %}, canInvitePeople: {% if enable_guest_invitation and user.permissions.can_invite_guest %} true {% else %} false {% endif %}, + customNavItems: {% if custom_nav_items %} JSON.parse('{{ custom_nav_items | escapejs }}') {% else %} {{'[]'}} {% endif %}, {% if request.user.is_authenticated and request.cur_note %} - curNoteMsg: '{{ request.cur_note.message|urlize }}', + curNoteMsg: '{{ request.cur_note.message|urlize|escapejs }}', curNoteID: '{{ request.cur_note.id }}', {% endif %} } diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 598265dce0..83fab2724e 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -63,6 +63,7 @@ from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE from seahub.constants import HASH_URLS, PERMISSION_READ LIBRARY_TEMPLATES = getattr(settings, 'LIBRARY_TEMPLATES', {}) +CUSTOM_NAV_ITEMS = getattr(settings, 'CUSTOM_NAV_ITEMS', '') from constance import config @@ -1267,7 +1268,8 @@ def react_fake_view(request, **kwargs): 'is_email_configured': IS_EMAIL_CONFIGURED, 'can_add_public_repo': request.user.permissions.can_add_public_repo(), 'folder_perm_enabled': folder_perm_enabled, - 'file_audit_enabled' : FILE_AUDIT_ENABLED + 'file_audit_enabled' : FILE_AUDIT_ENABLED, + 'custom_nav_items' : json.dumps(CUSTOM_NAV_ITEMS), }) @login_required