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