Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2c992a8
wip: implement landing page and sidebar UI
Venkateshvenki404224 Dec 7, 2025
37a5298
feat: implement Penguin UI sidebar and fix width issue (#19)
Venkateshvenki404224 Dec 25, 2025
c4d3b9f
fix: update toast colors and replace remove button with icon (#23)
Venkateshvenki404224 Dec 25, 2025
879ebaa
fix: the steps should have less z index than navbar
Venkateshvenki404224 Dec 26, 2025
60a8fc4
feat: add custom app section, in the custom app dialog show tabs if t…
Venkateshvenki404224 Dec 26, 2025
d46f5dc
fix(ui): add separate sections for Frappe Apps and Custom Apps
Venkateshvenki404224 Dec 26, 2025
2eec691
fix(ci): update Python version to 3.12
Venkateshvenki404224 Dec 26, 2025
1b7f485
fix(ci): update Python version to 3.14
Venkateshvenki404224 Dec 26, 2025
72d7f48
fix(ci): update node version to 24
Venkateshvenki404224 Dec 26, 2025
3b3be77
fix(ui): redesign the summery page
Venkateshvenki404224 Dec 26, 2025
b9720ab
fix: custom image build non type error
Venkateshvenki404224 Dec 26, 2025
4fe34a4
chore: removed the schedular to ping server
Venkateshvenki404224 Dec 26, 2025
f62e96b
feat: add DNS record instruction like frappe cloud
Venkateshvenki404224 Dec 26, 2025
07b5bc6
feat: improve domain UI and add custom apps management
Venkateshvenki404224 Dec 26, 2025
5e5c5da
feat: populate old server list when its already available.
Venkateshvenki404224 Dec 27, 2025
6d9c69d
feat(ux): add frappe icon in list apps and add app version for mainta…
Venkateshvenki404224 Dec 27, 2025
48fc193
fix: linter
Venkateshvenki404224 Dec 27, 2025
6595909
chore: rename the placeholder
Venkateshvenki404224 Dec 27, 2025
6ee1099
wip(custom_image): Working on Custom Image
Venkateshvenki404224 Dec 27, 2025
752ac37
feat: improve domain setup and apps management UI
Venkateshvenki404224 Jan 4, 2026
7249743
refactor: add translation support to error messages
Venkateshvenki404224 Jan 4, 2026
f048dd1
fix(security): secure GitHub token storage and improve custom apps ma…
Venkateshvenki404224 Jan 4, 2026
fe17d3c
fix(custom_image): improve validation, sanitization and performance
Venkateshvenki404224 Jan 4, 2026
c66e8f4
fix: improve custom image build and secure custom app
Venkateshvenki404224 Jan 4, 2026
6197375
wip: add URL state management for deployment resume
Venkateshvenki404224 Jan 6, 2026
02b7e5c
feat(deployment): sync custom image build status to site for seamless…
Venkateshvenki404224 Jan 7, 2026
2d5ef1f
refactor(ui): improve server details layout and styling
Venkateshvenki404224 Jan 7, 2026
bb91851
refactor: fix memory leaks, redundant operations, and improve validation
Venkateshvenki404224 Jan 7, 2026
c1ad4e8
chore: add app version fixtures
Venkateshvenki404224 Jan 13, 2026
5c97c9e
Merge branch 'develop' of github.com:BuildWithHussain/nano_press into…
Venkateshvenki404224 Jan 22, 2026
c93a6aa
refactor: remove old files
Venkateshvenki404224 Jan 22, 2026
204d376
refactor(ui): clean up server component code structure
Venkateshvenki404224 Jan 22, 2026
3b774f3
refactor(ui): clean up apps component code structure
Venkateshvenki404224 Jan 22, 2026
9ad1abc
refactor(ui): clean up domain component code structure
Venkateshvenki404224 Jan 22, 2026
a581c3a
refactor(ui): remove legacy dead code from summary and parent components
Venkateshvenki404224 Jan 22, 2026
bb4f793
fix(ui): prevent dialog flash and use consistent Frappe ORM methods
Venkateshvenki404224 Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.14'
Comment on lines 48 to +51
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Python 3.14 release status January 2026

💡 Result:

Status as of January 22, 2026:

  • Python 3.14 (final) was released October 7, 2025. [1][2]
  • Bugfix releases 3.14.1 and 3.14.2 were published in December 2025. [1][3]
  • The 3.14 release schedule lists the next expected bugfix (3.14.3) for February 3, 2026. [2]

Sources: official Python release page and PEP 745 (release schedule). [1] [2] [3]


🌐 Web query:

actions/setup-python v4 v5 Python 3.14 support compatibility

💡 Result:

Short answer: use v5 (or later). actions/setup-python@v5 supports installing Python 3.14 (including free‑threaded builds / prereleases when needed) via the python-version input; v4 is older and may not handle newer Python releases/tooling the same way. See GitHub's setup-python docs and release notes for v5 and examples showing 3.14 usage. [1][2][3][4]

Relevant links:

  • actions/setup-python releases (v4→v5 changelog). [1]
  • setup-python README / Marketplace (v5+ usage examples). [2]
  • GitHub Actions docs showing setup-python@v5 usage in Python matrices. [3]
  • Community/example showing 3.14 and free‑threaded 3.14 usage with setup-python@v5 (use allow-prereleases for pre-release builds). [4]

If you want, I can paste the exact workflow snippet to set up Python 3.14 (regular or free‑threaded) with setup-python@v5.


🌐 Web query:

frappe-bench Python 3.14 compatibility

💡 Result:

Short answer: Frappe/erpnext support for Python 3.14 is tied to the app version — Frappe/ERPNext v16 (and develop) require Python 3.14+, but older branches (v15 and earlier) do not accept 3.14. If your bench/apps are still on v15 or older, Python 3.14 will fail. [1][2][4]

Details and practical notes:

  • Official requirements list Frappe v16/develop as Python 3.14 (v14/v15 use 3.10+). [1]
  • Users report installing v16 needs Python 3.14; trying to install frappe (older constraint) on 3.14 produced “requires a different Python: 3.14.x not in '<3.14,>=3.10'”. That indicates older frappe packages will reject 3.14. [2]
  • Some third‑party deps (e.g., eventlet/socketio) may not yet be compatible with 3.14 — expect occasional dependency issues when moving to v16. [3]
  • Recommendation: run Python 3.14 only for benches where all apps are upgraded to v16/develop; for existing v15 or custom apps stick to a supported Python (3.10/3.11/3.12) until you upgrade and verify third‑party deps. [1][2][3]

Sources: Frappe docs and community threads. [1][2][3][4]


Upgrade actions/setup-python to v5 and verify Frappe version compatibility with Python 3.14.

Python 3.14 (released October 2025) is stable as of January 2026. However:

  1. actions/setup-python@v4 does not support Python 3.14 properly—upgrade to @v5 (or later), which explicitly supports Python 3.14.
  2. Only Frappe/ERPNext v16+ support Python 3.14. If your bench runs v15 or earlier, Python 3.14 will fail at install time. Verify that all apps in this bench are upgraded to v16+ before using Python 3.14.
  3. Some third-party dependencies may not yet be fully compatible with Python 3.14—monitor for dependency conflicts during testing.
🧰 Tools
🪛 actionlint (1.7.10)

49-49: the runner of "actions/setup-python@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/ci.yml around lines 48 - 51, The CI uses
actions/setup-python@v4 with python-version: '3.14' which lacks proper Python
3.14 support; update the action reference to actions/setup-python@v5 (or later)
and keep python-version at '3.14', and before merging verify your bench/apps are
running Frappe/ERPNext v16+ (and upgrade any apps) and run full dependency tests
to catch third‑party package incompatibilities with Python 3.14.


- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 24
check-latest: true

- name: Cache pip
Expand Down
75 changes: 75 additions & 0 deletions nano_press/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,78 @@ def check_domain_resolves_to_ip(domain: str, expected_ip: str) -> dict:
except Exception as e:
frappe.log_error(f"Error occurred while checking domain {domain}: {e}")
return {"success": False, "resolved_ips": [], "message": f"Error: {e}"}


@frappe.whitelist()
def register_custom_app(
app_name: str, github_url: str, branch: str, token: str | None = None, order: int | None = None
) -> dict:
try:
if not app_name or not github_url or not branch:
frappe.throw(_("app_name, github_url, and branch are required"))

app_name_normalized = app_name.lower().strip()

if frappe.db.exists("Apps", app_name_normalized):
app_doc = frappe.get_doc("Apps", app_name_normalized)
app_doc.repo_url = github_url
app_doc.branch = branch
if token:
app_doc.pat_token = token
if order is not None:
app_doc.order = order
app_doc.save(ignore_permissions=True)
else:
app_doc = frappe.get_doc(
{
"doctype": "Apps",
"app_name": app_name_normalized,
"repo_url": github_url,
"branch": branch,
"pat_token": token or "",
"is_custom": 1,
"enabled": 1,
"order": order if order is not None else 999,
}
)
app_doc.insert(ignore_permissions=True)

frappe.db.commit() # nosemgrep: frappe-semgrep-rules.rules.frappe-manual-commit

return {
"success": True,
"app_reference_id": app_doc.name,
"message": _("Custom app registered securely"),
}

except Exception as e:
frappe.log_error(f"Error registering custom app: {e}")
frappe.db.rollback()
return {"success": False, "message": str(e)}


@frappe.whitelist()
def delete_custom_app(app_name: str) -> dict:
try:
if not app_name:
frappe.throw(_("app_name is required"))

app_name_normalized = app_name.lower().strip()

if not frappe.db.exists("Apps", app_name_normalized):
return {"success": True, "message": _("App not found or already deleted")}

app_doc = frappe.get_doc("Apps", app_name_normalized)

if not app_doc.is_custom:
frappe.throw(_("Cannot delete non-custom apps"))

frappe.delete_doc("Apps", app_name_normalized, ignore_permissions=True)
frappe.db.commit() # nosemgrep: frappe-semgrep-rules.rules.frappe-manual-commit

return {"success": True, "message": _("Custom app deleted successfully")}

except Exception as e:
frappe.log_error(f"Error deleting custom app: {e}")
frappe.db.rollback()
return {"success": False, "message": str(e)}
26 changes: 26 additions & 0 deletions nano_press/fixtures/app_version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"docstatus": 0,
"doctype": "App Version",
"modified": "2025-12-27 17:08:23.781012",
"name": "version-14",
"scrubbed_version": "version-14",
"version": "Version 14"
},
{
"docstatus": 0,
"doctype": "App Version",
"modified": "2025-12-27 17:08:23.781478",
"name": "nightly",
"scrubbed_version": "nightly",
"version": "Nightly"
},
{
"docstatus": 0,
"doctype": "App Version",
"modified": "2025-12-27 17:08:23.780453",
"name": "version-15",
"scrubbed_version": "version-15",
"version": "Version 15"
}
]
17 changes: 9 additions & 8 deletions nano_press/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dt": "Custom DocPerm",
"filters": {"parent": ("in", ("Frappe Site", "Server", "Apps", "Custom Image", "Ansible Log"))},
},
{"dt": "App Version"},
]

