Merge pull request #11306 from rmehta/system-console
feat(system console): Added a System Console to help in debugging and Console Log
This commit is contained in:
commit
38e024ebbb
21 changed files with 315 additions and 24 deletions
|
|
@ -8,7 +8,8 @@
|
|||
"label",
|
||||
"action_type",
|
||||
"action",
|
||||
"group"
|
||||
"group",
|
||||
"hidden"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -31,20 +32,28 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Action Type",
|
||||
"options": "Server Action",
|
||||
"options": "Server Action\nRoute",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Action",
|
||||
"label": "Action / Route",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-09-24 09:11:39.860100",
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:03.845315",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Action",
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"script_type",
|
||||
"disabled",
|
||||
"column_break_3",
|
||||
"reference_doctype",
|
||||
"doctype_event",
|
||||
"api_method",
|
||||
"allow_guest",
|
||||
"column_break_3",
|
||||
"disabled",
|
||||
"section_break_8",
|
||||
"script",
|
||||
"help_section",
|
||||
|
|
@ -85,8 +85,9 @@
|
|||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-07 13:13:02.483963",
|
||||
"modified": "2020-08-24 16:44:41.060350",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ class ServerScript(Document):
|
|||
# validate if guest is allowed
|
||||
if frappe.session.user == 'Guest' and not self.allow_guest:
|
||||
raise frappe.PermissionError
|
||||
safe_exec(self.script)
|
||||
_globals, _locals = safe_exec(self.script)
|
||||
return _globals.frappe.flags # output can be stored in flags
|
||||
else:
|
||||
# wrong report type!
|
||||
raise frappe.DoesNotExistError
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@ if "validate" in doc.description:
|
|||
allow_guest = 1,
|
||||
script = '''
|
||||
frappe.response['message'] = 'hello'
|
||||
'''
|
||||
),
|
||||
dict(
|
||||
name='test_return_value',
|
||||
script_type = 'API',
|
||||
api_method = 'test_return_value',
|
||||
allow_guest = 1,
|
||||
script = '''
|
||||
frappe.flags = 'hello'
|
||||
'''
|
||||
)
|
||||
]
|
||||
|
|
@ -73,3 +82,6 @@ class TestServerScript(unittest.TestCase):
|
|||
response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual("hello", response.json()["message"])
|
||||
|
||||
def test_api_return(self):
|
||||
self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello')
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ CREATE TABLE `tabDocType Action` (
|
|||
`label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `modified` (`modified`)
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ CREATE TABLE "tabDocType Action" (
|
|||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"label" varchar(140) NOT NULL,
|
||||
"group" varchar(140) DEFAULT NULL,
|
||||
"group" text DEFAULT NULL,
|
||||
"action_type" varchar(140) NOT NULL,
|
||||
"action" varchar(140) NOT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class PostgresTable(DBTable):
|
|||
elif col.fieldtype in ("Check"):
|
||||
using_clause = "USING {}::smallint".format(col.fieldname)
|
||||
|
||||
query.append("ALTER COLUMN {0} TYPE {1} {2}".format(
|
||||
query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format(
|
||||
col.fieldname,
|
||||
get_definition(col.fieldtype, precision=col.precision, length=col.length),
|
||||
using_clause)
|
||||
|
|
|
|||
0
frappe/desk/doctype/console_log/__init__.py
Normal file
0
frappe/desk/doctype/console_log/__init__.py
Normal file
8
frappe/desk/doctype/console_log/console_log.js
Normal file
8
frappe/desk/doctype/console_log/console_log.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Console Log', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
52
frappe/desk/doctype/console_log/console_log.json
Normal file
52
frappe/desk/doctype/console_log/console_log.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "format:Log on {timestamp}",
|
||||
"creation": "2020-08-18 19:56:12.336427",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"script",
|
||||
"output"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "script",
|
||||
"fieldtype": "Code",
|
||||
"in_list_view": 1,
|
||||
"label": "Script",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "output",
|
||||
"fieldtype": "Code",
|
||||
"label": "Output",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-18 20:07:57.587344",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Console Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/desk/doctype/console_log/console_log.py
Normal file
10
frappe/desk/doctype/console_log/console_log.py
Normal file
|
|
@ -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 ConsoleLog(Document):
|
||||
pass
|
||||
10
frappe/desk/doctype/console_log/test_console_log.py
Normal file
10
frappe/desk/doctype/console_log/test_console_log.py
Normal file
|
|
@ -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 TestConsoleLog(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/system_console/__init__.py
Normal file
0
frappe/desk/doctype/system_console/__init__.py
Normal file
21
frappe/desk/doctype/system_console/system_console.js
Normal file
21
frappe/desk/doctype/system_console/system_console.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('System Console', {
|
||||
onload: function(frm) {
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: 'shift+enter',
|
||||
action: () => frm.execute_action('Execute'),
|
||||
page: frm.page,
|
||||
description: __('Execute Console script'),
|
||||
ignore_inputs: true,
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.page.set_primary_action(__("Execute"), () => {
|
||||
frm.execute_action('Execute');
|
||||
});
|
||||
}
|
||||
});
|
||||
68
frappe/desk/doctype/system_console/system_console.json
Normal file
68
frappe/desk/doctype/system_console/system_console.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "#List/Console Log/List",
|
||||
"action_type": "Route",
|
||||
"label": "Logs"
|
||||
},
|
||||
{
|
||||
"action": "frappe.desk.doctype.system_console.system_console.execute_code",
|
||||
"action_type": "Server Action",
|
||||
"hidden": 1,
|
||||
"label": "Execute"
|
||||
}
|
||||
],
|
||||
"creation": "2020-08-18 17:44:35.647815",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"console",
|
||||
"commit",
|
||||
"output"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"description": "To print output use <code>log(text)</code>",
|
||||
"fieldname": "console",
|
||||
"fieldtype": "Code",
|
||||
"label": "Console",
|
||||
"options": "Python"
|
||||
},
|
||||
{
|
||||
"fieldname": "output",
|
||||
"fieldtype": "Code",
|
||||
"label": "Output",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "commit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Commit"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:35.296877",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "System Console",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"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
|
||||
}
|
||||
38
frappe/desk/doctype/system_console/system_console.py
Normal file
38
frappe/desk/doctype/system_console/system_console.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils.safe_exec import safe_exec
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SystemConsole(Document):
|
||||
def run(self):
|
||||
frappe.only_for('System Manager')
|
||||
try:
|
||||
frappe.debug_log = []
|
||||
safe_exec(self.console)
|
||||
self.output = '\n'.join(frappe.debug_log)
|
||||
except: # noqa: E722
|
||||
self.output = frappe.get_traceback()
|
||||
|
||||
if self.commit:
|
||||
frappe.db.commit()
|
||||
else:
|
||||
frappe.db.rollback()
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype='Console Log',
|
||||
script=self.console,
|
||||
output=self.output)).insert()
|
||||
frappe.db.commit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_code(doc):
|
||||
console = frappe.get_doc(json.loads(doc))
|
||||
console.run()
|
||||
return console.as_dict()
|
||||
20
frappe/desk/doctype/system_console/test_system_console.py
Normal file
20
frappe/desk/doctype/system_console/test_system_console.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestSystemConsole(unittest.TestCase):
|
||||
def test_system_console(self):
|
||||
system_console = frappe.get_doc('System Console')
|
||||
system_console.console = 'log("hello")'
|
||||
system_console.run()
|
||||
|
||||
self.assertEqual(system_console.output, 'hello')
|
||||
|
||||
system_console.console = 'log(frappe.db.get_value("DocType", "DocType", "module"))'
|
||||
system_console.run()
|
||||
|
||||
self.assertEqual(system_console.output, 'Core')
|
||||
|
|
@ -134,7 +134,8 @@ log_types = (
|
|||
'Notification Log',
|
||||
'Email Queue',
|
||||
'DocShare',
|
||||
'Document Follow'
|
||||
'Document Follow',
|
||||
'Console Log'
|
||||
)
|
||||
|
||||
def delete_fields(args_dict, delete=0):
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ def parse_naming_series(parts, doctype='', doc=''):
|
|||
part = today.strftime("%d")
|
||||
elif e == 'YYYY':
|
||||
part = today.strftime('%Y')
|
||||
elif e == 'timestamp':
|
||||
part = str(today)
|
||||
elif e == 'FY':
|
||||
part = frappe.defaults.get_user_default("fiscal_year")
|
||||
elif e.startswith('{') and doc:
|
||||
|
|
|
|||
|
|
@ -321,22 +321,52 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
for (let action of this.meta.actions) {
|
||||
frappe.ui.form.on(this.doctype, 'refresh', () => {
|
||||
if (!this.is_new()) {
|
||||
this.add_custom_button(action.label, () => {
|
||||
if (action.action_type==='Server Action') {
|
||||
frappe.xcall(action.action, {doc: this.doc}).then(() => {
|
||||
frappe.msgprint({
|
||||
message: __('{} Complete', [action.label]),
|
||||
alert: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}, action.group);
|
||||
if (!action.hidden) {
|
||||
this.add_custom_button(action.label, () => {
|
||||
this.execute_action(action);
|
||||
}, action.group);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execute_action(action) {
|
||||
if (typeof action === 'string') {
|
||||
// called by label - maybe via custom script
|
||||
// frm.execute_action('Action')
|
||||
for (let _action of this.meta.actions) {
|
||||
if (_action.label === action) {
|
||||
action = _action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof action === 'string') {
|
||||
frappe.throw(`Action ${action} not found`);
|
||||
}
|
||||
}
|
||||
if (action.action_type==='Server Action') {
|
||||
frappe.xcall(action.action, {doc: this.doc}).then((doc) => {
|
||||
if (doc.doctype) {
|
||||
// document is returned by the method,
|
||||
// apply the changes locally and refresh
|
||||
frappe.model.sync(doc);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
// feedback
|
||||
frappe.msgprint({
|
||||
message: __('{} Complete', [action.label]),
|
||||
alert: true
|
||||
});
|
||||
});
|
||||
} else if (action.action_type==='Route') {
|
||||
frappe.set_route(action.action);
|
||||
}
|
||||
}
|
||||
|
||||
switch_doc(docname) {
|
||||
// record switch
|
||||
if(this.docname != docname && (!this.meta.in_dialog || this.in_form) && !this.meta.istable) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ def safe_exec(script, _globals=None, _locals=None):
|
|||
# execute script compiled by RestrictedPython
|
||||
exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
|
||||
|
||||
return exec_globals, _locals
|
||||
|
||||
def get_safe_globals():
|
||||
datautils = frappe._dict()
|
||||
if frappe.db:
|
||||
|
|
@ -48,6 +50,7 @@ def get_safe_globals():
|
|||
# make available limited methods of frappe
|
||||
json=json,
|
||||
dict=dict,
|
||||
log=frappe.log,
|
||||
_dict=frappe._dict,
|
||||
frappe=frappe._dict(
|
||||
flags=frappe._dict(),
|
||||
|
|
@ -99,7 +102,8 @@ def get_safe_globals():
|
|||
scrub=scrub,
|
||||
guess_mimetype=mimetypes.guess_type,
|
||||
html2text=html2text,
|
||||
dev_server=1 if os.environ.get('DEV_SERVER', False) else 0
|
||||
dev_server=1 if os.environ.get('DEV_SERVER', False) else 0,
|
||||
run_script=run_script
|
||||
)
|
||||
|
||||
add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception))
|
||||
|
|
@ -142,6 +146,10 @@ def read_sql(query, *args, **kwargs):
|
|||
else:
|
||||
raise frappe.PermissionError('Only SELECT SQL allowed in scripting')
|
||||
|
||||
def run_script(script):
|
||||
'''run another server script'''
|
||||
return frappe.get_doc('Server Script', script).execute_method()
|
||||
|
||||
def _getitem(obj, key):
|
||||
# guard function for RestrictedPython
|
||||
# allow any key to be accessed as long as it does not start with underscore
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue