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:
Rushabh Mehta 2020-09-03 11:12:23 +05:30 committed by GitHub
commit 38e024ebbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 315 additions and 24 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -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')

View file

@ -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`)

View file

@ -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")

View file

@ -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)

View 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) {
// }
});

View 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
}

View 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

View 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

View 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');
});
}
});

View 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
}

View 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()

View 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')

View file

@ -134,7 +134,8 @@ log_types = (
'Notification Log',
'Email Queue',
'DocShare',
'Document Follow'
'Document Follow',
'Console Log'
)
def delete_fields(args_dict, delete=0):

View file

@ -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:

View file

@ -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) {

View file

@ -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