# Includes in <head>
Expand Down Expand Up @@ -68,7 +69,7 @@
# ----------

# application home page (will override Website Settings)
# home_page = "login"
home_page = "index"

# website user home page (by Role)
# role_home_page = {
Expand Down Expand Up @@ -151,13 +152,13 @@
# Scheduled Tasks
# ---------------

scheduler_events = {
"cron": {
"*/5 * * * *": [
"nano_press.utils.ansible_runner.ping_server",
],
}
}
# scheduler_events = {
# "cron": {
# "*/5 * * * *": [
# "nano_press.utils.ansible_runner.ping_server",
# ],
# }
# }
Comment on lines +155 to +161
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file and understand the context
find . -name "hooks.py" -type f | head -20

Repository: BuildWithHussain/nano_press

Length of output: 92


🏁 Script executed:

# Search for the ping_server function to understand what it does
rg "def ping_server" -A 20

Repository: BuildWithHussain/nano_press

Length of output: 2336


🏁 Script executed:

# Search for references to ping_server in the codebase
rg "ping_server" --type py

Repository: BuildWithHussain/nano_press

Length of output: 352


🏁 Script executed:

# Search for scheduler_events usage
rg "scheduler_events" --type py

Repository: BuildWithHussain/nano_press

Length of output: 113


