From bc0f4f83bc34e98731f545e1bcc0b0cb036014b0 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Fri, 2 May 2025 23:05:24 +0000 Subject: [PATCH] Make URLs dynamic, add after_install hook, add README --- README.md | 22 ++ portal_dashboard/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 190 bytes .../__pycache__/hooks.cpython-310.pyc | Bin 0 -> 515 bytes portal_dashboard/config/__init__.py | 0 portal_dashboard/config/desktop.py | 10 + portal_dashboard/config/docs.py | 10 + portal_dashboard/hooks.py | 219 ++++++++++++++++++ portal_dashboard/install.py | 8 + portal_dashboard/modules.txt | 1 + portal_dashboard/patches.txt | 0 portal_dashboard/portal_dashboard/__init__.py | 0 portal_dashboard/public/.gitkeep | 0 portal_dashboard/templates/__init__.py | 0 portal_dashboard/templates/pages/__init__.py | 0 .../www/__pycache__/index.cpython-310.pyc | Bin 0 -> 1267 bytes portal_dashboard/www/index.html | 104 +++++++++ portal_dashboard/www/index.py | 39 ++++ 18 files changed, 414 insertions(+) create mode 100644 README.md create mode 100644 portal_dashboard/__init__.py create mode 100644 portal_dashboard/__pycache__/__init__.cpython-310.pyc create mode 100644 portal_dashboard/__pycache__/hooks.cpython-310.pyc create mode 100644 portal_dashboard/config/__init__.py create mode 100644 portal_dashboard/config/desktop.py create mode 100644 portal_dashboard/config/docs.py create mode 100644 portal_dashboard/hooks.py create mode 100644 portal_dashboard/install.py create mode 100644 portal_dashboard/modules.txt create mode 100644 portal_dashboard/patches.txt create mode 100644 portal_dashboard/portal_dashboard/__init__.py create mode 100644 portal_dashboard/public/.gitkeep create mode 100644 portal_dashboard/templates/__init__.py create mode 100644 portal_dashboard/templates/pages/__init__.py create mode 100644 portal_dashboard/www/__pycache__/index.cpython-310.pyc create mode 100644 portal_dashboard/www/index.html create mode 100644 portal_dashboard/www/index.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e53238 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Sea Haven Portal Dashboard (`portal_dashboard`) + +A lightweight, role-based landing page app for Frappe/ERPNext. Shows a clean tile-style interface for navigating to apps like ERP, Wiki, CRM — all based on user roles. + +--- + +## 🚀 Features + +- Clean, responsive tile dashboard +- Icons + labels based on Frappe user roles +- Dynamic subdomain routing (e.g., `accounting.yourdomain.com`) +- Auto-redirect for non-portal subdomains +- Auto-sets homepage on install + +--- + +## 🛠 Installation + +### 1. Get the app + +```bash +bench get-app portal_dashboard https://github.com/yourusername/portal_dashboard.git diff --git a/portal_dashboard/__init__.py b/portal_dashboard/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/portal_dashboard/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/portal_dashboard/__pycache__/__init__.cpython-310.pyc b/portal_dashboard/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91c43e07f42b746048d3ce1dc1034acfe37eaba3 GIT binary patch literal 190 zcmd1j<>g`kf>2q}3^5@67{oyaj6jY95EpX*i4=w?h7`tN22G|aRs%f)JwrcD##`L+ z@nxw+#hLke@$oAeikN`vz{D>f{fzwFRQ3xc<{_#Lq?CjOcu4-jMlgjooktOHRNL7c^~ zk!`>xbfE`ZFVQT_HX*qQMtwJ|Nn0+bz)hJG*RU8~e>3cS&GgTJSL~7+WB6in#xzM} zIX4K*3ZJfb*&S)7_@kI{D@ZX*Oel+5amh3qYIK2SvCI8;6N9|ukEq54RW7=`xLt@ZJ6^^&EHBcM xDopRRA6`AjIjW*eJ!8_pUj7p$(Y+bh&#re;5i}A{Ta#w!Ar9hjb&^i+^9xuTr%nI> literal 0 HcmV?d00001 diff --git a/portal_dashboard/config/__init__.py b/portal_dashboard/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/config/desktop.py b/portal_dashboard/config/desktop.py new file mode 100644 index 0000000..0054982 --- /dev/null +++ b/portal_dashboard/config/desktop.py @@ -0,0 +1,10 @@ +from frappe import _ + +def get_data(): + return [ + { + "module_name": "Portal Dashboard", + "type": "module", + "label": _("Portal Dashboard") + } + ] diff --git a/portal_dashboard/config/docs.py b/portal_dashboard/config/docs.py new file mode 100644 index 0000000..29b9d2e --- /dev/null +++ b/portal_dashboard/config/docs.py @@ -0,0 +1,10 @@ +""" +Configuration for docs +""" + +# source_link = "https://github.com/[org_name]/portal_dashboard" +# headline = "App that does everything" +# sub_heading = "Yes, you got that right the first time, everything" + +def get_context(context): + context.brand_html = "Portal Dashboard" diff --git a/portal_dashboard/hooks.py b/portal_dashboard/hooks.py new file mode 100644 index 0000000..673a750 --- /dev/null +++ b/portal_dashboard/hooks.py @@ -0,0 +1,219 @@ +app_name = "portal_dashboard" +app_title = "Portal Dashboard" +app_publisher = "Adam Moussa" +app_description = "A role based tile dashboard for navigation to internal resources" +app_email = "adam@seahaven.com" +app_license = "MIT" +after_install = "portal_dashboard.setup.install.run" +web_routes = [ + {"path": "/", "controller": "portal_dashboard.www.index", "login_required": 1} +] + +# Includes in +# ------------------ + +# include js, css files in header of desk.html +# app_include_css = "/assets/portal_dashboard/css/portal_dashboard.css" +# app_include_js = "/assets/portal_dashboard/js/portal_dashboard.js" + +# include js, css files in header of web template +# web_include_css = "/assets/portal_dashboard/css/portal_dashboard.css" +# web_include_js = "/assets/portal_dashboard/js/portal_dashboard.js" + +# include custom scss in every website theme (without file extension ".scss") +# website_theme_scss = "portal_dashboard/public/scss/website" + +# include js, css files in header of web form +# webform_include_js = {"doctype": "public/js/doctype.js"} +# webform_include_css = {"doctype": "public/css/doctype.css"} + +# include js in page +# page_js = {"page" : "public/js/file.js"} + +# include js in doctype views +# doctype_js = {"doctype" : "public/js/doctype.js"} +# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} +# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} +# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} + +# Home Pages +# ---------- + +# application home page (will override Website Settings) +# home_page = "login" + +# website user home page (by Role) +# role_home_page = { +# "Role": "home_page" +# } + +# Generators +# ---------- + +# automatically create page for each record of this doctype +# website_generators = ["Web Page"] + +# Jinja +# ---------- + +# add methods and filters to jinja environment +# jinja = { +# "methods": "portal_dashboard.utils.jinja_methods", +# "filters": "portal_dashboard.utils.jinja_filters" +# } + +# Installation +# ------------ + +# before_install = "portal_dashboard.install.before_install" +# after_install = "portal_dashboard.install.after_install" + +# Uninstallation +# ------------ + +# before_uninstall = "portal_dashboard.uninstall.before_uninstall" +# after_uninstall = "portal_dashboard.uninstall.after_uninstall" + +# Integration Setup +# ------------------ +# To set up dependencies/integrations with other apps +# Name of the app being installed is passed as an argument + +# before_app_install = "portal_dashboard.utils.before_app_install" +# after_app_install = "portal_dashboard.utils.after_app_install" + +# Integration Cleanup +# ------------------- +# To clean up dependencies/integrations with other apps +# Name of the app being uninstalled is passed as an argument + +# before_app_uninstall = "portal_dashboard.utils.before_app_uninstall" +# after_app_uninstall = "portal_dashboard.utils.after_app_uninstall" + +# Desk Notifications +# ------------------ +# See frappe.core.notifications.get_notification_config + +# notification_config = "portal_dashboard.notifications.get_notification_config" + +# Permissions +# ----------- +# Permissions evaluated in scripted ways + +# permission_query_conditions = { +# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", +# } +# +# has_permission = { +# "Event": "frappe.desk.doctype.event.event.has_permission", +# } + +# DocType Class +# --------------- +# Override standard doctype classes + +# override_doctype_class = { +# "ToDo": "custom_app.overrides.CustomToDo" +# } + +# Document Events +# --------------- +# Hook on document methods and events + +# doc_events = { +# "*": { +# "on_update": "method", +# "on_cancel": "method", +# "on_trash": "method" +# } +# } + +# Scheduled Tasks +# --------------- + +# scheduler_events = { +# "all": [ +# "portal_dashboard.tasks.all" +# ], +# "daily": [ +# "portal_dashboard.tasks.daily" +# ], +# "hourly": [ +# "portal_dashboard.tasks.hourly" +# ], +# "weekly": [ +# "portal_dashboard.tasks.weekly" +# ], +# "monthly": [ +# "portal_dashboard.tasks.monthly" +# ], +# } + +# Testing +# ------- + +# before_tests = "portal_dashboard.install.before_tests" + +# Overriding Methods +# ------------------------------ +# +# override_whitelisted_methods = { +# "frappe.desk.doctype.event.event.get_events": "portal_dashboard.event.get_events" +# } +# +# each overriding function accepts a `data` argument; +# generated from the base implementation of the doctype dashboard, +# along with any modifications made in other Frappe apps +# override_doctype_dashboards = { +# "Task": "portal_dashboard.task.get_dashboard_data" +# } + +# exempt linked doctypes from being automatically cancelled +# +# auto_cancel_exempted_doctypes = ["Auto Repeat"] + +# Ignore links to specified DocTypes when deleting documents +# ----------------------------------------------------------- + +# ignore_links_on_delete = ["Communication", "ToDo"] + +# Request Events +# ---------------- +# before_request = ["portal_dashboard.utils.before_request"] +# after_request = ["portal_dashboard.utils.after_request"] + +# Job Events +# ---------- +# before_job = ["portal_dashboard.utils.before_job"] +# after_job = ["portal_dashboard.utils.after_job"] + +# User Data Protection +# -------------------- + +# user_data_fields = [ +# { +# "doctype": "{doctype_1}", +# "filter_by": "{filter_by}", +# "redact_fields": ["{field_1}", "{field_2}"], +# "partial": 1, +# }, +# { +# "doctype": "{doctype_2}", +# "filter_by": "{filter_by}", +# "partial": 1, +# }, +# { +# "doctype": "{doctype_3}", +# "strict": False, +# }, +# { +# "doctype": "{doctype_4}" +# } +# ] + +# Authentication and authorization +# -------------------------------- + +# auth_hooks = [ +# "portal_dashboard.auth.validate" +# ] diff --git a/portal_dashboard/install.py b/portal_dashboard/install.py new file mode 100644 index 0000000..35eb886 --- /dev/null +++ b/portal_dashboard/install.py @@ -0,0 +1,8 @@ +import frappe + +def run(): + # Force home page to our portal dashboard + frappe.db.set_single_value("Website Settings", "home_page", "index") + + # Optional: log it + frappe.logger().info("Set home_page to 'index' for portal_dashboard.") diff --git a/portal_dashboard/modules.txt b/portal_dashboard/modules.txt new file mode 100644 index 0000000..e85aa60 --- /dev/null +++ b/portal_dashboard/modules.txt @@ -0,0 +1 @@ +Portal Dashboard \ No newline at end of file diff --git a/portal_dashboard/patches.txt b/portal_dashboard/patches.txt new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/portal_dashboard/__init__.py b/portal_dashboard/portal_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/public/.gitkeep b/portal_dashboard/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/templates/__init__.py b/portal_dashboard/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/templates/pages/__init__.py b/portal_dashboard/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/portal_dashboard/www/__pycache__/index.cpython-310.pyc b/portal_dashboard/www/__pycache__/index.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecb5e9fc200e07e45b766d69a193073677998f8a GIT binary patch literal 1267 zcmah}&2Q5%6t|tUY14M!n?M>ciBpH#B5|9h2?RTCLYn|-5~|YJP1B`GX1jEaItkb$ z;K-fBcHD*kgMYzSPMkS`IPjdbFxCT>{QUf0{Ci(NC8|_x1nWy}x%0zB=&Qb5T?8%< zVT&aY6j2p-cpp{GZ{R_@F4o6*0TQxc8?eP^5E6B9mvoI2Ji_ND#TS?_ zP?B16%{-je+stEJU~%WV2mFO@@dUmEYzeqnx_^mc!U-|nbf8wTBa2` zGrrH#IXX`l=wf#Fj1%J>YGb-Ym(L0#;|V&&Bh)O75Y)7?o0J^ajryVVL$98g4JVEh zD~`Byg1R!c_ns!@wo@B8oOPnUV9DK%l(E=oG_E}JnCuNhHW0pKbvNoIl}8z-SbHWI zPv-tycbw4i7_XWt7dR~zDARYNP#Jw5s2q<1CY1TYKlBp|g0`aQFu65}Hvn_}dL&iY z?!`egWGtE49}3BOYvT~gc)Yih%mNH#&1-G$vyh5pbpq$|-V|D5?>mq}mUJc4$8i5l zU2`Q5dr?n)9fXHQ=;izgmMHBsum?JgDY=Psw@)!ABm9vZ5$L!vg`~OjKqK7%@kS$A znmzB)EA$*;bVR706yCi=Ria4!zA|;AE-}|BaLwwUJB?1%V~sY4=}nGWi-m4S&r>wA zc{ZsdI<3gzblNZ&3>tn&*>OD{s^X@{LUtVUt$R8zO>**j1tnEze-q{<*|Dr|29fIo zVha>sNSJyI>f~LTTNNwA35Qj}OMn1+9PnOSNpFq{J%4fv_ zk8~Q!;_Rr;gj8lH0%;5Ba4819?5JEja6F;rIio&ju59WT(g)fV_Qob<2_}R#nVK!C zsN;|6s2d1;wZ?Vx65@&rDYTIHFypU)J#kDEM6Kk6=~4IW-p@sfNkktQ#!5!DwE%BoKl4V literal 0 HcmV?d00001 diff --git a/portal_dashboard/www/index.html b/portal_dashboard/www/index.html new file mode 100644 index 0000000..ebf20eb --- /dev/null +++ b/portal_dashboard/www/index.html @@ -0,0 +1,104 @@ + + + +
+
+ + +
+
+ + + + + Sea Haven Portal + + + + + + {% if frappe.session.user == "Guest" %} + + {% endif %} + +

