django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,815 @@
1
+ """
2
+ Startup display manager for Django CFG.
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+ from rich.text import Text
7
+ from rich.table import Table
8
+ from rich.panel import Panel
9
+ from .base import BaseDisplayManager, MAIN_PANEL_WIDTH, HALF_PANEL_WIDTH
10
+ from .ngrok import NgrokDisplayManager
11
+
12
+
13
+ class StartupDisplayManager(BaseDisplayManager):
14
+ """Manager for displaying startup information."""
15
+
16
+ def __init__(self, config=None):
17
+ """Initialize startup display manager."""
18
+ super().__init__(config)
19
+ self.ngrok_manager = NgrokDisplayManager(config)
20
+
21
+ def display_startup_info(self):
22
+ """Display startup information based on config.startup_info_mode."""
23
+ try:
24
+ if not self.config:
25
+ return
26
+
27
+ # Always check and display ngrok info first if active
28
+ self.ngrok_manager.display_if_active()
29
+
30
+ from django_cfg.core.config import StartupInfoMode
31
+ mode = self.config.startup_info_mode
32
+
33
+ if mode == StartupInfoMode.NONE:
34
+ self.display_minimal_info()
35
+ elif mode == StartupInfoMode.SHORT:
36
+ self.display_essential_info()
37
+ elif mode == StartupInfoMode.FULL:
38
+ self.display_complete_info()
39
+
40
+ except Exception:
41
+ # Silently fail - startup info is not critical
42
+ pass
43
+
44
+ def display_minimal_info(self):
45
+ """Display minimal startup info (NONE mode)."""
46
+ version = self.get_version()
47
+ panel_style, env_emoji, env_color = self.get_environment_style()
48
+
49
+ # Simple one-liner
50
+ info_text = Text()
51
+ info_text.append(f"{env_emoji} Django CFG ", style="bold")
52
+ info_text.append(f"v{version}", style="cyan")
53
+ info_text.append(" • ", style="dim")
54
+ info_text.append(f"{self.config.env_mode}", style=env_color)
55
+ info_text.append(" • ", style="dim")
56
+ info_text.append(f"{self.config.project_name}", style="white")
57
+
58
+ # Check for critical updates
59
+ try:
60
+ from ..version_checker import get_version_info
61
+ version_info = get_version_info()
62
+ if version_info.get('update_available'):
63
+ info_text.append(" • ", style="dim")
64
+ info_text.append("🚨 UPDATE AVAILABLE", style="bold yellow")
65
+ info_text.append(" (", style="dim")
66
+ info_text.append("poetry add django-cfg@latest", style="bright_blue")
67
+ info_text.append(")", style="dim")
68
+ except Exception:
69
+ pass
70
+
71
+ self.console.print(info_text)
72
+
73
+ def display_essential_info(self):
74
+ """Display essential startup info (SHORT mode)."""
75
+ version = self.get_version()
76
+ panel_style, env_emoji, env_color = self.get_environment_style()
77
+
78
+ # Create compact header
79
+ header_text = Text()
80
+ header_text.append(f"{env_emoji} Django CFG ", style="bold")
81
+ header_text.append(f"v{version}", style="cyan")
82
+ header_text.append(" • ", style="dim")
83
+ header_text.append(f"{self.config.env_mode}", style=env_color)
84
+ header_text.append(" • ", style="dim")
85
+ header_text.append(f"{self.config.project_name}", style="white")
86
+
87
+ header_panel = self.create_panel(
88
+ header_text,
89
+ title="",
90
+ border_style=panel_style,
91
+ width=None
92
+ )
93
+ header_panel.padding = (0, 1) # Override padding for header
94
+ self.console.print(header_panel)
95
+
96
+ # Check for updates first
97
+ self._display_update_notification_short()
98
+
99
+ # Create columns for essential info
100
+ self._display_essential_columns()
101
+
102
+ def display_complete_info(self):
103
+ """Display complete startup info (FULL mode)."""
104
+ version = self.get_version()
105
+ panel_style, env_emoji, env_color = self.get_environment_style()
106
+
107
+ # Get library info
108
+ from django_cfg.config import (
109
+ LIB_NAME, LIB_SITE_URL, LIB_DOCS_URL, LIB_GITHUB_URL,
110
+ LIB_SUPPORT_URL, LIB_HEALTH_URL
111
+ )
112
+
113
+ # Create main info table
114
+ info_table = self.create_table()
115
+ info_table.add_column("Setting", style="cyan", width=30)
116
+ info_table.add_column("Value", style="white")
117
+
118
+ info_table.add_row("📦 Version", version)
119
+ info_table.add_row("🔗 Prefix", "/cfg/")
120
+ info_table.add_row("🌍 Environment", self.config.env_mode)
121
+ info_table.add_row("🔧 Debug", str(self.config.debug))
122
+ info_table.add_row("🏗️ Project", self.config.project_name)
123
+
124
+ # Add environment source
125
+ env_source = getattr(self.config, '_environment', 'default_fallback')
126
+ info_table.add_row("🔍 Env Source", env_source)
127
+
128
+ info_table.add_row("🌐 Site", LIB_SITE_URL)
129
+ info_table.add_row("📚 Docs", LIB_DOCS_URL)
130
+ info_table.add_row("🐙 GitHub", LIB_GITHUB_URL)
131
+ info_table.add_row("🆘 Support", LIB_SUPPORT_URL)
132
+
133
+ # Use full URL for health endpoint
134
+ health_url = f"{self.get_base_url()}/cfg/health/"
135
+ info_table.add_row("❤️ Health", health_url)
136
+
137
+ # Create main panel with full width
138
+ main_panel = self.create_full_width_panel(
139
+ info_table,
140
+ title=f"{env_emoji} Django CFG Configuration",
141
+ border_style=panel_style
142
+ )
143
+
144
+ self.console.print(main_panel)
145
+
146
+ # Check for updates
147
+ self._display_update_notification_full()
148
+
149
+ # Create columns for apps and endpoints
150
+ self._display_main_columns()
151
+
152
+ # App-specific configuration panels
153
+ self._display_config_panels()
154
+
155
+ # Revolution info
156
+ self._display_revolution_info()
157
+
158
+ # Management commands
159
+ self._display_commands_info()
160
+
161
+ self.print_spacing()
162
+
163
+ def _display_update_notification_short(self):
164
+ """Display update notification for SHORT mode."""
165
+ try:
166
+ from ..version_checker import get_version_info
167
+ version_info = get_version_info()
168
+
169
+ if version_info.get('update_available'):
170
+ current = version_info.get('current_version', 'unknown')
171
+ latest = version_info.get('latest_version', 'unknown')
172
+
173
+ update_text = Text()
174
+ update_text.append("🚨 Update available: ", style="bold yellow")
175
+ update_text.append(f"{current}", style="red")
176
+ update_text.append(" → ", style="dim")
177
+ update_text.append(f"{latest}", style="green")
178
+ update_text.append("\n💡 Run: ", style="dim")
179
+ update_text.append("poetry add django-cfg@latest", style="bright_blue")
180
+
181
+ update_panel = self.create_panel(
182
+ update_text,
183
+ title="",
184
+ border_style="yellow",
185
+ width=None
186
+ )
187
+ update_panel.padding = (0, 2) # Override padding
188
+ self.console.print(update_panel)
189
+ except Exception:
190
+ pass
191
+
192
+ def _display_update_notification_full(self):
193
+ """Display update notification for FULL mode."""
194
+ try:
195
+ from ..version_checker import get_version_info
196
+ version_info = get_version_info()
197
+
198
+ if version_info.get('update_available'):
199
+ update_table = self.create_table()
200
+ update_table.add_column("Info", style="yellow", width=15)
201
+ update_table.add_column("Value", style="white")
202
+
203
+ current = version_info.get('current_version', 'unknown')
204
+ latest = version_info.get('latest_version', 'unknown')
205
+ update_url = version_info.get('update_url', '')
206
+
207
+ update_table.add_row("Current", f"[red]{current}[/red]")
208
+ update_table.add_row("Latest", f"[green]{latest}[/green]")
209
+ update_table.add_row("💡 Update", "[bright_blue]poetry add django-cfg@latest[/bright_blue]")
210
+ if update_url:
211
+ update_table.add_row("PyPI", update_url)
212
+
213
+ update_panel = self.create_full_width_panel(
214
+ update_table,
215
+ title="🚨 Update Available",
216
+ border_style="yellow"
217
+ )
218
+
219
+ self.console.print(update_panel)
220
+ except Exception:
221
+ pass
222
+
223
+ def _display_essential_columns(self):
224
+ """Display essential info in columns for SHORT mode."""
225
+ # Enabled apps
226
+ enabled_apps = self.config.get_enabled_apps() or []
227
+ apps_table = self.create_table()
228
+ apps_table.add_column("App", style="bright_blue")
229
+
230
+ for app in enabled_apps[:5]: # Limit for SHORT mode
231
+ app_name = app.split('.')[-1]
232
+ apps_table.add_row(f"• {app_name}")
233
+
234
+ # Key endpoints
235
+ endpoints_table = self.create_table()
236
+ endpoints_table.add_column("Endpoint", style="bright_green")
237
+
238
+ endpoints_table.add_row(f"• {self.get_base_url('cfg', 'health')}")
239
+ endpoints_table.add_row(f"• {self.get_base_url('api', 'payments')}")
240
+
241
+ # Use new two-column table method for perfect 50/50 layout
242
+ self.print_two_column_table(
243
+ left_content=apps_table,
244
+ right_content=endpoints_table,
245
+ left_title="📱 Enabled Apps",
246
+ right_title="🔗 Key Endpoints",
247
+ left_style="blue",
248
+ right_style="green"
249
+ )
250
+
251
+ def _display_main_columns(self):
252
+ """Display main columns (apps and endpoints) for FULL mode."""
253
+ # Enabled apps
254
+ enabled_apps = self.config.get_enabled_apps() or []
255
+ apps_table = self.create_table()
256
+ apps_table.add_column("App", style="bright_blue")
257
+
258
+ for app in enabled_apps:
259
+ app_name = app.split('.')[-1]
260
+ apps_table.add_row(f"• {app_name}")
261
+
262
+ # Endpoints
263
+ endpoints_table = self.create_table()
264
+ endpoints_table.add_column("Endpoint", style="bright_green")
265
+
266
+ # Add core endpoints
267
+ endpoints_table.add_row(f"• {self.get_base_url('cfg', 'health')}")
268
+ endpoints_table.add_row(f"• {self.get_base_url('cfg', 'commands')}")
269
+
270
+ # Add app-specific API endpoints based on enabled apps
271
+ for app in enabled_apps:
272
+ app_name = app.split('.')[-1]
273
+ if app_name in ['health', 'commands']:
274
+ continue
275
+ else:
276
+ endpoints_table.add_row(f"• {self.get_base_url('api', app_name)}")
277
+
278
+ # Use new two-column table method for perfect 50/50 layout
279
+ self.print_two_column_table(
280
+ left_content=apps_table,
281
+ right_content=endpoints_table,
282
+ left_title="📱 Enabled Apps",
283
+ right_title="🔗 Endpoints",
284
+ left_style="blue",
285
+ right_style="green"
286
+ )
287
+
288
+ def _display_config_panels(self):
289
+ """Display app-specific configuration panels."""
290
+ config_panels = []
291
+
292
+ # Payments configuration
293
+ if (self.config and hasattr(self.config, 'payments') and
294
+ self.config.payments and self.config.payments.enabled):
295
+
296
+ payment_table = self.create_table()
297
+ payment_table.add_column("Setting", style="cyan", width=20)
298
+ payment_table.add_column("Value", style="white")
299
+
300
+ payment_table.add_row("Enabled", f"[green]{self.config.payments.enabled}[/green]")
301
+ middleware_enabled = self.config.payments.middleware_enabled
302
+ color = 'green' if middleware_enabled else 'red'
303
+ payment_table.add_row("Middleware", f"[{color}]{middleware_enabled}[/{color}]")
304
+
305
+ config_panels.append(self.create_panel(
306
+ payment_table,
307
+ title="💳 Payments",
308
+ border_style="yellow"
309
+ ))
310
+
311
+ # Tasks configuration - removed from config_panels, will be shown separately
312
+
313
+ # Constance configuration
314
+ try:
315
+ constance_table = self.create_table()
316
+ constance_table.add_column("Setting", style="cyan", width=20)
317
+ constance_table.add_column("Value", style="white")
318
+
319
+ # Constance fields moved to separate block
320
+ # Get cache info
321
+ try:
322
+ cache_config = getattr(self.config, 'cache', None)
323
+ if cache_config:
324
+ constance_table.add_row("Cache", f"[green]{cache_config.backend}[/green]")
325
+ else:
326
+ constance_table.add_row("Cache", "[yellow]Not configured[/yellow]")
327
+ except Exception:
328
+ constance_table.add_row("Cache", "[red]Error[/red]")
329
+
330
+ # Add validation info
331
+ try:
332
+ from django_cfg.core.validation import ConfigurationValidator
333
+ validation_errors = ConfigurationValidator.validate(self.config)
334
+ error_count = len(validation_errors)
335
+ if error_count == 0:
336
+ constance_table.add_row("Validation", "[green]✓ Valid[/green]")
337
+ else:
338
+ constance_table.add_row("Validation", f"[red]✗ {error_count} errors[/red]")
339
+ except Exception:
340
+ constance_table.add_row("Validation", "[red]Error[/red]")
341
+
342
+ # Add installed apps count
343
+ try:
344
+ installed_apps = self.config.get_installed_apps() if hasattr(self.config, 'get_installed_apps') else []
345
+ constance_table.add_row("Apps", f"[blue]{len(installed_apps)}[/blue]")
346
+ except Exception:
347
+ constance_table.add_row("Apps", "[red]Error[/red]")
348
+
349
+ config_panels.append(self.create_panel(
350
+ constance_table,
351
+ title="⚙️ Configuration",
352
+ border_style="cyan"
353
+ ))
354
+ except Exception:
355
+ pass
356
+
357
+ # Show config panels
358
+ if len(config_panels) >= 2:
359
+ # Show first two panels in 50/50 layout
360
+ self.print_two_column_table(
361
+ left_content=config_panels[0].renderable,
362
+ right_content=config_panels[1].renderable,
363
+ left_title=config_panels[0].title,
364
+ right_title=config_panels[1].title,
365
+ left_style=config_panels[0].border_style,
366
+ right_style=config_panels[1].border_style
367
+ )
368
+
369
+ # Show remaining panels individually
370
+ for panel in config_panels[2:]:
371
+ self.console.print(panel)
372
+ elif config_panels:
373
+ # Show single panel
374
+ for panel in config_panels:
375
+ self.console.print(panel)
376
+
377
+ # Show Background Tasks separately
378
+ self._display_background_tasks()
379
+
380
+ # Show detailed Constance information (includes summary)
381
+ self._display_constance_details()
382
+
383
+ def _display_background_tasks(self):
384
+ """Display Background Tasks information in a separate panel."""
385
+ try:
386
+ # Always show Background Tasks section if config exists
387
+ if not self.config:
388
+ return
389
+
390
+ task_table = self.create_table()
391
+ task_table.add_column("Setting", style="cyan", width=20)
392
+ task_table.add_column("Value", style="white")
393
+
394
+ # Show real tasks status
395
+ tasks_enabled = self.config.should_enable_tasks()
396
+ if tasks_enabled:
397
+ task_table.add_row("Tasks Enabled", "[green]True[/green]")
398
+ else:
399
+ task_table.add_row("Tasks Enabled", "[yellow]False[/yellow]")
400
+
401
+ if hasattr(self.config, 'tasks') and self.config.tasks:
402
+ queue_name = getattr(self.config.tasks, 'queue_name', 'default')
403
+ task_table.add_row("Queue", f"[yellow]{queue_name}[/yellow]")
404
+ else:
405
+ task_table.add_row("Queue", "[yellow]default[/yellow]")
406
+
407
+ # Add worker command
408
+ task_table.add_row("Start Workers", "[bright_blue]poetry run python manage.py rundramatiq[/bright_blue]")
409
+
410
+ task_panel = self.create_full_width_panel(
411
+ task_table,
412
+ title="⚡ Background Tasks",
413
+ border_style="purple"
414
+ )
415
+
416
+ self.console.print(task_panel)
417
+
418
+ except Exception as e:
419
+ import traceback
420
+ print(f"❌ ERROR in _display_background_tasks: {e}")
421
+ traceback.print_exc()
422
+
423
+ def _display_constance_integrated_block(self, constance_config, all_fields):
424
+ """Display integrated Constance block with summary and field details."""
425
+ try:
426
+ # Create main content table that will contain everything
427
+ main_content = Table(show_header=False, box=None, padding=(0, 1))
428
+ main_content.add_column("Content", justify="left")
429
+
430
+ # 1. Add summary section
431
+ summary_table = self.create_table()
432
+ summary_table.add_column("Source", style="cyan", width=20)
433
+ summary_table.add_column("Fields", style="white")
434
+
435
+ # Count fields by source
436
+ user_fields = len(constance_config.fields)
437
+
438
+ # Count by app
439
+ tasks_count = 0
440
+ knowbase_count = 0
441
+ payments_count = 0
442
+
443
+ config = self.config
444
+ if config and config.should_enable_tasks():
445
+ try:
446
+ from django_cfg.modules.django_tasks import extend_constance_config_with_tasks
447
+ tasks_fields = extend_constance_config_with_tasks()
448
+ tasks_count = len(tasks_fields)
449
+ except:
450
+ pass
451
+
452
+ if config and config.enable_knowbase:
453
+ try:
454
+ from django_cfg.apps.knowbase.config import get_django_cfg_knowbase_constance_fields
455
+ knowbase_fields = get_django_cfg_knowbase_constance_fields()
456
+ knowbase_count = len(knowbase_fields)
457
+ except:
458
+ pass
459
+
460
+ if config and config.payments and config.payments.enabled:
461
+ try:
462
+ from django_cfg.apps.payments.config import get_django_cfg_payments_constance_fields
463
+ payments_fields = get_django_cfg_payments_constance_fields()
464
+ payments_count = len(payments_fields)
465
+ except:
466
+ pass
467
+
468
+ summary_table.add_row("User Defined", f"[blue]{user_fields}[/blue]")
469
+ if tasks_count > 0:
470
+ summary_table.add_row("Tasks Module", f"[green]{tasks_count}[/green]")
471
+ if knowbase_count > 0:
472
+ summary_table.add_row("Knowbase App", f"[green]{knowbase_count}[/green]")
473
+ if payments_count > 0:
474
+ summary_table.add_row("Payments App", f"[green]{payments_count}[/green]")
475
+ summary_table.add_row("Total", f"[yellow]{len(all_fields)}[/yellow]")
476
+
477
+ main_content.add_row(summary_table)
478
+ main_content.add_row("") # Spacer
479
+
480
+ # 2. Add field details section
481
+ groups = {}
482
+ for field in all_fields:
483
+ group = field.group
484
+ if group not in groups:
485
+ groups[group] = []
486
+ groups[group].append(field)
487
+
488
+ group_names = list(groups.keys())[:2]
489
+
490
+ if len(group_names) >= 2:
491
+ # Create two-column layout for field details
492
+ details_table = Table(show_header=False, box=None, padding=(0, 2))
493
+ details_table.add_column("Left", justify="left")
494
+ details_table.add_column("Right", justify="left")
495
+
496
+ # Left column - first group
497
+ left_table = self.create_table()
498
+ left_table.add_column("Field", style="bright_cyan")
499
+ left_table.add_column("Type", style="yellow")
500
+ for field in groups[group_names[0]][:8]:
501
+ left_table.add_row(field.name, field.field_type)
502
+
503
+ # Right column - second group
504
+ right_table = self.create_table()
505
+ right_table.add_column("Field", style="bright_cyan")
506
+ right_table.add_column("Type", style="yellow")
507
+ for field in groups[group_names[1]][:8]:
508
+ right_table.add_row(field.name, field.field_type)
509
+
510
+ # Create panels for each group
511
+ left_panel = Panel(
512
+ left_table,
513
+ title=f"🔧 {group_names[0]} Settings",
514
+ border_style="cyan",
515
+ expand=True,
516
+ padding=(1, 1)
517
+ )
518
+
519
+ right_panel = Panel(
520
+ right_table,
521
+ title=f"🔧 {group_names[1]} Settings",
522
+ border_style="blue",
523
+ expand=True,
524
+ padding=(1, 1)
525
+ )
526
+
527
+ details_table.add_row(left_panel, right_panel)
528
+ main_content.add_row(details_table)
529
+
530
+ elif len(group_names) == 1:
531
+ # Single group
532
+ single_table = self.create_table()
533
+ single_table.add_column("Field", style="bright_cyan")
534
+ single_table.add_column("Type", style="yellow")
535
+ single_table.add_column("Default", style="white")
536
+
537
+ for field in groups[group_names[0]][:10]:
538
+ default_str = str(field.default)[:20] + "..." if len(str(field.default)) > 20 else str(field.default)
539
+ single_table.add_row(field.name, field.field_type, default_str)
540
+
541
+ single_panel = Panel(
542
+ single_table,
543
+ title=f"🔧 {group_names[0]} Settings",
544
+ border_style="cyan",
545
+ expand=True,
546
+ padding=(1, 1)
547
+ )
548
+ main_content.add_row(single_panel)
549
+
550
+ # Create the main panel containing everything
551
+ integrated_panel = self.create_full_width_panel(
552
+ main_content,
553
+ title="📊 Constance Fields Summary",
554
+ border_style="purple"
555
+ )
556
+
557
+ self.console.print(integrated_panel)
558
+
559
+ except Exception as e:
560
+ import traceback
561
+ print(f"❌ ERROR in _display_constance_integrated_block: {e}")
562
+ traceback.print_exc()
563
+
564
+ def _display_constance_details(self):
565
+ """Display detailed Constance configuration information."""
566
+ try:
567
+ if not (self.config and hasattr(self.config, 'constance')):
568
+ return
569
+
570
+ constance_config = self.config.constance
571
+ all_fields = constance_config.get_all_fields()
572
+
573
+ if not all_fields:
574
+ return
575
+
576
+ # Show integrated summary and details in one block
577
+ self._display_constance_integrated_block(constance_config, all_fields)
578
+
579
+ except Exception as e:
580
+ import traceback
581
+ print(f"❌ ERROR in _display_constance_details: {e}")
582
+ traceback.print_exc()
583
+
584
+ def _display_constance_summary(self, constance_config, all_fields):
585
+ """Display summary of Constance fields by source."""
586
+ try:
587
+ # Count fields by source
588
+ user_fields = len(constance_config.fields) # User-defined fields
589
+ app_fields = constance_config._get_app_constance_fields()
590
+
591
+ # Count by app
592
+ tasks_count = 0
593
+ knowbase_count = 0
594
+ payments_count = 0
595
+
596
+ # Try to get individual app field counts
597
+ config = self.config
598
+ if config and config.should_enable_tasks():
599
+ try:
600
+ from django_cfg.modules.django_tasks import extend_constance_config_with_tasks
601
+ tasks_fields = extend_constance_config_with_tasks()
602
+ tasks_count = len(tasks_fields)
603
+ except:
604
+ pass
605
+
606
+ if config and config.enable_knowbase:
607
+ try:
608
+ from django_cfg.apps.knowbase.config import get_django_cfg_knowbase_constance_fields
609
+ knowbase_fields = get_django_cfg_knowbase_constance_fields()
610
+ knowbase_count = len(knowbase_fields)
611
+ except:
612
+ pass
613
+
614
+ if config and config.payments and config.payments.enabled:
615
+ try:
616
+ from django_cfg.apps.payments.config import get_django_cfg_payments_constance_fields
617
+ payments_fields = get_django_cfg_payments_constance_fields()
618
+ payments_count = len(payments_fields)
619
+ except:
620
+ pass
621
+
622
+ # Create summary table
623
+ summary_table = self.create_table()
624
+ summary_table.add_column("Source", style="cyan", width=20)
625
+ summary_table.add_column("Fields", style="white")
626
+
627
+ summary_table.add_row("User Defined", f"[blue]{user_fields}[/blue]")
628
+
629
+ if tasks_count > 0:
630
+ summary_table.add_row("Tasks Module", f"[green]{tasks_count}[/green]")
631
+ if knowbase_count > 0:
632
+ summary_table.add_row("Knowbase App", f"[green]{knowbase_count}[/green]")
633
+ if payments_count > 0:
634
+ summary_table.add_row("Payments App", f"[green]{payments_count}[/green]")
635
+
636
+ summary_table.add_row("Total", f"[yellow]{len(all_fields)}[/yellow]")
637
+
638
+ summary_panel = self.create_panel(
639
+ summary_table,
640
+ title="📊 Constance Fields Summary",
641
+ border_style="purple"
642
+ )
643
+
644
+ self.console.print(summary_panel)
645
+
646
+ except Exception as e:
647
+ import traceback
648
+ print(f"❌ ERROR in _display_constance_summary: {e}")
649
+ traceback.print_exc()
650
+
651
+ def _display_revolution_info(self):
652
+ """Display Django Revolution information."""
653
+ try:
654
+ from django_revolution import get_revolution_info
655
+ revolution_info = get_revolution_info()
656
+
657
+ revolution_table = self.create_table()
658
+ revolution_table.add_column("Setting", style="cyan", width=30)
659
+ revolution_table.add_column("Value", style="white")
660
+
661
+ revolution_table.add_row("📦 Version", revolution_info.get('version', 'unknown'))
662
+ revolution_table.add_row("📊 Zones", str(revolution_info.get('zones_count', 0)))
663
+ revolution_table.add_row("📱 Apps", str(revolution_info.get('apps_count', 0)))
664
+ revolution_table.add_row("🔗 API Prefix", revolution_info.get('api_prefix', '/api/'))
665
+
666
+ revolution_panel = self.create_panel(
667
+ revolution_table,
668
+ title="🚀 Django Revolution",
669
+ border_style="blue"
670
+ )
671
+
672
+ self.console.print(revolution_panel)
673
+ except ImportError:
674
+ # Django Revolution not available
675
+ pass
676
+ except Exception:
677
+ pass
678
+
679
+ def _display_commands_info(self):
680
+ """Display management commands information."""
681
+ try:
682
+ from ..commands_collector import get_all_commands, get_commands_with_descriptions
683
+
684
+ # Get command counts
685
+ all_commands = get_all_commands()
686
+ core_count = sum(len(commands) for commands in all_commands.get('django_cfg_core', {}).values())
687
+ app_count = sum(len(commands) for commands in all_commands.get('django_cfg_apps', {}).values())
688
+ project_count = sum(len(commands) for commands in all_commands.get('project_commands', {}).values())
689
+ total_count = core_count + app_count + project_count
690
+
691
+ # Main commands info
692
+ commands_table = self.create_table()
693
+ commands_table.add_column("Type", style="cyan", width=20)
694
+ commands_table.add_column("Count", style="white")
695
+
696
+ commands_table.add_row("🔧 Core Commands", str(core_count))
697
+ commands_table.add_row("📱 App Commands", str(app_count))
698
+ commands_table.add_row("🏗️ Project Commands", str(project_count))
699
+ commands_table.add_row("📊 Total", str(total_count))
700
+
701
+ commands_panel = self.create_full_width_panel(
702
+ commands_table,
703
+ title="⚡ Management Commands",
704
+ border_style="purple"
705
+ )
706
+
707
+ self.console.print(commands_panel)
708
+
709
+ # Detailed commands breakdown
710
+ self._display_commands_breakdown()
711
+
712
+ except Exception as e:
713
+ import traceback
714
+ print(f"❌ ERROR in _display_commands_info: {e}")
715
+ traceback.print_exc()
716
+
717
+ def _display_commands_breakdown(self):
718
+ """Display detailed commands breakdown."""
719
+ try:
720
+ from ..commands_collector import get_all_commands
721
+ all_commands = get_all_commands()
722
+
723
+ columns_content = []
724
+
725
+ # Core commands
726
+ core_commands_dict = all_commands.get('django_cfg_core', {})
727
+ if core_commands_dict:
728
+ core_table = self.create_table()
729
+ core_table.add_column("Command", style="bright_blue")
730
+
731
+ # Flatten all core commands
732
+ all_core_commands = []
733
+ for commands_list in core_commands_dict.values():
734
+ all_core_commands.extend(commands_list)
735
+
736
+ for cmd in all_core_commands[:15]: # Limit to first 15
737
+ core_table.add_row(f"• {cmd}")
738
+
739
+ columns_content.append(self.create_panel(
740
+ core_table,
741
+ title="🔧 Core Commands",
742
+ border_style="blue"
743
+ ))
744
+
745
+ # App commands
746
+ app_commands_dict = all_commands.get('django_cfg_apps', {})
747
+ if app_commands_dict:
748
+ app_table = self.create_table()
749
+ app_table.add_column("Command", style="bright_green")
750
+
751
+ for app_name, commands_list in app_commands_dict.items():
752
+ if commands_list:
753
+ app_table.add_row(f"[bold]{app_name.title()}:[/bold]")
754
+ for cmd in commands_list[:3]: # Limit per app
755
+ app_table.add_row(f" • {cmd}")
756
+
757
+ columns_content.append(self.create_panel(
758
+ app_table,
759
+ title="📱 App Commands",
760
+ border_style="green"
761
+ ))
762
+
763
+ # Show command columns in 50/50 layout
764
+ if len(columns_content) == 2:
765
+ self.print_two_column_table(
766
+ left_content=columns_content[0].renderable,
767
+ right_content=columns_content[1].renderable,
768
+ left_title="🔧 Core Commands",
769
+ right_title="📱 App Commands",
770
+ left_style="blue",
771
+ right_style="green"
772
+ )
773
+ elif len(columns_content) == 1:
774
+ # Single column - show as panel
775
+ self.console.print(columns_content[0])
776
+ elif columns_content:
777
+ # Fallback for other cases
778
+ self.print_columns(columns_content, equal=True, expand=True)
779
+
780
+ # Project commands (separate panel due to length)
781
+ project_commands_dict = all_commands.get('project_commands', {})
782
+ if project_commands_dict:
783
+ # Flatten all project commands
784
+ all_project_commands = []
785
+ for commands_list in project_commands_dict.values():
786
+ all_project_commands.extend(commands_list)
787
+
788
+ # Split commands into two columns
789
+ mid_point = len(all_project_commands) // 2
790
+ left_commands = all_project_commands[:mid_point]
791
+ right_commands = all_project_commands[mid_point:]
792
+
793
+ # Create two-column table inside one panel
794
+ project_columns_table = Table(show_header=False, box=None, padding=(0, 2))
795
+ project_columns_table.add_column("Left", justify="left")
796
+ project_columns_table.add_column("Right", justify="left")
797
+
798
+ # Create content for each column
799
+ left_content = "\n".join([f"• {cmd}" for cmd in left_commands[:15]]) # Limit to 15 per column
800
+ right_content = "\n".join([f"• {cmd}" for cmd in right_commands[:15]])
801
+
802
+ project_columns_table.add_row(left_content, right_content)
803
+
804
+ project_panel = self.create_full_width_panel(
805
+ project_columns_table,
806
+ title="🏗️ Project Commands",
807
+ border_style="yellow"
808
+ )
809
+
810
+ self.console.print(project_panel)
811
+
812
+ except Exception as e:
813
+ import traceback
814
+ print(f"❌ ERROR in _display_commands_breakdown: {e}")
815
+ traceback.print_exc()