🏁 Script executed:

# Search for health/status update mechanisms
rg "health|status|verify" --type py | grep -i "server\|scheduler" | head -30

Repository: BuildWithHussain/nano_press

Length of output: 1607


🏁 Script executed:

# Search for where verify_status is modified/updated
rg "verify_status\s*=" --type py -B 2 -A 2

Repository: BuildWithHussain/nano_press

Length of output: 467


🏁 Script executed:

# Check the Server doctype definition to understand verify_status
find . -path "*doctype/server*" -type f | head -20

Repository: BuildWithHussain/nano_press

Length of output: 324


🏁 Script executed:

# Look at the full Server.py file to understand verify_status lifecycle
cat -n nano_press/nano_press/doctype/server/server.py | head -100

Repository: BuildWithHussain/nano_press

Length of output: 3190


🏁 Script executed:

# Get the full Server.py file to understand all status changes
wc -l nano_press/nano_press/doctype/server/server.py

Repository: BuildWithHussain/nano_press

Length of output: 121


🏁 Script executed:

# Read the rest of the Server.py file
cat -n nano_press/nano_press/doctype/server/server.py | tail -200

Repository: BuildWithHussain/nano_press

Length of output: 8056


🏁 Script executed:

# Search for all methods/functions that change verify_status or check it
ast-grep --pattern 'def $_($_) {
  $$$
  verify_status
  $$$
}' --type py

