django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.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()