Sea Haven Portal

+
+ {% for app in apps %} + + +
{{ app.label }}
+
+ {% endfor %} +
+ + diff --git a/portal_dashboard/www/index.py b/portal_dashboard/www/index.py new file mode 100644 index 0000000..4b5ff7c --- /dev/null +++ b/portal_dashboard/www/index.py @@ -0,0 +1,39 @@ +import frappe + +def get_context(context): + host = frappe.local.request.host + base_domain = ".".join(host.split(".")[1:]) + + if not host.startswith("portal."): + frappe.local.flags.redirect_location = "/app" + raise frappe.Redirect + + user = frappe.session.user + roles = frappe.get_roles(user) + + user = frappe.session.user + roles = frappe.get_roles(user) + + apps = [ + { + "label": "ERP", + "icon": "fa-warehouse", + "url": "https://accounting.{base_domain}", + "roles": ["Accounts User", "Accounts Manager"] + }, + { + "label": "Wiki", + "icon": "fa-book", + "url": "https://wiki.{base_domain}", + "roles": ["Employee", "System Manager"] + }, + { + "label": "CRM", + "icon": "fa-user-friends", + "url": "https://crm.{base_domain}", + "roles": ["Sales User", "Sales Manager"] + } + ] + + context.apps = [app for app in apps if any(role in roles for role in app["roles"])] + return context