├── .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 |
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 | + ''
29 | + '- '
30 | + __(
31 | 'If the user has any of the listed {0}, '
32 | + 'then the visibility of the plugin will depend on the '
33 | + 'status of the {1} checkbox.',
34 | [
35 | '
' + __('Roles') + '
',
36 | '' + __('Hidden From Listed Roles') + '
'
37 | ]
38 | )
39 | + ' '
40 | + '- '
41 | + __(
42 | 'If the user exists in the listed {0}, '
43 | + 'then the visibility of the plugin will '
44 | + 'depend on the status of the {1} checkbox, '
45 | + 'bypassing the visibility status of listed {2}.',
46 | [
47 | '
' + __('Users') + '
',
48 | '' + __('Hidden From Listed Users') + '
',
49 | '' + __('Roles') + '
'
50 | ]
51 | )
52 | + ' '
53 | + '
'
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 | + '' + frm._update.tags[idx] + '>'
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 |
114 |
117 |
118 |
119 |
145 |
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 | )
--------------------------------------------------------------------------------