├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── active_users ├── __init__.py ├── active_users │ ├── __init__.py │ └── doctype │ │ ├── __init__.py │ │ ├── active_users_has_role │ │ ├── __init__.py │ │ ├── active_users_has_role.json │ │ └── active_users_has_role.py │ │ ├── active_users_has_user │ │ ├── __init__.py │ │ ├── active_users_has_user.json │ │ └── active_users_has_user.py │ │ ├── active_users_has_user_type │ │ ├── __init__.py │ │ ├── active_users_has_user_type.json │ │ └── active_users_has_user_type.py │ │ ├── active_users_notification_receiver │ │ ├── __init__.py │ │ ├── active_users_notification_receiver.json │ │ └── active_users_notification_receiver.py │ │ └── active_users_settings │ │ ├── __init__.py │ │ ├── active_users_settings.js │ │ ├── active_users_settings.json │ │ └── active_users_settings.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── hooks.py ├── modules.txt ├── patches.txt ├── public │ ├── build.json │ ├── css │ │ └── active_users.bundle.css │ └── js │ │ └── active_users.bundle.js ├── setup │ ├── __init__.py │ ├── install.py │ └── migrate.py ├── utils │ ├── __init__.py │ ├── api.py │ ├── common.py │ └── update.py └── version.py ├── images └── image.png ├── pyproject.toml ├── requirements.txt └── setup.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: kid1194 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[REQ]" 5 | labels: enhancement 6 | assignees: kid1194 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | node_modules 7 | __pycache__ 8 | dist 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2022 Level Up Marketing & Development Services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 9 | to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies 12 | or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 17 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 19 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include active_users *.css 8 | recursive-include active_users *.csv 9 | recursive-include active_users *.html 10 | recursive-include active_users *.ico 11 | recursive-include active_users *.js 12 | recursive-include active_users *.json 13 | recursive-include active_users *.md 14 | recursive-include active_users *.png 15 | recursive-include active_users *.py 16 | recursive-include active_users *.svg 17 | recursive-include active_users *.txt 18 | recursive-exclude active_users *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frappe Active Users 2 | 3 | A small plugin for Frappe that displays a list of current active users. 4 | 5 |

6 | Active Users 7 |

