diff --git a/frappe/config/settings.py b/frappe/config/settings.py index e43abd9fcb..0112c7ccff 100644 --- a/frappe/config/settings.py +++ b/frappe/config/settings.py @@ -23,6 +23,11 @@ def get_data(): "description": _("Company, Fiscal Year and Currency defaults"), "hide_count": True }, + { + "type": "doctype", + "name": "Log Settings", + "description": _("Log cleanup and notification configuration") + }, { "type": "doctype", "name": "Error Log", diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index 27a2892ca8..98dc91806d 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -40,7 +40,11 @@ def add_authentication_log(subject, user, operation="Login", status="Success"): "operation": operation, }).insert(ignore_permissions=True, ignore_links=True) -def clear_authentication_logs(): - """clear 100 day old authentication logs""" +def clear_activity_logs(days=None): + """clear 90 day old authentication logs or configured in log settings""" + + if not days: + days = 90 + frappe.db.sql("""delete from `tabActivity Log` where \ - creation< (NOW() - INTERVAL '100' DAY)""") \ No newline at end of file + creation< (NOW() - INTERVAL '{0}' DAY)""".format(days)) \ No newline at end of file diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py index 6cc8275404..ec02aaf446 100644 --- a/frappe/core/doctype/error_log/error_log.py +++ b/frappe/core/doctype/error_log/error_log.py @@ -17,9 +17,6 @@ def set_old_logs_as_seen(): frappe.db.sql("""UPDATE `tabError Log` SET `seen`=1 WHERE `seen`=0 AND `creation` < (NOW() - INTERVAL '7' DAY)""") - # clear old logs - frappe.db.sql("""DELETE FROM `tabError Log` WHERE `creation` < (NOW() - INTERVAL '30' DAY)""") - @frappe.whitelist() def clear_error_logs(): '''Flush all Error Logs''' diff --git a/frappe/core/doctype/log_setting_user/__init__.py b/frappe/core/doctype/log_setting_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/log_setting_user/log_setting_user.js b/frappe/core/doctype/log_setting_user/log_setting_user.js new file mode 100644 index 0000000000..a1eb824e22 --- /dev/null +++ b/frappe/core/doctype/log_setting_user/log_setting_user.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Log Setting User', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/log_setting_user/log_setting_user.json b/frappe/core/doctype/log_setting_user/log_setting_user.json new file mode 100644 index 0000000000..7f4b0ef874 --- /dev/null +++ b/frappe/core/doctype/log_setting_user/log_setting_user.json @@ -0,0 +1,34 @@ +{ + "actions": [], + "autoname": "field:user", + "creation": "2020-10-08 13:09:36.034430", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1, + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-10-08 17:22:04.690348", + "modified_by": "Administrator", + "module": "Core", + "name": "Log Setting User", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/log_setting_user/log_setting_user.py b/frappe/core/doctype/log_setting_user/log_setting_user.py new file mode 100644 index 0000000000..df6d55f0a9 --- /dev/null +++ b/frappe/core/doctype/log_setting_user/log_setting_user.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class LogSettingUser(Document): + pass diff --git a/frappe/core/doctype/log_setting_user/test_log_setting_user.py b/frappe/core/doctype/log_setting_user/test_log_setting_user.py new file mode 100644 index 0000000000..507c02d87d --- /dev/null +++ b/frappe/core/doctype/log_setting_user/test_log_setting_user.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestLogSettingUser(unittest.TestCase): + pass diff --git a/frappe/core/doctype/log_settings/__init__.py b/frappe/core/doctype/log_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/log_settings/log_settings.js b/frappe/core/doctype/log_settings/log_settings.js new file mode 100644 index 0000000000..09a2086a1d --- /dev/null +++ b/frappe/core/doctype/log_settings/log_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Log Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/log_settings/log_settings.json b/frappe/core/doctype/log_settings/log_settings.json new file mode 100644 index 0000000000..8a2596b35c --- /dev/null +++ b/frappe/core/doctype/log_settings/log_settings.json @@ -0,0 +1,83 @@ +{ + "actions": [], + "creation": "2020-10-08 12:12:21.694424", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "error_log_notification_section", + "users_to_notify", + "log_cleanup_section", + "clear_error_log_after", + "clear_activity_log_after", + "column_break_4", + "clear_email_queue_after" + ], + "fields": [ + { + "fieldname": "log_cleanup_section", + "fieldtype": "Section Break", + "label": "Log Cleanup" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "error_log_notification_section", + "fieldtype": "Section Break", + "label": "Error Log Notification" + }, + { + "fieldname": "users_to_notify", + "fieldtype": "Table MultiSelect", + "label": "Users To Notify", + "options": "Log Setting User" + }, + { + "default": "90", + "description": "In Days", + "fieldname": "clear_error_log_after", + "fieldtype": "Int", + "label": "Clear Error log After" + }, + { + "default": "90", + "description": "In Days", + "fieldname": "clear_activity_log_after", + "fieldtype": "Int", + "label": "Clear Activity Log After" + }, + { + "default": "90", + "description": "In Days", + "fieldname": "clear_email_queue_after", + "fieldtype": "Int", + "label": "Clear Email Queue After" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-10-13 12:18:48.649038", + "modified_by": "Administrator", + "module": "Core", + "name": "Log Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py new file mode 100644 index 0000000000..6d59cdeb29 --- /dev/null +++ b/frappe/core/doctype/log_settings/log_settings.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.model.document import Document + +class LogSettings(Document): + def clear_logs(self): + self.clear_error_logs() + self.clear_activity_logs() + self.clear_email_queue() + + def clear_error_logs(self): + frappe.db.sql(""" DELETE FROM `tabError Log` + WHERE `creation` < (NOW() - INTERVAL '{0}' DAY) + """.format(self.clear_error_log_after)) + + def clear_activity_logs(self): + from frappe.core.doctype.activity_log.activity_log import clear_activity_logs + clear_activity_logs(days=self.clear_activity_log_after) + + def clear_email_queue(self): + from frappe.email.queue import clear_outbox + clear_outbox(days=self.clear_email_queue_after) + +def run_log_clean_up(): + doc = frappe.get_doc("Log Settings") + doc.clear_logs() + +@frappe.whitelist() +def has_unseen_error_log(user): + + def _get_response(show_alert=True): + return { + 'show_alert': True, + 'message': _("You have unseen {0}").format(' Error Logs ') + } + + if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"): + log_settings = frappe.get_cached_doc('Log Settings') + + if log_settings.users_to_notify: + if user in [u.user for u in log_settings.users_to_notify]: + return _get_response() + else: + return _get_response(show_alert=False) + else: + return _get_response() \ No newline at end of file diff --git a/frappe/core/doctype/log_settings/test_log_settings.py b/frappe/core/doctype/log_settings/test_log_settings.py new file mode 100644 index 0000000000..2824c71c88 --- /dev/null +++ b/frappe/core/doctype/log_settings/test_log_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestLogSettings(unittest.TestCase): + pass diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index e7db6f9045..d0a65defa4 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -22,7 +22,7 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(all_job.frequency, 'All') daily_job = frappe.get_doc('Scheduled Job Type', - dict(method='frappe.email.queue.clear_outbox')) + dict(method='frappe.email.queue.set_expiry_for_email_queue')) self.assertEqual(daily_job.frequency, 'Daily') # check if cron jobs are synced @@ -38,7 +38,7 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(updated_scheduled_job.frequency, "Hourly") def test_daily_job(self): - job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox')) + job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.set_expiry_for_email_queue')) job.db_set('last_execution', '2019-01-01 00:00:00') self.assertTrue(job.is_event_due(get_datetime('2019-01-02 00:00:06'))) self.assertFalse(job.is_event_due(get_datetime('2019-01-01 00:00:06'))) diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 25b6f93f83..f780aebdc1 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -584,14 +584,15 @@ def prepare_message(email, recipient, recipients_list): return safe_encode(message.as_string()) -def clear_outbox(): - """Remove low priority older than 31 days in Outbox and expire mails not sent for 7 days. - Called daily via scheduler. +def clear_outbox(days=None): + """Remove low priority older than 31 days in Outbox or configured in Log Settings. Note: Used separate query to avoid deadlock """ + if not days: + days=31 email_queues = frappe.db.sql_list("""SELECT `name` FROM `tabEmail Queue` - WHERE `priority`=0 AND `modified` < (NOW() - INTERVAL '31' DAY)""") + WHERE `priority`=0 AND `modified` < (NOW() - INTERVAL '{0}' DAY)""".format(days)) if email_queues: frappe.db.sql("""DELETE FROM `tabEmail Queue` WHERE `name` IN ({0})""".format( @@ -602,6 +603,11 @@ def clear_outbox(): ','.join(['%s']*len(email_queues) )), tuple(email_queues)) +def set_expiry_for_email_queue(): + ''' Mark emails as expire that has not sent for 7 days. + Called daily via scheduler. + ''' + frappe.db.sql(""" UPDATE `tabEmail Queue` SET `status`='Expired' diff --git a/frappe/hooks.py b/frappe/hooks.py index c68c69a3bb..ff90cf0af6 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -206,7 +206,7 @@ scheduler_events = { "frappe.utils.password.delete_password_reset_cache" ], "daily": [ - "frappe.email.queue.clear_outbox", + "frappe.email.queue.set_expiry_for_email_queue", "frappe.desk.notifications.clear_notifications", "frappe.core.doctype.error_log.error_log.set_old_logs_as_seen", "frappe.desk.doctype.event.event.send_event_digest", @@ -215,7 +215,6 @@ scheduler_events = { "frappe.realtime.remove_old_task_logs", "frappe.utils.scheduler.restrict_scheduler_events_if_dormant", "frappe.email.doctype.auto_email_report.auto_email_report.send_daily", - "frappe.core.doctype.activity_log.activity_log.clear_authentication_logs", "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record", "frappe.desk.form.document_follow.send_daily_updates", "frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points", @@ -223,7 +222,8 @@ scheduler_events = { "frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", "frappe.automation.doctype.auto_repeat.auto_repeat.set_auto_repeat_as_completed", "frappe.email.doctype.unhandled_email.unhandled_email.remove_old_unhandled_emails", - "frappe.core.doctype.prepared_report.prepared_report.delete_expired_prepared_reports" + "frappe.core.doctype.prepared_report.prepared_report.delete_expired_prepared_reports", + "frappe.core.doctype.log_settings.log_settings.run_log_clean_up" ], "daily_long": [ "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily", diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 97d5239a72..99c1647455 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -139,6 +139,26 @@ frappe.Application = Class.extend({ } }); }, 300000); // check every 5 minutes + + if(frappe.user.has_role("System Manager")){ + setInterval(function() { + frappe.call({ + method: 'frappe.core.doctype.log_settings.log_settings.has_unseen_error_log', + args: { + user: frappe.session.user + }, + callback: function(r) { + console.log(r); + if(r.message.show_alert){ + frappe.show_alert({ + indicator: 'red', + message: r.message.message + }); + } + } + }); + }, 600000); // check every 10 minutes + } } this.fetch_tags(); diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 0efcb47948..ff7e6d534c 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -130,8 +130,10 @@ class TestEmail(unittest.TestCase): def test_expired(self): self.test_email_queue() frappe.db.sql("UPDATE `tabEmail Queue` SET `modified`=(NOW() - INTERVAL '8' day)") - from frappe.email.queue import clear_outbox - clear_outbox() + + from frappe.email.queue import set_expiry_for_email_queue + set_expiry_for_email_queue() + email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Expired'""", as_dict=1) self.assertEqual(len(email_queue), 1) queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient` diff --git a/frappe/tests/test_scheduler.py b/frappe/tests/test_scheduler.py index 30818e6f17..df3d735488 100644 --- a/frappe/tests/test_scheduler.py +++ b/frappe/tests/test_scheduler.py @@ -31,7 +31,7 @@ class TestScheduler(TestCase): enqueue_events(site = frappe.local.site) frappe.flags.execute_job = False - self.assertTrue('frappe.email.queue.clear_outbox', frappe.flags.enqueued_jobs) + self.assertTrue('frappe.email.queue.set_expiry_for_email_queue', frappe.flags.enqueued_jobs) self.assertTrue('frappe.utils.change_log.check_for_update', frappe.flags.enqueued_jobs) self.assertTrue('frappe.email.doctype.auto_email_report.auto_email_report.send_monthly', frappe.flags.enqueued_jobs)