From 4a4c9c36278b6b4b04444784c30797fddeaf1988 Mon Sep 17 00:00:00 2001 From: kid1194 Date: Wed, 9 Nov 2022 23:49:28 +0300 Subject: [PATCH] Update to v1.0.4 --- active_users/__init__.py | 5 +- .../active_users_has_role.json | 5 +- .../active_users_has_user.json | 5 +- .../active_users_has_user_type.json | 5 +- .../__init__.py | 0 .../active_users_notification_receiver.json | 40 ++++ .../active_users_notification_receiver.py | 11 ++ .../active_users_settings.js | 19 +- .../active_users_settings.json | 111 ++++++++++- .../active_users_settings.py | 11 +- active_users/api/handler.py | 115 ------------ active_users/hooks.py | 176 +---------------- .../public/css/active_users.bundle.css | 176 +++++++---------- active_users/public/js/active_users.bundle.js | 177 +++++++++--------- active_users/setup/install.py | 25 +-- active_users/setup/migrate.py | 37 ++++ active_users/utils/__init__.py | 8 + active_users/utils/access.py | 17 ++ active_users/utils/api.py | 109 +++++++++++ active_users/utils/common.py | 79 ++++++++ active_users/utils/update.py | 122 ++++++++++++ 21 files changed, 743 insertions(+), 510 deletions(-) rename active_users/{api => active_users/doctype/active_users_notification_receiver}/__init__.py (100%) create mode 100644 active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.json create mode 100644 active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.py delete mode 100644 active_users/api/handler.py create mode 100644 active_users/setup/migrate.py create mode 100644 active_users/utils/__init__.py create mode 100644 active_users/utils/access.py create mode 100644 active_users/utils/api.py create mode 100644 active_users/utils/common.py create mode 100644 active_users/utils/update.py diff --git a/active_users/__init__.py b/active_users/__init__.py index 6573f9d..39d6556 100644 --- a/active_users/__init__.py +++ b/active_users/__init__.py @@ -4,7 +4,4 @@ # Licence: Please refer to license.txt -from __future__ import unicode_literals - - -__version__ = "1.0.4" +__version__ = "1.0.4" \ No newline at end of file diff --git a/active_users/active_users/doctype/active_users_has_role/active_users_has_role.json b/active_users/active_users/doctype/active_users_has_role/active_users_has_role.json index 4ec6d45..6c31765 100644 --- a/active_users/active_users/doctype/active_users_has_role/active_users_has_role.json +++ b/active_users/active_users/doctype/active_users_has_role/active_users_has_role.json @@ -14,7 +14,10 @@ "label": "Role", "options": "Role", "reqd": 1, - "in_list_view": 1 + "bold": 1, + "in_list_view": 1, + "allow_in_quick_entry": 1, + "ignore_user_permissions": 1 } ], "istable": 1, diff --git a/active_users/active_users/doctype/active_users_has_user/active_users_has_user.json b/active_users/active_users/doctype/active_users_has_user/active_users_has_user.json index 2cde24d..e299acd 100644 --- a/active_users/active_users/doctype/active_users_has_user/active_users_has_user.json +++ b/active_users/active_users/doctype/active_users_has_user/active_users_has_user.json @@ -14,7 +14,10 @@ "label": "User", "options": "User", "reqd": 1, - "in_list_view": 1 + "bold": 1, + "in_list_view": 1, + "allow_in_quick_entry": 1, + "ignore_user_permissions": 1 } ], "istable": 1, diff --git a/active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.json b/active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.json index 239a7a5..78a84ab 100644 --- a/active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.json +++ b/active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.json @@ -14,7 +14,10 @@ "label": "User Type", "options": "User Type", "reqd": 1, - "in_list_view": 1 + "bold": 1, + "in_list_view": 1, + "allow_in_quick_entry": 1, + "ignore_user_permissions": 1 } ], "istable": 1, diff --git a/active_users/api/__init__.py b/active_users/active_users/doctype/active_users_notification_receiver/__init__.py similarity index 100% rename from active_users/api/__init__.py rename to active_users/active_users/doctype/active_users_notification_receiver/__init__.py diff --git a/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.json b/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.json new file mode 100644 index 0000000..6428f03 --- /dev/null +++ b/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "editable_grid": 1, + "autoname": "hash", + "creation": "2022-04-04 04:04:04.119400", + "description": "Active users notification receiver", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "reqd": 1, + "bold": 1, + "in_list_view": 1, + "allow_in_quick_entry": 1, + "ignore_user_permissions": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2022-04-04 04:04:04.119400", + "modified_by": "Administrator", + "module": "Active Users", + "name": "Active Users Notification Receiver", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.py b/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.py new file mode 100644 index 0000000..298a732 --- /dev/null +++ b/active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.py @@ -0,0 +1,11 @@ +# Active Users © 2022 +# Author: Ameen Ahmed +# Company: Level Up Marketing & Software Development Services +# Licence: Please refer to license.txt + + +from frappe.model.document import Document + + +class ActiveUsersNotificationReceiver(Document): + pass diff --git a/active_users/active_users/doctype/active_users_settings/active_users_settings.js b/active_users/active_users/doctype/active_users_settings/active_users_settings.js index 9fee2ec..b21575b 100644 --- a/active_users/active_users/doctype/active_users_settings/active_users_settings.js +++ b/active_users/active_users/doctype/active_users_settings/active_users_settings.js @@ -8,7 +8,24 @@ frappe.provide('frappe._active_users'); frappe.ui.form.on('Active Users Settings', { + refresh: function(frm) { + frm.get_field('update_note').$wrapper.html( + cint(frm.doc.has_update) + ? 'A new version is available' + : 'No new version is found' + ); + }, + check_for_update: function(frm) { + if (frappe._active_users._init) + frappe._active_users._init.request( + 'check_for_update', + function(ret) { + if (ret) frm.reload_doc(); + } + ); + }, after_save: function(frm) { - if (frappe._active_users._init) frappe._active_users._init.update_settings(); + if (frappe._active_users._init) + frappe._active_users._init.update_settings(); } }); \ No newline at end of file diff --git a/active_users/active_users/doctype/active_users_settings/active_users_settings.json b/active_users/active_users/doctype/active_users_settings/active_users_settings.json index 98d267f..85a09f6 100644 --- a/active_users/active_users/doctype/active_users_settings/active_users_settings.json +++ b/active_users/active_users/doctype/active_users_settings/active_users_settings.json @@ -5,7 +5,7 @@ "engine": "InnoDB", "field_order": [ "info_section", - "is_enabled", + "enabled", "info_column", "refresh_interval", "allow_manual_refresh", @@ -16,7 +16,18 @@ "hidden_from_listed_users", "users", "visibility_column", - "visibility_note" + "visibility_note", + "update_section", + "auto_check_for_update", + "send_update_notification", + "update_notification_sender", + "update_notification_receivers", + "update_column", + "update_note", + "has_update", + "latest_version", + "latest_check", + "check_for_update" ], "fields": [ { @@ -24,7 +35,7 @@ "fieldtype": "Section Break" }, { - "fieldname": "is_enabled", + "fieldname": "enabled", "fieldtype": "Check", "label": "Is Enabled", "default": "1" @@ -38,13 +49,15 @@ "fieldtype": "Int", "label": "Refresh Interval (Minutes)", "description": "The number of minutes to wait before refreshing the active users list", - "default": "5" + "default": "5", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "allow_manual_refresh", "fieldtype": "Check", "label": "Allow Manual Refresh", - "default": "1" + "default": "1", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "visibility_section", @@ -55,29 +68,34 @@ "fieldname": "user_types", "fieldtype": "Table MultiSelect", "label": "User Types To Display", - "options": "Active Users Has User Type" + "options": "Active Users Has User Type", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "hidden_from_listed_roles", "fieldtype": "Check", - "label": "Hidden From Listed Roles" + "label": "Hidden From Listed Roles", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "roles", "fieldtype": "Table MultiSelect", "label": "Roles", - "options": "Active Users Has Role" + "options": "Active Users Has Role", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "hidden_from_listed_users", "fieldtype": "Check", - "label": "Hidden From Listed Users" + "label": "Hidden From Listed Users", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "users", "fieldtype": "Table MultiSelect", "label": "Users", - "options": "Active Users Has User" + "options": "Active Users Has User", + "read_only_depends_on": "eval:!doc.enabled" }, { "fieldname": "visibility_column", @@ -89,6 +107,79 @@ "label": "", "options": "
Note:
\n", "read_only": 1 + }, + { + "fieldname": "update_section", + "fieldtype": "Section Break", + "label": "Update Settings" + }, + { + "fieldname": "auto_check_for_update", + "fieldtype": "Check", + "label": "Auto Check", + "description": "Auto check for new version daily", + "default": "1", + "read_only_depends_on": "eval:!doc.enabled" + }, + { + "fieldname": "send_update_notification", + "fieldtype": "Check", + "label": "Send Update Notification", + "default": "1", + "read_only_depends_on": "eval:!doc.enabled || !doc.auto_check_for_update" + }, + { + "fieldname": "update_notification_sender", + "fieldtype": "Link", + "label": "Update Notification Sender", + "options": "User", + "read_only_depends_on": "eval:!doc.enabled || !doc.send_update_notification", + "mandatory_depends_on": "eval:doc.send_update_notification", + "ignore_user_permissions": 1 + }, + { + "fieldname": "update_notification_receivers", + "fieldtype": "Table MultiSelect", + "label": "Update Notification Receivers", + "options": "Active Users Notification Receiver", + "read_only_depends_on": "eval:!doc.enabled || !doc.send_update_notification", + "mandatory_depends_on": "eval:doc.send_update_notification" + }, + { + "fieldname": "update_column", + "fieldtype": "Column Break" + }, + { + "fieldname": "update_note", + "fieldtype": "HTML", + "label": "Update Note", + "read_only": 1 + }, + { + "fieldname": "has_update", + "fieldtype": "Check", + "label": "Has Update", + "hidden": 1, + "is_virtual": 1 + }, + { + "fieldname": "latest_version", + "fieldtype": "Data", + "label": "Latest Version", + "read_only": 1 + }, + { + "fieldname": "latest_check", + "fieldtype": "Data", + "label": "Latest Check", + "read_only": 1 + }, + { + "fieldname": "check_for_update", + "fieldtype": "Button", + "label": "Check Now", + "description": "Check for new version manually", + "read_only_depends_on": "eval:!doc.enabled" } ], "icon": "fa fa-cog", diff --git a/active_users/active_users/doctype/active_users_settings/active_users_settings.py b/active_users/active_users/doctype/active_users_settings/active_users_settings.py index b69205a..db8a7ba 100644 --- a/active_users/active_users/doctype/active_users_settings/active_users_settings.py +++ b/active_users/active_users/doctype/active_users_settings/active_users_settings.py @@ -7,10 +7,15 @@ import frappe from frappe.model.document import Document -from active_users.api.handler import _SETTINGS_CACHE_KEY +from active_users.utils import clear_document_cache, compare_versions +from active_users import __version__ class ActiveUsersSettings(Document): + @property + def has_update(self): + return 1 if compare_versions(self.latest_version, __version__) > 0 else 0 + + def before_save(self): - frappe.clear_cache(doctype="Active Users Settings") - frappe.cache().hdel(_SETTINGS_CACHE_KEY, frappe.session.user) + clear_document_cache(self.doctype) diff --git a/active_users/api/handler.py b/active_users/api/handler.py deleted file mode 100644 index 2eb694a..0000000 --- a/active_users/api/handler.py +++ /dev/null @@ -1,115 +0,0 @@ -# Active Users © 2022 -# Author: Ameen Ahmed -# Company: Level Up Marketing & Software Development Services -# Licence: Please refer to license.txt - - -import json - -import frappe -from frappe.utils import cint, has_common, now, add_to_date - - -_SETTINGS_CACHE_KEY = "active_users_settings" -_SETTINGS_DOCTYPE = "Active Users Settings" - - -def on_login(login_manager): - frappe.cache().hdel(_SETTINGS_CACHE_KEY, login_manager.user) - - -def on_logout(): - frappe.cache().hdel(_SETTINGS_CACHE_KEY, frappe.session.user) - - -@frappe.whitelist() -def get_settings(): - user = frappe.session.user - cache = frappe.cache().hget(_SETTINGS_CACHE_KEY, user) - if ( - isinstance(cache, dict) and - "refresh_interval" in cache and - "allow_manual_refresh" in cache - ): - return cache - - result = { - "is_enabled": False, - "refresh_interval": 5, - "allow_manual_refresh": False, - } - status = 0 - settings = frappe.get_cached_doc(_SETTINGS_DOCTYPE, _SETTINGS_DOCTYPE) - - if not settings.is_enabled: - status = 2 - - if not status and settings.users: - users = [v.user for v in settings.users] - if users and user in users: - status = 2 if settings.hidden_from_listed_users else 1 - - if not status and settings.roles: - roles = [v.role for v in settings.roles] - if roles and has_common(roles, frappe.get_roles()): - status = 2 if settings.hidden_from_listed_roles else 1 - - if status == 1: - result["is_enabled"] = True - result["refresh_interval"] = cint(settings.refresh_interval) - result["allow_manual_refresh"] = True if settings.allow_manual_refresh else False - - frappe.cache().hset(_SETTINGS_CACHE_KEY, user, result) - return result - - -@frappe.whitelist() -def get_users(): - settings = frappe.get_cached_doc(_SETTINGS_DOCTYPE, _SETTINGS_DOCTYPE) - user_types = [v.user_type for v in settings.user_types] - sys_settings = frappe.get_cached_doc("System Settings", "System Settings") - tp = [0, -20, 0] - - sess_expiry = sys_settings.session_expiry - if not sess_expiry or not isinstance(sess_expiry, str): - sess_expiry = sys_settings.session_expiry_mobile - if not sess_expiry or not isinstance(sess_expiry, str): - sess_expiry = "" - - try: - if sess_expiry: - sess_list = sess_expiry.split(":") - if sess_list and not isinstance(sess_list, list): - sess_list = [sess_list] - if sess_list and isinstance(sess_list, list): - idx = 0 - for v in sess_list: - if v and isinstance(v, str): - tpv = cint(v) - if tpv: - tp[idx] = -abs(tpv) - idx += 1 - else: - return {"error": True, "message": "The system session expiry value is invalid."} - except Exception: - return {"error": True, "message": "Unable to parse the system session expiry value."} - - end = now() - start = add_to_date(end, hours=tp[0], minutes=tp[1], seconds=tp[2], as_string=True, as_datetime=True) - - try: - data = frappe.get_all( - "User", - fields=["name", "full_name", "user_image"], - filters={ - "enabled": 1, - "user_type": ["in", user_types], - "last_active": ["between", [start, end]], - }, - order_by="full_name asc", - limit_page_length=0, - ) - return {"users": data} - except Exception as ex: - print(f"Exception in get_users(): {ex}") # just for debugging purposes in console - return {"error": True, "message": "Unable to get the list of active users."} diff --git a/active_users/hooks.py b/active_users/hooks.py index c275761..e107e72 100644 --- a/active_users/hooks.py +++ b/active_users/hooks.py @@ -16,181 +16,25 @@ app_color = "blue" app_email = "kid1194@gmail.com" app_license = "MIT" -is_frappe_above_v13 = int(frappe_version.split('.')[0]) > 13 -# Includes in -# ------------------ -# include js, css files in header of desk.html -# app_include_css = "/assets/active_users/css/select.css" -# app_include_js = "/assets/active_users/js/select.js" +is_frappe_above_v13 = int(frappe_version.split('.')[0]) > 13 + app_include_css = ['active_users.bundle.css'] if is_frappe_above_v13 else ['/assets/active_users/css/active_users.css'] app_include_js = ['active_users.bundle.js'] if is_frappe_above_v13 else ['/assets/active_users/js/active_users.js'] -# include custom scss in every website theme (without file extension ".scss") -# website_theme_scss = "active_users/public/scss/website" - -# include js, css files in header of web form -# webform_include_js = {"doctype": "public/js/doctype.js"} -# webform_include_css = {"doctype": "public/css/doctype.css"} - -# include js in page -# page_js = {"page" : "public/js/file.js"} - -# include js in doctype views -# doctype_js = {"doctype" : "public/js/doctype.js"} -# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} -# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} -# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} - -# Home Pages -# ---------- - -# application home page (will override Website Settings) -# home_page = "login" -# website user home page (by Role) -# role_home_page = { -# "Role": "home_page" -# } - -# Generators -# ---------- - -# automatically create page for each record of this doctype -# website_generators = ["Web Page"] - -# Jinja -# ---------- - -# add methods and filters to jinja environment -# jinja = { -# "methods": "active_users.utils.jinja_methods", -# "filters": "active_users.utils.jinja_filters" -# } - -# Installation -# ------------ - -# before_install = "active_users.install.before_install" after_install = "active_users.setup.install.after_install" +after_migrate = "active_users.setup.migrate.after_migrate" -# login -on_login = ["active_users.api.handler.on_login"] -on_logout = ["active_users.api.handler.on_logout"] - -# Desk Notifications -# ------------------ -# See frappe.core.notifications.get_notification_config - -# notification_config = "active_users.notifications.get_notification_config" - -# Permissions -# ----------- -# Permissions evaluated in scripted ways - -# permission_query_conditions = { -# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", -# } -# -# has_permission = { -# "Event": "frappe.desk.doctype.event.event.has_permission", -# } - -# DocType Class -# --------------- -# Override standard doctype classes - -# override_doctype_class = { -# "ToDo": "custom_app.overrides.CustomToDo" -# } - -# Document Events -# --------------- -# Hook on document methods and events - -# doc_events = { -# "*": { -# "on_update": "method", -# "on_cancel": "method", -# "on_trash": "method" -# } -# } - -# Scheduled Tasks -# --------------- - -# scheduler_events = { -# "all": [ -# "active_users.tasks.all" -# ], -# "daily": [ -# "active_users.tasks.daily" -# ], -# "hourly": [ -# "active_users.tasks.hourly" -# ], -# "weekly": [ -# "active_users.tasks.weekly" -# ], -# "monthly": [ -# "active_users.tasks.monthly" -# ], -# } - -# Testing -# ------- - -# before_tests = "active_users.install.before_tests" - -# Overriding Methods -# ------------------------------ -# -# override_whitelisted_methods = { -# "frappe.desk.doctype.event.event.get_events": "active_users.event.get_events" -# } -# -# each overriding function accepts a `data` argument; -# generated from the base implementation of the doctype dashboard, -# along with any modifications made in other Frappe apps -# override_doctype_dashboards = { -# "Task": "active_users.task.get_dashboard_data" -# } - -# exempt linked doctypes from being automatically cancelled -# -# auto_cancel_exempted_doctypes = ["Auto Repeat"] - - -# User Data Protection -# -------------------- - -# user_data_fields = [ -# { -# "doctype": "{doctype_1}", -# "filter_by": "{filter_by}", -# "redact_fields": ["{field_1}", "{field_2}"], -# "partial": 1, -# }, -# { -# "doctype": "{doctype_2}", -# "filter_by": "{filter_by}", -# "partial": 1, -# }, -# { -# "doctype": "{doctype_3}", -# "strict": False, -# }, -# { -# "doctype": "{doctype_4}" -# } -# ] -# Authentication and authorization -# -------------------------------- +on_login = ["active_users.utils.access.on_login"] +on_logout = ["active_users.utils.access.on_logout"] -# auth_hooks = [ -# "active_users.auth.validate" -# ] +scheduler_events = { + "daily": [ + "active_users.utils.update.auto_check_for_update" + ] +} \ No newline at end of file diff --git a/active_users/public/css/active_users.bundle.css b/active_users/public/css/active_users.bundle.css index dcc428b..90f9232 100644 --- a/active_users/public/css/active_users.bundle.css +++ b/active_users/public/css/active_users.bundle.css @@ -16,21 +16,6 @@ html[dir="rtl"] .active-users-list { left: 0; right: auto; } -.active-users-list::-webkit-scrollbar { - width: 2px; - background: #EBEEF0; -} -.active-users-list::-webkit-scrollbar-track { - background: #EBEEF0; -} -.active-users-list::-webkit-scrollbar-thumb { - background: #C0C6CC; - border-radius: 10px; - max-height: 30px; -} -.active-users-list::-webkit-scrollbar-thumb:hover { - background: #98A1A9; -} .active-users-list-header, .active-users-list-footer { background: #dddddd; @@ -71,7 +56,7 @@ html[dir="rtl"] .active-users-footer-text { html[dir="rtl"] .active-users-footer-reload { text-align: left; } -.active-users-footer-reload::hover { +.active-users-footer-reload:hover { text-decoration: none; color: #000; } @@ -85,114 +70,85 @@ html[dir="rtl"] .active-users-footer-reload { justify-content: center; height: 260px; } +.active-users-list-loading { + display: flex; + align-items: center; + justify-content: center; + height: 260px; +} +.active-users-list-loading-box, +.active-users-list-loading-box:before, +.active-users-list-loading-box:after { + border-radius: 50%; + width: 2em; + height: 2em; + animation-fill-mode: both; + animation: active-users-loader 1.8s infinite ease-in-out; +} .active-users-list-loading-box { position: relative; - left: 0; - top: 0; - bottom: 0; - right: 0; - width: 15px; - height: 15px; - -o-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - -ms-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - -webkit-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - -moz-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - -o-animation: active-users-loader ease infinite 4.6s; - -ms-animation: active-users-loader ease infinite 4.6s; - -webkit-animation: active-users-loader ease infinite 4.6s; - -moz-animation: active-users-loader ease infinite 4.6s; - animation: active-users-loader ease infinite 4.6s; + color: #888; + font-size: 7px; + position: relative; + text-indent: -9999em; + transform: translateZ(0); + animation-delay: -0.16s; +} +.active-users-list-loading-box:before, +.active-users-list-loading-box:after { + content: ''; + position: absolute; + top: 0; +} +.active-users-list-loading-box:before { + left: -3.5em; + animation-delay: -0.32s; +} +.active-users-list-loading-box:after { + left: 3.5em; } @keyframes active-users-loader { - 0%, - 100% { - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - } - 25% { - box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - } - 50% { - box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - } - 75% { - box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; - } + 0%, 80%, 100% { box-shadow: 0 2.5em 0 -1.3em } + 40% { box-shadow: 0 2.5em 0 0 } } @-o-keyframes active-users-loader { - 0%, - 100% { - -o-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - } - 25% { - -o-box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - } - 50% { - -o-box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - } - 75% { - -o-box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; - box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; + 0%, 80%, 100% { + -o-box-shadow: 0 2.5em 0 -1.3em; + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + -o-box-shadow: 0 2.5em 0 0; + box-shadow: 0 2.5em 0 0; } } @-ms-keyframes active-users-loader { - 0%, - 100% { - -ms-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - } - 25% { - -ms-box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - } - 50% { - -ms-box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - } - 75% { - -ms-box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; - box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; + 0%, 80%, 100% { + -ms-box-shadow: 0 2.5em 0 -1.3em; + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + -ms-box-shadow: 0 2.5em 0 0; + box-shadow: 0 2.5em 0 0; } } @-webkit-keyframes active-users-loader { - 0%, - 100% { - -webkit-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - } - 25% { - -webkit-box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - } - 50% { - -webkit-box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - } - 75% { - -webkit-box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; - box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; + 0%, 80%, 100% { + -webkit-box-shadow: 0 2.5em 0 -1.3em; + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + -webkit-box-shadow: 0 2.5em 0 0; + box-shadow: 0 2.5em 0 0; } } @-moz-keyframes active-users-loader { - 0%, - 100% { - -moz-box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - box-shadow: 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888; - } - 25% { - -moz-box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - box-shadow: -15px 15px #888888, -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6; - } - 50% { - -moz-box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - box-shadow: -15px -15px #d6d6d6, 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888; - } - 75% { - -moz-box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; - box-shadow: 15px -15px #888888, 15px 15px #d6d6d6, -15px 15px #888888, -15px -15px #d6d6d6; + 0%, 80%, 100% { + -moz-box-shadow: 0 2.5em 0 -1.3em; + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + -moz-box-shadow: 0 2.5em 0 0; + box-shadow: 0 2.5em 0 0; } } .active-users-list-item { @@ -200,7 +156,7 @@ html[dir="rtl"] .active-users-footer-reload { padding: 5px 10px; border-bottom: 1px solid #f6f6f6; } -.active-users-list-item::last-child { +.active-users-list-item:last-child { border-bottom: 0; } .active-users-item-avatar { diff --git a/active_users/public/js/active_users.bundle.js b/active_users/public/js/active_users.bundle.js index b210af5..38dd495 100644 --- a/active_users/public/js/active_users.bundle.js +++ b/active_users/public/js/active_users.bundle.js @@ -5,28 +5,32 @@ * Licence: Please refer to license.txt */ -frappe.provide('frappe.ActiveUsers'); + frappe.provide('frappe._active_users'); frappe.provide('frappe.dom'); -frappe.ActiveUsers = class ActiveUsers { + +class ActiveUsers { constructor() { if (frappe.desk == null) { frappe.throw(__('Active Users plugin can not be used outside Desk.')); return; } + this.started = true; this.is_online = frappe.is_online ? frappe.is_online() : false; + this.on_online = null; + this.on_offline = null; var me = this; $(window).on('online', function() { me.is_online = true; - me._on_online && me._on_online.call(me); - me._on_online = null; + me.on_online && me.on_online.call(me); + me.on_online = null; }); $(window).on('offline', function() { me.is_online = false; - me._on_offline && me._on_offline.call(me); - me._on_offline = null; + me.on_offline && me.on_offline.call(me); + me.on_offline = null; }); this.settings = {}; @@ -42,83 +46,65 @@ frappe.ActiveUsers = class ActiveUsers { this.data = this._on_online = this._on_offline = this._syncing = null; this.$app = this.$body = this.$loading = this.$footer = this.$reload = null; } - error(msg) { + error(msg, args) { this.destroy(); - frappe.throw(__(msg)); + frappe.throw(__(msg, args)); } request(method, callback, type) { var me = this; - let data = { - method: 'active_users.api.handler.' + method, - 'async': true, - freeze: false, - }, - p = null; - - try { - p = frappe.call(data); - p.then(function(res) { - if (res && $.isPlainObject(res)) res = res.message || res; - if (!$.isPlainObject(res)) { - me.error('Active Users plugin received invalid ' + type + '.'); - return; - } - if (res.error) { - me.error(res.message); - return; + return new Promise(function(resolve, reject) { + let data = { + method: 'active_users.utils.api.' + method, + 'async': true, + freeze: false, + callback: function(res) { + if (res && $.isPlainObject(res)) res = res.message || res; + if (!$.isPlainObject(res)) { + me.error('Active Users plugin received invalid ' + type + '.'); + reject(); + return; + } + if (res.error) { + me.error(res.message); + reject(); + return; + } + let val = callback && callback.call(me, res); + resolve(val || res); } - callback.call(me, res); - }); - } catch(e) { - this.error('An error has occurred while sending a request.'); - (console.error || console.log)('[Active Users]', e); - } - - return p; + }; + try { + frappe.call(data); + } catch(e) { + (console.error || console.log)('[Active Users]', e); + this.error('An error has occurred while sending a request.'); + reject(); + } + }); } setup() { if (!this.is_online) { - this._on_online = this.setup; + this.on_online = this.setup; return; } - var me = this; this.sync_settings() .then(function() { - if (!me.settings.is_enabled) return; + if (!me.settings.enabled) return; frappe.run_serially([ function() { me.setup_display(); }, function() { me.sync_reload(); }, ]); }); } - update_settings() { - if (!this.is_online) { - this._on_online = this.update_settings; - return; - } - - var me = this; - this.sync_settings() - .then(function() { - if (!me.settings.is_enabled) me.destroy(); - else { - if (!me.data) { - me.data = []; - frappe.run_serially([ - function() { me.setup_display(); }, - function() { me.sync_reload(); }, - ]); - } else me.setup_manual_sync(); - } - }); - } sync_settings() { return this.request( 'get_settings', function(res) { + res.enabled = cint(res.enabled); + res.refresh_interval = cint(res.refresh_interval) * 60000; + res.allow_manual_refresh = cint(res.allow_manual_refresh); this.settings = res; - this.settings.refresh_interval = cint(this.settings.refresh_interval) * 60000; }, 'settings' ); @@ -151,7 +137,7 @@ frappe.ActiveUsers = class ActiveUsers { @@ -169,8 +155,8 @@ frappe.ActiveUsers = class ActiveUsers { this.setup_manual_sync(); - if (frappe.ActiveUsers._ready) return; - frappe.ActiveUsers._ready = true; + if (frappe._active_users._ready) return; + frappe._active_users._ready = true; frappe.dom.eval(` (function(){window.$clamp=function(c,d){function s(a,b){n.getComputedStyle||(n.getComputedStyle=function(a,b){this.el=a;this.getPropertyValue=function(b){var c=/(\-([a-z]){1})/g;"float"==b&&(b="styleFloat");c.test(b)&&(b=b.replace(c,function(a,b,c){return c.toUpperCase()}));return a.currentStyle&&a.currentStyle[b]?a.currentStyle[b]:null};return this});return n.getComputedStyle(a,null).getPropertyValue(b)}function t(a){a=a||c.clientHeight;var b=u(c);return Math.max(Math.floor(a/b),0)}function x(a){return u(c)* a}function u(a){var b=s(a,"line-height");"normal"==b&&(b=1.2*parseInt(s(a,"font-size")));return parseInt(b)}function l(a){if(a.lastChild.children&&0 0: + doc.latest_version = latest_version + if cint(doc.send_update_notification): + enqueue_send_notification( + latest_version, + doc.update_notification_sender, + [v.user for v in doc.update_notification_receivers], + markdown(response.get("body")) + ) + + doc.save(ignore_permissions=True) + return 1 + + +## Self +def compare_versions(verA, verB): + verA = verA.split(".") + lenA = len(verA) + verB = verB.split(".") + lenB = len(verB) + + if lenA > lenB: + for i in range(lenB, lenA): + verB.append(0) + elif lenA < lenB: + for i in range(lenA, lenB): + verA.append(0) + + for a, b in zip(verA, verB): + d = cint(a) - cint(b) + if d == 0: + continue + return 1 if d > 0 else -1 + + return 0 + + +## Self +def enqueue_send_notification(version, sender, receivers, message): + frappe.enqueue( + "active_users.utils.update.send_notification", + job_name=f"active_users-send-notification-{version}", + is_async=True, + version=version, + sender=sender, + receivers=receivers, + message=message + ) + + +## Self +def send_notification(version, sender, receivers, message): + for receiver in receivers: + if is_notifications_enabled(user): + (frappe.new_doc("Notification Log") + .update({ + "document_type": _SETTINGS_, + "document_name": _SETTINGS_, + "from_user": sender, + "for_user": receiver, + "subject": "Active Users: A New Version Is Available", + "type": "Alert", + "email_content": f"

Version {version}

{message}", + }) + .insert(ignore_permissions=True, ignore_mandatory=True)) \ No newline at end of file