From 1d6dbeac4c226646cd957c98ad212921e59a54ce Mon Sep 17 00:00:00 2001 From: Benjamin Hindman Date: Tue, 16 Sep 2025 10:52:20 -0500 Subject: [PATCH 1/2] add install hooks and fixtures --- admin_panel/fixtures/page.json | 25 +++++++++++++++++++++++ admin_panel/fixtures/role.json | 14 +++++++++++++ admin_panel/hooks.py | 36 ++++++++++++++++++++++++---------- admin_panel/install.py | 23 ++++++++++++++++++++++ 4 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 admin_panel/fixtures/page.json create mode 100644 admin_panel/fixtures/role.json create mode 100644 admin_panel/install.py diff --git a/admin_panel/fixtures/page.json b/admin_panel/fixtures/page.json new file mode 100644 index 0000000..cbfd063 --- /dev/null +++ b/admin_panel/fixtures/page.json @@ -0,0 +1,25 @@ +{ + "creation": "2024-09-15 10:00:00.000000", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2024-09-15 10:00:00.000000", + "modified_by": "Administrator", + "module": "Admin Panel", + "name": "account-management", + "owner": "Administrator", + "page_name": "account-management", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "System Manager" + } + ], + "script": "", + "standard": "Yes", + "style": "", + "system_page": 0, + "title": "Account Management" +} \ No newline at end of file diff --git a/admin_panel/fixtures/role.json b/admin_panel/fixtures/role.json new file mode 100644 index 0000000..289f6c9 --- /dev/null +++ b/admin_panel/fixtures/role.json @@ -0,0 +1,14 @@ +{ + "creation": "2024-09-15 10:00:00.000000", + "desk_access": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Role", + "idx": 0, + "modified": "2024-09-15 10:00:00.000000", + "modified_by": "Administrator", + "name": "Accounts Manager", + "owner": "Administrator", + "role_name": "Accounts Manager", + "two_factor_auth": 0 +} \ No newline at end of file diff --git a/admin_panel/hooks.py b/admin_panel/hooks.py index 6e09846..227f915 100644 --- a/admin_panel/hooks.py +++ b/admin_panel/hooks.py @@ -11,15 +11,15 @@ # required_apps = [] # Each item in the list will be shown as an app in the apps page -# add_to_apps_screen = [ -# { -# "name": "admin_panel", -# "logo": "/assets/admin_panel/logo.png", -# "title": "Admin Panel", -# "route": "/admin_panel", -# "has_permission": "admin_panel.api.permission.has_app_permission" -# } -# ] +add_to_apps_screen = [ + { + "name": "admin_panel", + "logo": "/assets/admin_panel/logo.png", + "title": "Admin Panel", + "route": "/app/account-management", + "has_permission": "admin_panel.api.permission.has_app_permission" + } +] # Includes in # ------------------ @@ -86,7 +86,7 @@ # ------------ # before_install = "admin_panel.install.before_install" -# after_install = "admin_panel.install.after_install" +after_install = "admin_panel.install.after_install" # Uninstallation # ------------ @@ -237,3 +237,19 @@ # "Logging DocType Name": 30 # days to retain logs # } +# Fixtures for installation +# ------------------------- +fixtures = [ + { + "doctype": "Page", + "filters": { + "name": ["in", ["account-management"]] + } + }, + { + "doctype": "Role", + "filters": { + "name": ["in", ["Accounts Manager"]] + } + } +] \ No newline at end of file diff --git a/admin_panel/install.py b/admin_panel/install.py new file mode 100644 index 0000000..fc2a623 --- /dev/null +++ b/admin_panel/install.py @@ -0,0 +1,23 @@ +import frappe + +def after_install(): + """Run after app installation""" + + # Create Accounts Manager role if it doesn't exist + if not frappe.db.exists("Role", "Accounts Manager"): + role_doc = frappe.get_doc({ + "doctype": "Role", + "role_name": "Accounts Manager", + "desk_access": 1, + "disabled": 0 + }) + role_doc.insert(ignore_permissions=True) + frappe.db.commit() + + # Import fixtures + frappe.reload_doc("admin_panel", "page", "account_management", force=True) + + # Clear cache + frappe.clear_cache() + + print("Admin Panel app installed successfully!") \ No newline at end of file From eb1af2736b005933548d576a5e79a8df9127c711 Mon Sep 17 00:00:00 2001 From: Benjamin Hindman Date: Mon, 22 Sep 2025 12:34:08 -0500 Subject: [PATCH 2/2] add page to fixtures --- .../account_management/account_management.js | 18 ------------- .../account_management.json | 25 ++++++++++++++++++ admin_panel/api/admin_api.py | 9 +++++++ admin_panel/fixtures/page.json | 4 ++- admin_panel/fixtures/role.json | 14 ---------- admin_panel/hooks.py | 12 +++------ admin_panel/install.py | 23 ---------------- admin_panel/public/logo-black.png | Bin 0 -> 12209 bytes 8 files changed, 40 insertions(+), 65 deletions(-) create mode 100644 admin_panel/admin_panel/page/account_management/account_management.json delete mode 100644 admin_panel/fixtures/role.json delete mode 100644 admin_panel/install.py create mode 100644 admin_panel/public/logo-black.png diff --git a/admin_panel/admin_panel/page/account_management/account_management.js b/admin_panel/admin_panel/page/account_management/account_management.js index d9ad3d3..a62a2bb 100644 --- a/admin_panel/admin_panel/page/account_management/account_management.js +++ b/admin_panel/admin_panel/page/account_management/account_management.js @@ -1,22 +1,4 @@ frappe.pages['account-management'].on_page_load = function(wrapper) { - if (!frappe.user_roles.includes('Accounts Manager')) { - var page = frappe.ui.make_app_page({ - parent: wrapper, - title: 'Flash Account Manager', - single_column: true - }); - - page.main.html(` -
-
-