Repository: BuildWithHussain/nano_press

Length of output: 250


🏁 Script executed:

# More targeted search for all verify-related methods
rg "def.*verify|verify_status|prepare" nano_press/nano_press/doctype/server/server.py -B 2 -A 10

Repository: BuildWithHussain/nano_press

Length of output: 4522


🏁 Script executed:

# Check if ping_server actually updates the database/verify_status
rg "ping_server" -A 30 nano_press/utils/ansible_runner.py | grep -A 30 "def ping_server"

Repository: BuildWithHussain/nano_press

Length of output: 880


🏁 Script executed:

# Look for any database/status updates in ping_server
rg "ping_server" -A 50 nano_press/api.py | head -80

Repository: BuildWithHussain/nano_press

Length of output: 1309


🏁 Script executed:

# Check if there are other places where verify_status might be degraded or reset
rg 'verify_status\s*=' nano_press/nano_press/doctype/server/ -B 3 -A 3

Repository: BuildWithHussain/nano_press

Length of output: 1819


🏁 Script executed:

# See if there's a separate verify method that might call ping_server and update status
rg "def.*verify" nano_press/nano_press/doctype/server/server.py -B 2 -A 20

Repository: BuildWithHussain/nano_press

Length of output: 53


🏁 Script executed:

# Get the full ping_server function from ansible_runner.py
rg "def ping_server" -A 60 nano_press/utils/ansible_runner.py

Repository: BuildWithHussain/nano_press

Length of output: 1642


🏁 Script executed:

# Check if verify_status can be changed back from "Prepared" to something else
rg "verify_status.*=" nano_press/ -r --type py -B 2 -A 1

Repository: BuildWithHussain/nano_press

Length of output: 3349


🏁 Script executed:

# Look at the git history to understand why the scheduler was commented out
git log --oneline -20 nano_press/hooks.py

Repository: BuildWithHussain/nano_press

Length of output: 151


🏁 Script executed:

# Check if there are any git diffs or comments about the scheduler removal
git log -p --all -S "scheduler_events" -- nano_press/hooks.py | head -100

Repository: BuildWithHussain/nano_press

Length of output: 53


Scheduler removal will cause verify_status to become stale without automatic refresh.

The commented-out scheduler_events was responsible for running ping_server every 5 minutes. The ping_server function actively updates the database with verify_status ("Verified" or "Failed") and last_verified_at. Without this scheduler, verify_status will no longer be automatically refreshed—if a server becomes unreachable, the status remains stale until manual verification. Since critical operations (creating sites, building images, remote builds) check verify_status before proceeding, users could silently fail with outdated status information. If this is intentional, guard the scheduler with a config flag or provide an alternative mechanism to keep status fresh.

🤖 Prompt for AI Agents
In `@nano_press/hooks.py` around lines 155 - 161, Removing the scheduler_events
block disables the periodic call to ping_server which updates verify_status and
last_verified_at, causing stale verification state; restore automatic refresh by
re-adding the scheduler_events cron entry or gating it behind a config flag
(e.g., ENABLE_PING_SCHEDULER) so ping_server runs every 5 minutes, or implement
an alternative refresh mechanism (e.g., trigger ping_server in relevant
workflows like create_site/build_image/remote_build) and ensure code paths that
check verify_status (and read last_verified_at) call the refresh when necessary.