8 | 9 | --- 10 | 11 | ### Table of Contents 12 | - [Requirements](#requirements) 13 | - [Setup](#setup) 14 | - [Install](#install) 15 | - [Update](#update) 16 | - [Uninstall](#uninstall) 17 | - [Usage](#usage) 18 | - [Contributors](#contributors) 19 | - [Issues](#issues) 20 | - [License](#license) 21 | 22 | --- 23 | 24 | ### Requirements 25 | - Frappe >= v13.0.0 26 | 27 | --- 28 | 29 | ### Setup 30 | 31 | ⚠️ *Important* ⚠️ 32 | 33 | *Do not forget to replace [sitename] with the name of your site in all commands.* 34 | 35 | #### Install 36 | 1. Go to bench directory 37 | 38 | ``` 39 | cd ~/frappe-bench 40 | ``` 41 | 42 | 2. Get plugin from Github 43 | 44 | *(Required only once)* 45 | 46 | ``` 47 | bench get-app https://github.com/kid1194/frappe-active-users 48 | ``` 49 | 50 | 3. Build plugin 51 | 52 | *(Required only once)* 53 | 54 | ``` 55 | bench build --app active_users 56 | ``` 57 | 58 | 4. Install plugin on a specific site 59 | 60 | ``` 61 | bench --site [sitename] install-app active_users 62 | ``` 63 | 64 | 5. Check the usage section below 65 | 66 | #### Update 67 | 1. Go to app directory 68 | 69 | ``` 70 | cd ~/frappe-bench/apps/active_users 71 | ``` 72 | 73 | 2. Get updates from Github 74 | 75 | ``` 76 | git pull 77 | ``` 78 | 79 | 3. Go to bench directory 80 | 81 | ``` 82 | cd ~/frappe-bench 83 | ``` 84 | 85 | 4. Build plugin 86 | 87 | ``` 88 | bench build --app active_users 89 | ``` 90 | 91 | 5. Update a specific site 92 | 93 | ``` 94 | bench --site [sitename] migrate 95 | ``` 96 | 97 | 6. Restart bench 98 | 99 | ``` 100 | bench restart 101 | ``` 102 | 103 | #### Uninstall 104 | 1. Go to bench directory 105 | 106 | ``` 107 | cd ~/frappe-bench 108 | ``` 109 | 110 | 2. Uninstall plugin from a specific site 111 | 112 | ``` 113 | bench --site [sitename] uninstall-app active_users 114 | ``` 115 | 116 | 3. Remove plugin from bench 117 | 118 | ``` 119 | bench remove-app active_users 120 | ``` 121 | 122 | 4. Restart bench 123 | 124 | ``` 125 | bench restart 126 | ``` 127 | 128 | --- 129 | 130 | ### Usage 131 | 1. Go to **Active Users Settings** 132 | 2. Check the **Is Enabled** box and set the desired **Refresh Interval** 133 | 3. Choose the **Roles** and **Users** that you want the plugin to be visible to or hidden from 134 | 135 | --- 136 | 137 | ### Contributors 138 | - [Monolith Online](https://github.com/monolithon) (Testing & Debugging) 139 | 140 | --- 141 | 142 | ### Issues 143 | If you find bug in the plugin, please create a [bug report](https://github.com/kid1194/frappe-active-users/issues/new?assignees=kid1194&labels=bug&template=bug_report.md&title=%5BBUG%5D) and let us know about it. 144 | 145 | --- 146 | 147 | ### License 148 | This repository has been released under the [MIT License](https://github.com/kid1194/frappe-active-users/blob/main/LICENSE). 149 | -------------------------------------------------------------------------------- /active_users/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | __version__ = "1.0.10" -------------------------------------------------------------------------------- /active_users/active_users/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_role/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_role/active_users_has_role.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_copy": 1, 4 | "allow_import": 1, 5 | "editable_grid": 1, 6 | "autoname": "hash", 7 | "creation": "2022-04-04 04:04:04.119400", 8 | "description": "Active users role visibility", 9 | "doctype": "DocType", 10 | "document_type": "Document", 11 | "engine": "InnoDB", 12 | "field_order": [ 13 | "role" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "role", 18 | "fieldtype": "Link", 19 | "label": "Role", 20 | "options": "Role", 21 | "reqd": 1, 22 | "bold": 1, 23 | "in_list_view": 1, 24 | "allow_in_quick_entry": 1, 25 | "ignore_user_permissions": 1 26 | } 27 | ], 28 | "istable": 1, 29 | "links": [], 30 | "modified": "2022-04-04 04:04:04.119400", 31 | "modified_by": "Administrator", 32 | "module": "Active Users", 33 | "name": "Active Users Has Role", 34 | "owner": "Administrator", 35 | "permissions": [], 36 | "sort_field": "modified", 37 | "sort_order": "DESC", 38 | "states": [], 39 | "track_changes": 1 40 | } -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_role/active_users_has_role.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.model.document import Document 8 | 9 | 10 | class ActiveUsersHasRole(Document): 11 | pass -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user/active_users_has_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_copy": 1, 4 | "allow_import": 1, 5 | "editable_grid": 1, 6 | "autoname": "hash", 7 | "creation": "2022-04-04 04:04:04.119400", 8 | "description": "Active users user visibility", 9 | "doctype": "DocType", 10 | "document_type": "Document", 11 | "engine": "InnoDB", 12 | "field_order": [ 13 | "user" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "user", 18 | "fieldtype": "Link", 19 | "label": "User", 20 | "options": "User", 21 | "reqd": 1, 22 | "bold": 1, 23 | "in_list_view": 1, 24 | "allow_in_quick_entry": 1, 25 | "ignore_user_permissions": 1 26 | } 27 | ], 28 | "istable": 1, 29 | "links": [], 30 | "modified": "2022-04-04 04:04:04.119400", 31 | "modified_by": "Administrator", 32 | "module": "Active Users", 33 | "name": "Active Users Has User", 34 | "owner": "Administrator", 35 | "permissions": [], 36 | "sort_field": "modified", 37 | "sort_order": "DESC", 38 | "states": [], 39 | "track_changes": 1 40 | } -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user/active_users_has_user.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.model.document import Document 8 | 9 | 10 | class ActiveUsersHasUser(Document): 11 | pass -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user_type/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_copy": 1, 4 | "allow_import": 1, 5 | "editable_grid": 1, 6 | "autoname": "hash", 7 | "creation": "2022-04-04 04:04:04.119400", 8 | "description": "Active users visible user type", 9 | "doctype": "DocType", 10 | "document_type": "Document", 11 | "engine": "InnoDB", 12 | "field_order": [ 13 | "user_type" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "user_type", 18 | "fieldtype": "Link", 19 | "label": "User Type", 20 | "options": "User Type", 21 | "reqd": 1, 22 | "bold": 1, 23 | "in_list_view": 1, 24 | "allow_in_quick_entry": 1, 25 | "ignore_user_permissions": 1 26 | } 27 | ], 28 | "istable": 1, 29 | "links": [], 30 | "modified": "2022-04-04 04:04:04.119400", 31 | "modified_by": "Administrator", 32 | "module": "Active Users", 33 | "name": "Active Users Has User Type", 34 | "owner": "Administrator", 35 | "permissions": [], 36 | "sort_field": "modified", 37 | "sort_order": "DESC", 38 | "states": [], 39 | "track_changes": 1 40 | } -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_has_user_type/active_users_has_user_type.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.model.document import Document 8 | 9 | 10 | class ActiveUsersHasUserType(Document): 11 | pass -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_notification_receiver/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_copy": 1, 4 | "allow_import": 1, 5 | "editable_grid": 1, 6 | "autoname": "hash", 7 | "creation": "2022-04-04 04:04:04.119400", 8 | "description": "Active users notification receiver", 9 | "doctype": "DocType", 10 | "document_type": "Document", 11 | "engine": "InnoDB", 12 | "field_order": [ 13 | "user" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "user", 18 | "fieldtype": "Link", 19 | "label": "User", 20 | "options": "User", 21 | "reqd": 1, 22 | "bold": 1, 23 | "in_list_view": 1, 24 | "allow_in_quick_entry": 1, 25 | "ignore_user_permissions": 1 26 | } 27 | ], 28 | "istable": 1, 29 | "links": [], 30 | "modified": "2022-04-04 04:04:04.119400", 31 | "modified_by": "Administrator", 32 | "module": "Active Users", 33 | "name": "Active Users Notification Receiver", 34 | "owner": "Administrator", 35 | "permissions": [], 36 | "sort_field": "modified", 37 | "sort_order": "DESC", 38 | "states": [], 39 | "track_changes": 1 40 | } -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_notification_receiver/active_users_notification_receiver.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.model.document import Document 8 | 9 | 10 | class ActiveUsersNotificationReceiver(Document): 11 | pass -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_settings/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_settings/active_users_settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Active Users © 2023 3 | * Author: Ameen Ahmed 4 | * Company: Level Up Marketing & Software Development Services 5 | * Licence: Please refer to LICENSE file 6 | */ 7 | 8 | frappe.provide('frappe._active_users'); 9 | 10 | frappe.ui.form.on('Active Users Settings', { 11 | setup: function(frm) { 12 | frm._visibility_ready = false; 13 | frm._update = { 14 | ready: false, 15 | messages: [ 16 | __('App is up to date'), 17 | __('A new version is available'), 18 | ], 19 | tags: ['span', 'strong'], 20 | classes: ['text-muted', 'text-danger'], 21 | }; 22 | }, 23 | refresh: function(frm) { 24 | if (!frm._visibility_ready) { 25 | frm._visibility_ready = true; 26 | frm.get_field('visibility_note').$wrapper.html( 27 | '
' + __('Note') + ':
' 28 | + '' 54 | ); 55 | } 56 | if (!frm._update.ready) { 57 | frm._update.ready = true; 58 | let idx = cint(frm.doc.has_update); 59 | frm.get_field('update_note').$wrapper.html( 60 | '<' + frm._update.tags[idx] 61 | + 'class="' + frm._update.classes[idx] + '">' 62 | + frm._update.messages[idx] 63 | + '' 64 | ); 65 | } 66 | }, 67 | check_for_update: function(frm) { 68 | if (frappe._active_users._init) 69 | frappe._active_users._init.request( 70 | 'check_for_update', 71 | function(ret) { frm.reload_doc(); } 72 | ); 73 | }, 74 | after_save: function(frm) { 75 | if (frappe._active_users._init) 76 | frappe._active_users._init.update_settings(); 77 | }, 78 | }); -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_settings/active_users_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "creation": "2022-04-04 04:04:04.119400", 4 | "description": "Active users settings", 5 | "doctype": "DocType", 6 | "document_type": "Document", 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "info_section", 10 | "enabled", 11 | "info_column", 12 | "refresh_interval", 13 | "allow_manual_refresh", 14 | "visibility_section", 15 | "user_types", 16 | "hidden_from_listed_roles", 17 | "roles", 18 | "hidden_from_listed_users", 19 | "users", 20 | "visibility_column", 21 | "visibility_note", 22 | "update_section", 23 | "auto_check_for_update", 24 | "send_update_notification", 25 | "update_notification_sender", 26 | "update_notification_receivers", 27 | "update_column", 28 | "update_note", 29 | "has_update", 30 | "latest_version", 31 | "latest_check", 32 | "check_for_update" 33 | ], 34 | "fields": [ 35 | { 36 | "fieldname": "info_section", 37 | "fieldtype": "Section Break" 38 | }, 39 | { 40 | "fieldname": "enabled", 41 | "fieldtype": "Check", 42 | "label": "Is Enabled", 43 | "default": "1" 44 | }, 45 | { 46 | "fieldname": "info_column", 47 | "fieldtype": "Column Break" 48 | }, 49 | { 50 | "fieldname": "refresh_interval", 51 | "fieldtype": "Int", 52 | "label": "Refresh Interval (Minutes)", 53 | "description": "The number of minutes to wait before refreshing the active users list", 54 | "default": "5", 55 | "non_negative": 1, 56 | "read_only_depends_on": "eval:!doc.enabled" 57 | }, 58 | { 59 | "fieldname": "allow_manual_refresh", 60 | "fieldtype": "Check", 61 | "label": "Allow Manual Refresh", 62 | "default": "1", 63 | "read_only_depends_on": "eval:!doc.enabled" 64 | }, 65 | { 66 | "fieldname": "visibility_section", 67 | "fieldtype": "Section Break", 68 | "label": "Visibility" 69 | }, 70 | { 71 | "fieldname": "user_types", 72 | "fieldtype": "Table MultiSelect", 73 | "label": "User Types To Display", 74 | "options": "Active Users Has User Type", 75 | "read_only_depends_on": "eval:!doc.enabled" 76 | }, 77 | { 78 | "fieldname": "hidden_from_listed_roles", 79 | "fieldtype": "Check", 80 | "label": "Hidden From Listed Roles", 81 | "read_only_depends_on": "eval:!doc.enabled" 82 | }, 83 | { 84 | "fieldname": "roles", 85 | "fieldtype": "Table MultiSelect", 86 | "label": "Roles", 87 | "options": "Active Users Has Role", 88 | "read_only_depends_on": "eval:!doc.enabled" 89 | }, 90 | { 91 | "fieldname": "hidden_from_listed_users", 92 | "fieldtype": "Check", 93 | "label": "Hidden From Listed Users", 94 | "read_only_depends_on": "eval:!doc.enabled" 95 | }, 96 | { 97 | "fieldname": "users", 98 | "fieldtype": "Table MultiSelect", 99 | "label": "Users", 100 | "options": "Active Users Has User", 101 | "read_only_depends_on": "eval:!doc.enabled" 102 | }, 103 | { 104 | "fieldname": "visibility_column", 105 | "fieldtype": "Column Break" 106 | }, 107 | { 108 | "fieldname": "visibility_note", 109 | "fieldtype": "HTML", 110 | "label": "", 111 | "options": "", 112 | "read_only": 1 113 | }, 114 | { 115 | "fieldname": "update_section", 116 | "fieldtype": "Section Break", 117 | "label": "Update Settings" 118 | }, 119 | { 120 | "fieldname": "auto_check_for_update", 121 | "fieldtype": "Check", 122 | "label": "Auto Check", 123 | "description": "Auto check for new version daily", 124 | "default": "1", 125 | "read_only_depends_on": "eval:!doc.enabled" 126 | }, 127 | { 128 | "fieldname": "send_update_notification", 129 | "fieldtype": "Check", 130 | "label": "Send Update Notification", 131 | "default": "1", 132 | "read_only_depends_on": "eval:!doc.enabled || !doc.auto_check_for_update" 133 | }, 134 | { 135 | "fieldname": "update_notification_sender", 136 | "fieldtype": "Link", 137 | "label": "Update Notification Sender", 138 | "options": "User", 139 | "read_only_depends_on": "eval:!doc.enabled || !doc.send_update_notification", 140 | "mandatory_depends_on": "eval:doc.send_update_notification", 141 | "ignore_user_permissions": 1 142 | }, 143 | { 144 | "fieldname": "update_notification_receivers", 145 | "fieldtype": "Table MultiSelect", 146 | "label": "Update Notification Receivers", 147 | "options": "Active Users Notification Receiver", 148 | "read_only_depends_on": "eval:!doc.enabled || !doc.send_update_notification", 149 | "mandatory_depends_on": "eval:doc.send_update_notification" 150 | }, 151 | { 152 | "fieldname": "update_column", 153 | "fieldtype": "Column Break" 154 | }, 155 | { 156 | "fieldname": "update_note", 157 | "fieldtype": "HTML", 158 | "label": "Update Note", 159 | "read_only": 1 160 | }, 161 | { 162 | "fieldname": "has_update", 163 | "fieldtype": "Check", 164 | "label": "Has Update", 165 | "read_only": 1, 166 | "hidden": 1 167 | }, 168 | { 169 | "fieldname": "latest_version", 170 | "fieldtype": "Data", 171 | "label": "Latest Version", 172 | "read_only": 1 173 | }, 174 | { 175 | "fieldname": "latest_check", 176 | "fieldtype": "Data", 177 | "label": "Latest Check", 178 | "read_only": 1 179 | }, 180 | { 181 | "fieldname": "check_for_update", 182 | "fieldtype": "Button", 183 | "label": "Check Now", 184 | "description": "Check for new version manually", 185 | "read_only_depends_on": "eval:!doc.enabled" 186 | } 187 | ], 188 | "icon": "fa fa-cog", 189 | "issingle": 1, 190 | "links": [], 191 | "modified": "2022-04-04 04:04:04.119400", 192 | "modified_by": "Administrator", 193 | "module": "Active Users", 194 | "name": "Active Users Settings", 195 | "owner": "Administrator", 196 | "permissions": [ 197 | { 198 | "amend": 1, 199 | "cancel": 1, 200 | "create": 1, 201 | "delete": 1, 202 | "email": 1, 203 | "export": 1, 204 | "import": 1, 205 | "print": 1, 206 | "read": 1, 207 | "report": 1, 208 | "role": "System Manager", 209 | "set_user_permissions": 1, 210 | "share": 1, 211 | "submit": 1, 212 | "write": 1 213 | }, 214 | { 215 | "amend": 1, 216 | "cancel": 1, 217 | "create": 1, 218 | "delete": 1, 219 | "email": 1, 220 | "export": 1, 221 | "import": 1, 222 | "print": 1, 223 | "read": 1, 224 | "report": 1, 225 | "role": "Administrator", 226 | "set_user_permissions": 1, 227 | "share": 1, 228 | "submit": 1, 229 | "write": 1 230 | } 231 | ], 232 | "sort_field": "modified", 233 | "sort_order": "DESC", 234 | "states": [], 235 | "track_changes": 1 236 | } -------------------------------------------------------------------------------- /active_users/active_users/doctype/active_users_settings/active_users_settings.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.model.document import Document 8 | 9 | from active_users.utils.common import ( 10 | _CACHE_, 11 | _CACHE_INTERVAL_, 12 | clear_document_cache, 13 | del_cache, 14 | clear_cache 15 | ) 16 | 17 | 18 | class ActiveUsersSettings(Document): 19 | def before_save(self): 20 | clear_document_cache(self.doctype) 21 | if self.refresh_interval < _CACHE_INTERVAL_: 22 | clear_cache(_CACHE_) 23 | else: 24 | del_cache(_CACHE_, "settings") -------------------------------------------------------------------------------- /active_users/config/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to license.txt -------------------------------------------------------------------------------- /active_users/config/desktop.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to license.txt 5 | 6 | 7 | from frappe import _ 8 | 9 | 10 | def get_data(): 11 | return [ 12 | { 13 | "module_name": "Active Users", 14 | "color": "blue", 15 | "icon": "octicon octicon-person", 16 | "type": "module", 17 | "label": _("Active Users") 18 | } 19 | ] -------------------------------------------------------------------------------- /active_users/config/docs.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to license.txt 5 | 6 | 7 | """ 8 | Configuration for docs 9 | """ 10 | 11 | 12 | def get_context(context): 13 | context.brand_html = "Active Users" 14 | -------------------------------------------------------------------------------- /active_users/hooks.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from .version import __frappe_version_min_14__ 8 | 9 | 10 | app_name = "active_users" 11 | app_title = "Active Users" 12 | app_publisher = "Ameen Ahmed (Level Up)" 13 | app_description = "Frappe plugin that displays a list of current active users." 14 | app_icon = "octicon octicon-person" 15 | app_color = "blue" 16 | app_email = "kid1194@gmail.com" 17 | app_license = "MIT" 18 | 19 | 20 | app_include_css = [ 21 | "active_users.bundle.css" 22 | ] if __frappe_version_min_14__ else [ 23 | "/assets/active_users/css/active_users.css" 24 | ] 25 | app_include_js = [ 26 | "active_users.bundle.js" 27 | ] if __frappe_version_min_14__ else [ 28 | "/assets/active_users/js/active_users.js" 29 | ] 30 | 31 | 32 | after_install = "active_users.setup.install.after_install" 33 | after_migrate = "active_users.setup.migrate.after_migrate" 34 | 35 | 36 | scheduler_events = { 37 | "daily": [ 38 | "active_users.utils.update.auto_check_for_update" 39 | ] 40 | } -------------------------------------------------------------------------------- /active_users/modules.txt: -------------------------------------------------------------------------------- 1 | Active Users -------------------------------------------------------------------------------- /active_users/patches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kid1194/frappe-active-users/6f9b2161291a644948fab53de766bf280e2640c5/active_users/patches.txt -------------------------------------------------------------------------------- /active_users/public/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "active_users/js/active_users.js": ["public/js/active_users.bundle.js"], 3 | "active_users/css/active_users.css": ["public/css/active_users.bundle.css"] 4 | } -------------------------------------------------------------------------------- /active_users/public/css/active_users.bundle.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Frappe Active Users © 2023 3 | * Author: Ameen Ahmed 4 | * Company: Level Up Marketing & Software Development Services 5 | * Licence: Please refer to LICENSE file 6 | */ 7 | 8 | .active-users-list { 9 | left: auto; 10 | right: 0; 11 | padding: 0; 12 | width: 260px; 13 | overflow: hidden; 14 | } 15 | html[dir="rtl"] .active-users-list { 16 | left: 0; 17 | right: auto; 18 | } 19 | .active-users-list-header, 20 | .active-users-list-footer { 21 | background: #ddd; 22 | } 23 | [data-theme="dark"] .active-users-list-header, 24 | [data-theme="dark"] .active-users-list-footer { 25 | background: #222; 26 | } 27 | .active-users-list-header, 28 | .active-users-footer-text { 29 | font-weight: 300; 30 | line-height: 1.2; 31 | color: #000; 32 | } 33 | .active-users-list-header { 34 | padding: 10px 25px; 35 | height: 40px; 36 | font-size: .9rem; 37 | } 38 | [data-theme="dark"] .active-users-list-header, 39 | [data-theme="dark"] .active-users-footer-text { 40 | color: #fff; 41 | } 42 | html[dir="rtl"] .active-users-list-header, 43 | html[dir="rtl"] .active-users-footer-text { 44 | text-align: right; 45 | } 46 | .active-users-list-footer { 47 | padding: 5px 25px; 48 | } 49 | .active-users-list-footer > div { 50 | align-items: center; 51 | height: 30px; 52 | } 53 | .active-users-footer-text { 54 | font-size: .8rem; 55 | } 56 | .active-users-footer-icon { 57 | width: 50px; 58 | } 59 | .active-users-footer-reload { 60 | display: block; 61 | text-align: right; 62 | color: #888; 63 | text-decoration: none; 64 | } 65 | html[dir="rtl"] .active-users-footer-reload { 66 | text-align: left; 67 | } 68 | .active-users-footer-reload:hover { 69 | text-decoration: none; 70 | color: #000; 71 | } 72 | [data-theme="dark"] .active-users-footer-reload:hover { 73 | color: #fff; 74 | } 75 | .active-users-list-body { 76 | max-height: 260px; 77 | overflow-y: auto; 78 | } 79 | .active-users-list-loading { 80 | display: flex; 81 | align-items: center; 82 | justify-content: center; 83 | height: 260px; 84 | } 85 | .active-users-list-loading-box, 86 | .active-users-list-loading-box:before, 87 | .active-users-list-loading-box:after { 88 | border-radius: 50%; 89 | width: 2em; 90 | height: 2em; 91 | animation-fill-mode: both; 92 | animation: active-users-loader 1.8s infinite ease-in-out; 93 | } 94 | .active-users-list-loading-box { 95 | position: relative; 96 | color: #888; 97 | font-size: 7px; 98 | text-indent: -9999em; 99 | transform: translateZ(0); 100 | animation-delay: -0.16s; 101 | } 102 | [data-theme="dark"] .active-users-list-loading-box { 103 | color: #fff; 104 | } 105 | .active-users-list-loading-box:before, 106 | .active-users-list-loading-box:after { 107 | content: ''; 108 | position: absolute; 109 | top: 0; 110 | } 111 | .active-users-list-loading-box:before { 112 | left: -3.5em; 113 | animation-delay: -0.32s; 114 | } 115 | .active-users-list-loading-box:after { 116 | left: 3.5em; 117 | } 118 | @keyframes active-users-loader { 119 | 0%, 80%, 100% { box-shadow: 0 2.5em 0 -1.3em } 120 | 40% { box-shadow: 0 2.5em 0 0 } 121 | } 122 | @-o-keyframes active-users-loader { 123 | 0%, 80%, 100% { 124 | -o-box-shadow: 0 2.5em 0 -1.3em; 125 | box-shadow: 0 2.5em 0 -1.3em; 126 | } 127 | 40% { 128 | -o-box-shadow: 0 2.5em 0 0; 129 | box-shadow: 0 2.5em 0 0; 130 | } 131 | } 132 | @-ms-keyframes active-users-loader { 133 | 0%, 80%, 100% { 134 | -ms-box-shadow: 0 2.5em 0 -1.3em; 135 | box-shadow: 0 2.5em 0 -1.3em; 136 | } 137 | 40% { 138 | -ms-box-shadow: 0 2.5em 0 0; 139 | box-shadow: 0 2.5em 0 0; 140 | } 141 | } 142 | @-webkit-keyframes active-users-loader { 143 | 0%, 80%, 100% { 144 | -webkit-box-shadow: 0 2.5em 0 -1.3em; 145 | box-shadow: 0 2.5em 0 -1.3em; 146 | } 147 | 40% { 148 | -webkit-box-shadow: 0 2.5em 0 0; 149 | box-shadow: 0 2.5em 0 0; 150 | } 151 | } 152 | @-moz-keyframes active-users-loader { 153 | 0%, 80%, 100% { 154 | -moz-box-shadow: 0 2.5em 0 -1.3em; 155 | box-shadow: 0 2.5em 0 -1.3em; 156 | } 157 | 40% { 158 | -moz-box-shadow: 0 2.5em 0 0; 159 | box-shadow: 0 2.5em 0 0; 160 | } 161 | } 162 | .active-users-list-item { 163 | align-items: center; 164 | padding: 5px 10px; 165 | border-bottom: 1px solid #f6f6f6; 166 | } 167 | [data-theme="dark"] .active-users-list-item { 168 | border-bottom: 1px solid #262626; 169 | } 170 | .active-users-list-item:last-child { 171 | border-bottom: 0; 172 | } 173 | .active-users-item-avatar { 174 | padding: 0 15px; 175 | } 176 | .active-users-item-avatar > .avatar { 177 | width: 30px; 178 | height: 30px; 179 | } 180 | .active-users-item-name { 181 | padding-left: 0; 182 | padding-right: 15px; 183 | font-size: .8rem; 184 | text-align: left; 185 | display: -moz-box; 186 | display: -webkit-box; 187 | overflow: hidden; 188 | text-overflow: ellipsis; 189 | -o-box-orient: vertical; 190 | -o-line-clamp: 1; 191 | -moz-box-orient: vertical; 192 | -moz-line-clamp: 1; 193 | -webkit-box-orient: vertical; 194 | -webkit-line-clamp: 1; 195 | } 196 | html[dir="rtl"] .active-users-item-name { 197 | padding-left: 15px; 198 | padding-right: 0; 199 | text-align: right; 200 | } -------------------------------------------------------------------------------- /active_users/public/js/active_users.bundle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Frappe Active Users © 2023 3 | * Author: Ameen Ahmed 4 | * Company: Level Up Marketing & Software Development Services 5 | * Licence: Please refer to LICENSE file 6 | */ 7 | 8 | 9 | frappe.provide('frappe._active_users'); 10 | frappe.provide('frappe.dom'); 11 | 12 | 13 | class ActiveUsers { 14 | constructor() { 15 | if (frappe.desk == null) { 16 | frappe.throw(__('Active Users plugin can not be used outside Desk.')); 17 | return; 18 | } 19 | this.is_online = frappe.is_online ? frappe.is_online() : false; 20 | this.on_online = null; 21 | this.on_offline = null; 22 | 23 | var me = this; 24 | $(window).on('online', function() { 25 | me.is_online = true; 26 | me.on_online && me.on_online.call(me); 27 | me.on_online = null; 28 | }); 29 | $(window).on('offline', function() { 30 | me.is_online = false; 31 | me.on_offline && me.on_offline.call(me); 32 | me.on_offline = null; 33 | }); 34 | 35 | this.settings = {}; 36 | this.data = []; 37 | 38 | this.setup(); 39 | } 40 | destroy() { 41 | this.clear_sync(); 42 | if (this.$loading) this.$loading.hide(); 43 | if (this.$reload) this.$reload.off('click').hide(); 44 | if (this.$app) this.$app.remove(); 45 | this.data = this._on_online = this._on_offline = this._syncing = null; 46 | this.$app = this.$body = this.$loading = this.$footer = this.$reload = null; 47 | } 48 | error(msg, args) { 49 | this.destroy(); 50 | frappe.throw(__(msg, args)); 51 | } 52 | request(method, callback, type) { 53 | var me = this; 54 | return new Promise(function(resolve, reject) { 55 | let data = { 56 | method: 'active_users.utils.api.' + method, 57 | 'async': true, 58 | freeze: false, 59 | callback: function(res) { 60 | if (res && $.isPlainObject(res)) res = res.message || res; 61 | if (!$.isPlainObject(res)) { 62 | me.error('Active Users plugin received invalid ' + type + '.'); 63 | reject(); 64 | return; 65 | } 66 | if (res.error) { 67 | me.error(res.message); 68 | reject(); 69 | return; 70 | } 71 | let val = callback && callback.call(me, res); 72 | resolve(val || res); 73 | } 74 | }; 75 | try { 76 | frappe.call(data); 77 | } catch(e) { 78 | (console.error || console.log)('[Active Users]', e); 79 | this.error('An error has occurred while sending a request.'); 80 | reject(); 81 | } 82 | }); 83 | } 84 | setup() { 85 | if (!this.is_online) { 86 | this.on_online = this.setup; 87 | return; 88 | } 89 | var me = this; 90 | this.sync_settings() 91 | .then(function() { 92 | if (!me.settings.enabled) return; 93 | Promise.resolve() 94 | .then(function() { me.setup_display(); }) 95 | .then(function() { me.sync_reload(); }); 96 | }); 97 | } 98 | sync_settings() { 99 | return this.request( 100 | 'get_settings', 101 | function(res) { 102 | res.enabled = cint(res.enabled); 103 | res.refresh_interval = cint(res.refresh_interval) * 60000; 104 | res.allow_manual_refresh = cint(res.allow_manual_refresh); 105 | this.settings = res; 106 | }, 107 | 'settings' 108 | ); 109 | } 110 | setup_display() { 111 | let title = __('Active Users'); 112 | this.$app = $(` 113 | 146 | `); 147 | $('header.navbar > .container > .navbar-collapse > ul.navbar-nav').prepend(this.$app.get(0)); 148 | 149 | this.$body = this.$app.find('.active-users-list-body').first(); 150 | this.$loading = this.$body.find('.active-users-list-loading').first().hide(); 151 | this.$footer = this.$app.find('.active-users-footer-text').first(); 152 | this.$reload = this.$app.find('.active-users-footer-reload').first(); 153 | 154 | this.setup_manual_sync(); 155 | } 156 | setup_manual_sync() { 157 | if (!this.settings.allow_manual_refresh) { 158 | this.$reload.off('click').hide(); 159 | return; 160 | } 161 | var me = this; 162 | this.$reload.show().click(function(e) { 163 | e.preventDefault(); 164 | if (!me._syncing) me.sync_reload(); 165 | }); 166 | } 167 | sync_reload() { 168 | if (!this.is_online) return; 169 | this.clear_sync(); 170 | var me = this; 171 | Promise.resolve() 172 | .then(function() { me.sync_data(); }) 173 | .then(function() { me.setup_sync(); }); 174 | } 175 | clear_sync() { 176 | if (this.sync_timer) { 177 | window.clearInterval(this.sync_timer); 178 | this.sync_timer = null; 179 | } 180 | } 181 | sync_data() { 182 | this._syncing = true; 183 | if (this.data.length) { 184 | this.$footer.html(''); 185 | this.$body.empty(); 186 | } 187 | this.$loading.show(); 188 | this.request( 189 | 'get_users', 190 | function(res) { 191 | this.data = res.users && Array.isArray(res.users) ? res.users : []; 192 | this.$loading.hide(); 193 | this.update_list(); 194 | this._syncing = null; 195 | }, 196 | 'users list' 197 | ); 198 | } 199 | setup_sync() { 200 | var me = this; 201 | this.sync_timer = window.setInterval(function() { 202 | me.sync_data(); 203 | }, this.settings.refresh_interval); 204 | } 205 | update_settings() { 206 | if (!this.is_online) { 207 | this.on_online = this.update_settings; 208 | return; 209 | } 210 | var me = this; 211 | this.sync_settings() 212 | .then(function() { 213 | if (!me.settings.enabled) { 214 | me.destroy(); 215 | return; 216 | } 217 | Promise.resolve() 218 | .then(function() { me.setup_manual_sync(); }) 219 | .then(function() { me.sync_reload(); }); 220 | }); 221 | } 222 | update_list() { 223 | var me = this; 224 | this.data.forEach(function(v) { 225 | let avatar = frappe.get_avatar(null, v.full_name, v.user_image), 226 | name = v.full_name, 227 | item = $(` 228 |
229 |
${avatar}
230 |
${name}
231 |
232 | `); 233 | me.$body.append(item.get(0)); 234 | }); 235 | this.$footer.html(__('Total') + ': ' + this.data.length); 236 | } 237 | } 238 | 239 | frappe._active_users.init = function() { 240 | if (frappe._active_users._init) frappe._active_users._init.destory(); 241 | if (frappe.desk == null) return; 242 | frappe._active_users._init = new ActiveUsers(); 243 | }; 244 | 245 | $(document).ready(function() { 246 | frappe._active_users.init(); 247 | }); -------------------------------------------------------------------------------- /active_users/setup/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file -------------------------------------------------------------------------------- /active_users/setup/install.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | import frappe 8 | 9 | from active_users.utils.common import ( 10 | _SETTINGS_, 11 | settings, 12 | clear_document_cache 13 | ) 14 | from .migrate import after_migrate 15 | 16 | 17 | def after_install(): 18 | clear_document_cache(_SETTINGS_) 19 | frappe.clear_cache() 20 | 21 | doc = settings(True) 22 | 23 | user_types = None 24 | if doc.user_types: 25 | user_types = [v.user_type for v in doc.user_types] 26 | 27 | if ( 28 | ( 29 | not user_types or 30 | "System User" not in user_types 31 | ) and frappe.db.exists("User Type", "System User") 32 | ): 33 | doc.append("user_types", {"user_type": "System User"}) 34 | 35 | roles = frappe.get_all( 36 | "Role", 37 | fields=["name"], 38 | filters={ 39 | "name": ["in", ["Administrator", "System Manager"]], 40 | }, 41 | pluck="name" 42 | ) 43 | if roles: 44 | 45 | user_roles = None 46 | if doc.roles: 47 | user_roles = [v.role for v in doc.roles] 48 | 49 | for r in roles: 50 | if not user_roles or r not in user_roles: 51 | doc.append("roles", {"role": r}) 52 | 53 | doc.save(ignore_permissions=True) 54 | 55 | after_migrate() -------------------------------------------------------------------------------- /active_users/setup/migrate.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe.utils import now 8 | from frappe.utils.user import get_system_managers 9 | 10 | from active_users import __version__ 11 | from active_users.utils.common import settings 12 | 13 | 14 | def after_migrate(): 15 | managers = get_system_managers(only_name=True) 16 | if managers: 17 | 18 | doc = settings(True) 19 | 20 | if ( 21 | not doc.update_notification_sender or 22 | doc.update_notification_sender not in managers 23 | ): 24 | admin = "Administrator" 25 | doc.update_notification_sender = admin if admin in managers else managers.pop(0) 26 | 27 | receivers = None 28 | if doc.update_notification_receivers: 29 | receivers = [v.user for v in doc.update_notification_receivers] 30 | 31 | for manager in managers: 32 | if not receivers or manager not in receivers: 33 | doc.append( 34 | "update_notification_receivers", 35 | {"user": manager} 36 | ) 37 | 38 | doc.latest_version = __version__ 39 | doc.latest_check = now() 40 | 41 | doc.save(ignore_permissions=True) -------------------------------------------------------------------------------- /active_users/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from .common import * 8 | from .update import * -------------------------------------------------------------------------------- /active_users/utils/api.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | import frappe 8 | from frappe import _, _dict 9 | from frappe.utils import ( 10 | cint, 11 | has_common, 12 | now, 13 | add_to_date, 14 | get_datetime, 15 | now_datetime 16 | ) 17 | 18 | from .common import ( 19 | _SETTINGS_, 20 | _CACHE_, 21 | _CACHE_INTERVAL_, 22 | settings, 23 | get_cache, 24 | set_cache, 25 | del_cache, 26 | log_error 27 | ) 28 | 29 | 30 | @frappe.whitelist() 31 | def get_settings(): 32 | cache_key = "settings" 33 | cache = get_cache(_CACHE_, cache_key) 34 | 35 | if cache and isinstance(cache, dict): 36 | return cache 37 | 38 | result = _dict({ 39 | "enabled": 0, 40 | "refresh_interval": 5, 41 | "allow_manual_refresh": 0 42 | }) 43 | status = 0 44 | app = settings() 45 | 46 | if not cint(app.enabled): 47 | status = 2 48 | 49 | if not status and app.users: 50 | users = [v.user for v in app.users] 51 | if users and frappe.session.user in users: 52 | status = 2 if cint(app.hidden_from_listed_users) else 1 53 | 54 | if not status and app.roles: 55 | roles = [v.role for v in app.roles] 56 | if roles and has_common(roles, frappe.get_roles()): 57 | status = 2 if cint(app.hidden_from_listed_roles) else 1 58 | 59 | if status == 1: 60 | result.enabled = 1 61 | result.refresh_interval = cint(app.refresh_interval) 62 | result.allow_manual_refresh = 1 if cint(app.allow_manual_refresh) else 0 63 | 64 | set_cache(_CACHE_, cache_key, result) 65 | return result 66 | 67 | 68 | @frappe.whitelist() 69 | def get_users(): 70 | app = settings() 71 | 72 | if not cint(app.enabled): 73 | return {"users": []} 74 | 75 | cache_key = "users" 76 | 77 | if app.refresh_interval >= _CACHE_INTERVAL_: 78 | cache = get_cache(_CACHE_, cache_key) 79 | 80 | if cache and isinstance(cache, dict): 81 | if get_datetime(cache.expiry) >= now_datetime(): 82 | return {"users": cache.data} 83 | 84 | del_cache(_CACHE_, cache_key) 85 | 86 | 87 | tp = [0, -20, 0] 88 | sess_expiry = frappe.get_system_settings("session_expiry") 89 | if not sess_expiry or not isinstance(sess_expiry, str): 90 | sess_expiry = frappe.get_system_settings("session_expiry_mobile") 91 | if not sess_expiry or not isinstance(sess_expiry, str): 92 | sess_expiry = "" 93 | 94 | try: 95 | if sess_expiry: 96 | sess_list = sess_expiry.split(":") 97 | if sess_list and not isinstance(sess_list, list): 98 | sess_list = [sess_list] 99 | if sess_list and isinstance(sess_list, list): 100 | idx = 0 101 | for v in sess_list: 102 | if v and isinstance(v, str): 103 | tpv = cint(v) 104 | if tpv: 105 | tp[idx] = -abs(tpv) 106 | idx += 1 107 | 108 | else: 109 | return {"error": 1, "message": _("The system session expiry value is invalid.")} 110 | 111 | except Exception as exc: 112 | log_error(exc) 113 | return {"error": 1, "message": _("Unable to parse the system session expiry value.")} 114 | 115 | now_dt = now() 116 | start = add_to_date(now_dt, hours=tp[0], minutes=tp[1], seconds=tp[2], as_string=True, as_datetime=True) 117 | user_types = [v.user_type for v in app.user_types] 118 | 119 | try: 120 | data = frappe.get_all( 121 | "User", 122 | fields=["name", "full_name", "user_image"], 123 | filters={ 124 | "enabled": 1, 125 | "name": ["!=", frappe.session.user], 126 | "user_type": ["in", user_types], 127 | "last_active": [">=", start], 128 | }, 129 | order_by="full_name asc", 130 | limit_page_length=0, 131 | ) 132 | 133 | if app.refresh_interval >= _CACHE_INTERVAL_: 134 | set_cache(_CACHE_, cache_key, _dict({ 135 | "data": data, 136 | "expiry": add_to_date( 137 | now_dt, minutes=_CACHE_INTERVAL_, 138 | as_string=True, as_datetime=True 139 | ) 140 | })) 141 | 142 | return {"users": data} 143 | 144 | except Exception as exc: 145 | log_error(exc) 146 | return {"error": 1, "message": _("Unable to get the list of active users.")} -------------------------------------------------------------------------------- /active_users/utils/common.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | import json 8 | 9 | import frappe 10 | 11 | 12 | _LOGGER = frappe.logger("active_users", file_count=50) 13 | 14 | 15 | _SETTINGS_ = "Active Users Settings" 16 | _CACHE_ = "Active Users" 17 | _CACHE_INTERVAL_ = 10 18 | 19 | 20 | def error(msg, throw=True): 21 | frappe.log_error("Active Users", msg) 22 | if throw: 23 | frappe.throw(msg, title="Active Users") 24 | 25 | 26 | def log_error(data): 27 | if _LOGGER: 28 | _LOGGER.error(data) 29 | 30 | 31 | def get_cache(dt, key): 32 | return frappe.cache().hget(dt, key) 33 | 34 | 35 | def set_cache(dt, key, data): 36 | frappe.cache().hset(dt, key, data) 37 | 38 | 39 | def del_cache(dt, key): 40 | frappe.cache().hdel(dt, key) 41 | 42 | 43 | def clear_cache(dt): 44 | frappe.cache().delete_key(dt) 45 | 46 | 47 | def clear_document_cache(dt, name=None): 48 | if name is None: 49 | name = dt 50 | 51 | frappe.clear_cache(doctype=dt) 52 | frappe.clear_document_cache(dt, name) 53 | clear_cache(dt) 54 | 55 | 56 | def get_cached_doc(dt, name=None, for_update=False): 57 | if name is None: 58 | name = dt 59 | elif isinstance(name, bool): 60 | for_update = name 61 | name = dt 62 | 63 | if for_update: 64 | clear_document_cache(dt) 65 | 66 | return frappe.get_cached_doc(dt, name, for_update=for_update) 67 | 68 | 69 | def settings(for_update=False): 70 | return get_cached_doc(_SETTINGS_, for_update) 71 | 72 | 73 | def parse_json(data, default=None): 74 | if default is None: 75 | default = data 76 | 77 | try: 78 | return json.loads(data) 79 | except Exception: 80 | return default -------------------------------------------------------------------------------- /active_users/utils/update.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | import json 8 | import re 9 | 10 | import frappe 11 | from frappe import _ 12 | from frappe.utils import ( 13 | cint, 14 | get_request_session, 15 | now, 16 | markdown 17 | ) 18 | 19 | from frappe.desk.doctype.notification_settings.notification_settings import ( 20 | is_notifications_enabled 21 | ) 22 | 23 | from active_users import __version__ 24 | from active_users.version import __frappe_version_min_15__ 25 | from .common import ( 26 | _SETTINGS_, 27 | log_error, 28 | settings, 29 | parse_json 30 | ) 31 | 32 | 33 | # Daily Schedule 34 | def auto_check_for_update(): 35 | doc = settings() 36 | if cint(doc.enabled) and cint(doc.auto_check_for_update): 37 | check_for_update() 38 | 39 | 40 | # Self 41 | def check_for_update(): 42 | try: 43 | http = get_request_session() 44 | request = http.request( 45 | "GET", 46 | "https://api.github.com/repos/kid1194/frappe-active-users/releases/latest" 47 | ) 48 | status_code = request.status_code 49 | data = request.json() 50 | except Exception as exc: 51 | log_error(exc) 52 | return 0 53 | 54 | if status_code != 200 and status_code != 201: 55 | return 0 56 | 57 | data = parse_json(data) 58 | 59 | if ( 60 | not data or not isinstance(data, dict) or 61 | not data.get("tag_name") or not data.get("body") 62 | ): 63 | return 0 64 | 65 | latest_version = re.sub("[^0-9\.]", "", str(data.get("tag_name"))) 66 | has_update = 1 if compare_versions(latest_version, __version__) > 0 else 0 67 | 68 | doc = settings(True) 69 | doc.has_update = has_update 70 | doc.latest_check = now() 71 | 72 | if has_update: 73 | doc.latest_version = latest_version 74 | if cint(doc.send_update_notification): 75 | enqueue_send_notification( 76 | latest_version, 77 | doc.update_notification_sender, 78 | [v.user for v in doc.update_notification_receivers], 79 | markdown(response.get("body")) 80 | ) 81 | 82 | doc.save(ignore_permissions=True) 83 | return 1 84 | 85 | 86 | ## Self 87 | def compare_versions(verA, verB): 88 | verA = verA.split(".") 89 | lenA = len(verA) 90 | verB = verB.split(".") 91 | lenB = len(verB) 92 | 93 | if lenA > lenB: 94 | for i in range(lenB, lenA): 95 | verB.append(0) 96 | elif lenA < lenB: 97 | for i in range(lenA, lenB): 98 | verA.append(0) 99 | 100 | for a, b in zip(verA, verB): 101 | d = cint(a) - cint(b) 102 | if d == 0: 103 | continue 104 | return 1 if d > 0 else -1 105 | 106 | return 0 107 | 108 | 109 | ## Self 110 | def enqueue_send_notification(version, sender, receivers, message): 111 | if __frappe_version_min_15__: 112 | frappe.enqueue( 113 | "active_users.utils.update.send_notification", 114 | job_id=f"active_users-send-notification-{version}", 115 | is_async=True, 116 | version=version, 117 | sender=sender, 118 | receivers=receivers, 119 | message=message 120 | ) 121 | else: 122 | frappe.enqueue( 123 | "active_users.utils.update.send_notification", 124 | job_name=f"active_users-send-notification-{version}", 125 | is_async=True, 126 | version=version, 127 | sender=sender, 128 | receivers=receivers, 129 | message=message 130 | ) 131 | 132 | 133 | ## Self 134 | def send_notification(version, sender, receivers, message): 135 | for receiver in receivers: 136 | if is_notifications_enabled(user): 137 | (frappe.new_doc("Notification Log") 138 | .update({ 139 | "document_type": _SETTINGS_, 140 | "document_name": _SETTINGS_, 141 | "from_user": sender, 142 | "for_user": receiver, 143 | "subject": "{0}: {1}".format( 144 | _("Active Users"), _("A New Version Is Available") 145 | ), 146 | "type": "Alert", 147 | "email_content": "

{0} {1}

{2}".format( 148 | _("Version"), version, message 149 | ), 150 | }) 151 | .insert(ignore_permissions=True, ignore_mandatory=True)) -------------------------------------------------------------------------------- /active_users/version.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from frappe import __version__ as frappe_version 8 | 9 | 10 | __frappe_version__ = int(frappe_version.split(".")[0]) 11 | __frappe_version_min_15__ = __frappe_version__ > 14 12 | __frappe_version_min_14__ = __frappe_version__ > 13 -------------------------------------------------------------------------------- /images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kid1194/frappe-active-users/6f9b2161291a644948fab53de766bf280e2640c5/images/image.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "active_users" 3 | authors = [ 4 | {name = "Ameen Ahmed (Level Up)", email = "kid1194@gmail.com"} 5 | ] 6 | description = "Frappe plugin that displays a list of current active users." 7 | keywords = ["frappe", "users", "active users"] 8 | classifiers = [ 9 | "Development Status :: 5 - Production/Stable", 10 | "License :: OSI Approved :: MIT License", 11 | "Programming Language :: JavaScript" 12 | ] 13 | requires-python = ">=3.6" 14 | readme = "README.md" 15 | dynamic = ["version"] 16 | dependencies = [ 17 | "frappe>=13.0.0" 18 | ] 19 | 20 | [project.urls] 21 | Documentation = "https://github.com/kid1194/frappe-active-users" 22 | Source = "https://github.com/kid1194/frappe-active-users" 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | frappe>=13.0.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Active Users © 2023 2 | # Author: Ameen Ahmed 3 | # Company: Level Up Marketing & Software Development Services 4 | # Licence: Please refer to LICENSE file 5 | 6 | 7 | from setuptools import setup, find_packages 8 | from active_users import __version__ as version 9 | 10 | 11 | with open("requirements.txt") as f: 12 | install_requires = f.read().strip().split("\n") 13 | 14 | 15 | setup( 16 | name="active_users", 17 | version=version, 18 | description="Frappe plugin that displays a list of current active users.", 19 | author="Ameen Ahmed (Level Up)", 20 | author_email="kid1194@gmail.com", 21 | packages=find_packages(), 22 | zip_safe=False, 23 | include_package_data=True, 24 | install_requires=install_requires 25 | ) --------------------------------------------------------------------------------