Access Denied

-

You do not have permission to access this page. Please contact your administrator to get the "Account Manager" role.

-
-
- `); - return; - } - var page = frappe.ui.make_app_page({ parent: wrapper, title: 'Flash Account Manager', diff --git a/admin_panel/admin_panel/page/account_management/account_management.json b/admin_panel/admin_panel/page/account_management/account_management.json new file mode 100644 index 0000000..214910a --- /dev/null +++ b/admin_panel/admin_panel/page/account_management/account_management.json @@ -0,0 +1,25 @@ +{ + "creation": "2025-09-22 10:00:00.000000", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2025-09-22 10:00:00.000000", + "modified_by": "Administrator", + "module": "Admin Panel", + "name": "account-management", + "owner": "Administrator", + "page_name": "account-management", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "System Manager" + } + ], + "script": "", + "standard": "Yes", + "style": "", + "system_page": 0, + "title": "Account Management" +} \ No newline at end of file diff --git a/admin_panel/api/admin_api.py b/admin_panel/api/admin_api.py index b5ffd2a..93377e0 100644 --- a/admin_panel/api/admin_api.py +++ b/admin_panel/api/admin_api.py @@ -26,3 +26,12 @@ def update_account_level(uid, level): frappe.logger().error(f"Unexpected error updating account {uid} to level {level}: {str(e)}") frappe.response['http_status_code'] = 500 return GENERIC_ERROR + +def has_app_permission(): + """Check if the current user has permission to access the admin panel app""" + if frappe.session.user == "Guest": + return False + if "Accounts Manager" in frappe.get_roles(): + return True + + return False diff --git a/admin_panel/fixtures/page.json b/admin_panel/fixtures/page.json index cbfd063..c17161e 100644 --- a/admin_panel/fixtures/page.json +++ b/admin_panel/fixtures/page.json @@ -1,3 +1,4 @@ +[ { "creation": "2024-09-15 10:00:00.000000", "docstatus": 0, @@ -22,4 +23,5 @@ "style": "", "system_page": 0, "title": "Account Management" -} \ No newline at end of file +} +] \ No newline at end of file diff --git a/admin_panel/fixtures/role.json b/admin_panel/fixtures/role.json deleted file mode 100644 index 289f6c9..0000000 --- a/admin_panel/fixtures/role.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "creation": "2024-09-15 10:00:00.000000", - "desk_access": 1, - "disabled": 0, - "docstatus": 0, - "doctype": "Role", - "idx": 0, - "modified": "2024-09-15 10:00:00.000000", - "modified_by": "Administrator", - "name": "Accounts Manager", - "owner": "Administrator", - "role_name": "Accounts Manager", - "two_factor_auth": 0 -} \ No newline at end of file diff --git a/admin_panel/hooks.py b/admin_panel/hooks.py index 227f915..bf21e2e 100644 --- a/admin_panel/hooks.py +++ b/admin_panel/hooks.py @@ -14,10 +14,10 @@ add_to_apps_screen = [ { "name": "admin_panel", - "logo": "/assets/admin_panel/logo.png", + "logo": "/assets/admin_panel/logo-black.png", "title": "Admin Panel", "route": "/app/account-management", - "has_permission": "admin_panel.api.permission.has_app_permission" + "has_permission": "admin_panel.api.admin_api.has_app_permission" } ] @@ -86,7 +86,7 @@ # ------------ # before_install = "admin_panel.install.before_install" -after_install = "admin_panel.install.after_install" +# after_install = "admin_panel.install.after_install" # Uninstallation # ------------ @@ -245,11 +245,5 @@ "filters": { "name": ["in", ["account-management"]] } - }, - { - "doctype": "Role", - "filters": { - "name": ["in", ["Accounts Manager"]] - } } ] \ No newline at end of file diff --git a/admin_panel/install.py b/admin_panel/install.py deleted file mode 100644 index fc2a623..0000000 --- a/admin_panel/install.py +++ /dev/null @@ -1,23 +0,0 @@ -import frappe - -def after_install(): - """Run after app installation""" - - # Create Accounts Manager role if it doesn't exist - if not frappe.db.exists("Role", "Accounts Manager"): - role_doc = frappe.get_doc({ - "doctype": "Role", - "role_name": "Accounts Manager", - "desk_access": 1, - "disabled": 0 - }) - role_doc.insert(ignore_permissions=True) - frappe.db.commit() - - # Import fixtures - frappe.reload_doc("admin_panel", "page", "account_management", force=True) - - # Clear cache - frappe.clear_cache() - - print("Admin Panel app installed successfully!") \ No newline at end of file diff --git a/admin_panel/public/logo-black.png b/admin_panel/public/logo-black.png new file mode 100644 index 0000000000000000000000000000000000000000..7e96c2d04e7573bdb68a8c9917024ce5178b7b8a GIT binary patch literal 12209 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 F{}0HseM$fT literal 0 HcmV?d00001