# Testing
Expand Down
Empty file.
48 changes: 48 additions & 0 deletions nano_press/nano_press/doctype/app_version/app_version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"actions": [],
"autoname": "",
"creation": "2025-12-27 00:00:00",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": ["version", "scrubbed_version"],
"fields": [
{
"fieldname": "version",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Version",
"reqd": 1,
"unique": 1
},
{
"fieldname": "scrubbed_version",
"fieldtype": "Data",
"hidden": 1,
"label": "Scrubbed Version"
}
],
"links": [],
"modified": "2025-12-27 16:34:33.317253",
"modified_by": "Administrator",
"module": "Nano Press",
"name": "App Version",
"naming_rule": "By script",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"read": 1,
"role": "System Manager",
"write": 1
},
{
"read": 1,
"role": "Nano Press User"
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
11 changes: 11 additions & 0 deletions nano_press/nano_press/doctype/app_version/app_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) 2025, Build With Hussain and contributors
# For license information, please see license.txt

from frappe.model.document import Document


class AppVersion(Document):
def autoname(self):
if self.version:
self.scrubbed_version = self.version.lower().replace(" ", "-")
self.name = self.scrubbed_version
29 changes: 24 additions & 5 deletions nano_press/nano_press/doctype/apps/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
"engine": "InnoDB",
"field_order": [
"app_name",
"app_logo",
"scrubbed_name",
"repo_url",
"repository_owner",
"column_break_jjcw",
"branch",
"order",
"pat_token",
"column_break_eisq",
"is_custom",
"is_public",
"frappe",
"enabled"
Expand Down Expand Up @@ -51,11 +54,11 @@
"non_negative": 1
},
{
"depends_on": "eval: doc.is_public == 0",
"depends_on": "eval: doc.is_custom == 1",
"description": "Required for private repositories. If provided, the app will be marked as private.",
"fieldname": "pat_token",
"fieldtype": "Data",
"label": "PAT Token",
"mandatory_depends_on": "eval: doc.is_private == 1"
"fieldtype": "Password",
"label": "PAT Token"
},
{
"fieldname": "scrubbed_name",
Expand Down Expand Up @@ -86,12 +89,28 @@
"fieldname": "frappe",
"fieldtype": "Check",
"label": "Frappe"
},
{
"fieldname": "column_break_eisq",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "is_custom",
"fieldtype": "Check",
"label": "Custom"
},
{
"fieldname": "app_logo",
"fieldtype": "Attach Image",
"label": "App Logo"
}
],
"grid_page_length": 50,
"image_field": "app_logo",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-10-24 19:24:48.880196",
"modified": "2025-12-26 22:09:46.554687",
"modified_by": "Administrator",
"module": "Nano Press",
"name": "Apps",
Expand Down
31 changes: 30 additions & 1 deletion nano_press/nano_press/doctype/apps/apps.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
# Copyright (c) 2025, Venkatesh M and contributors
# For license information, please see license.txt

# import frappe
import frappe
from frappe.model.document import Document


class Apps(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from frappe.types import DF

app_name: DF.Data
branch: DF.Data
enabled: DF.Check
frappe: DF.Check
is_custom: DF.Check
is_public: DF.Check
order: DF.Int | None
pat_token: DF.Password | None
repo_url: DF.Data
repository_owner: DF.Data | None
scrubbed_name: DF.Data | None

# end: auto-generated types

def before_insert(self):
self.scrubbed_name = self.app_name.replace(" ", "_").lower()

def validate(self):
if self.is_custom:
if self.pat_token:
self.is_public = 0
else:
self.is_public = 1
6 changes: 3 additions & 3 deletions nano_press/nano_press/doctype/custom_image/custom_image.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
{
"default": "version-15",
"fieldname": "frappe_version",
"fieldtype": "Select",
"fieldtype": "Link",
"label": "Frappe Version",
"options": "version-15\nversion-14"
"options": "App Version"
},
{
"fieldname": "column_break_zpqr",
Expand Down Expand Up @@ -104,7 +104,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-10-23 23:50:38.572587",
"modified": "2025-12-27 17:02:53.063389",
"modified_by": "Administrator",
"module": "Nano Press",
"name": "Custom Image",
Expand Down
Loading