Merge branch 'develop' of github.com:frappe/frappe into dashdash
8
.snyk
|
|
@ -1,5 +1,5 @@
|
|||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.14.1
|
||||
version: v1.13.5
|
||||
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||
ignore:
|
||||
SNYK-JS-AWESOMPLETE-174474:
|
||||
|
|
@ -10,6 +10,10 @@ ignore:
|
|||
- showdown > yargs > os-locale > mem:
|
||||
reason: No patch available
|
||||
expires: '2019-06-11T14:12:04.995Z'
|
||||
SNYK-PYTHON-PYYAML-550022:
|
||||
- '*':
|
||||
reason: Project is not directly dependant on the package
|
||||
expires: 2021-04-01T18:02:21.256Z
|
||||
# patches apply the minimum changes required to fix a vulnerability
|
||||
patch:
|
||||
'npm:extend:20180424':
|
||||
|
|
@ -18,5 +22,3 @@ patch:
|
|||
SNYK-JS-LODASH-450202:
|
||||
- frappe-datatable > lodash:
|
||||
patched: '2020-01-31T01:33:09.889Z'
|
||||
- snyk > snyk-nuget-plugin > dotnet-deps-parser > lodash:
|
||||
patched: '2020-02-21T02:41:07.568Z'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ context('API Resources', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
it('Creates two Comments', () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ context('Awesome Bar', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Barcode', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
function get_dialog_with_barcode() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Link', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.create_records({
|
||||
doctype: 'ToDo',
|
||||
description: 'this is a test todo for link'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Rating', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
function get_dialog_with_rating() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name;
|
|||
context('Control Date, Time and DateTime', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.insert_doc('DocType', datetime_doctype, true);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Depends On', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Depends On',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('FileUploader', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
function open_upload_dialog() {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Grid Pagination', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('List View', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('List View Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
it('Default settings', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
it('add custom column in report', () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ context('Recorder', () => {
|
|||
});
|
||||
|
||||
it('Navigate to Recorder', () => {
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.awesomebar('recorder');
|
||||
cy.get('h1').should('contain', 'Recorder');
|
||||
cy.location('hash').should('eq', '#recorder');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Relative Timeframe', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name;
|
|||
context('Report View', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.insert_doc('DocType', custom_submittable_doctype, true);
|
||||
cy.clear_cache();
|
||||
cy.insert_doc(doctype_name, {
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals
|
|||
doctype = doc.doctype
|
||||
|
||||
import frappe.permissions
|
||||
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user)
|
||||
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user, raise_exception=throw)
|
||||
if throw and not out:
|
||||
if doc:
|
||||
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from frappe.utils.error import make_error_snapshot
|
|||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request
|
||||
from frappe import _
|
||||
import frappe.recorder
|
||||
import frappe.monitor
|
||||
|
||||
local_manager = LocalManager([frappe.local])
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ def application(request):
|
|||
init_request(request)
|
||||
|
||||
frappe.recorder.record()
|
||||
frappe.monitor.start()
|
||||
|
||||
if frappe.local.form_dict.cmd:
|
||||
response = frappe.handler.handle()
|
||||
|
|
@ -91,6 +93,7 @@ def application(request):
|
|||
if response and hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
frappe.monitor.stop(response)
|
||||
frappe.recorder.dump()
|
||||
|
||||
frappe.destroy()
|
||||
|
|
|
|||
65
frappe/automation/desk_page/tools/tools.json
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"icon": "octicon octicon-briefcase",
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]",
|
||||
"title": "Tools"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Email"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-cog",
|
||||
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]",
|
||||
"title": "Automation"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]",
|
||||
"title": "Event Streaming"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 14:53:24.980279",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"idx": 0,
|
||||
"label": "Tools",
|
||||
"modified": "2020-03-05 11:27:26.106013",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "ToDo",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Note",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "File",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Assignment Rule",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Auto Repeat",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -235,7 +235,7 @@ def add_home_page(bootinfo, docs):
|
|||
except (frappe.DoesNotExistError, frappe.PermissionError):
|
||||
if frappe.message_log:
|
||||
frappe.message_log.pop()
|
||||
page = frappe.desk.desk_page.get('desktop')
|
||||
page = frappe.desk.desk_page.get('workspace')
|
||||
|
||||
bootinfo['home_page'] = page.name
|
||||
docs.append(page)
|
||||
|
|
|
|||
70
frappe/core/desk_page/settings/settings.json
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"icon": "fa fa-th",
|
||||
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Data"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-envelope",
|
||||
"links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Email / Notifications"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-globe",
|
||||
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Website"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-wrench",
|
||||
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Core"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-print",
|
||||
"links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Printing"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-random",
|
||||
"links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Workflow"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:09:40.527211",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 1,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"idx": 0,
|
||||
"label": "Settings",
|
||||
"modified": "2020-03-05 11:27:25.766522",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Settings",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 1,
|
||||
"shortcuts": [
|
||||
{
|
||||
"icon": "octicon octicon-settings",
|
||||
"is_query_report": 0,
|
||||
"link_to": "System Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-print",
|
||||
"is_query_report": 0,
|
||||
"link_to": "Print Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-globe",
|
||||
"is_query_report": 0,
|
||||
"link_to": "Website Settings",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
57
frappe/core/desk_page/users/users.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"icon": "fa fa-group",
|
||||
"links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Users"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-group",
|
||||
"links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Logs"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-lock",
|
||||
"links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]",
|
||||
"title": "Permissions"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:12:16.754449",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"idx": 0,
|
||||
"label": "Users",
|
||||
"modified": "2020-03-05 11:27:26.166080",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Users",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "User",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Role",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "permission-manager",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "user-profile",
|
||||
"type": "Page"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -39,3 +39,20 @@ class ModuleDef(Document):
|
|||
|
||||
frappe.clear_cache()
|
||||
frappe.setup_module_map()
|
||||
|
||||
def on_trash(self):
|
||||
"""Delete module name from modules.txt"""
|
||||
modules = None
|
||||
if frappe.local.module_app.get(frappe.scrub(self.name)):
|
||||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f:
|
||||
content = f.read()
|
||||
if self.name in content.splitlines():
|
||||
modules = list(filter(None, content.splitlines()))
|
||||
modules.remove(self.name)
|
||||
|
||||
if modules:
|
||||
with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f:
|
||||
f.write("\n".join(modules))
|
||||
|
||||
frappe.clear_cache()
|
||||
frappe.setup_module_map()
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ class ScheduledJobType(Document):
|
|||
|
||||
def update_scheduler_log(self, status):
|
||||
if not self.create_log:
|
||||
# self.get_next_execution will work properly iff self.last_execution is properly set
|
||||
if self.frequency == "All" and status == 'Start':
|
||||
self.db_set('last_execution', now_datetime(), update_modified=False)
|
||||
frappe.db.commit()
|
||||
return
|
||||
if not self.scheduler_log:
|
||||
self.scheduler_log = frappe.get_doc(dict(doctype = 'Scheduled Job Log', scheduled_job_type=self.name)).insert(ignore_permissions=True)
|
||||
|
|
|
|||
|
|
@ -98,7 +98,11 @@ class User(Document):
|
|||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
self.send_password_notification(self.__new_password)
|
||||
create_contact(self, ignore_mandatory=True)
|
||||
frappe.enqueue(
|
||||
'frappe.core.doctype.user.user.create_contact',
|
||||
user=self,
|
||||
ignore_mandatory=True
|
||||
)
|
||||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)
|
||||
|
||||
|
|
@ -1034,7 +1038,8 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
|
|||
from frappe.contacts.doctype.contact.contact import get_contact_name
|
||||
if user.name in ["Administrator", "Guest"]: return
|
||||
|
||||
if not get_contact_name(user.email):
|
||||
contact_exists = get_contact_name(user.email)
|
||||
if not contact_exists:
|
||||
contact = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": user.first_name,
|
||||
|
|
@ -1052,6 +1057,34 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
|
|||
if user.mobile_no:
|
||||
contact.add_phone(user.mobile_no, is_primary_mobile_no=True)
|
||||
contact.insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory)
|
||||
else:
|
||||
contact = frappe.get_doc("Contact", contact_exists)
|
||||
contact.first_name = user.first_name
|
||||
contact.last_name = user.last_name
|
||||
contact.gender = user.gender
|
||||
|
||||
# Add mobile number if phone does not exists in contact
|
||||
if user.phone and not any(new_contact.phone == user.phone for new_contact in contact.phone_nos):
|
||||
# Set primary phone if there is no primary phone number
|
||||
contact.add_phone(
|
||||
user.phone,
|
||||
is_primary_phone=not any(
|
||||
new_contact.is_primary_phone == 1 for new_contact in contact.phone_nos
|
||||
)
|
||||
)
|
||||
|
||||
# Add mobile number if mobile does not exists in contact
|
||||
if user.mobile_no and not any(new_contact.phone == user.mobile_no for new_contact in contact.phone_nos):
|
||||
# Set primary mobile if there is no primary mobile number
|
||||
contact.add_phone(
|
||||
user.mobile_no,
|
||||
is_primary_mobile_no=not any(
|
||||
new_contact.is_primary_mobile_no == 1 for new_contact in contact.phone_nos
|
||||
)
|
||||
)
|
||||
|
||||
contact.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_keys(user):
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class Dashboard {
|
|||
chart_container.appendTo(this.container);
|
||||
|
||||
frappe.model.with_doc("Dashboard Chart", chart.chart).then( chart_doc => {
|
||||
let dashboard_chart = new DashboardChart(chart_doc, chart_container);
|
||||
let dashboard_chart = new frappe.ui.DashboardChart(chart_doc, chart_container);
|
||||
dashboard_chart.show();
|
||||
});
|
||||
});
|
||||
|
|
@ -215,7 +215,7 @@ class DashboardChart {
|
|||
|
||||
render_date_range_fields() {
|
||||
if (!this.date_field_wrapper || !this.date_field_wrapper.is(':visible')) {
|
||||
this.date_field_wrapper =
|
||||
this.date_field_wrapper =
|
||||
$(`<div class="dashboard-date-field pull-right"></div>`)
|
||||
.insertBefore(this.chart_container.find('.chart-wrapper'));
|
||||
|
||||
|
|
@ -304,15 +304,15 @@ class DashboardChart {
|
|||
}
|
||||
|
||||
setup_filter_button() {
|
||||
|
||||
|
||||
this.is_document_type = this.chart_doc.chart_type!== 'Report' && this.chart_doc.chart_type!=='Custom';
|
||||
this.filter_button =
|
||||
this.filter_button =
|
||||
$(`<div class="filter-chart btn btn-default btn-xs pull-right">${__("Filter")}</div>`);
|
||||
this.filter_button.prependTo(this.chart_container);
|
||||
|
||||
this.filter_button.on('click', () => {
|
||||
let fields;
|
||||
|
||||
|
||||
frappe.dashboard_utils.get_filters_for_chart_type(this.chart_doc)
|
||||
.then(filters => {
|
||||
if (!this.is_document_type) {
|
||||
|
|
@ -370,8 +370,8 @@ class DashboardChart {
|
|||
}
|
||||
|
||||
dialog.show();
|
||||
dialog.set_values(this.filters);
|
||||
|
||||
dialog.set_values(this.filters);
|
||||
|
||||
}
|
||||
|
||||
create_filter_group_and_add_filters(parent) {
|
||||
|
|
|
|||
0
frappe/core/page/workspace/__init__.py
Normal file
3
frappe/core/page/workspace/workspace.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
frappe.pages['workspace'].on_page_load = function(wrapper) {
|
||||
frappe.utils.set_title(__("Home"));
|
||||
}
|
||||
23
frappe/core/page/workspace/workspace.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2020-02-27 15:07:57.124916",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "icon-th",
|
||||
"idx": 0,
|
||||
"modified": "2020-02-27 15:07:57.124916",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "workspace",
|
||||
"owner": "Administrator",
|
||||
"page_name": "workspace",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0
|
||||
}
|
||||
50
frappe/custom/desk_page/customization/customization.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Dashboards"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-glass",
|
||||
"links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Form Customization"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Other"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:15:03.839594",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"idx": 0,
|
||||
"label": "Customization",
|
||||
"modified": "2020-03-05 11:27:26.137718",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customization",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Customize Form",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Custom Role",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"is_query_report": 0,
|
||||
"link_to": "Custom Script",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
344
frappe/desk/desktop.py
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
# Author - Shivam Mishra <shivam@frappe.io>
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _, DoesNotExistError
|
||||
from frappe.boot import get_allowed_pages, get_allowed_reports
|
||||
from frappe.cache_manager import build_domain_restriced_doctype_cache, build_domain_restriced_page_cache, build_table_count_cache
|
||||
|
||||
class Workspace:
|
||||
def __init__(self, page_name):
|
||||
self.page_name = page_name
|
||||
|
||||
def build_cache(self):
|
||||
self.doc = frappe.get_doc("Desk Page", self.page_name)
|
||||
|
||||
user = frappe.get_user()
|
||||
user.build_permissions()
|
||||
self.user = user
|
||||
|
||||
self.allowed_pages = get_allowed_pages()
|
||||
self.allowed_reports = get_allowed_reports()
|
||||
|
||||
self.table_counts = build_table_count_cache()
|
||||
self.restricted_doctypes = build_domain_restriced_doctype_cache()
|
||||
self.restricted_pages = build_domain_restriced_page_cache()
|
||||
|
||||
def is_item_allowed(self, name, item_type):
|
||||
item_type = item_type.lower()
|
||||
|
||||
if item_type == "doctype":
|
||||
return (name in self.user.can_read and name in self.restricted_doctypes)
|
||||
if item_type == "page":
|
||||
return (name in self.allowed_pages and name in self.restricted_pages)
|
||||
if item_type == "report":
|
||||
return name in self.allowed_reports
|
||||
if item_type == "help":
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def build_workspace(self):
|
||||
self.cards = {
|
||||
'label': self.doc.charts_label,
|
||||
'items': self.get_cards()
|
||||
}
|
||||
|
||||
self.charts = {
|
||||
'label': self.doc.shortcuts_label,
|
||||
'items': self.get_charts()
|
||||
}
|
||||
|
||||
self.shortcuts = {
|
||||
'label': self.doc.shortcuts_label,
|
||||
'items': self.get_shortcuts()
|
||||
}
|
||||
|
||||
def get_cards(self):
|
||||
cards = self.doc.cards + get_custom_reports_and_doctypes(self.doc.module)
|
||||
default_country = frappe.db.get_default("country")
|
||||
|
||||
def _doctype_contains_a_record(name):
|
||||
exists = self.table_counts.get(name)
|
||||
if not exists:
|
||||
if not frappe.db.get_value('DocType', name, 'issingle'):
|
||||
exists = frappe.db.count(name)
|
||||
else:
|
||||
exists = True
|
||||
self.table_counts[name] = exists
|
||||
return exists
|
||||
|
||||
def _prepare_item(item):
|
||||
if item.dependencies:
|
||||
incomplete_dependencies = [d for d in item.dependencies if not _doctype_contains_a_record(d)]
|
||||
if len(incomplete_dependencies):
|
||||
item.incomplete_dependencies = incomplete_dependencies
|
||||
|
||||
if item.onboard:
|
||||
# Mark Spotlights for initial
|
||||
if item.get("type") == "doctype":
|
||||
name = item.get("name")
|
||||
count = _doctype_contains_a_record(name)
|
||||
|
||||
item["count"] = count
|
||||
|
||||
return item
|
||||
|
||||
new_data = []
|
||||
for section in cards:
|
||||
new_items = []
|
||||
if isinstance(section.links, str):
|
||||
links = json.loads(section.links)
|
||||
else:
|
||||
links = section.links
|
||||
|
||||
for item in links:
|
||||
item = frappe._dict(item)
|
||||
|
||||
# Condition: based on country
|
||||
if item.country and item.country != default_country:
|
||||
continue
|
||||
|
||||
# Check if user is allowed to view
|
||||
if self.is_item_allowed(item.name, item.type):
|
||||
prepared_item = _prepare_item(item)
|
||||
new_items.append(item)
|
||||
|
||||
if new_items:
|
||||
if isinstance(section, frappe._dict):
|
||||
new_section = section.copy()
|
||||
else:
|
||||
new_section = section.as_dict().copy()
|
||||
new_section["links"] = new_items
|
||||
new_section["label"] = section.title
|
||||
new_data.append(new_section)
|
||||
|
||||
return new_data
|
||||
|
||||
def get_charts(self):
|
||||
if frappe.has_permission("Dashboard Chart", throw=False):
|
||||
return [chart for chart in self.doc.charts]
|
||||
return []
|
||||
|
||||
def get_shortcuts(self):
|
||||
items = []
|
||||
for item in self.doc.shortcuts:
|
||||
new_item = item.as_dict().copy()
|
||||
new_item['name'] = _(item.link_to)
|
||||
if self.is_item_allowed(item.link_to, item.type):
|
||||
if item.type == "Page":
|
||||
page = self.allowed_pages[item.link_to]
|
||||
new_item['label'] = _(page.get("title", frappe.unscrub(item.link_to)))
|
||||
if item.type == "Report":
|
||||
report = self.allowed_reports.get(item.link_to, {})
|
||||
if report.get("report_type") in ["Query Report", "Script Report"]:
|
||||
new_item['is_query_report'] = 1
|
||||
|
||||
items.append(new_item)
|
||||
|
||||
return items
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_desktop_page(page):
|
||||
"""Applies permissions, customizations and returns the configruration for a page
|
||||
on desk.
|
||||
|
||||
Args:
|
||||
page (string): page name
|
||||
|
||||
Returns:
|
||||
dict: dictionary of cards, charts and shortcuts to be displayed on website
|
||||
"""
|
||||
wspace = Workspace(page)
|
||||
try:
|
||||
wspace.build_cache()
|
||||
wspace.build_workspace()
|
||||
return {
|
||||
'charts': wspace.charts,
|
||||
'shortcuts': wspace.shortcuts,
|
||||
'cards': wspace.cards,
|
||||
'allow_customization': not wspace.doc.disable_user_customization
|
||||
}
|
||||
|
||||
except DoesNotExistError:
|
||||
if frappe.message_log:
|
||||
frappe.message_log.pop()
|
||||
return None
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_desk_sidebar_items():
|
||||
"""Get list of sidebar items for desk
|
||||
"""
|
||||
# don't get domain restricted pages
|
||||
filters = {'restrict_to_domain': ['in', frappe.get_active_domains()]}
|
||||
|
||||
if not frappe.local.conf.developer_mode:
|
||||
filters['developer_mode_only'] = '0'
|
||||
|
||||
# pages sorted based on pinned to top and then by name
|
||||
order_by = "pin_to_top desc, pin_to_bottom asc, name asc"
|
||||
pages = frappe.get_all("Desk Page", fields=["name", "category"], filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
|
||||
from collections import defaultdict
|
||||
sidebar_items = defaultdict(list)
|
||||
|
||||
for page in pages:
|
||||
# The order will be maintained while categorizing
|
||||
sidebar_items[page["category"]].append(page)
|
||||
return sidebar_items
|
||||
|
||||
def get_table_with_counts():
|
||||
counts = frappe.cache().get_value("information_schema:counts")
|
||||
if not counts:
|
||||
counts = build_table_count_cache()
|
||||
|
||||
return counts
|
||||
|
||||
def get_custom_reports_and_doctypes(module):
|
||||
return [
|
||||
frappe._dict({
|
||||
"title": "Custom Reports",
|
||||
"links": get_custom_report_list(module)
|
||||
}),
|
||||
frappe._dict({
|
||||
"title": "Custom DocTypes",
|
||||
"links": get_custom_doctype_list(module)
|
||||
})
|
||||
]
|
||||
|
||||
def get_custom_doctype_list(module):
|
||||
doctypes = frappe.get_list("DocType", fields=["name"], filters={"custom": 1, "istable": 0, "module": module}, order_by="name", ignore_permissions=True)
|
||||
|
||||
out = []
|
||||
for d in doctypes:
|
||||
out.append({
|
||||
"type": "doctype",
|
||||
"name": d.name,
|
||||
"label": _(d.name)
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_custom_report_list(module):
|
||||
"""Returns list on new style reports for modules."""
|
||||
reports = frappe.get_list("Report", fields=["name", "ref_doctype", "report_type"], filters=
|
||||
{"is_standard": "No", "disabled": 0, "module": module},
|
||||
order_by="name", ignore_permissions=True)
|
||||
|
||||
out = []
|
||||
for r in reports:
|
||||
out.append({
|
||||
"type": "report",
|
||||
"doctype": r.ref_doctype,
|
||||
"is_query_report": 1 if r.report_type in ("Query Report", "Script Report", "Custom Report") else 0,
|
||||
"label": _(r.name),
|
||||
"name": r.name
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
def make_them_pages():
|
||||
"""Helper function to make pages
|
||||
"""
|
||||
pages = [
|
||||
('Desk', 'frappe', 'octicon octicon-calendar'),
|
||||
('Settings', 'frappe', 'octicon octicon-settings'),
|
||||
('Users and Permissions', 'frappe', 'octicon octicon-settings'),
|
||||
('Customization', 'frappe', 'octicon octicon-settings'),
|
||||
('Integrations', 'frappe', 'octicon octicon-globe'),
|
||||
('Core', 'frappe', 'octicon octicon-circuit-board'),
|
||||
('Website', 'frappe', 'octicon octicon-globe'),
|
||||
('Getting Started', 'erpnext', 'fa fa-check-square-o'),
|
||||
('Accounts', 'erpnext', 'octicon octicon-repo'),
|
||||
('Selling', 'erpnext', 'octicon octicon-tag'),
|
||||
('Buying', 'erpnext', 'octicon octicon-briefcase'),
|
||||
('Stock', 'erpnext', 'octicon octicon-package'),
|
||||
('Assets', 'erpnext', 'octicon octicon-database'),
|
||||
('Projects', 'erpnext', 'octicon octicon-rocket'),
|
||||
('CRM', 'erpnext', 'octicon octicon-broadcast'),
|
||||
('Support', 'erpnext', 'fa fa-check-square-o'),
|
||||
('HR', 'erpnext', 'octicon octicon-organization'),
|
||||
('Quality Management', 'erpnext', 'fa fa-check-square-o'),
|
||||
('Manufacturing', 'erpnext', 'octicon octicon-tools'),
|
||||
('Retail', 'erpnext', 'octicon octicon-credit-card'),
|
||||
('Education', 'erpnext', 'octicon octicon-mortar-board'),
|
||||
('Healthcare', 'erpnext', 'fa fa-heartbeat'),
|
||||
('Agriculture', 'erpnext', 'octicon octicon-globe'),
|
||||
('Non Profit', 'erpnext', 'octicon octicon-heart'),
|
||||
('Help', 'erpnext', 'octicon octicon-device-camera-video')
|
||||
]
|
||||
|
||||
for page in pages:
|
||||
print("Processing Page: {0}".format(page[0]))
|
||||
make_them_cards(page[0], page[2])
|
||||
|
||||
|
||||
def make_them_cards(page_name, from_module=None, to_module=None, icon=None):
|
||||
from frappe.desk.moduleview import get
|
||||
|
||||
if not from_module:
|
||||
from_module = page_name
|
||||
|
||||
if not to_module:
|
||||
to_module = page_name
|
||||
|
||||
try:
|
||||
modules = get(from_module)['data']
|
||||
except:
|
||||
return
|
||||
|
||||
# Find or make page doc
|
||||
if frappe.db.exists("Desk Page", page_name):
|
||||
page = frappe.get_doc("Desk Page", page_name)
|
||||
print("--- Got Page: {0}".format(page.name))
|
||||
else:
|
||||
page = frappe.new_doc("Desk Page")
|
||||
page.label = page_name
|
||||
page.cards = []
|
||||
page.icon = icon
|
||||
print("--- New Page: {0}".format(page.name))
|
||||
|
||||
# Guess Which Module
|
||||
if not to_module and frappe.db.exists("Module Def", page_name):
|
||||
page.module = page_name
|
||||
|
||||
if to_module:
|
||||
page.module = to_module
|
||||
elif frappe.db.exists("Module Def", page_name):
|
||||
page.module = page_name
|
||||
|
||||
for data in modules:
|
||||
# Create a New Card Child Doc
|
||||
card = frappe.new_doc("Desk Card")
|
||||
|
||||
# Data clean up
|
||||
for item in data['items']:
|
||||
try:
|
||||
del item['count']
|
||||
del item['incomplete_dependencies']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Set Child doc values
|
||||
card.title = data['label']
|
||||
card.icon = data.get('icon')
|
||||
# Pretty dump JSON
|
||||
card.links = json.dumps(data['items'], indent=4, sort_keys=True)
|
||||
|
||||
# Set Parent attributes
|
||||
card.parent = page.name
|
||||
card.parenttype = page.doctype
|
||||
card.parentfield = "cards"
|
||||
|
||||
# Add cards to page doc
|
||||
print("------- Adding Card: {0}".format(card.title))
|
||||
page.cards.append(card)
|
||||
|
||||
# End it all
|
||||
page.save()
|
||||
frappe.db.commit()
|
||||
return
|
||||
0
frappe/desk/doctype/desk_card/__init__.py
Normal file
8
frappe/desk/doctype/desk_card/desk_card.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('Desk Card', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
54
frappe/desk/doctype/desk_card/desk_card.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-01-29 14:45:54.383089",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"column_break_2",
|
||||
"icon",
|
||||
"section_break_3",
|
||||
"links"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "links",
|
||||
"fieldtype": "Code",
|
||||
"label": "Links",
|
||||
"options": "JSON",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "icon",
|
||||
"fieldtype": "Data",
|
||||
"label": "Icon"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-03 12:40:42.595122",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desk Card",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/desk/doctype/desk_card/desk_card.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 DeskCard(Document):
|
||||
pass
|
||||
10
frappe/desk/doctype/desk_card/test_desk_card.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 TestDeskCard(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/desk_chart/__init__.py
Normal file
47
frappe/desk/doctype/desk_chart/desk_chart.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-01-23 13:44:03.882158",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"chart_name",
|
||||
"label",
|
||||
"size"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "chart_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Chart Name",
|
||||
"options": "Dashboard Chart",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "size",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Size",
|
||||
"options": "Full\nHalf"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-01-23 16:47:16.265651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desk Chart",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/desk/doctype/desk_chart/desk_chart.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 DeskChart(Document):
|
||||
pass
|
||||
0
frappe/desk/doctype/desk_page/__init__.py
Normal file
8
frappe/desk/doctype/desk_page/desk_page.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('Desk Page', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
188
frappe/desk/doctype/desk_page/desk_page.json
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:label",
|
||||
"beta": 1,
|
||||
"creation": "2020-01-23 13:45:59.470592",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label",
|
||||
"module",
|
||||
"restrict_to_domain",
|
||||
"category",
|
||||
"icon",
|
||||
"column_break_3",
|
||||
"onboarding",
|
||||
"developer_mode_only",
|
||||
"disable_user_customization",
|
||||
"pin_to_top",
|
||||
"pin_to_bottom",
|
||||
"section_break_2",
|
||||
"charts_label",
|
||||
"charts",
|
||||
"section_break_15",
|
||||
"shortcuts_label",
|
||||
"shortcuts",
|
||||
"section_break_18",
|
||||
"cards_label",
|
||||
"cards"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Name",
|
||||
"length": 22,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dashboards"
|
||||
},
|
||||
{
|
||||
"fieldname": "charts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Charts",
|
||||
"options": "Desk Chart"
|
||||
},
|
||||
{
|
||||
"fieldname": "shortcuts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Shortcuts",
|
||||
"options": "Desk Shortcut"
|
||||
},
|
||||
{
|
||||
"fieldname": "onboarding",
|
||||
"fieldtype": "Data",
|
||||
"label": "Onboarding"
|
||||
},
|
||||
{
|
||||
"fieldname": "restrict_to_domain",
|
||||
"fieldtype": "Link",
|
||||
"label": "Restrict to Domain",
|
||||
"options": "Domain",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Module",
|
||||
"options": "Module Def"
|
||||
},
|
||||
{
|
||||
"fieldname": "icon",
|
||||
"fieldtype": "Data",
|
||||
"label": "Icon"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "cards",
|
||||
"fieldtype": "Table",
|
||||
"label": "Cards",
|
||||
"options": "Desk Card"
|
||||
},
|
||||
{
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Select",
|
||||
"label": "Category",
|
||||
"options": "Modules\nDomains\nPlaces\nAdministration",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "developer_mode_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Developer Mode Only",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.pin_to_bottom!=1",
|
||||
"fieldname": "pin_to_top",
|
||||
"fieldtype": "Check",
|
||||
"label": "Pin To Top",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disable_user_customization",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable User Customization",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.pin_to_top!=1",
|
||||
"fieldname": "pin_to_bottom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Pin To Bottom",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "charts_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "shortcuts_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "cards_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shortcuts"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Link Cards"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-03-02 20:08:44.856046",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desk Page",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
18
frappe/desk/doctype/desk_page/desk_page.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# -*- 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.modules.export_file import export_to_files
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DeskPage(Document):
|
||||
def validate(self):
|
||||
if (not (frappe.flags.in_install or frappe.flags.in_patch or frappe.flags.in_test or frappe.flags.in_fixtures)
|
||||
and not frappe.conf.developer_mode):
|
||||
frappe.throw(_("You need to be in developer mode to edit this document"))
|
||||
|
||||
def on_update(self):
|
||||
export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module)
|
||||
10
frappe/desk/doctype/desk_page/test_desk_page.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 TestDeskPage(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/desk_shortcut/__init__.py
Normal file
91
frappe/desk/doctype/desk_shortcut/desk_shortcut.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-01-23 13:44:59.248426",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"type",
|
||||
"icon",
|
||||
"column_break_4",
|
||||
"link_to",
|
||||
"is_query_report",
|
||||
"section_break_5",
|
||||
"stats_filter",
|
||||
"column_break_3",
|
||||
"color",
|
||||
"format"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "DocType\nReport\nPage",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "link_to",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Link To",
|
||||
"options": "type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stats_filter",
|
||||
"fieldtype": "Code",
|
||||
"label": "Count Filter",
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "For example: {} Open",
|
||||
"fieldname": "format",
|
||||
"fieldtype": "Data",
|
||||
"label": "Format"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Count Filter"
|
||||
},
|
||||
{
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Color"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.type === \"Report\"",
|
||||
"fieldname": "is_query_report",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Query Report"
|
||||
},
|
||||
{
|
||||
"fieldname": "icon",
|
||||
"fieldtype": "Data",
|
||||
"label": "Icon"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-28 12:59:46.870172",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desk Shortcut",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/desk/doctype/desk_shortcut/desk_shortcut.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 DeskShortcut(Document):
|
||||
pass
|
||||
|
|
@ -101,10 +101,15 @@ def is_continue_slide_required(first_slide):
|
|||
def create_onboarding_docs(values, doctype=None, app=None, slide_type=None):
|
||||
data = json.loads(values)
|
||||
doc = frappe.new_doc(doctype)
|
||||
if hasattr(doc, 'create_onboarding_docs'):
|
||||
doc.create_onboarding_docs(data)
|
||||
else:
|
||||
create_generic_onboarding_doc(data, doctype, slide_type)
|
||||
try:
|
||||
if hasattr(doc, 'create_onboarding_docs'):
|
||||
doc.flags.ignore_validate = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.create_onboarding_docs(data)
|
||||
else:
|
||||
create_generic_onboarding_doc(data, doctype, slide_type)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def create_generic_onboarding_doc(data, doctype, slide_type):
|
||||
if slide_type == 'Settings':
|
||||
|
|
@ -117,8 +122,8 @@ def create_generic_onboarding_doc(data, doctype, slide_type):
|
|||
doc = frappe.new_doc(doctype)
|
||||
for entry in data:
|
||||
doc.set(entry, data.get(entry))
|
||||
doc.flags.ignore_validate = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.flags.ignore_links = True
|
||||
doc.insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import unique
|
||||
|
||||
class Tag(Document):
|
||||
pass
|
||||
|
|
@ -81,7 +82,7 @@ class DocTags:
|
|||
if not tl:
|
||||
tags = ''
|
||||
else:
|
||||
tl = list(set(filter(lambda x: x, tl)))
|
||||
tl = unique(filter(lambda x: x, tl))
|
||||
tags = ',' + ','.join(tl)
|
||||
try:
|
||||
frappe.db.sql("update `tab%s` set _user_tags=%s where name=%s" % \
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ def add_all_roles_to(name):
|
|||
user.save()
|
||||
|
||||
def disable_future_access():
|
||||
frappe.db.set_default('desktop:home_page', 'desktop')
|
||||
frappe.db.set_default('desktop:home_page', 'workspace')
|
||||
frappe.db.set_value('System Settings', 'System Settings', 'setup_complete', 1)
|
||||
frappe.db.set_value('System Settings', 'System Settings', 'is_first_startup', 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2019-07-22 12:23:38.425877",
|
||||
"modified": "2020-03-02 15:17:13.041650",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "user-profile",
|
||||
|
|
@ -18,5 +18,6 @@
|
|||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0
|
||||
"system_page": 0,
|
||||
"title": "User Profile"
|
||||
}
|
||||
|
|
@ -30,9 +30,12 @@ def get_form_params():
|
|||
"""Stringify GET request parameters."""
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
is_report = data.get('view') == 'Report'
|
||||
|
||||
data.pop('cmd', None)
|
||||
data.pop('data', None)
|
||||
data.pop('ignore_permissions', None)
|
||||
data.pop('view', None)
|
||||
|
||||
if "csrf_token" in data:
|
||||
del data["csrf_token"]
|
||||
|
|
@ -65,10 +68,11 @@ def get_form_params():
|
|||
|
||||
df = frappe.get_meta(parenttype).get_field(fieldname)
|
||||
|
||||
fieldname = df.fieldname if df else None
|
||||
report_hide = df.report_hide if df else None
|
||||
|
||||
# remove the field from the query if the report hide flag is set
|
||||
if report_hide:
|
||||
# remove the field from the query if the report hide flag is set and current view is Report
|
||||
if report_hide and is_report:
|
||||
fields.remove(field)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,8 @@ scheduler_events = {
|
|||
"frappe.email.doctype.email_account.email_account.pull",
|
||||
"frappe.email.doctype.email_account.email_account.notify_unreplied",
|
||||
"frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment",
|
||||
'frappe.utils.global_search.sync_global_search'
|
||||
'frappe.utils.global_search.sync_global_search',
|
||||
"frappe.monitor.flush",
|
||||
],
|
||||
"hourly": [
|
||||
"frappe.model.utils.link_count.update_link_count",
|
||||
|
|
|
|||
43
frappe/integrations/desk_page/integrations/integrations.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Backup"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Google Services"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Webhook"
|
||||
},
|
||||
{
|
||||
"links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Authentication"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-star",
|
||||
"links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]",
|
||||
"title": "Payments"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:16:18.714190",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 1,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"icon": "frapicon-dashboard",
|
||||
"idx": 0,
|
||||
"label": "Integrations",
|
||||
"modified": "2020-03-05 11:27:26.195829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Integrations",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": []
|
||||
}
|
||||
|
|
@ -801,8 +801,8 @@ class BaseDocument(object):
|
|||
else:
|
||||
# get values from old doc
|
||||
if self.get('parent_doc'):
|
||||
self.parent_doc.get_latest()
|
||||
ref_doc = [d for d in self.parent_doc.get(self.parentfield) if d.name == self.name][0]
|
||||
parent_doc = self.parent_doc.get_latest()
|
||||
ref_doc = [d for d in parent_doc.get(self.parentfield) if d.name == self.name][0]
|
||||
else:
|
||||
ref_doc = self.get_latest()
|
||||
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ class Document(BaseDocument):
|
|||
|
||||
# check for child tables
|
||||
for df in self.meta.get_table_fields():
|
||||
high_permlevel_fields = frappe.get_meta(df.options).meta.get_high_permlevel_fields()
|
||||
high_permlevel_fields = frappe.get_meta(df.options).get_high_permlevel_fields()
|
||||
if high_permlevel_fields:
|
||||
for d in self.get(df.fieldname):
|
||||
d.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe
|
|||
("data_migration", "data_migration_plan"),
|
||||
("desk", "onboarding_slide_field"),
|
||||
("desk", "onboarding_slide_help_link"),
|
||||
("desk", "onboarding_slide")):
|
||||
("desk", "onboarding_slide"),
|
||||
("desk", "desk_card"),
|
||||
("desk", "desk_chart"),
|
||||
("desk", "desk_shortcut"),
|
||||
("desk", "desk_page")):
|
||||
files.append(os.path.join(frappe.get_app_path("frappe"), d[0],
|
||||
"doctype", d[1], d[1] + ".json"))
|
||||
|
||||
|
|
@ -75,7 +79,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F
|
|||
# load in sequence - warning for devs
|
||||
document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format',
|
||||
'website_theme', 'web_form', 'notification', 'print_style',
|
||||
'data_migration_mapping', 'data_migration_plan', 'onboarding_slide']
|
||||
'data_migration_mapping', 'data_migration_plan', 'onboarding_slide', 'desk_page']
|
||||
for doctype in document_types:
|
||||
doctype_path = os.path.join(start_path, doctype)
|
||||
if os.path.exists(doctype_path):
|
||||
|
|
|
|||
107
frappe/monitor.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
import traceback
|
||||
import frappe
|
||||
import os
|
||||
import uuid
|
||||
import rq
|
||||
|
||||
|
||||
MONITOR_REDIS_KEY = "monitor-transactions"
|
||||
MONITOR_MAX_ENTRIES = 1000000
|
||||
|
||||
|
||||
def start(transaction_type="request", method=None, kwargs=None):
|
||||
if frappe.conf.monitor:
|
||||
frappe.local.monitor = Monitor(transaction_type, method, kwargs)
|
||||
|
||||
|
||||
def stop(response=None):
|
||||
if frappe.conf.monitor and hasattr(frappe.local, "monitor"):
|
||||
frappe.local.monitor.dump(response)
|
||||
|
||||
|
||||
def log_file():
|
||||
return os.path.join(frappe.utils.get_bench_path(), "logs", "monitor.json.log")
|
||||
|
||||
|
||||
class Monitor:
|
||||
def __init__(self, transaction_type, method, kwargs):
|
||||
try:
|
||||
self.data = frappe._dict(
|
||||
{
|
||||
"site": frappe.local.site,
|
||||
"timestamp": datetime.utcnow(),
|
||||
"transaction_type": transaction_type,
|
||||
"uuid": str(uuid.uuid4()),
|
||||
}
|
||||
)
|
||||
|
||||
if transaction_type == "request":
|
||||
self.collect_request_meta()
|
||||
else:
|
||||
self.collect_job_meta(method, kwargs)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
def collect_request_meta(self):
|
||||
self.data.request = frappe._dict(
|
||||
{
|
||||
"ip": frappe.local.request_ip,
|
||||
"method": frappe.request.method,
|
||||
"path": frappe.request.path,
|
||||
}
|
||||
)
|
||||
|
||||
def collect_job_meta(self, method, kwargs):
|
||||
self.data.job = frappe._dict({"method": method, "scheduled": False, "wait": 0})
|
||||
if "run_scheduled_job" in method:
|
||||
self.data.job.method = kwargs["job_type"]
|
||||
self.data.job.scheduled = True
|
||||
|
||||
job = rq.get_current_job()
|
||||
if job:
|
||||
self.data.uuid = job.id
|
||||
waitdiff = self.data.timestamp - job.enqueued_at
|
||||
self.data.job.wait = int(waitdiff.total_seconds() * 1000000)
|
||||
|
||||
def dump(self, response=None):
|
||||
try:
|
||||
timediff = datetime.utcnow() - self.data.timestamp
|
||||
# Obtain duration in microseconds
|
||||
self.data.duration = int(timediff.total_seconds() * 1000000)
|
||||
|
||||
if self.data.transaction_type == "request":
|
||||
self.data.request.status_code = response.status_code
|
||||
self.data.request.response_length = int(response.headers["Content-Length"])
|
||||
|
||||
self.store()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
def store(self):
|
||||
if frappe.cache().llen(MONITOR_REDIS_KEY) > MONITOR_MAX_ENTRIES:
|
||||
frappe.cache().ltrim(MONITOR_REDIS_KEY, 1, -1)
|
||||
serialized = json.dumps(self.data, sort_keys=True, default=str)
|
||||
frappe.cache().rpush(MONITOR_REDIS_KEY, serialized)
|
||||
|
||||
|
||||
def flush():
|
||||
try:
|
||||
# Fetch all the logs without removing from cache
|
||||
logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1)
|
||||
if logs:
|
||||
logs = list(map(frappe.safe_decode, logs))
|
||||
with open(log_file(), "a", os.O_NONBLOCK) as f:
|
||||
f.write("\n".join(logs))
|
||||
f.write("\n")
|
||||
# Remove fetched entries from cache
|
||||
frappe.cache().ltrim(MONITOR_REDIS_KEY, len(logs) - 1, -1)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
|
@ -263,4 +263,6 @@ frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26
|
|||
frappe.patches.v12_0.setup_email_linking
|
||||
frappe.patches.v12_0.fix_home_settings_for_all_users
|
||||
frappe.patches.v12_0.change_existing_dashboard_chart_filters
|
||||
execute:frappe.delete_doc("Test Runner")execute:frappe.delete_doc_if_exists('DocType', 'Google Maps Settings')
|
||||
execute:frappe.delete_doc("Test Runner")
|
||||
execute:frappe.delete_doc_if_exists('DocType', 'Google Maps Settings')
|
||||
execute:frappe.db.set_default('desktop:home_page', 'workspace')
|
||||
|
|
|
|||
|
|
@ -441,8 +441,8 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
});
|
||||
},
|
||||
setup_field_settings: function() {
|
||||
var me = this;
|
||||
this.page.main.on("click", ".field-settings", function() {
|
||||
|
||||
this.page.main.find(".field-settings").on("click", () => {
|
||||
var field = $(this).parent();
|
||||
|
||||
// new dialog
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@
|
|||
"public/js/frappe/utils/energy_point_utils.js",
|
||||
"public/js/frappe/utils/dashboard_utils.js",
|
||||
"public/js/frappe/ui/chart.js",
|
||||
"public/js/frappe/ui/dashboard_chart.js",
|
||||
"public/js/frappe/barcode_scanner/index.js"
|
||||
],
|
||||
"css/module.min.css": [
|
||||
|
|
@ -346,9 +347,6 @@
|
|||
"js/social.min.js": [
|
||||
"public/js/frappe/social/social_home.js"
|
||||
],
|
||||
"js/modules.min.js": [
|
||||
"public/js/frappe/views/modules_home.js"
|
||||
],
|
||||
"js/barcode_scanner.min.js": [
|
||||
"public/js/frappe/barcode_scanner/quagga.js"
|
||||
],
|
||||
|
|
|
|||
12
frappe/public/icons/frapicon-dashboard.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/dashboard</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/dashboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(1.700000, 1.000000)">
|
||||
<path d="M6,0 C6.368,0 6.66666667,0.298666667 6.66666667,0.666666667 L6.66666667,8.66666667 C6.66666667,9.03466667 6.368,9.33333333 6,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 1.95399252e-14,9.03466667 1.95399252e-14,8.66666667 L1.95399252e-14,0.666666667 C1.95399252e-14,0.298666667 0.298666667,0 0.666666667,0 L6,0 Z M14,-4.4408921e-16 C14.368,-4.4408921e-16 14.6666667,0.298666667 14.6666667,0.666666667 L14.6666667,4.66666667 C14.6666667,5.03466667 14.368,5.33333333 14,5.33333333 L8.66666667,5.33333333 C8.29866667,5.33333333 8,5.03466667 8,4.66666667 L8,0.666666667 C8,0.298666667 8.29866667,-4.4408921e-16 8.66666667,-4.4408921e-16 L14,-4.4408921e-16 Z" id="Combined-Shape" fill="#A1ABB4"></path>
|
||||
<path d="M6,10.6666667 C6.368,10.6666667 6.66666667,10.9653333 6.66666667,11.3333333 L6.66666667,15.3333333 C6.66666667,15.7013333 6.368,16 6,16 L0.666666667,16 C0.298666667,16 1.95399252e-14,15.7013333 1.95399252e-14,15.3333333 L1.95399252e-14,11.3333333 C1.95399252e-14,10.9653333 0.298666667,10.6666667 0.666666667,10.6666667 L6,10.6666667 Z M14,6.66666667 C14.368,6.66666667 14.6666667,6.96533333 14.6666667,7.33333333 L14.6666667,15.3333333 C14.6666667,15.7013333 14.368,16 14,16 L8.66666667,16 C8.29866667,16 8,15.7013333 8,15.3333333 L8,7.33333333 C8,6.96533333 8.29866667,6.66666667 8.66666667,6.66666667 L14,6.66666667 Z" id="Combined-Shape" fill="#415668" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
10
frappe/public/icons/frapicon-income.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/income</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/income" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M16,12.8586667 L16,14 C16,15.1046667 13.9106667,16 11.3333333,16 C8.756,16 6.66666667,15.1046667 6.66666667,14 L6.66666667,14 L6.66666667,12.8586667 C7.73133333,13.574 9.366,14 11.3333333,14 C13.3006667,14 14.9353333,13.574 16,12.8586667 L16,12.8586667 Z M16,9.52533333 L16,10.6666667 C16,11.7713333 13.9106667,12.6666667 11.3333333,12.6666667 C8.756,12.6666667 6.66666667,11.7713333 6.66666667,10.6666667 L6.66666667,10.6666667 L6.66666667,9.52533333 C7.73133333,10.2406667 9.366,10.6666667 11.3333333,10.6666667 C13.3006667,10.6666667 14.9353333,10.2406667 16,9.52533333 L16,9.52533333 Z M11.3333333,5.33333333 C13.9106622,5.33333333 16,6.22876383 16,7.33333333 C16,8.43790283 13.9106622,9.33333333 11.3333333,9.33333333 C8.7560045,9.33333333 6.66666667,8.43790283 6.66666667,7.33333333 C6.66666667,6.22876383 8.7560045,5.33333333 11.3333333,5.33333333 Z" id="Combined-Shape" fill="#415668" fill-rule="nonzero"></path>
|
||||
<path d="M2.44249065e-14,10.8586667 C1.06466667,11.574 2.69933333,12 4.66666667,12 C4.894,12 5.11533333,11.9926667 5.33333333,11.982 L5.33333333,11.982 L5.33333333,13.9773333 C5.11533333,13.9906667 4.89333333,14 4.66666667,14 C2.08933333,14 2.44249065e-14,13.1046667 2.44249065e-14,12 L2.44249065e-14,12 Z M2.44249065e-14,7.52533333 C1.06466667,8.24066667 2.69933333,8.66666667 4.66666667,8.66666667 C4.894,8.66666667 5.11533333,8.65933333 5.33333333,8.64866667 L5.33333333,8.64866667 L5.33333333,10.644 C5.11533333,10.6573333 4.89333333,10.6666667 4.66666667,10.6666667 C2.08933333,10.6666667 2.44249065e-14,9.77133333 2.44249065e-14,8.66666667 L2.44249065e-14,8.66666667 Z M-5.57776048e-13,4.192 C1.06466667,4.90733333 2.69933333,5.33333333 4.66666667,5.33333333 C5.36133333,5.33333333 6.012,5.27733333 6.61333333,5.17733333 C5.80666667,5.73733333 5.34333333,6.46866667 5.33533333,7.31066667 C5.116,7.324 4.894,7.33333333 4.66666667,7.33333333 C2.08933333,7.33333333 -5.57776048e-13,6.438 -5.57776048e-13,5.33333333 L-5.57776048e-13,5.33333333 Z M4.66666667,0 C7.2439955,0 9.33333333,0.8954305 9.33333333,2 C9.33333333,3.1045695 7.2439955,4 4.66666667,4 C2.08933783,4 2.48689958e-14,3.1045695 2.48689958e-14,2 C2.48689958e-14,0.8954305 2.08933783,0 4.66666667,0 Z" id="Combined-Shape" fill="#A1ABB4"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
12
frappe/public/icons/frapicon-mail.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/invoice/mail</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/invoice/mail" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="letter" transform="translate(0.000000, 1.000000)">
|
||||
<path d="M15,0 L1,0 C0.4,0 0,0.4 0,1 L0,2.4 L8,6.9 L16,2.5 L16,1 C16,0.4 15.6,0 15,0 Z" id="Path" fill="#A1ABB4"></path>
|
||||
<path d="M7.5,8.9 L0,4.7 L0,13 C0,13.6 0.4,14 1,14 L15,14 C15.6,14 16,13.6 16,13 L16,4.7 L8.5,8.9 C8.22,9.04 7.78,9.04 7.5,8.9 Z" id="Path" fill="#415668" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 842 B |
11
frappe/public/icons/frapicon-normal.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/folder/normal</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/folder/normal" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="folder-15" transform="translate(0.000000, 0.700000)" fill="#415668" fill-rule="nonzero">
|
||||
<path d="M8.33333333,2.66666667 L6.33333333,0 L0.666666667,0 C0.298476833,0 0,0.298476833 0,0.666666667 L0,12.6666667 C0,13.7712362 0.8954305,14.6666667 2,14.6666667 L14,14.6666667 C15.1045695,14.6666667 16,13.7712362 16,12.6666667 L16,3.33333333 C16,2.9651435 15.7015232,2.66666667 15.3333333,2.66666667 L8.33333333,2.66666667 Z" id="Path"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 915 B |
12
frappe/public/icons/frapicon-phone.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/invoice/phone</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/invoice/phone" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="phone-call">
|
||||
<path d="M15.285,12.305 L12.707,9.711 C12.317,9.318 11.682,9.318 11.291,9.709 L9,12 L4,7 L6.294,4.706 C6.684,4.316 6.685,3.683 6.295,3.292 L3.715,0.708 C3.324,0.317 2.691,0.317 2.3,0.708 L0.004,3.003 L0,3 C0,10.18 5.82,16 13,16 L15.283,13.717 C15.673,13.327 15.674,12.696 15.285,12.305 Z" id="Path" fill="#415668" fill-rule="nonzero"></path>
|
||||
<path d="M8,0 C12.411,0 16,3.589 16,8 L14,8 C14,4.691 11.309,2 8,2 L8,0 Z M8,4 C10.206,4 12,5.794 12,8 L10,8 C10,6.897 9.103,6 8,6 L8,4 Z" id="Combined-Shape" fill="#A1ABB4"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1 KiB |
12
frappe/public/icons/frapicon-property.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/property</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/property" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="new-construction" transform="translate(1.000000, 1.000000)">
|
||||
<path d="M15.5246667,2.028 L8.858,0.028 C8.65616136,-0.0324717007 8.43761545,0.00603303671 8.2685973,0.131844525 C8.09957915,0.257656014 7.99998537,0.455963879 8,0.666666667 L8,4 L9.33333333,4 L9.33333333,1.56266667 L14.6666667,3.16266667 L14.6666667,14.6666667 L10.6666667,14.6666667 L10.6666667,6 C10.6666667,5.63181017 10.3681898,5.33333333 10,5.33333333 L3.33333333,5.33333333 C2.9651435,5.33333333 2.66666667,5.63181017 2.66666667,6 L2.66666667,14.6666667 L1.33333333,14.6666667 L1.33333333,8 L0.666666667,8 C0.298476833,8 0,8.29847683 0,8.66666667 L0,15.3333333 C0,15.7015232 0.298476833,16 0.666666667,16 L15.3333333,16 C15.7015232,16 16,15.7015232 16,15.3333333 L16,2.66666667 C16,2.3721545 15.8067889,2.11252499 15.5246667,2.028 Z M8,14 L5.33333333,14 L5.33333333,12.6666667 L8,12.6666667 L8,14 Z M8,11.3333333 L5.33333333,11.3333333 L5.33333333,10 L8,10 L8,11.3333333 Z M8,8.66666667 L5.33333333,8.66666667 L5.33333333,7.33333333 L8,7.33333333 L8,8.66666667 Z" id="Shape" fill="#415668" fill-rule="nonzero"></path>
|
||||
<rect id="Rectangle" fill="#A1ABB4" x="12" y="5.33333333" width="1.33333333" height="8"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
12
frappe/public/icons/frapicon-purchase.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/purchase</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/purchase" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="bag-16" transform="translate(1.000000, 0.500000)">
|
||||
<path d="M16,4.3972168 L16,15 C16,16.1045695 15.1045695,17 14,17 L2,17 C0.8954305,17 1.3527075e-16,16.1045695 0,15 L0,4.3972168 L16,4.3972168 Z M11.6363636,7.16666667 C11.2,7.16666667 10.9090909,7.45555556 10.9090909,7.88888889 C10.9090909,9.47777778 9.6,10.7777778 8,10.7777778 C6.4,10.7777778 5.09090909,9.47777778 5.09090909,7.88888889 C5.09090909,7.45555556 4.8,7.16666667 4.36363636,7.16666667 C3.92727273,7.16666667 3.63636364,7.45555556 3.63636364,7.88888889 C3.63636364,10.2722222 5.6,12.2222222 8,12.2222222 C10.4,12.2222222 12.3636364,10.2722222 12.3636364,7.88888889 C12.3636364,7.45555556 12.0727273,7.16666667 11.6363636,7.16666667 Z" id="Shape" fill="#415668" fill-rule="nonzero"></path>
|
||||
<path d="M0,3 L1.49222874,1.12925513 C2.06146543,0.415626854 2.92465315,1.0558663e-15 3.83750348,0 L12.1683182,0 C13.0831751,-6.12145698e-16 13.9480333,0.417447486 14.5171563,1.13373106 L16,3 L16,3 L0,3 Z" id="Path-2" fill="#A1ABB4"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
12
frappe/public/icons/frapicon-reports.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/reports</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/reports" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="icons/navigation/dashboard" transform="translate(0.800000, 1.000000)" fill="#112B42" fill-rule="nonzero">
|
||||
<path d="M16.2857143,2.5 L16.2857143,7.26902668 L16.2857143,7.26902668 L11.9220779,7.26902668 C11.7032818,7.26906827 11.4982329,7.36737803 11.3613275,7.53318731 L11.2988052,7.62157447 L9.87116883,10 L7.50680519,4.09173512 C7.40433373,3.83523222 7.16527403,3.6589327 6.88983266,3.63673596 C6.64882146,3.61731381 6.41639286,3.71881295 6.26674592,3.90311574 L6.2078961,3.98706113 L4.23771429,7.26902668 L0.285714286,7.26902668 L0.285714286,2.5 C0.285714286,1.11928813 1.40500241,2.53632657e-16 2.78571429,0 L13.7857143,0 C15.1664262,-1.47995115e-15 16.2857143,1.11928813 16.2857143,2.5 Z" id="Path" opacity="0.396856399"></path>
|
||||
<path d="M0.285714286,13.5 L0.285714286,8.73097332 L0.285714286,8.73097332 L4.64935065,8.73097332 C4.86814675,8.73093173 5.07319563,8.63262197 5.2101011,8.46681269 L5.27262338,8.37842553 L6.70025974,6 L9.06462338,11.9104456 C9.15434161,12.1350283 9.34876191,12.2981168 9.58061609,12.3501369 L9.68207792,12.3654867 L9.74025974,12.3654867 C9.95905584,12.3654451 10.1641047,12.2671353 10.3010102,12.101326 L10.3635325,12.0129389 L12.3337143,8.73097332 L16.2857143,8.73097332 L16.2857143,13.5 C16.2857143,14.8807119 15.1664262,16 13.7857143,16 L2.78571429,16 C1.40500241,16 0.285714286,14.8807119 0.285714286,13.5 Z" id="Path" opacity="0.800000012"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
12
frappe/public/icons/frapicon-sales.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/sales</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/sales" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-10" transform="translate(1.000000, 2.000000)">
|
||||
<path d="M16,5.11230469 L16,12 C16,13.1045695 15.1045695,14 14,14 L2,14 C0.8954305,14 1.3527075e-16,13.1045695 0,12 L0,5.11230469 L16,5.11230469 Z M12.5,10 L8.5,10 C8.22385763,10 8,10.2238576 8,10.5 C8,10.7761424 8.22385763,11 8.5,11 L8.5,11 L12.5,11 C12.7761424,11 13,10.7761424 13,10.5 C13,10.2238576 12.7761424,10 12.5,10 L12.5,10 Z" id="Combined-Shape" fill="#415668"></path>
|
||||
<path d="M2,0 L14,0 C15.1045695,-2.02906125e-16 16,0.8954305 16,2 L16,3.64526367 L16,3.64526367 L0,3.64526367 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z" id="Rectangle-Copy" fill="#A1ABB4"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
12
frappe/public/icons/frapicon-settings.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>icons/navigation/settings</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="icons/navigation/settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="switches" transform="translate(1.000000, 0.500000)">
|
||||
<path d="M4,8 L12,8 C14.1818182,8 16,6.18181818 16,4 C16,1.81818182 14.1818182,0 12,0 L4,0 C1.81818182,0 0,1.81818182 0,4 C0,6.18181818 1.81818182,8 4,8 Z M4,1.45454545 C5.38181818,1.45454545 6.54545455,2.61818182 6.54545455,4 C6.54545455,5.38181818 5.38181818,6.54545455 4,6.54545455 C2.61818182,6.54545455 1.45454545,5.38181818 1.45454545,4 C1.45454545,2.61818182 2.61818182,1.45454545 4,1.45454545 Z" id="Shape" fill="#A1ABB4"></path>
|
||||
<path d="M12,9 L4,9 C1.81818182,9 0,10.8181818 0,13 C0,15.1818182 1.81818182,17 4,17 L12,17 C14.1818182,17 16,15.1818182 16,13 C16,10.8181818 14.1818182,9 12,9 Z M12,15.5454545 C10.6181818,15.5454545 9.45454545,14.3818182 9.45454545,13 C9.45454545,11.6181818 10.6181818,10.4545455 12,10.4545455 C13.3818182,10.4545455 14.5454545,11.6181818 14.5454545,13 C14.5454545,14.3818182 13.3818182,15.5454545 12,15.5454545 Z" id="Shape" fill="#415668"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -155,7 +155,8 @@ frappe.ui.form.Layout = Class.extend({
|
|||
doctype: this.doctype,
|
||||
parent: this.column.wrapper.get(0),
|
||||
frm: this.frm,
|
||||
render_input: render
|
||||
render_input: render,
|
||||
doc: this.doc
|
||||
});
|
||||
|
||||
fieldobj.layout = this;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
let me = this;
|
||||
return new Promise(resolve => {
|
||||
frappe.model.with_doctype(this.doctype, function() {
|
||||
me.check_quick_entry_doc();
|
||||
me.set_meta_and_mandatory_fields();
|
||||
if(me.is_quick_entry()) {
|
||||
me.render_dialog();
|
||||
|
|
@ -44,16 +45,16 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
},
|
||||
|
||||
set_meta_and_mandatory_fields: function(){
|
||||
let fields = frappe.get_meta(this.doctype).fields;
|
||||
if (fields.length < 7) {
|
||||
// if less than 7 fields, then show everything
|
||||
this.mandatory = fields;
|
||||
} else {
|
||||
// prepare a list of mandatory and bold fields
|
||||
this.mandatory = $.map(fields,
|
||||
function(d) { return ((d.reqd || d.bold || d.allow_in_quick_entry) && !d.read_only) ? $.extend({}, d) : null; });
|
||||
}
|
||||
this.meta = frappe.get_meta(this.doctype);
|
||||
let fields = this.meta.fields;
|
||||
|
||||
// prepare a list of mandatory, bold and allow in quick entry fields
|
||||
this.mandatory = $.map(fields, function(d) {
|
||||
return ((d.reqd || d.bold || d.allow_in_quick_entry) && !d.read_only) ? $.extend({}, d) : null;
|
||||
});
|
||||
},
|
||||
|
||||
check_quick_entry_doc: function() {
|
||||
if (!this.doc) {
|
||||
this.doc = frappe.model.get_new_doc(this.doctype, null, null, true);
|
||||
}
|
||||
|
|
@ -66,8 +67,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
|
||||
this.validate_for_prompt_autoname();
|
||||
|
||||
if (this.has_child_table()
|
||||
|| !this.mandatory.length) {
|
||||
if (this.has_child_table() || !this.mandatory.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -103,8 +103,8 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("New {0}", [__(this.doctype)]),
|
||||
fields: this.mandatory,
|
||||
doc: this.doc
|
||||
});
|
||||
this.dialog.doc = this.doc;
|
||||
|
||||
this.register_primary_action();
|
||||
this.render_edit_in_full_page_link();
|
||||
|
|
|
|||
|
|
@ -344,7 +344,8 @@ frappe.views.BaseList = class BaseList {
|
|||
filters: this.get_filters_for_args(),
|
||||
order_by: this.sort_selector.get_sql_string(),
|
||||
start: this.start,
|
||||
page_length: this.page_length
|
||||
page_length: this.page_length,
|
||||
view: this.view
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
|
||||
this.view = 'List';
|
||||
// initialize with saved order by
|
||||
this.sort_by = this.view_user_settings.sort_by || 'modified';
|
||||
this.sort_order = this.view_user_settings.sort_order || 'desc';
|
||||
|
|
|
|||
|
|
@ -135,23 +135,16 @@ $.extend(frappe.model, {
|
|||
&& df.ignore_user_permissions != 1
|
||||
&& allowed_records.length);
|
||||
|
||||
function is_document_allowed(doc) {
|
||||
return doc && (!has_user_permissions || allowed_records.includes(doc));
|
||||
}
|
||||
|
||||
// don't set defaults for "User" link field using User Permissions!
|
||||
if (df.fieldtype==="Link" && df.options!=="User") {
|
||||
// 1 - look in user permissions for document_type=="Setup".
|
||||
// We don't want to include permissions of transactions to be used for defaults.
|
||||
if (df.linked_document_type==="Setup" && has_user_permissions && default_doc) {
|
||||
if (df.linked_document_type==="Setup"
|
||||
&& has_user_permissions && default_doc) {
|
||||
return default_doc;
|
||||
}
|
||||
|
||||
if (is_document_allowed(df["default"])) {
|
||||
user_default = df["default"];
|
||||
}
|
||||
|
||||
if (!df.ignore_user_permissions && !user_default) {
|
||||
if(!df.ignore_user_permissions) {
|
||||
// 2 - look in user defaults
|
||||
var user_defaults = frappe.defaults.get_user_defaults(df.options);
|
||||
if (user_defaults && user_defaults.length===1) {
|
||||
|
|
@ -168,8 +161,11 @@ $.extend(frappe.model, {
|
|||
user_default = frappe.boot.user.last_selected_values[df.options];
|
||||
}
|
||||
|
||||
var is_allowed_user_default = user_default &&
|
||||
(!has_user_permissions || allowed_records.includes(user_default));
|
||||
|
||||
// is this user default also allowed as per user permissions?
|
||||
if (is_document_allowed(user_default)) {
|
||||
if (is_allowed_user_default) {
|
||||
return user_default;
|
||||
}
|
||||
}
|
||||
|
|
@ -191,8 +187,9 @@ $.extend(frappe.model, {
|
|||
|
||||
} else if (df["default"][0]===":") {
|
||||
var boot_doc = frappe.model.get_default_from_boot_docs(df, doc, parent_doc);
|
||||
var is_allowed_boot_doc = !has_user_permissions || allowed_records.includes(boot_doc);
|
||||
|
||||
if (is_document_allowed(boot_doc)) {
|
||||
if (is_allowed_boot_doc) {
|
||||
return boot_doc;
|
||||
}
|
||||
} else if (df.fieldname===meta.title_field) {
|
||||
|
|
@ -201,7 +198,8 @@ $.extend(frappe.model, {
|
|||
}
|
||||
|
||||
// is this default value is also allowed as per user permissions?
|
||||
if (df.fieldtype!=="Link" || df.options==="User" || is_document_allowed(df.default)) {
|
||||
var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default);
|
||||
if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) {
|
||||
return df["default"];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ frappe.route = function() {
|
|||
|
||||
if(route[0]) {
|
||||
const title_cased_route = frappe.utils.to_title_case(route[0]);
|
||||
|
||||
if (title_cased_route === 'Desktop') {
|
||||
if (title_cased_route === 'Workspace') {
|
||||
frappe.views.pageview.show('');
|
||||
}
|
||||
|
||||
|
|
|
|||
183
frappe/public/js/frappe/ui/dashboard_chart.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
frappe.provide('ui')
|
||||
|
||||
frappe.ui.DashboardChart = class DashboardChart {
|
||||
constructor(chart_doc, chart_container, options) {
|
||||
this.chart_doc = chart_doc;
|
||||
this.container = chart_container;
|
||||
this.options = options || {};
|
||||
this.chart_args = {};
|
||||
}
|
||||
|
||||
show() {
|
||||
this.get_settings().then(() => {
|
||||
this.prepare_chart_object();
|
||||
this.prepare_container();
|
||||
if (!this.options.hide_actions || this.options.hide_actions == undefined) {
|
||||
this.prepare_chart_actions();
|
||||
}
|
||||
this.fetch(this.filters).then((data) => {
|
||||
if (!this.options.hide_last_sync || this.options.hide_last_sync == undefined) {
|
||||
this.update_last_synced();
|
||||
}
|
||||
this.data = data;
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
prepare_container() {
|
||||
const column_width_map = {
|
||||
"Half": "6",
|
||||
"Full": "12",
|
||||
};
|
||||
let columns = column_width_map[this.chart_doc.width];
|
||||
this.chart_container = $(`<div class="col-sm-${columns} chart-column-container">
|
||||
<div class="chart-wrapper">
|
||||
<div class="chart-loading-state text-muted">${__("Loading...")}</div>
|
||||
<div class="chart-empty-state hide text-muted">${__("No Data")}</div>
|
||||
</div>
|
||||
</div>`);
|
||||
this.chart_container.appendTo(this.container);
|
||||
|
||||
if (!this.options.hide_last_sync || this.options.hide_last_sync == undefined) {
|
||||
let last_synced_text = $(`<span class="text-muted last-synced-text"></span>`);
|
||||
last_synced_text.prependTo(this.chart_container);
|
||||
}
|
||||
}
|
||||
|
||||
prepare_chart_actions() {
|
||||
let actions = [
|
||||
{
|
||||
label: __("Refresh"),
|
||||
action: 'action-refresh',
|
||||
handler: () => {
|
||||
this.fetch(this.filters, true).then(data => {
|
||||
this.update_chart_object();
|
||||
this.data = data;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __("Edit..."),
|
||||
action: 'action-edit',
|
||||
handler: () => {
|
||||
frappe.set_route('Form', 'Dashboard Chart', this.chart_doc.name);
|
||||
}
|
||||
}
|
||||
];
|
||||
if (this.chart_doc.document_type) {
|
||||
actions.push({
|
||||
label: __("{0} List", [this.chart_doc.document_type]),
|
||||
action: 'action-list',
|
||||
handler: () => {
|
||||
frappe.set_route('List', this.chart_doc.document_type);
|
||||
}
|
||||
})
|
||||
}
|
||||
this.set_chart_actions(actions);
|
||||
}
|
||||
|
||||
set_chart_actions(actions) {
|
||||
this.chart_actions = $(`<div class="chart-actions btn-group dropdown pull-right">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"> <button class="btn btn-default btn-xs"><span class="caret"></span></button>
|
||||
</a>
|
||||
<ul class="dropdown-menu" style="max-height: 300px; overflow-y: auto;">
|
||||
${actions.map(action => `<li><a data-action="${action.action}">${action.label}</a></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`);
|
||||
|
||||
this.chart_actions.find("a[data-action]").each((i, o) => {
|
||||
const action = o.dataset.action;
|
||||
$(o).click(actions.find(a => a.action === action));
|
||||
});
|
||||
this.chart_actions.prependTo(this.chart_container);
|
||||
}
|
||||
|
||||
fetch(filters, refresh=false) {
|
||||
this.chart_container.find('.chart-loading-state').removeClass('hide');
|
||||
let method = this.settings ? this.settings.method
|
||||
: 'frappe.desk.doctype.dashboard_chart.dashboard_chart.get';
|
||||
|
||||
return frappe.xcall(
|
||||
method,
|
||||
{
|
||||
chart_name: this.chart_doc.name,
|
||||
filters: filters,
|
||||
refresh: refresh ? 1 : 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.chart_container.find('.chart-loading-state').addClass('hide');
|
||||
if (!this.data) {
|
||||
this.chart_container.find('.chart-empty-state').removeClass('hide');
|
||||
} else {
|
||||
this.prepare_chart_args();
|
||||
|
||||
if (!this.chart) {
|
||||
this.chart = new frappe.Chart(this.chart_container.find(".chart-wrapper")[0], this.chart_args);
|
||||
} else {
|
||||
this.chart.update(this.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepare_chart_args() {
|
||||
const chart_type_map = {
|
||||
"Line": "line",
|
||||
"Bar": "bar",
|
||||
};
|
||||
|
||||
this.chart_args.data = this.data;
|
||||
this.chart_args.type = chart_type_map[this.chart_doc.type];
|
||||
this.chart_args.colors = [this.chart_doc.color || "light-blue"];
|
||||
this.chart_args.axisOptions = {
|
||||
xIsSeries: this.chart_doc.timeseries,
|
||||
shortenYAxisNumbers: 1
|
||||
}
|
||||
|
||||
if (!this.options.hide_title || this.options.hide_title == undefined) {
|
||||
this.chart_args.title = this.chart_doc.chart_name;
|
||||
}
|
||||
}
|
||||
|
||||
update_last_synced() {
|
||||
let last_synced_text = __("Last synced {0}", [comment_when(this.chart_doc.last_synced_on)]);
|
||||
this.container.find(".last-synced-text").html(last_synced_text);
|
||||
}
|
||||
|
||||
update_chart_object() {
|
||||
frappe.db.get_doc("Dashboard Chart", this.chart_doc.name).then(doc => {
|
||||
this.chart_doc = doc;
|
||||
this.prepare_chart_object();
|
||||
this.update_last_synced();
|
||||
});
|
||||
}
|
||||
|
||||
prepare_chart_object() {
|
||||
this.filters = JSON.parse(this.chart_doc.filters_json || '{}');
|
||||
}
|
||||
|
||||
get_settings() {
|
||||
if (this.chart_doc.chart_type == 'Custom') {
|
||||
// custom source
|
||||
if (frappe.dashboards && frappe.dashboards.chart_sources[this.chart_doc.source]) {
|
||||
this.settings = frappe.dashboards.chart_sources[this.chart_doc.source];
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return frappe.xcall('frappe.desk.doctype.dashboard_chart_source.dashboard_chart_source.get_config',
|
||||
{name: this.chart_doc.source})
|
||||
.then(config => {
|
||||
frappe.dom.eval(config);
|
||||
this.settings = frappe.dashboards.chart_sources[this.chart_doc.source];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,8 +94,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
})
|
||||
.on('scroll', function() {
|
||||
var $input = $('input:focus');
|
||||
if($input.length && ['Date', 'Datetime',
|
||||
'Time'].includes($input.attr('data-fieldtype'))) {
|
||||
if ($input.length && ['Date', 'Datetime', 'Time'].includes($input.attr('data-fieldtype'))) {
|
||||
$input.blur();
|
||||
}
|
||||
});
|
||||
|
|
@ -197,5 +196,3 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
this.header.find('.modal-title').toggleClass('cursor-pointer');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ frappe.ui.GroupBy = class {
|
|||
this.groupby_select = this.groupby_edit_area.find('select.groupby');
|
||||
this.aggregate_function_select = this.groupby_edit_area.find('select.aggregate-function');
|
||||
this.aggregate_on_select = this.groupby_edit_area.find('select.aggregate-on');
|
||||
|
||||
this.aggregate_on_html = ``;
|
||||
// set default to count
|
||||
this.aggregate_function_select.val("count");
|
||||
this.page.wrapper.find(".frappe-list").append(
|
||||
|
|
@ -51,21 +51,32 @@ frappe.ui.GroupBy = class {
|
|||
}
|
||||
|
||||
show_hide_aggregate_on() {
|
||||
this.report_view.meta.fields.forEach((field) => {
|
||||
let fn = this.aggregate_function_select.val();
|
||||
if(fn === 'sum' || fn === 'avg') {
|
||||
// pick numeric fields for sum / avg
|
||||
if(frappe.model.is_numeric_field(field.fieldtype)) {
|
||||
this.aggregate_on_select.append(
|
||||
$('<option>', { value : field.fieldname })
|
||||
.text(field.label));
|
||||
let fn = this.aggregate_function_select.val();
|
||||
if (fn === 'sum' || fn === 'avg') {
|
||||
if (!this.aggregate_on_html.length) {
|
||||
this.aggregate_on_html = `<option value="" disabled selected>
|
||||
${__("Select Field...")}</option>`;
|
||||
|
||||
for (let doctype in this.all_fields) {
|
||||
const doctype_fields = this.all_fields[doctype];
|
||||
doctype_fields.forEach(field => {
|
||||
// pick numeric fields for sum / avg
|
||||
if (frappe.model.is_numeric_field(field.fieldtype)) {
|
||||
let option_text = doctype == this.doctype
|
||||
? field.label
|
||||
: `${field.label} (${doctype})`;
|
||||
this.aggregate_on_html+= `<option data-doctype="${doctype}"
|
||||
value="${field.fieldname}">${option_text}</option>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.aggregate_on_select.show();
|
||||
} else {
|
||||
// count, so no aggregate function
|
||||
this.aggregate_on_select.hide();
|
||||
}
|
||||
});
|
||||
this.aggregate_on_select.html(this.aggregate_on_html);
|
||||
this.aggregate_on_select.show();
|
||||
} else {
|
||||
// count, so no aggregate function
|
||||
this.aggregate_on_select.hide();
|
||||
}
|
||||
}
|
||||
|
||||
get_settings() {
|
||||
|
|
@ -114,8 +125,10 @@ frappe.ui.GroupBy = class {
|
|||
|
||||
if (this.aggregate_function === 'count') {
|
||||
this.aggregate_on = 'name';
|
||||
this.aggregate_on_doctype = null;
|
||||
} else {
|
||||
this.aggregate_on = this.aggregate_on_select.val();
|
||||
this.aggregate_on_doctype = this.aggregate_on_select.find(':selected').attr('data-doctype');
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -143,11 +156,13 @@ frappe.ui.GroupBy = class {
|
|||
|
||||
set_args(args) {
|
||||
if (this.aggregate_function && this.group_by) {
|
||||
let aggregate_column;
|
||||
if(this.aggregate_function === 'count') {
|
||||
let aggregate_column, aggregate_on_field;
|
||||
if (this.aggregate_function === 'count') {
|
||||
aggregate_column = 'count(`tab'+ this.doctype + '`.`name`)';
|
||||
} else {
|
||||
aggregate_column = `${this.aggregate_function}(\`tab${this.doctype}\`.\`${this.aggregate_on}\`)`;
|
||||
aggregate_column =
|
||||
`${this.aggregate_function}(\`tab${this.aggregate_on_doctype}\`.\`${this.aggregate_on}\`)`;
|
||||
aggregate_on_field = '`tab' + this.aggregate_on_doctype + '`.`' + this.aggregate_on + '`';
|
||||
}
|
||||
|
||||
this.report_view.group_by = this.group_by;
|
||||
|
|
@ -166,10 +181,14 @@ frappe.ui.GroupBy = class {
|
|||
// rebuild fields for group by
|
||||
args.fields = this.report_view.get_fields();
|
||||
|
||||
// add aggregate column in both query args and report view
|
||||
this.report_view.fields.push(['_aggregate_column', this.doctype]);
|
||||
// add aggregate column in both query args and report views
|
||||
this.report_view.fields.push(['_aggregate_column', this.aggregate_on_doctype || this.doctype]);
|
||||
args.fields.push(aggregate_column + ' as _aggregate_column');
|
||||
|
||||
if (aggregate_on_field) {
|
||||
args.fields.push(aggregate_on_field);
|
||||
}
|
||||
|
||||
// setup columns in datatable
|
||||
this.report_view.setup_columns();
|
||||
|
||||
|
|
@ -195,7 +214,7 @@ frappe.ui.GroupBy = class {
|
|||
};
|
||||
} else {
|
||||
// get properties of "aggregate_on", for example Net Total
|
||||
docfield = Object.assign({}, frappe.meta.docfield_map[this.doctype][this.aggregate_on]);
|
||||
docfield = Object.assign({}, frappe.meta.docfield_map[this.aggregate_on_doctype][this.aggregate_on]);
|
||||
if (this.aggregate_function === 'sum') {
|
||||
docfield.label = __('Sum of {0}', [docfield.label]);
|
||||
} else {
|
||||
|
|
@ -230,9 +249,12 @@ frappe.ui.GroupBy = class {
|
|||
}
|
||||
|
||||
get_group_by_fields() {
|
||||
let group_by_fields = {};
|
||||
this.group_by_fields = {};
|
||||
this.all_fields = {};
|
||||
|
||||
let fields = this.report_view.meta.fields.filter(f => ["Select", "Link", "Data", "Int"].includes(f.fieldtype));
|
||||
group_by_fields[this.doctype] = fields;
|
||||
this.group_by_fields[this.doctype] = fields;
|
||||
this.all_fields[this.doctype] = this.report_view.meta.fields;
|
||||
|
||||
const standard_fields_filter = df =>
|
||||
!in_list(frappe.model.no_value_type, df.fieldtype) && !df.report_hide;
|
||||
|
|
@ -243,10 +265,10 @@ frappe.ui.GroupBy = class {
|
|||
table_fields.forEach(df => {
|
||||
const cdt = df.options;
|
||||
const child_table_fields = frappe.meta.get_docfields(cdt).filter(standard_fields_filter);
|
||||
group_by_fields[cdt] = child_table_fields;
|
||||
this.group_by_fields[cdt] = child_table_fields;
|
||||
this.all_fields[cdt] = child_table_fields;
|
||||
});
|
||||
|
||||
return group_by_fields;
|
||||
return this.group_by_fields;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,17 +14,30 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
|
|||
this.$next_btn = this.slides_footer.find('.next-btn');
|
||||
this.$complete_btn = this.slides_footer.find('.complete-btn');
|
||||
this.$action_button = this.slides_footer.find('.next-btn');
|
||||
|
||||
if (this.help_links) {
|
||||
this.$help_links = $(`<div class="text-center">
|
||||
<div class="help-links"></div>
|
||||
</div>`).appendTo(this.$body);
|
||||
this.setup_help_links();
|
||||
}
|
||||
|
||||
this.$skip_btn = this.slides_footer.find('.skip-btn').on('click', () => {
|
||||
$('.onboarding-dialog').modal('toggle');
|
||||
});
|
||||
}
|
||||
|
||||
setup_form() {
|
||||
super.setup_form();
|
||||
const fields = this.get_atomic_fields();
|
||||
|
||||
// remove link indicator
|
||||
fields.map((field) => {
|
||||
if (field.fieldtype == 'Link') {
|
||||
$('.link-btn').remove();
|
||||
}
|
||||
});
|
||||
|
||||
if (fields.length == 1) {
|
||||
this.$form_wrapper.addClass("text-center");
|
||||
} else {
|
||||
|
|
@ -33,8 +46,13 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
|
|||
}
|
||||
|
||||
before_show() {
|
||||
(this.id === 0) ?
|
||||
this.$next_btn.text(__('Let\'s Start')) : this.$next_btn.text(__('Next'));
|
||||
if (this.id === 0) {
|
||||
this.$next_btn.text(__('Let\'s Go'));
|
||||
this.$skip_btn.removeClass('hide');
|
||||
} else {
|
||||
this.$next_btn.text(__('Next'));
|
||||
this.$skip_btn.addClass('hide');
|
||||
}
|
||||
//last slide
|
||||
if (this.is_last_slide()) {
|
||||
this.$complete_btn.removeClass('hide').addClass('action primary');
|
||||
|
|
@ -143,7 +161,10 @@ frappe.setup.OnboardingDialog = class OnboardingDialog {
|
|||
before_load: ($footer) => {
|
||||
$footer.find('.prev-btn').addClass('hide');
|
||||
$footer.find('.next-btn').removeClass('btn-default').addClass('btn-primary action');
|
||||
$footer.find('.text-right').prepend(
|
||||
$footer.find('.prev-div').prepend(
|
||||
$(`<a class="skip-btn text-muted btn btn-link btn-sm hide">
|
||||
${__("Do It Later")}</a>`));
|
||||
$footer.find('.next-div').prepend(
|
||||
$(`<a class="complete-btn btn btn-primary btn-sm hide">
|
||||
${__("Complete")}</a>`));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ frappe.ui.Slide = class Slide {
|
|||
// Form methods
|
||||
get_atomic_fields() {
|
||||
var fields = JSON.parse(JSON.stringify(this.fields));
|
||||
if(this.add_more) {
|
||||
if (this.add_more) {
|
||||
this.count = 1;
|
||||
fields = fields.map((field, i) => {
|
||||
if (field.fieldname) {
|
||||
|
|
@ -149,9 +149,14 @@ frappe.ui.Slide = class Slide {
|
|||
bind_fields_to_action_btn() {
|
||||
var me = this;
|
||||
this.reqd_fields.map((field) => {
|
||||
field.$wrapper.on('change input', () => {
|
||||
field.$wrapper.on('change input click', () => {
|
||||
me.reset_action_button_state();
|
||||
});
|
||||
field.$wrapper.on('keydown', 'input', e => {
|
||||
if (e.key == 'Enter') {
|
||||
me.reset_action_button_state();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -332,10 +337,10 @@ frappe.ui.Slides = class Slides {
|
|||
|
||||
make_prev_next_buttons() {
|
||||
$(`<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="col-sm-4 text-left prev-div">
|
||||
<a class="prev-btn btn btn-default btn-sm" tabindex="0">${__("Previous")}</a>
|
||||
</div>
|
||||
<div class="col-sm-8 text-right">
|
||||
<div class="col-sm-8 text-right next-div">
|
||||
<a class="next-btn btn btn-default btn-sm" tabindex="0">${__("Next")}</a>
|
||||
</div>
|
||||
</div>`).appendTo(this.$footer);
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="!hidden"
|
||||
class="border module-box"
|
||||
:class="{ 'hovered-box': hovered }"
|
||||
:data-module-name="module_name"
|
||||
>
|
||||
<div class="flush-top">
|
||||
<div class="module-box-content">
|
||||
<div class="level">
|
||||
<a class="module-box-link" :href="type === 'module' ? '#modules/' + module_name : link">
|
||||
<h4 class="h4">
|
||||
<div>
|
||||
<i :class="icon_class" style="color:#8d99a6;font-size:18px;margin-right:6px;"></i>
|
||||
{{ label }}
|
||||
</div>
|
||||
</h4>
|
||||
</a>
|
||||
<dropdown v-if="dropdown_links && dropdown_links.length" :items="dropdown_links">
|
||||
<span class="pull-right">
|
||||
<i class="octicon octicon-chevron-down text-muted"></i>
|
||||
</span>
|
||||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "./Dropdown.vue";
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"index",
|
||||
"name",
|
||||
"label",
|
||||
"category",
|
||||
"type",
|
||||
"module_name",
|
||||
"link",
|
||||
"count",
|
||||
"onboard_present",
|
||||
"links",
|
||||
"description",
|
||||
"hidden",
|
||||
"icon"
|
||||
],
|
||||
components: {
|
||||
Dropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovered: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
icon_class() {
|
||||
if (this.icon) {
|
||||
return this.icon;
|
||||
} else {
|
||||
return "octicon octicon-file-text";
|
||||
}
|
||||
},
|
||||
dropdown_links() {
|
||||
return this.type === 'module' ? this.links
|
||||
.filter(link => !link.hidden)
|
||||
.concat([
|
||||
{ label: __('Customize'), action: () => this.$emit('customize'), class: 'border-top' }
|
||||
]) : [];
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "frappe/public/less/variables";
|
||||
|
||||
.module-box {
|
||||
border-radius: 4px;
|
||||
padding: 5px 15px;
|
||||
display: block;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.module-box.sortable-chosen {
|
||||
background-color: @disabled-background;
|
||||
border-color: @disabled-background;
|
||||
}
|
||||
|
||||
.modules-container:not(.dragging) .module-box:hover {
|
||||
border-color: @text-muted;
|
||||
}
|
||||
|
||||
.hovered-box {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.octicon-chevron-down {
|
||||
font-size: 14px;
|
||||
padding: 4px 6px 2px 6px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: @btn-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.octicon-chevron-down:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.module-box-content {
|
||||
width: 100%;
|
||||
|
||||
p {
|
||||
margin-top: 5px;
|
||||
font-size: 80%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.module-box-link {
|
||||
flex: 1;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
text-decoration: none;
|
||||
--moz-text-decoration-line: none;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
padding: 15px;
|
||||
width: 54px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.open-notification {
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.shortcut-tag {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="section-header level text-muted">
|
||||
<div class="module-category h6 uppercase">{{ __(this.category) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="modules-container" :class="{'dragging': dragging}" ref="modules-container">
|
||||
<desk-module-box
|
||||
v-for="(module, index) in modules"
|
||||
:key="module.module_name"
|
||||
:index="index"
|
||||
v-bind="module"
|
||||
@customize="show_module_card_customize_dialog(module)"
|
||||
></desk-module-box>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeskModuleBox from "./DeskModuleBox.vue";
|
||||
|
||||
export default {
|
||||
props: ['category', 'modules'],
|
||||
components: {
|
||||
DeskModuleBox
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
fetched_module_links: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!frappe.utils.is_mobile()) {
|
||||
this.setup_sortable();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setup_sortable() {
|
||||
let modules_container =this.$refs['modules-container'];
|
||||
this.sortable = new Sortable(modules_container, {
|
||||
animation: 150,
|
||||
onStart: () => this.dragging = true,
|
||||
onEnd: () => {
|
||||
this.dragging = false;
|
||||
let modules = Array.from(modules_container.querySelectorAll('.module-box'))
|
||||
.map(node => node.dataset.moduleName);
|
||||
|
||||
this.$emit('module-order-change', {
|
||||
module_category: this.category,
|
||||
modules
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
show_module_card_customize_dialog(module) {
|
||||
const me = this;
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Customize Shortcuts'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Shortcuts'),
|
||||
fieldname: 'links',
|
||||
fieldtype: 'MultiSelectPills',
|
||||
get_data: () => {
|
||||
const module_links = me.fetched_module_links[module.module_name];
|
||||
if (!module_links) {
|
||||
return frappe.xcall('frappe.desk.moduleview.get_links_for_module', {
|
||||
app: module.app,
|
||||
module: module.module_name,
|
||||
}).then(links => {
|
||||
me.fetched_module_links[module.module_name] = links;
|
||||
return links;
|
||||
});
|
||||
} else {
|
||||
return module_links;
|
||||
}
|
||||
},
|
||||
default: module.links.filter(l => !l.hidden).map(l => l.name)
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: ({ links }) => {
|
||||
frappe.call('frappe.desk.moduleview.update_links_for_module', {
|
||||
module_name: module.module_name,
|
||||
links: links || []
|
||||
}).then(r => {
|
||||
this.$emit('update-desktop-settings', r.message);
|
||||
});
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.modules-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
grid-auto-rows: minmax(62px, 1fr);
|
||||
column-gap: 15px;
|
||||
row-gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
<template>
|
||||
<div class="modules-page-container" v-if="home_settings_fetched">
|
||||
<a
|
||||
class="btn-show-hide text-muted text-medium"
|
||||
@click="show_hide_cards_dialog"
|
||||
>
|
||||
{{ __('Show / Hide Cards') }}
|
||||
</a>
|
||||
<div
|
||||
class="modules-section"
|
||||
v-for="(category, i) in module_categories" :key="category"
|
||||
>
|
||||
<desk-section
|
||||
v-if="get_modules_for_category(category).length"
|
||||
:category="category"
|
||||
:modules="get_modules_for_category(category)"
|
||||
@update-desktop-settings="update_desktop_settings"
|
||||
@module-order-change="update_module_order"
|
||||
>
|
||||
</desk-section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DeskSection from './DeskSection.vue';
|
||||
import { generate_route } from './utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DeskSection
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
module_categories: ['Modules', 'Domains', 'Places', 'Administration'],
|
||||
modules: [],
|
||||
home_settings_fetched: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch_desktop_settings();
|
||||
},
|
||||
methods: {
|
||||
fetch_desktop_settings() {
|
||||
frappe.call('frappe.desk.moduleview.get_desktop_settings')
|
||||
.then(r => {
|
||||
if (r.message) {
|
||||
this.update_desktop_settings(r.message);
|
||||
this.home_settings_fetched = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
update_desktop_settings(desktop_settings) {
|
||||
this.modules = this.add_routes_for_module_links(desktop_settings);
|
||||
},
|
||||
add_routes_for_module_links(user_settings) {
|
||||
for (let category in user_settings) {
|
||||
user_settings[category] = user_settings[category].map(m => {
|
||||
m.links = (m.links || []).map(link => {
|
||||
link.route = generate_route(link);
|
||||
return link;
|
||||
});
|
||||
return m;
|
||||
});
|
||||
}
|
||||
return user_settings;
|
||||
},
|
||||
update_module_order({ module_category, modules }) {
|
||||
frappe.call('frappe.desk.moduleview.update_modules_order', { module_category, modules });
|
||||
},
|
||||
get_modules_for_category(category) {
|
||||
return this.modules[category] || [];
|
||||
},
|
||||
show_hide_cards_dialog() {
|
||||
frappe.call('frappe.desk.moduleview.get_options_for_show_hide_cards')
|
||||
.then(r => {
|
||||
let { user_options, global_options } = r.message;
|
||||
|
||||
let user_value = `User (${frappe.session.user})`
|
||||
let fields = [
|
||||
{
|
||||
label: __('Setup For'),
|
||||
fieldname: 'setup_for',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
{
|
||||
label: __('User ({0})', [frappe.session.user]),
|
||||
value: user_value
|
||||
},
|
||||
{
|
||||
label: __('Everyone'),
|
||||
value: 'Everyone'
|
||||
}
|
||||
],
|
||||
default: user_value,
|
||||
depends_on: doc => frappe.user_roles.includes('System Manager'),
|
||||
onchange() {
|
||||
let value = d.get_value('setup_for');
|
||||
let field = d.get_field('setup_for');
|
||||
let description = value === 'Everyone' ? __('Hide cards for all users') : '';
|
||||
field.set_description(description);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let user_section = this.module_categories.map(category => {
|
||||
let options = user_options.filter(m => m.category === category);
|
||||
return {
|
||||
label: category,
|
||||
fieldname: `user:${category}`,
|
||||
fieldtype: 'MultiCheck',
|
||||
options,
|
||||
columns: 2
|
||||
}
|
||||
}).filter(f => f.options.length > 0);
|
||||
|
||||
user_section = [
|
||||
{
|
||||
fieldname: 'user_section',
|
||||
fieldtype: 'Section Break',
|
||||
depends_on: doc => doc.setup_for === user_value
|
||||
}
|
||||
].concat(user_section);
|
||||
|
||||
let global_section = this.module_categories.map(category => {
|
||||
let options = global_options.filter(m => m.category === category);
|
||||
return {
|
||||
label: category,
|
||||
fieldname: `global:${category}`,
|
||||
fieldtype: 'MultiCheck',
|
||||
options,
|
||||
columns: 2
|
||||
}
|
||||
}).filter(f => f.options.length > 0);
|
||||
|
||||
global_section = [
|
||||
{
|
||||
fieldname: 'global_section',
|
||||
fieldtype: 'Section Break',
|
||||
depends_on: doc => doc.setup_for === 'Everyone'
|
||||
}
|
||||
].concat(global_section);
|
||||
|
||||
fields = fields.concat(user_section, global_section);
|
||||
|
||||
let old_values = null;
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Show / Hide Cards'),
|
||||
fields: fields,
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: (values) => {
|
||||
if (values.setup_for === 'Everyone') {
|
||||
this.update_global_modules(d);
|
||||
} else {
|
||||
this.update_user_modules(d, old_values);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
|
||||
// deepcopy
|
||||
old_values = JSON.parse(JSON.stringify(d.get_values()));
|
||||
});
|
||||
},
|
||||
|
||||
update_user_modules(d, old_values) {
|
||||
let new_values = d.get_values();
|
||||
let category_map = {};
|
||||
for (let category of this.module_categories) {
|
||||
let old_modules = old_values[`user:${category}`] || [];
|
||||
let new_modules = new_values[`user:${category}`] || [];
|
||||
|
||||
let removed = old_modules.filter(module => !new_modules.includes(module));
|
||||
let added = new_modules.filter(module => !old_modules.includes(module));
|
||||
|
||||
category_map[category] = { added, removed };
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.desk.moduleview.update_hidden_modules',
|
||||
args: { category_map },
|
||||
btn: d.get_primary_btn()
|
||||
}).then(r => {
|
||||
this.update_desktop_settings(r.message);
|
||||
d.hide();
|
||||
});
|
||||
},
|
||||
|
||||
update_global_modules(d) {
|
||||
let blocked_modules = [];
|
||||
for (let category of this.module_categories) {
|
||||
let field = d.get_field(`global:${category}`);
|
||||
if (field) {
|
||||
let unchecked_options = field.get_unchecked_options();
|
||||
blocked_modules = blocked_modules.concat(unchecked_options);
|
||||
}
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.desk.moduleview.update_global_hidden_modules',
|
||||
args: {
|
||||
modules: blocked_modules
|
||||
},
|
||||
btn: d.get_primary_btn()
|
||||
}).then(r => {
|
||||
this.update_desktop_settings(r.message);
|
||||
d.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.modules-page-container {
|
||||
position: relative;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 30px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.modules-section {
|
||||
position: relative;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn-show-hide {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 39px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toolbar-underlay {
|
||||
margin: 70px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
<template>
|
||||
<Popover :align="align">
|
||||
<slot></slot>
|
||||
<ul slot="popover-content" class="list-reset border">
|
||||
<li v-for="item of dropdownItems" :key="item.label" :class="item.class || null">
|
||||
<a v-if="item.route" class="list-item" :href="item.route">{{ item.label }}</a>
|
||||
<div v-else class="list-item" @click="item.action">{{ item.label }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Popover>
|
||||
</template>
|
||||
<script>
|
||||
import Popover from "./Popover.vue";
|
||||
|
||||
export default {
|
||||
name: "Dropdown",
|
||||
components: {
|
||||
Popover
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "Dropdown"
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: "right"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropdownItems() {
|
||||
return (this.items || []).map(item => {
|
||||
if (typeof item === "string") {
|
||||
return {
|
||||
label: item,
|
||||
action: console.log
|
||||
};
|
||||
}
|
||||
if (!item.action && item.route) {
|
||||
item.action = this.setRoute.bind(this, item.route);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setRoute(route) {
|
||||
this.$router.push(route);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.list-reset {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
width: 16rem;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08);
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
.list-item:hover {
|
||||
background-color: #f0f4f7;
|
||||
}
|
||||
.list-item {
|
||||
padding: 14px;
|
||||
transition: all 0.1s ease-in;
|
||||
}
|
||||
a {
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="sections.length" class="sections-container">
|
||||
<div v-for="section in sections"
|
||||
:key="section.label"
|
||||
class="border section-box"
|
||||
>
|
||||
<h4 class="h4"> {{ section.label }} </h4>
|
||||
<module-link-item v-for="item in section.items"
|
||||
:key="section.label + item.label"
|
||||
:data-youtube-id="item.type==='help' ? item.youtube_id : false"
|
||||
v-bind="item"
|
||||
>
|
||||
</module-link-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="sections-container">
|
||||
<div v-for="n in 3" :key="n" class="skeleton-section-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModuleLinkItem from "./ModuleLinkItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModuleLinkItem
|
||||
},
|
||||
props: ['module_name', 'sections'],
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.sections-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
column-gap: 15px;
|
||||
row-gap: 15px;
|
||||
}
|
||||
|
||||
.section-box {
|
||||
padding: 5px 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.skeleton-section-box {
|
||||
background-color: #f5f7fa;
|
||||
height: 250px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<div class="link-item flush-top small"
|
||||
:class="{'onboard-spotlight': onboard, 'disabled-link': disabled_dependent}"
|
||||
@mouseover="mouseover" @mouseleave="mouseleave"
|
||||
>
|
||||
<span :class="['indicator', indicator_color]"></span>
|
||||
|
||||
<span v-if="disabled_dependent" class="link-content text-muted">{{ label || __(name) }}</span>
|
||||
<a v-else class="link-content" :href="route" @click.prevent="handle_click">
|
||||
{{ label || __(name) }}
|
||||
</a>
|
||||
<div v-if="disabled_dependent" v-show="popover_active"
|
||||
@mouseover="popover_hover = true" @mouseleave="popover_hover = false"
|
||||
class="module-link-popover popover fade top in" role="tooltip"
|
||||
>
|
||||
<div class="arrow"></div>
|
||||
<h3 class="popover-title" style="display: none;"></h3>
|
||||
<div class="popover-content" style="padding: 12px;">
|
||||
<div class="small text-muted">{{ __("You need to create these first: ") }}</div>
|
||||
<div class="small">{{ __(incomplete_dependencies.join(", ")) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['label', 'name', 'dependencies', 'incomplete_dependencies',
|
||||
'onboard', 'count', 'route', 'doctype', 'open_count', 'youtube_id'],
|
||||
data() {
|
||||
return {
|
||||
hover: false,
|
||||
popover_hover: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disabled_dependent() {
|
||||
return this.dependencies && this.incomplete_dependencies;
|
||||
},
|
||||
|
||||
indicator_color() {
|
||||
if(this.open_count) {
|
||||
return 'red';
|
||||
}
|
||||
if(this.onboard) {
|
||||
return this.count ? 'blue' : 'orange';
|
||||
};
|
||||
return 'grey';
|
||||
},
|
||||
|
||||
popover_active() {
|
||||
return this.popover_hover || this.hover;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mouseover() {
|
||||
$('.module-link-popover').hide();
|
||||
this.hover = true;
|
||||
},
|
||||
|
||||
mouseleave() {
|
||||
setTimeout(() => {
|
||||
this.hover = false;
|
||||
}, 300);
|
||||
},
|
||||
|
||||
handle_click(e) {
|
||||
if (this.youtube_id) {
|
||||
frappe.help.show_video(this.youtube_id);
|
||||
} else {
|
||||
frappe.set_route(this.route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" scoped>
|
||||
.link-item {
|
||||
position: relative;
|
||||
margin: 10px 0px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.onboard-spotlight {
|
||||
.link-content {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
// Overriding indicator styles
|
||||
.indicator {
|
||||
margin-right: 5px;
|
||||
color: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.link-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.popover {
|
||||
display: block;
|
||||
top: -60px;
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
.popover.top > .arrow {
|
||||
left: 20%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<template>
|
||||
<div class="modules-page-container">
|
||||
<module-detail
|
||||
v-if="
|
||||
this.route && modules_list.map(m => m.module_name).includes(route[1])
|
||||
"
|
||||
:module_name="route[1]"
|
||||
:sections="current_module_sections"
|
||||
></module-detail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModuleDetail from './ModuleDetail.vue'
|
||||
import { generate_route } from './utils.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModuleDetail,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
route: frappe.get_route(),
|
||||
current_module_label: '',
|
||||
current_module_sections: [],
|
||||
modules_data_cache: {},
|
||||
modules_list: frappe.boot.allowed_modules.filter(
|
||||
d => (d.type === 'module' || d.category === 'Places') && !d.blocked
|
||||
),
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.update_current_module()
|
||||
},
|
||||
mounted() {
|
||||
frappe.module_links = {}
|
||||
frappe.route.on('change', () => {
|
||||
this.update_current_module()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
update_current_module() {
|
||||
let route = frappe.get_route()
|
||||
if (route[0] === 'modules') {
|
||||
this.route = route
|
||||
let module = this.modules_list.filter(m => m.module_name == route[1])[0]
|
||||
let module_name = module && (module.label || module.module_name)
|
||||
let title = this.current_module_label
|
||||
? this.current_module_label
|
||||
: module_name
|
||||
|
||||
frappe.modules.home && frappe.modules.home.page.set_title(title)
|
||||
|
||||
if (!frappe.modules.home) {
|
||||
setTimeout(() => {
|
||||
frappe.modules.home.page.set_title(title)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
if (module_name) {
|
||||
this.get_module_sections(module.module_name)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_module_sections(module_name) {
|
||||
let cache = this.modules_data_cache[module_name]
|
||||
if (cache) {
|
||||
this.current_module_sections = cache
|
||||
} else {
|
||||
this.current_module_sections = []
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.moduleview.get',
|
||||
args: {
|
||||
module: module_name,
|
||||
},
|
||||
callback: r => {
|
||||
var m = frappe.get_module(module_name)
|
||||
this.current_module_sections = r.message.data
|
||||
this.process_data(module_name, this.current_module_sections)
|
||||
this.modules_data_cache[module_name] = this.current_module_sections
|
||||
},
|
||||
freeze: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
process_data(module_name, data) {
|
||||
frappe.module_links[module_name] = []
|
||||
data.forEach(function(section) {
|
||||
section.items.forEach(function(item) {
|
||||
item.route = generate_route(item)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.modules-page-container {
|
||||
margin: 15px 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
<template>
|
||||
<div class="inline-block relative" :class="{ 'w-full': this.fullwidth }" v-outside="closePopover">
|
||||
<div @click="togglePopover">
|
||||
<slot :togglePopover="togglePopover" :closePopover="closePopover"></slot>
|
||||
</div>
|
||||
<div v-show="isOpen" class="absolute mt-default z-20" :class="popoverClasses">
|
||||
<slot name="popover-content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
let instances = [];
|
||||
|
||||
function onDocumentClick(e, el, fn) {
|
||||
let target = e.target;
|
||||
if (el !== target && !el.contains(target)) {
|
||||
fn(e);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "Popover",
|
||||
props: {
|
||||
align: {
|
||||
default: "left"
|
||||
},
|
||||
fullwidth: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
directives: {
|
||||
outside: {
|
||||
bind(el, binding) {
|
||||
el.dataset.outsideClickIndex = instances.length;
|
||||
|
||||
const fn = binding.value;
|
||||
const click = function(e) {
|
||||
onDocumentClick(e, el, fn);
|
||||
};
|
||||
|
||||
document.addEventListener("click", click);
|
||||
instances.push(click);
|
||||
},
|
||||
unbind(el) {
|
||||
const index = el.dataset.outsideClickIndex;
|
||||
const handler = instances[index];
|
||||
document.addEventListener("click", handler);
|
||||
instances.splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
popoverClasses() {
|
||||
return {
|
||||
"pin-r": this.align === "right",
|
||||
"pin-l": this.align === "left",
|
||||
"w-full": this.fullwidth === true
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
togglePopover() {
|
||||
this.isOpen = !this.isOpen;
|
||||
},
|
||||
closePopover() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.pin-r {
|
||||
right: 0;
|
||||
}
|
||||
.pin-l {
|
||||
left: 0;
|
||||
}
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.mt-default {
|
||||
margin-top: 25px;
|
||||
}
|
||||
.z-20 {
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
function generate_route(item) {
|
||||
if(item.type==="doctype") {
|
||||
item.doctype = item.name;
|
||||
}
|
||||
let route = '';
|
||||
if(!item.route) {
|
||||
if(item.link) {
|
||||
route=strip(item.link, "#");
|
||||
} else if(item.type==="doctype") {
|
||||
if(frappe.model.is_single(item.doctype)) {
|
||||
route = 'Form/' + item.doctype;
|
||||
} else {
|
||||
if (item.filters) {
|
||||
frappe.route_options=item.filters;
|
||||
}
|
||||
route="List/" + item.doctype;
|
||||
}
|
||||
} else if(item.type==="report" && item.is_query_report) {
|
||||
route="query-report/" + item.name;
|
||||
} else if(item.type==="report") {
|
||||
route="List/" + item.doctype + "/Report/" + item.name;
|
||||
} else if(item.type==="page") {
|
||||
route=item.name;
|
||||
}
|
||||
|
||||
route = '#' + route;
|
||||
} else {
|
||||
route = item.route;
|
||||
}
|
||||
|
||||
if(item.route_options) {
|
||||
route += "?" + $.map(item.route_options, function(value, key) {
|
||||
return encodeURIComponent(key) + "=" + encodeURIComponent(value); }).join('&');
|
||||
}
|
||||
|
||||
// if(item.type==="page" || item.type==="help" || item.type==="report" ||
|
||||
// (item.doctype && frappe.model.can_read(item.doctype))) {
|
||||
// item.shown = true;
|
||||
// }
|
||||
return route;
|
||||
}
|
||||
|
||||
export {
|
||||
generate_route
|
||||
};
|
||||
310
frappe/public/js/frappe/views/desktop/desktop.js
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
import ChartWidget from "../widgets/chart_widget";
|
||||
import WidgetGroup from "../widgets/widget_group";
|
||||
|
||||
export default class Desktop {
|
||||
constructor({ wrapper }) {
|
||||
this.wrapper = wrapper;
|
||||
window.desk = this;
|
||||
this.pages = {};
|
||||
this.sidebar_items = {};
|
||||
this.sidebar_categories = [
|
||||
"Modules",
|
||||
"Domains",
|
||||
"Places",
|
||||
"Administration"
|
||||
];
|
||||
this.make();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.make_container();
|
||||
// this.show_loading_state();
|
||||
this.fetch_desktop_settings().then(() => {
|
||||
this.route();
|
||||
this.make_sidebar();
|
||||
this.setup_events();
|
||||
// this.hide_loading_state();
|
||||
});
|
||||
}
|
||||
|
||||
route() {
|
||||
let page = this.get_page_to_show();
|
||||
this.show_page(page);
|
||||
}
|
||||
|
||||
make_container() {
|
||||
this.container = $(`<div class="desk-container row">
|
||||
<div class="desk-sidebar"></div>
|
||||
<div class="desk-body"></div>
|
||||
</div>`);
|
||||
|
||||
this.container.appendTo(this.wrapper);
|
||||
this.sidebar = this.container.find(".desk-sidebar");
|
||||
this.body = this.container.find(".desk-body");
|
||||
}
|
||||
|
||||
show_loading_state() {
|
||||
// Add skeleton
|
||||
let loading_sidebar = $(
|
||||
'<div class="skeleton skeleton-full" style="height: 90vh;"></div>'
|
||||
);
|
||||
let loading_body = $(
|
||||
`<div class="skeleton skeleton-full" style="height: 90vh;"></div>`
|
||||
);
|
||||
|
||||
// Append skeleton to body
|
||||
loading_sidebar.appendTo(this.sidebar);
|
||||
loading_body.appendTo(this.body);
|
||||
}
|
||||
|
||||
hide_loading_state() {
|
||||
// Remove all skeleton
|
||||
this.container.find(".skeleton").remove();
|
||||
}
|
||||
|
||||
fetch_desktop_settings() {
|
||||
return frappe
|
||||
.call("frappe.desk.desktop.get_desk_sidebar_items")
|
||||
.then(response => {
|
||||
if (response.message) {
|
||||
this.desktop_settings = response.message;
|
||||
} else {
|
||||
frappe.throw({
|
||||
title: "Couldn't Load Desk",
|
||||
message:
|
||||
"Something went wrong while loading Desk. <b>Please relaod the page</b>. If the problem persists, contact the Administrator",
|
||||
indicator: "red",
|
||||
primary_action: {
|
||||
label: "Reload",
|
||||
action: () => location.reload()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
make_sidebar() {
|
||||
const get_sidebar_item = function(item) {
|
||||
return $(`<a href="${"desk#workspace/" +
|
||||
item.name}" class="sidebar-item ${
|
||||
item.selected ? "selected" : ""
|
||||
}">
|
||||
<span>${item.name}</span>
|
||||
</div>`);
|
||||
};
|
||||
|
||||
const make_sidebar_category_item = item => {
|
||||
if (item.name == this.get_page_to_show()) {
|
||||
item.selected = true;
|
||||
this.current_page = item.name;
|
||||
}
|
||||
let $item = get_sidebar_item(item);
|
||||
$item.appendTo(this.sidebar);
|
||||
this.sidebar_items[item.name] = $item;
|
||||
};
|
||||
|
||||
const make_category_title = name => {
|
||||
let $title = $(
|
||||
`<div class="sidebar-group-title h6 uppercase">${name}</div>`
|
||||
);
|
||||
$title.appendTo(this.sidebar);
|
||||
};
|
||||
|
||||
this.sidebar_categories.forEach(category => {
|
||||
if (this.desktop_settings.hasOwnProperty(category)) {
|
||||
make_category_title(category);
|
||||
this.desktop_settings[category].forEach(item => {
|
||||
make_sidebar_category_item(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_page(page) {
|
||||
if (this.current_page && this.pages[this.current_page]) {
|
||||
this.pages[this.current_page].hide();
|
||||
}
|
||||
|
||||
if (this.sidebar_items && this.sidebar_items[this.current_page]) {
|
||||
this.sidebar_items[this.current_page].removeClass("selected");
|
||||
this.sidebar_items[page].addClass("selected");
|
||||
}
|
||||
this.current_page = page;
|
||||
localStorage.current_desk_page = page;
|
||||
frappe.set_route("workspace", page);
|
||||
|
||||
this.pages[page] ? this.pages[page].show() : this.make_page(page);
|
||||
}
|
||||
|
||||
get_page_to_show() {
|
||||
let page =
|
||||
frappe.get_route()[1] ||
|
||||
localStorage.current_desk_page ||
|
||||
this.desktop_settings["Modules"][0].name;
|
||||
return page;
|
||||
}
|
||||
|
||||
make_page(page) {
|
||||
const $page = new DesktopPage({
|
||||
container: this.body,
|
||||
page_name: page
|
||||
});
|
||||
|
||||
this.pages[page] = $page;
|
||||
return $page;
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
$(document).keydown(e => {
|
||||
if (e.keyCode == 9) {
|
||||
console.log("navigate");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopPage {
|
||||
constructor({ container, page_name }) {
|
||||
this.container = container;
|
||||
this.page_name = page_name;
|
||||
this.sections = {};
|
||||
this.allow_customization = false
|
||||
this.make();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.page.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.page.hide();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.make_page();
|
||||
this.get_data().then(res => {
|
||||
this.data = res.message;
|
||||
this.allow_customization = this.data.allow_customization
|
||||
// this.make_onboarding()
|
||||
if (!this.data) {
|
||||
delete localStorage.current_desk_page
|
||||
frappe.set_route('workspace')
|
||||
}
|
||||
|
||||
!this.sections["onboarding"] &&
|
||||
this.data.charts.items.length &&
|
||||
this.make_charts();
|
||||
this.data.shortcuts.items.length && this.make_shortcuts();
|
||||
this.data.cards.items.length && this.make_cards();
|
||||
});
|
||||
}
|
||||
|
||||
make_page() {
|
||||
this.page = $(
|
||||
`<div class="desk-page" data-page-name=${this.page_name}></div>`
|
||||
);
|
||||
this.page.appendTo(this.container);
|
||||
}
|
||||
|
||||
get_data() {
|
||||
return frappe.call("frappe.desk.desktop.get_desktop_page", {
|
||||
page: this.page_name
|
||||
});
|
||||
}
|
||||
|
||||
make_onboarding() {
|
||||
this.sections["onboarding"] = new WidgetGroup({
|
||||
title: `Getting Started`,
|
||||
container: this.page,
|
||||
type: "onboarding",
|
||||
columns: 1,
|
||||
widgets: [
|
||||
{
|
||||
label: "Unlock Great Customer Experience",
|
||||
subtitle: "Just a few steps, and you’re good to go.",
|
||||
steps: [
|
||||
{
|
||||
label: "Configure Lead Sources",
|
||||
completed: true
|
||||
},
|
||||
{
|
||||
label: "Add Your Leads",
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
label: "Create Your First Opportunity",
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
label: "Onboard your Sales Team",
|
||||
completed: false
|
||||
},
|
||||
{
|
||||
label: "Assign Territories",
|
||||
completed: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
make_charts() {
|
||||
this.sections["charts"] = new WidgetGroup({
|
||||
title: this.data.charts.label || `${this.page_name} Dashboard`,
|
||||
container: this.page,
|
||||
type: "chart",
|
||||
columns: 1,
|
||||
allow_sorting: false,
|
||||
widgets: this.data.charts.items
|
||||
});
|
||||
}
|
||||
|
||||
make_shortcuts() {
|
||||
this.sections["shortcuts"] = new WidgetGroup({
|
||||
title: this.data.shortcuts.label || `Your Shortcuts`,
|
||||
container: this.page,
|
||||
type: "bookmark",
|
||||
columns: 3,
|
||||
allow_sorting: this.allow_customization && frappe.is_mobile(),
|
||||
widgets: this.data.shortcuts.items
|
||||
});
|
||||
}
|
||||
|
||||
make_cards() {
|
||||
let cards = new WidgetGroup({
|
||||
title: this.data.cards.label || `Reports & Masters`,
|
||||
container: this.page,
|
||||
type: "links",
|
||||
columns: 3,
|
||||
allow_sorting: this.allow_customization && frappe.is_mobile(),
|
||||
widgets: this.data.cards.items
|
||||
});
|
||||
|
||||
this.sections["cards"] = cards;
|
||||
|
||||
const legend = [
|
||||
{
|
||||
color: "blue",
|
||||
description: __("Important")
|
||||
},
|
||||
{
|
||||
color: "orange",
|
||||
description: __("No Records Created")
|
||||
},
|
||||
{
|
||||
color: "red",
|
||||
description: __("DocType has Open Entries")
|
||||
}
|
||||
].map(item => {
|
||||
return `<div class="legend-item small text-muted justify-flex-start">
|
||||
<span class="indicator ${item.color}"></span>
|
||||
<span class="link-content ellipsis" draggable="false">${item.description}</span>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
$(`<div class="legend">
|
||||
${legend.join("\n")}
|
||||
</div>`).insertAfter(cards.body);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
import Desktop from './components/Desktop.vue';
|
||||
import Desktop from './desktop/desktop.js';
|
||||
|
||||
frappe.provide('frappe.views.pageview');
|
||||
frappe.provide("frappe.standard_pages");
|
||||
|
|
@ -38,24 +37,25 @@ frappe.views.pageview = {
|
|||
});
|
||||
}
|
||||
},
|
||||
|
||||
show: function(name) {
|
||||
if(!name) {
|
||||
name = (frappe.boot ? frappe.boot.home_page : window.page_name);
|
||||
|
||||
if(name === "desktop") {
|
||||
if(!frappe.pages.desktop) {
|
||||
let page = frappe.container.add_page('desktop');
|
||||
if(name === "workspace") {
|
||||
if(!frappe.workspace) {
|
||||
let page = frappe.container.add_page('workspace');
|
||||
let container = $('<div class="container"></div>').appendTo(page);
|
||||
container = $('<div></div>').appendTo(container);
|
||||
|
||||
new Vue({
|
||||
el: container[0],
|
||||
render: h => h(Desktop)
|
||||
});
|
||||
frappe.workspace = new Desktop({
|
||||
wrapper: container
|
||||
})
|
||||
}
|
||||
|
||||
frappe.container.change_to('desktop');
|
||||
frappe.utils.set_title(__('Home'));
|
||||
frappe.container.change_to('workspace');
|
||||
frappe.workspace.route();
|
||||
frappe.utils.set_title(__('Desk'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -186,6 +186,4 @@ frappe.views.ModulesFactory = class ModulesFactory extends frappe.views.Factory
|
|||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
|
@ -14,6 +14,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
super.setup_defaults();
|
||||
this.page_title = __('Report:') + ' ' + this.page_title;
|
||||
this.menu_items = this.report_menu_items();
|
||||
this.view = 'Report';
|
||||
|
||||
const route = frappe.get_route();
|
||||
if (route.length === 4) {
|
||||
|
|
@ -936,7 +937,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!docfield) return;
|
||||
if (!docfield || docfield.report_hide) return;
|
||||
|
||||
let title = __(docfield ? docfield.label : toTitle(fieldname));
|
||||
if (doctype !== this.doctype) {
|
||||
|
|
@ -1034,7 +1035,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
return {
|
||||
name: name,
|
||||
doctype: col.docfield.parent,
|
||||
content: d[cdt_field(col.field)],
|
||||
content: d[cdt_field(col.field)] || d[col.field],
|
||||
editable: Boolean(name && this.is_editable(col.docfield, d)),
|
||||
format: value => {
|
||||
return frappe.format(value, col.docfield, { always_show_decimals: true }, d);
|
||||
|
|
|
|||
55
frappe/public/js/frappe/views/widgets/base_widget.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
export default class Widget {
|
||||
constructor(opts) {
|
||||
Object.assign(this, opts);
|
||||
this.make();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
//
|
||||
}
|
||||
|
||||
customize() {
|
||||
|
||||
}
|
||||
|
||||
make() {
|
||||
this.make_widget();
|
||||
this.widget.appendTo(this.container);
|
||||
this.setup_events();
|
||||
}
|
||||
|
||||
make_widget() {
|
||||
this.widget = $(`<div class="widget">
|
||||
<div class="widget-head">
|
||||
<div class="widget-title"></div>
|
||||
<div class="widget-control"></div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
this.title_field = this.widget.find(".widget-title");
|
||||
this.body = this.widget.find(".widget-body");
|
||||
this.action_area = this.widget.find(".widget-control");
|
||||
this.head = this.widget.find(".widget-head");
|
||||
this.set_title();
|
||||
this.set_actions();
|
||||
this.set_body();
|
||||
}
|
||||
|
||||
set_title() {
|
||||
this.title_field[0].innerHTML = this.label || this.name;
|
||||
}
|
||||
|
||||
set_actions() {
|
||||
//
|
||||
}
|
||||
|
||||
set_body() {
|
||||
//
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
//
|
||||
}
|
||||
}
|
||||
60
frappe/public/js/frappe/views/widgets/chart_widget.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import Widget from "./base_widget.js";
|
||||
|
||||
export default class ChartWidget extends Widget {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
//
|
||||
}
|
||||
|
||||
customize() {
|
||||
this.setup_customize_actions();
|
||||
}
|
||||
|
||||
make_chart() {
|
||||
this.body.empty()
|
||||
frappe.model.with_doc("Dashboard Chart", this.chart_name).then(chart_doc => {
|
||||
chart_doc.width = 'Full'
|
||||
this.dashboard = new frappe.ui.DashboardChart(chart_doc, this.body, { hide_title: true, hide_last_sync: true, hide_actions: true });
|
||||
this.dashboard.show();
|
||||
});
|
||||
|
||||
this.summary && this.set_summary();
|
||||
}
|
||||
|
||||
set_body() {
|
||||
this.widget.addClass('dashboard-widget-box')
|
||||
this.make_chart();
|
||||
}
|
||||
|
||||
set_summary() {
|
||||
let summary = $(`<span class="dashboard-summary">$ 54,231</span>`);
|
||||
this.title_field.addClass('text-muted')
|
||||
summary.appendTo(this.body);
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
//
|
||||
}
|
||||
|
||||
setup_customize_actions() {
|
||||
this.action_area.empty()
|
||||
const buttons = $(`<button type="button" class="btn btn-xs btn-secondary btn-default selected">Resize</button>
|
||||
<button class="btn btn-secondary btn-light btn-danger btn-xs"><i class="fa fa-trash" aria-hidden="true"></i></button>`);
|
||||
buttons.appendTo(this.action_area);
|
||||
}
|
||||
|
||||
set_actions() {
|
||||
return
|
||||
this.action_area.empty()
|
||||
const buttons = $(`<div class="btn-group btn-group-xs" role="group" aria-label="Basic example">
|
||||
<button type="button" class="btn btn-secondary btn-default selected">Monthly</button>
|
||||
<button type="button" class="btn btn-secondary btn-default">Quaterly</button>
|
||||
<button type="button" class="btn btn-secondary btn-default">Yearly</button>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-light btn-default btn-xs"><i class="fa fa-refresh" aria-hidden="true"></i></button>`);
|
||||
buttons.appendTo(this.action_area);
|
||||
}
|
||||
}
|
||||
96
frappe/public/js/frappe/views/widgets/links_widget.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import Widget from "./base_widget.js";
|
||||
import { generate_route } from "./utils";
|
||||
|
||||
export default class LinksWidget extends Widget {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
//
|
||||
}
|
||||
|
||||
set_body() {
|
||||
this.options = {};
|
||||
this.options.links = this.links;
|
||||
this.widget.addClass("links-widget-box");
|
||||
const is_link_disabled = item => {
|
||||
return item.dependencies && item.incomplete_dependencies;
|
||||
};
|
||||
const disabled_dependent = item => {
|
||||
return is_link_disabled(item) ? "disabled-link" : "";
|
||||
};
|
||||
|
||||
const get_indicator_color = item => {
|
||||
if (item.open_count) {
|
||||
return "red";
|
||||
}
|
||||
if (item.onboard) {
|
||||
return item.count ? "blue" : "orange";
|
||||
}
|
||||
return "grey";
|
||||
};
|
||||
|
||||
const get_link_for_item = item => {
|
||||
if (is_link_disabled(item)) {
|
||||
return `<span class="link-content ellipsis disabled-link">${
|
||||
item.label ? item.label : item.name
|
||||
}</span>
|
||||
<div class="module-link-popover popover fade top in" role="tooltip" style="display: none;">
|
||||
<div class="arrow"></div>
|
||||
<h3 class="popover-title" style="display: none;"></h3>
|
||||
<div class="popover-content" style="padding: 12px;">
|
||||
<div class="small text-muted">${__("You need to create these first: ")}</div>
|
||||
<div class="small">${item.incomplete_dependencies.join(", ")}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (item.youtube_id)
|
||||
return `<span class="link-content help-video-link ellipsis" data-youtubeid="${item.youtube_id}">
|
||||
${item.label ? item.label : item.name}</span>`;
|
||||
|
||||
return `<a data-route="${generate_route(item)}" class="link-content ellipsis">
|
||||
${item.label ? item.label : item.name}</a>`;
|
||||
};
|
||||
|
||||
this.link_list = this.links.map(item => {
|
||||
return $(`<div class="link-item flush-top small ${
|
||||
item.onboard ? "onboard-spotlight" : ""
|
||||
} ${disabled_dependent(item)}" type="${item.type}">
|
||||
<span class="indicator ${get_indicator_color(item)}"></span>
|
||||
${get_link_for_item(item)}
|
||||
</div>`);
|
||||
});
|
||||
|
||||
this.link_list.forEach(link => link.appendTo(this.body));
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
this.link_list.forEach(link => {
|
||||
// Bind Popver Event
|
||||
const link_label = link.find(".link-content");
|
||||
|
||||
if (link.hasClass("disabled-link")) {
|
||||
const popover = link.find(".module-link-popover");
|
||||
|
||||
link_label.mouseover(() => {
|
||||
popover.show();
|
||||
});
|
||||
link_label.mouseout(() => popover.hide());
|
||||
} else {
|
||||
if (link_label.hasClass("help-video-link")) {
|
||||
link_label.click(event => {
|
||||
let yt_id = event.target.dataset.youtubeid;
|
||||
frappe.help.show_video(yt_id);
|
||||
});
|
||||
} else {
|
||||
link_label.click(event => {
|
||||
let route = event.target.dataset.route;
|
||||
frappe.set_route(route);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
44
frappe/public/js/frappe/views/widgets/onboarding_widget.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import Widget from "./base_widget.js";
|
||||
|
||||
export default class OnboardingWidget extends Widget {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
window.onb = this;
|
||||
}
|
||||
|
||||
refresh() { }
|
||||
|
||||
customize() { }
|
||||
|
||||
make_body() {
|
||||
this.steps.forEach(step => {
|
||||
this.add_step(step);
|
||||
})
|
||||
}
|
||||
|
||||
add_step(step) {
|
||||
let $step = $(`<div class="onboarding-step">
|
||||
<i class="fa fa-check-circle ${step.completed ? 'complete' : 'incomplete'}" aria-hidden="true"></i>${step.label}
|
||||
</div>`)
|
||||
|
||||
$step.appendTo(this.body)
|
||||
return $step
|
||||
}
|
||||
|
||||
set_body() {
|
||||
this.widget.addClass('onboarding-widget-box')
|
||||
this.make_body();
|
||||
}
|
||||
|
||||
set_title() {
|
||||
super.set_title();
|
||||
let subtitle = $(`<div class="widget-subtitle">${this.subtitle}</div>`)
|
||||
subtitle.appendTo(this.head);
|
||||
}
|
||||
|
||||
setup_events() { }
|
||||
|
||||
setup_customize_actions() { }
|
||||
|
||||
set_actions() { }
|
||||
}
|
||||
68
frappe/public/js/frappe/views/widgets/shortcut_widget.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import Widget from "./base_widget.js";
|
||||
import { generate_route } from "./utils";
|
||||
// import { get_luminosity, shadeColor } from "./utils";
|
||||
|
||||
String.prototype.format = function () {
|
||||
var i = 0, args = arguments;
|
||||
return this.replace(/{}/g, function () {
|
||||
return typeof args[i] != 'undefined' ? args[i++] : '';
|
||||
});
|
||||
};
|
||||
|
||||
export default class ShortcutWidget extends Widget {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
//
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
this.widget.click(() => {
|
||||
let route = generate_route(this)
|
||||
frappe.set_route(route)
|
||||
})
|
||||
}
|
||||
|
||||
set_actions() {
|
||||
this.widget.addClass('shortcut-widget-box');
|
||||
const get_filter = new Function(`return ${this.stats_filter}`)
|
||||
if (this.type == "DocType" && this.stats_filter) {
|
||||
frappe.db.count(this.link_to, {
|
||||
filters: get_filter()
|
||||
}).then(count => this.set_count(count))
|
||||
}
|
||||
}
|
||||
|
||||
set_title() {
|
||||
if (this.icon) {
|
||||
this.title_field[0].innerHTML = `<div>
|
||||
<i class="${this.icon}" style="color: rgb(141, 153, 166); font-size: 18px; margin-right: 6px;"></i>
|
||||
${this.label || this.name}
|
||||
</div>`
|
||||
}
|
||||
else {
|
||||
super.set_title();
|
||||
}
|
||||
}
|
||||
|
||||
set_count(count) {
|
||||
const get_label = () => {
|
||||
if (this.format) {
|
||||
return this.format.format(count);
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
this.action_area.empty();
|
||||
const label = get_label();
|
||||
const buttons = $(`<div class="small pill">${label}</div>`);
|
||||
if(this.color) {
|
||||
buttons.css('background-color', this.color);
|
||||
buttons.css('color', frappe.ui.color.get_contrast_color(this.color))
|
||||
}
|
||||
|
||||
buttons.appendTo(this.action_area);
|
||||
}
|
||||
}
|
||||