From cbf12f9ed91b295f1e72af1012201def7460f3ea Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 22 Jan 2021 10:41:58 +0800 Subject: [PATCH] show last access time on admin users page (#4795) Co-authored-by: lian --- frontend/src/models/sysadmin-admin-user.js | 1 + frontend/src/models/sysadmin-user.js | 1 + .../pages/sys-admin/users/users-content.js | 4 +- seahub/api2/endpoints/admin/users.py | 122 +++++++++++++++--- 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/frontend/src/models/sysadmin-admin-user.js b/frontend/src/models/sysadmin-admin-user.js index 5724d0463b..a7a9442d32 100644 --- a/frontend/src/models/sysadmin-admin-user.js +++ b/frontend/src/models/sysadmin-admin-user.js @@ -6,6 +6,7 @@ class SysAdminAdminUser { this.contact_email = object.contact_email; this.login_id = object.login_id; this.last_login = object.last_login; + this.last_access_time = object.last_access_time; this.create_time = object.create_time; this.is_active = object.is_active; this.is_staff = object.is_staff; diff --git a/frontend/src/models/sysadmin-user.js b/frontend/src/models/sysadmin-user.js index d09f189af4..b59532bc8d 100644 --- a/frontend/src/models/sysadmin-user.js +++ b/frontend/src/models/sysadmin-user.js @@ -5,6 +5,7 @@ class SysAdminUser { this.contact_email = object.contact_email; this.login_id = object.login_id; this.last_login = object.last_login; + this.last_access_time = object.last_access_time; this.create_time = object.create_time; this.is_active = object.is_active; this.is_staff = object.is_staff; diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js index 5e33581d3c..605359af42 100644 --- a/frontend/src/pages/sys-admin/users/users-content.js +++ b/frontend/src/pages/sys-admin/users/users-content.js @@ -83,7 +83,7 @@ class Content extends Component { const colSpaceText = {spaceEl}{` / ${gettext('Quota')}`}; const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`; - const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')}`; + const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')} / ${gettext('Last Access')}`; if (isPro) { columns.push( {width: '20%', text: colNameText}, @@ -422,6 +422,8 @@ class Item extends Component { {`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`}
{`${item.last_login ? moment(item.last_login).fromNow() : '--'}`} +
+ {`${item.last_access_time ? moment(item.last_access_time).fromNow() : '--'}`} {(item.email != username && isOpIconShown) && diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py index 49ab80037d..ec54c3558a 100644 --- a/seahub/api2/endpoints/admin/users.py +++ b/seahub/api2/endpoints/admin/users.py @@ -19,6 +19,7 @@ from seaserv import seafile_api, ccnet_api from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error, to_python_boolean +from seahub.api2.models import TokenV2 import seahub.settings as settings from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \ @@ -33,9 +34,12 @@ from seahub.profile.settings import CONTACT_CACHE_TIMEOUT, CONTACT_CACHE_PREFIX, from seahub.utils import is_valid_username2, is_org_context, \ is_pro_version, normalize_cache_key, is_valid_email, \ IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \ - gen_shared_link, gen_shared_upload_link + gen_shared_link, gen_shared_upload_link, \ + get_file_audit_events, get_file_update_events + from seahub.utils.file_size import get_file_size_unit -from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr +from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \ + datetime_to_isoformat_timestr, utc_to_local from seahub.utils.user_permissions import get_user_role from seahub.utils.repo import normalize_repo_status_code from seahub.constants import DEFAULT_ADMIN @@ -57,6 +61,43 @@ logger = logging.getLogger(__name__) json_content_type = 'application/json; charset=utf-8' +def get_user_last_access_time(email, last_login_time): + + device_last_access = '' + audit_last_access = '' + update_last_access = '' + + devices = TokenV2.objects.filter(user=email).order_by('-last_accessed') + if devices: + device_last_access = devices[0].last_accessed + + audit_events = get_file_audit_events(email, 0, None, 0, 1) or [] + if audit_events: + audit_last_access = audit_events[0].timestamp + + update_events = get_file_update_events(email, 0, None, 0, 1) or [] + if update_events: + update_last_access = update_events[0].timestamp + + last_access_time_list = [] + if last_login_time: + last_access_time_list.append(last_login_time) + + if device_last_access: + last_access_time_list.append(device_last_access) + + if audit_last_access: + last_access_time_list.append(utc_to_local(audit_last_access)) + + if update_last_access: + last_access_time_list.append(utc_to_local(update_last_access)) + + if not last_access_time_list: + return '' + else: + return datetime_to_isoformat_timestr(sorted(last_access_time_list)[-1]) + + def get_user_upload_link_info(uls): data = {} @@ -204,6 +245,7 @@ def update_user_info(request, user, password, is_active, is_staff, role, logger.error(e) seafile_api.set_user_quota(email, -1) + def get_user_info(email): user = User.objects.get(email=email) @@ -297,8 +339,15 @@ class AdminAdminUsers(APIView): user_info['quota_total'] = -1 user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) + last_login_obj = UserLastLogin.objects.get_by_username(user.email) - user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else '' + if last_login_obj: + user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) + user_info['last_access_time'] = get_user_last_access_time(user.email, + last_login_obj.last_login) + else: + user_info['last_login'] = '' + user_info['last_access_time'] = get_user_last_access_time(user.email, '') try: admin_role = AdminRole.objects.get_admin_role(user.email) @@ -312,6 +361,7 @@ class AdminAdminUsers(APIView): } return Response(result) + class AdminUsers(APIView): authentication_classes = (TokenAuthentication, SessionAuthentication) @@ -363,7 +413,13 @@ class AdminUsers(APIView): info['quota_total'] = seafile_api.get_user_quota(user.email) last_login_obj = UserLastLogin.objects.get_by_username(user.email) - info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else '' + if last_login_obj: + info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) + info['last_access_time'] = get_user_last_access_time(user.email, + last_login_obj.last_login) + else: + info['last_login'] = '' + info['last_access_time'] = get_user_last_access_time(user.email, '') info['role'] = get_user_role(user) @@ -423,8 +479,10 @@ class AdminUsers(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) try: - data = self.get_info_of_users_order_by_quota_usage(source, direction, - page, per_page) + data = self.get_info_of_users_order_by_quota_usage(source, + direction, + page, + per_page) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' @@ -448,8 +506,10 @@ class AdminUsers(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) try: - data = self.get_info_of_users_order_by_quota_usage(source, direction, - page, per_page) + data = self.get_info_of_users_order_by_quota_usage(source, + direction, + page, + per_page) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' @@ -489,10 +549,19 @@ class AdminUsers(APIView): info['quota_usage'] = -1 info['quota_total'] = -1 - info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) - last_login_obj = UserLastLogin.objects.get_by_username(user.email) - info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else '' info['role'] = get_user_role(user) + + info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) + + last_login_obj = UserLastLogin.objects.get_by_username(user.email) + if last_login_obj: + info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) + info['last_access_time'] = get_user_last_access_time(user.email, + last_login_obj.last_login) + else: + info['last_login'] = '' + info['last_access_time'] = get_user_last_access_time(user.email, '') + if getattr(settings, 'MULTI_INSTITUTION', False): info['institution'] = profile.institution if profile else '' @@ -569,8 +638,7 @@ class AdminUsers(APIView): if is_org_context(request): org_id = request.user.org.org_id - org_quota_mb = seafile_api.get_org_quota(org_id) / \ - get_file_size_unit('MB') + org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB') if quota_total_mb > org_quota_mb: error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb @@ -611,6 +679,7 @@ class AdminUsers(APIView): c, None, [email2contact_email(email)]) + add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email} except Exception as e: logger.error(str(e)) @@ -669,8 +738,16 @@ class AdminLDAPUsers(APIView): info['quota_total'] = seafile_api.get_user_quota(user.email) info['quota_usage'] = seafile_api.get_user_self_usage(user.email) info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) + last_login_obj = UserLastLogin.objects.get_by_username(user.email) - info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else '' + if last_login_obj: + info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) + info['last_access_time'] = get_user_last_access_time(user.email, + last_login_obj.last_login) + else: + info['last_login'] = '' + info['last_access_time'] = get_user_last_access_time(user.email, '') + data.append(info) result = {'ldap_user_list': data, 'has_next_page': has_next_page} @@ -763,8 +840,16 @@ class AdminSearchUser(APIView): info['quota_total'] = seafile_api.get_user_quota(user.email) info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) + last_login_obj = UserLastLogin.objects.get_by_username(user.email) - info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else '' + if last_login_obj: + info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) + info['last_access_time'] = get_user_last_access_time(user.email, + last_login_obj.last_login) + else: + info['last_login'] = '' + info['last_access_time'] = get_user_last_access_time(user.email, '') + info['role'] = get_user_role(user) if getattr(settings, 'MULTI_INSTITUTION', False): @@ -885,8 +970,7 @@ class AdminUser(APIView): if is_org_context(request): org_id = request.user.org.org_id - org_quota_mb = seafile_api.get_org_quota(org_id) / \ - get_file_size_unit('MB') + org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB') if quota_total_mb > org_quota_mb: error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb @@ -1016,7 +1100,7 @@ class AdminUserResetPassword(APIView): contact_email = Profile.objects.get_contact_email_by_user(email) try: send_html_email(_(u'Password has been reset on %s') % get_site_name(), - 'sysadmin/user_reset_email.html', c, None, [contact_email]) + 'sysadmin/user_reset_email.html', c, None, [contact_email]) reset_tip = _('Successfully reset password to %(passwd)s, an email has been sent to %(user)s.') % \ {'passwd': new_password, 'user': contact_email} except Exception as e: @@ -1200,7 +1284,7 @@ class AdminUserBeSharedRepos(APIView): if email not in nickname_dict: if '@seafile_group' in email: group_id = get_group_id_by_repo_owner(email) - group_name= group_id_to_name(group_id) + group_name = group_id_to_name(group_id) nickname_dict[email] = group_name else: nickname_dict[email] = email2nickname(email)