jettask 0.2.9__tar.gz → 0.2.13__tar.gz

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 (178) hide show
  1. {jettask-0.2.9/jettask.egg-info → jettask-0.2.13}/PKG-INFO +1 -1
  2. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/cli.py +139 -88
  3. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/namespace_data_access.py +52 -25
  4. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/main.jsx +2 -0
  5. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/ScheduledTasks.jsx +2 -1
  6. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Settings.jsx +2 -1
  7. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/services/api.js +49 -4
  8. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/services/queueTrend.js +16 -2
  9. {jettask-0.2.9 → jettask-0.2.13/jettask.egg-info}/PKG-INFO +1 -1
  10. {jettask-0.2.9 → jettask-0.2.13}/pyproject.toml +1 -1
  11. {jettask-0.2.9 → jettask-0.2.13}/LICENSE +0 -0
  12. {jettask-0.2.9 → jettask-0.2.13}/MANIFEST.in +0 -0
  13. {jettask-0.2.9 → jettask-0.2.13}/README.md +0 -0
  14. {jettask-0.2.9 → jettask-0.2.13}/jettask/__init__.py +0 -0
  15. {jettask-0.2.9 → jettask-0.2.13}/jettask/config/__init__.py +0 -0
  16. {jettask-0.2.9 → jettask-0.2.13}/jettask/config/performance.py +0 -0
  17. {jettask-0.2.9 → jettask-0.2.13}/jettask/constants.py +0 -0
  18. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/__init__.py +0 -0
  19. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/app.py +0 -0
  20. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/app_importer.py +0 -0
  21. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/consumer_manager.py +0 -0
  22. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/context.py +0 -0
  23. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/delay_scanner.py +0 -0
  24. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/enums.py +0 -0
  25. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/event_pool.py +0 -0
  26. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/heartbeat_process.py +0 -0
  27. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/message.py +0 -0
  28. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/offline_worker_recovery.py +0 -0
  29. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/retry.py +0 -0
  30. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/task.py +0 -0
  31. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/task_batch.py +0 -0
  32. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/unified_manager_base.py +0 -0
  33. {jettask-0.2.9 → jettask-0.2.13}/jettask/core/worker_scanner.py +0 -0
  34. {jettask-0.2.9 → jettask-0.2.13}/jettask/exceptions.py +0 -0
  35. {jettask-0.2.9 → jettask-0.2.13}/jettask/executors/__init__.py +0 -0
  36. {jettask-0.2.9 → jettask-0.2.13}/jettask/executors/asyncio.py +0 -0
  37. {jettask-0.2.9 → jettask-0.2.13}/jettask/executors/base.py +0 -0
  38. {jettask-0.2.9 → jettask-0.2.13}/jettask/executors/common.py +0 -0
  39. {jettask-0.2.9 → jettask-0.2.13}/jettask/executors/multi_asyncio.py +0 -0
  40. {jettask-0.2.9 → jettask-0.2.13}/jettask/monitor/run_backlog_collector.py +0 -0
  41. {jettask-0.2.9 → jettask-0.2.13}/jettask/monitor/stream_backlog_monitor.py +0 -0
  42. {jettask-0.2.9 → jettask-0.2.13}/jettask/monitoring/__init__.py +0 -0
  43. {jettask-0.2.9 → jettask-0.2.13}/jettask/monitoring/file_watcher.py +0 -0
  44. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/pg_consumer_v2.py +0 -0
  45. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql/add_execution_time_field.sql +0 -0
  46. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql/create_new_tables.sql +0 -0
  47. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql/create_tables_v3.sql +0 -0
  48. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql/migrate_to_new_structure.sql +0 -0
  49. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql/modify_time_fields.sql +0 -0
  50. {jettask-0.2.9 → jettask-0.2.13}/jettask/pg_consumer/sql_utils.py +0 -0
  51. {jettask-0.2.9 → jettask-0.2.13}/jettask/router.py +0 -0
  52. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/__init__.py +0 -0
  53. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/add_execution_count.sql +0 -0
  54. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/add_priority_field.sql +0 -0
  55. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/add_scheduler_id.sql +0 -0
  56. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/add_scheduler_id_index.sql +0 -0
  57. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/loader.py +0 -0
  58. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/make_scheduler_id_required.sql +0 -0
  59. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/manager.py +0 -0
  60. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/migrate_interval_seconds.sql +0 -0
  61. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/models.py +0 -0
  62. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/multi_namespace_scheduler.py +0 -0
  63. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/performance_optimization.sql +0 -0
  64. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/run_scheduler.py +0 -0
  65. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/scheduler.py +0 -0
  66. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/schema.sql +0 -0
  67. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/unified_manager.py +0 -0
  68. {jettask-0.2.9 → jettask-0.2.13}/jettask/scheduler/unified_scheduler_manager.py +0 -0
  69. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/__init__.py +0 -0
  70. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/error_handler.py +0 -0
  71. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/exception_hook.py +0 -0
  72. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/helpers.py +0 -0
  73. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/logger.py +0 -0
  74. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/serializer.py +0 -0
  75. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/serializer_optimized.py +0 -0
  76. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/task_logger.py +0 -0
  77. {jettask-0.2.9 → jettask-0.2.13}/jettask/utils/traceback_filter.py +0 -0
  78. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/__init__.py +0 -0
  79. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/__main__.py +0 -0
  80. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/api.py +0 -0
  81. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/__init__.py +0 -0
  82. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/__init__.py +0 -0
  83. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/v1/__init__.py +0 -0
  84. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/v1/monitoring.py +0 -0
  85. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/v1/namespaces.py +0 -0
  86. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/v1/queues.py +0 -0
  87. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/api/v1/tasks.py +0 -0
  88. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/config.py +0 -0
  89. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/core/__init__.py +0 -0
  90. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/core/cache.py +0 -0
  91. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/core/database.py +0 -0
  92. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/core/exceptions.py +0 -0
  93. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/data_access.py +0 -0
  94. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/data_api.py +0 -0
  95. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/dependencies.py +0 -0
  96. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/init_meta_db.py +0 -0
  97. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/main.py +0 -0
  98. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/main_unified.py +0 -0
  99. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/main_v2.py +0 -0
  100. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/models/__init__.py +0 -0
  101. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/models/requests.py +0 -0
  102. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/models/responses.py +0 -0
  103. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/namespace_api.py +0 -0
  104. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/namespace_api_old.py +0 -0
  105. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/queue_backlog_api.py +0 -0
  106. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/queue_stats_v2.py +0 -0
  107. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/redis_monitor_api.py +0 -0
  108. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/services/__init__.py +0 -0
  109. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/start.py +0 -0
  110. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/backend/unified_api_router.py +0 -0
  111. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/cleanup_deprecated_tables.sql +0 -0
  112. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/config.py +0 -0
  113. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/db_init.py +0 -0
  114. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/index.html +0 -0
  115. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/package.json +0 -0
  116. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/App.css +0 -0
  117. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/App.jsx +0 -0
  118. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -0
  119. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -0
  120. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -0
  121. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -0
  122. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -0
  123. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -0
  124. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -0
  125. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/TaskFilter.jsx +0 -0
  126. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -0
  127. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -0
  128. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -0
  129. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -0
  130. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -0
  131. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -0
  132. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/AppLayout.css +0 -0
  133. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -0
  134. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/Header.css +0 -0
  135. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/Header.jsx +0 -0
  136. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/SideMenu.css +0 -0
  137. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -0
  138. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/TabsNav.css +0 -0
  139. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -0
  140. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/UserInfo.css +0 -0
  141. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -0
  142. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -0
  143. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -0
  144. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -0
  145. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/index.css +0 -0
  146. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Alerts.jsx +0 -0
  147. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Dashboard/index.css +0 -0
  148. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -0
  149. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Dashboard.jsx +0 -0
  150. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -0
  151. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -0
  152. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Queues.jsx +0 -0
  153. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/pages/Workers.jsx +0 -0
  154. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/utils/suppressWarnings.js +0 -0
  155. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/src/utils/userPreferences.js +0 -0
  156. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/frontend/vite.config.js +0 -0
  157. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/gradio_app.py +0 -0
  158. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/integrated_gradio_app.py +0 -0
  159. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/models/__init__.py +0 -0
  160. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/models/namespace.py +0 -0
  161. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/models.py +0 -0
  162. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/multi_namespace_consumer.py +0 -0
  163. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/pg_consumer.py +0 -0
  164. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/run.py +0 -0
  165. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/run_monitor.py +0 -0
  166. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/run_webui.py +0 -0
  167. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/schema.sql +0 -0
  168. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/sql/batch_upsert_functions.sql +0 -0
  169. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/sql/init_database.sql +0 -0
  170. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/task_center.py +0 -0
  171. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/task_center_client.py +0 -0
  172. {jettask-0.2.9 → jettask-0.2.13}/jettask/webui/unified_consumer_manager.py +0 -0
  173. {jettask-0.2.9 → jettask-0.2.13}/jettask.egg-info/SOURCES.txt +0 -0
  174. {jettask-0.2.9 → jettask-0.2.13}/jettask.egg-info/dependency_links.txt +0 -0
  175. {jettask-0.2.9 → jettask-0.2.13}/jettask.egg-info/entry_points.txt +0 -0
  176. {jettask-0.2.9 → jettask-0.2.13}/jettask.egg-info/requires.txt +0 -0
  177. {jettask-0.2.9 → jettask-0.2.13}/jettask.egg-info/top_level.txt +0 -0
  178. {jettask-0.2.9 → jettask-0.2.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jettask
3
- Version: 0.2.9
3
+ Version: 0.2.13
4
4
  Summary: A high-performance distributed task queue system with web monitoring
5
5
  Author-email: JetTask Team <support@jettask.io>
6
6
  License-Expression: MIT
@@ -477,16 +477,17 @@ def scheduler(task_center, interval, batch_size, check_interval, debug):
477
477
  @cli.command()
478
478
  @click.option('--port', default=8080, help='前端服务器端口')
479
479
  @click.option('--host', default='0.0.0.0', help='前端服务器监听地址')
480
+ @click.option('--api-url', default='http://localhost:8001', help='后端 API 服务器地址')
480
481
  @click.option('--auto-install', is_flag=True, default=True, help='自动安装缺失的依赖')
481
482
  @click.option('--force-install', is_flag=True, help='强制重新安装依赖')
482
483
  @click.option('--build-only', is_flag=True, help='仅构建生产版本,不启动服务器')
483
- def frontend(port, host, auto_install, force_install, build_only):
484
+ def frontend(port, host, api_url, auto_install, force_install, build_only):
484
485
  """启动 WebUI 前端界面
485
486
 
486
487
  启动生产版本服务器:
487
488
  1. 检查 Node.js 和 npm 是否安装
488
- 2. 复制前端代码到用户目录并安装依赖
489
- 3. 构建并启动生产版本服务器
489
+ 2. 在包目录安装依赖并构建
490
+ 3. 启动生产版本服务器
490
491
 
491
492
  示例:
492
493
  # 启动生产版本服务器(默认)
@@ -495,6 +496,9 @@ def frontend(port, host, auto_install, force_install, build_only):
495
496
  # 指定端口
496
497
  jettask frontend --port 3000
497
498
 
499
+ # 指定后端API地址
500
+ jettask frontend --api-url http://192.168.1.100:8001
501
+
498
502
  # 仅构建生产版本
499
503
  jettask frontend --build-only
500
504
 
@@ -505,79 +509,12 @@ def frontend(port, host, auto_install, force_install, build_only):
505
509
  import shutil
506
510
  from pathlib import Path
507
511
 
508
- # 获取源前端目录路径(包中的)
509
- source_frontend_dir = Path(__file__).parent.parent / "webui" / "frontend"
510
- if not source_frontend_dir.exists():
511
- click.echo(f"错误:前端源目录不存在: {source_frontend_dir}", err=True)
512
+ # 获取前端目录路径
513
+ frontend_dir = Path(__file__).parent.parent / "webui" / "frontend"
514
+ if not frontend_dir.exists():
515
+ click.echo(f"错误:前端目录不存在: {frontend_dir}", err=True)
512
516
  sys.exit(1)
513
517
 
514
- # 获取用户工作目录(在用户主目录下)
515
- user_home = Path.home()
516
- user_jettask_dir = user_home / ".jettask"
517
- user_frontend_dir = user_jettask_dir / "frontend"
518
-
519
- # 确保用户目录存在
520
- user_jettask_dir.mkdir(parents=True, exist_ok=True)
521
-
522
- # 检查是否需要复制/更新前端代码
523
- should_copy = False
524
- if not user_frontend_dir.exists():
525
- should_copy = True
526
- click.echo(f"首次使用,正在初始化前端环境...")
527
- else:
528
- # 检查版本或时间戳来决定是否需要更新
529
- source_package_json = source_frontend_dir / "package.json"
530
- user_package_json = user_frontend_dir / "package.json"
531
- if not user_package_json.exists():
532
- should_copy = True
533
- else:
534
- # 比较修改时间
535
- if source_package_json.stat().st_mtime > user_package_json.stat().st_mtime:
536
- should_copy = True
537
- click.echo("检测到前端代码更新,正在同步...")
538
-
539
- if should_copy:
540
- # 复制前端代码到用户目录
541
- if user_frontend_dir.exists():
542
- # 保留 node_modules 和 dist 目录
543
- node_modules_backup = None
544
- dist_backup = None
545
-
546
- node_modules_dir = user_frontend_dir / "node_modules"
547
- dist_dir = user_frontend_dir / "dist"
548
-
549
- if node_modules_dir.exists() and not force_install:
550
- # 备份 node_modules
551
- node_modules_backup = user_jettask_dir / "node_modules_backup"
552
- if node_modules_backup.exists():
553
- shutil.rmtree(node_modules_backup)
554
- shutil.move(str(node_modules_dir), str(node_modules_backup))
555
-
556
- if dist_dir.exists():
557
- # 备份 dist
558
- dist_backup = user_jettask_dir / "dist_backup"
559
- if dist_backup.exists():
560
- shutil.rmtree(dist_backup)
561
- shutil.move(str(dist_dir), str(dist_backup))
562
-
563
- # 删除旧的前端目录
564
- shutil.rmtree(user_frontend_dir)
565
-
566
- # 复制新的前端代码
567
- shutil.copytree(source_frontend_dir, user_frontend_dir, ignore=shutil.ignore_patterns('node_modules', 'dist', '.git'))
568
-
569
- # 恢复备份
570
- if 'node_modules_backup' in locals() and node_modules_backup and node_modules_backup.exists():
571
- shutil.move(str(node_modules_backup), str(user_frontend_dir / "node_modules"))
572
-
573
- if 'dist_backup' in locals() and dist_backup and dist_backup.exists():
574
- shutil.move(str(dist_backup), str(user_frontend_dir / "dist"))
575
-
576
- click.echo(f"✓ 前端代码已同步到: {user_frontend_dir}")
577
-
578
- # 工作目录切换到用户前端目录
579
- frontend_dir = user_frontend_dir
580
-
581
518
  # 检查 Node.js 是否安装
582
519
  node_cmd = shutil.which('node')
583
520
  if not node_cmd:
@@ -608,7 +545,7 @@ def frontend(port, host, auto_install, force_install, build_only):
608
545
 
609
546
  # 切换到前端目录
610
547
  os.chdir(frontend_dir)
611
- click.echo(f"工作目录: {frontend_dir}")
548
+ click.echo(f"前端目录: {frontend_dir}")
612
549
 
613
550
  # 检查 package.json 是否存在
614
551
  package_json = frontend_dir / "package.json"
@@ -651,7 +588,8 @@ def frontend(port, host, auto_install, force_install, build_only):
651
588
  # 构建或启动
652
589
  try:
653
590
  # 构建生产版本
654
- dist_dir = frontend_dir / "dist"
591
+ # 注意:根据 vite.config.js,构建输出到 ../static/dist
592
+ dist_dir = frontend_dir.parent / "static" / "dist"
655
593
 
656
594
  # 检查是否需要构建
657
595
  need_build = not dist_dir.exists() or build_only
@@ -681,35 +619,148 @@ def frontend(port, host, auto_install, force_install, build_only):
681
619
  sys.exit(1)
682
620
 
683
621
  click.echo(f"\n正在启动生产版本服务器...")
684
- click.echo(f" - 地址: http://{host}:{port}")
685
- click.echo(f" - 本地访问: http://localhost:{port}")
686
- click.echo(f" - 静态文件目录: {dist_dir}")
622
+ click.echo(f" - 访问地址: http://localhost:{port}")
623
+ if host != 'localhost':
624
+ click.echo(f" - 网络地址: http://{host}:{port}")
625
+ click.echo(f" - 静态文件: {dist_dir}")
626
+ click.echo(f" - API 地址: {api_url}")
687
627
  click.echo("\n按 Ctrl+C 停止服务器\n")
688
628
 
689
629
  # 使用 Python 内置的 HTTP 服务器提供静态文件
690
630
  import http.server
691
631
  import socketserver
632
+ import urllib.request
633
+ import urllib.parse
634
+ import json
692
635
 
693
- class Handler(http.server.SimpleHTTPRequestHandler):
636
+ class ProxyHandler(http.server.SimpleHTTPRequestHandler):
694
637
  def __init__(self, *args, **kwargs):
695
638
  super().__init__(*args, directory=str(dist_dir), **kwargs)
696
639
 
640
+ def proxy_request(self, method='GET', body=None):
641
+ """代理 API 请求到后端服务器"""
642
+ if self.path.startswith('/api/') or self.path.startswith('/ws/'):
643
+ # 构建目标 URL
644
+ target_url = api_url + self.path
645
+
646
+ try:
647
+ # 准备请求
648
+ req = urllib.request.Request(target_url, method=method)
649
+
650
+ # 复制请求头
651
+ for header, value in self.headers.items():
652
+ if header.lower() not in ['host', 'connection']:
653
+ req.add_header(header, value)
654
+
655
+ # 添加请求体
656
+ if body:
657
+ req.data = body
658
+
659
+ # 发送请求
660
+ with urllib.request.urlopen(req, timeout=30) as response:
661
+ # 返回响应
662
+ self.send_response(response.getcode())
663
+ for header, value in response.headers.items():
664
+ if header.lower() not in ['connection', 'transfer-encoding']:
665
+ self.send_header(header, value)
666
+ self.end_headers()
667
+ self.wfile.write(response.read())
668
+ return True
669
+
670
+ except urllib.error.HTTPError as e:
671
+ self.send_response(e.code)
672
+ self.end_headers()
673
+ self.wfile.write(e.read())
674
+ return True
675
+ except Exception as e:
676
+ self.send_error(502, f"Bad Gateway: {str(e)}")
677
+ return True
678
+ return False
679
+
697
680
  def do_GET(self):
698
- # 对于 SPA 应用,所有路由都返回 index.html
699
- if self.path != '/' and not Path(dist_dir / self.path[1:]).exists():
700
- self.path = '/index.html'
681
+ # 先尝试代理 API 请求
682
+ if self.proxy_request('GET'):
683
+ return
684
+
685
+ # 对于 index.html,注入 API URL
686
+ if self.path == '/' or self.path == '/index.html' or (
687
+ self.path != '/' and not Path(dist_dir / self.path[1:]).exists()
688
+ ):
689
+ # 读取 index.html
690
+ index_path = dist_dir / 'index.html'
691
+ if index_path.exists():
692
+ with open(index_path, 'r', encoding='utf-8') as f:
693
+ html_content = f.read()
694
+
695
+ # 注入 API URL 配置(使用相对路径,因为我们会代理)
696
+ api_config = f'''
697
+ <script>
698
+ window.JETTASK_API_URL = ''; // 使用相对路径,由服务器代理
699
+ console.log('API requests will be proxied by server');
700
+ </script>
701
+ '''
702
+
703
+ # 在 </head> 标签前注入配置
704
+ html_content = html_content.replace('</head>', api_config + '</head>')
705
+
706
+ # 发送响应
707
+ self.send_response(200)
708
+ self.send_header('Content-type', 'text/html')
709
+ self.send_header('Content-Length', str(len(html_content.encode('utf-8'))))
710
+ self.end_headers()
711
+ self.wfile.write(html_content.encode('utf-8'))
712
+ return
713
+
714
+ # 其他文件正常处理
701
715
  return super().do_GET()
702
716
 
717
+ def do_POST(self):
718
+ # 读取请求体
719
+ content_length = int(self.headers.get('Content-Length', 0))
720
+ body = self.rfile.read(content_length) if content_length > 0 else None
721
+
722
+ # 代理 POST 请求
723
+ if self.proxy_request('POST', body):
724
+ return
725
+
726
+ # 不支持的请求
727
+ self.send_error(405, "Method Not Allowed")
728
+
729
+ def do_PUT(self):
730
+ content_length = int(self.headers.get('Content-Length', 0))
731
+ body = self.rfile.read(content_length) if content_length > 0 else None
732
+ if self.proxy_request('PUT', body):
733
+ return
734
+ self.send_error(405, "Method Not Allowed")
735
+
736
+ def do_DELETE(self):
737
+ if self.proxy_request('DELETE'):
738
+ return
739
+ self.send_error(405, "Method Not Allowed")
740
+
703
741
  def log_message(self, format, *args):
704
742
  # 自定义日志格式
705
743
  click.echo(f"[{self.log_date_time_string()}] {format % args}")
706
744
 
707
745
  # 创建服务器
708
- with socketserver.TCPServer((host, port), Handler) as httpd:
709
- try:
710
- httpd.serve_forever()
711
- except KeyboardInterrupt:
712
- pass
746
+ class ReuseAddrTCPServer(socketserver.TCPServer):
747
+ allow_reuse_address = True
748
+ allow_reuse_port = True
749
+
750
+ try:
751
+ click.echo(f"正在绑定到 {host}:{port}...")
752
+ httpd = ReuseAddrTCPServer((host, port), ProxyHandler)
753
+ click.echo(f"✓ 服务器已启动,监听 {host}:{port}")
754
+ click.echo(f"✓ API 请求将被代理到 {api_url}")
755
+ httpd.serve_forever()
756
+ except OSError as e:
757
+ if e.errno == 98: # Address already in use
758
+ click.echo(f"错误:端口 {port} 已被占用,请尝试其他端口", err=True)
759
+ else:
760
+ click.echo(f"错误:{e}", err=True)
761
+ sys.exit(1)
762
+ except KeyboardInterrupt:
763
+ pass
713
764
  except subprocess.CalledProcessError as e:
714
765
  click.echo(f"错误:命令执行失败 - {e}", err=True)
715
766
  sys.exit(1)
@@ -181,35 +181,62 @@ class NamespaceDataAccessManager:
181
181
 
182
182
  async def get_namespace_config(self, namespace_name: str) -> dict:
183
183
  """从任务中心API获取命名空间配置"""
184
- url = f"{self.task_center_base_url}/api/namespaces/{namespace_name}"
185
-
184
+ # 直接从数据库获取,而不是通过HTTP调用自己
185
+ # 这避免了循环依赖和网络问题
186
186
  try:
187
- session = await self._get_session()
188
- async with session.get(url) as resp:
189
- if resp.status == 200:
190
- data = await resp.json()
191
- # API返回的是redis_config和pg_config,直接使用
192
- redis_config = data.get('redis_config', {})
193
- pg_config = data.get('pg_config', {})
194
-
195
- # 兼容旧格式:如果有redis_url和pg_url字段
196
- if not redis_config and data.get('redis_url'):
197
- redis_config = {'url': data.get('redis_url')}
198
-
199
- if not pg_config and data.get('pg_url'):
200
- pg_config = {'url': data.get('pg_url')}
201
-
187
+ # 先尝试从缓存或直接数据库访问
188
+ from jettask.webui.backend.models import NamespaceModel
189
+ from jettask.webui.backend.db_init import get_async_session
190
+ from sqlalchemy import select
191
+
192
+ async with get_async_session() as session:
193
+ stmt = select(NamespaceModel).where(NamespaceModel.name == namespace_name)
194
+ result = await session.execute(stmt)
195
+ namespace = result.scalar_one_or_none()
196
+
197
+ if namespace:
202
198
  return {
203
- 'name': data.get('name'),
204
- 'redis_config': redis_config,
205
- 'pg_config': pg_config
199
+ 'name': namespace.name,
200
+ 'redis_config': namespace.redis_config or {},
201
+ 'pg_config': namespace.pg_config or {}
206
202
  }
207
203
  else:
208
- raise ValueError(f"无法获取命名空间 {namespace_name} 的配置: HTTP {resp.status}")
209
- except Exception as e:
210
- logger.error(f"获取命名空间 {namespace_name} 配置失败: {e}")
211
- traceback.print_exc()
212
- raise
204
+ raise ValueError(f"命名空间 {namespace_name} 不存在")
205
+ except (ImportError, Exception) as e:
206
+ # 如果无法直接访问数据库,则通过HTTP调用(但使用127.0.0.1)
207
+ logger.warning(f"直接数据库访问失败,回退到HTTP调用: {e}")
208
+ base_url = self.task_center_base_url
209
+ if 'localhost' in base_url:
210
+ base_url = base_url.replace('localhost', '127.0.0.1')
211
+ url = f"{base_url}/api/namespaces/{namespace_name}"
212
+
213
+ try:
214
+ session = await self._get_session()
215
+ async with session.get(url) as resp:
216
+ if resp.status == 200:
217
+ data = await resp.json()
218
+ # API返回的是redis_config和pg_config,直接使用
219
+ redis_config = data.get('redis_config', {})
220
+ pg_config = data.get('pg_config', {})
221
+
222
+ # 兼容旧格式:如果有redis_url和pg_url字段
223
+ if not redis_config and data.get('redis_url'):
224
+ redis_config = {'url': data.get('redis_url')}
225
+
226
+ if not pg_config and data.get('pg_url'):
227
+ pg_config = {'url': data.get('pg_url')}
228
+
229
+ return {
230
+ 'name': data.get('name'),
231
+ 'redis_config': redis_config,
232
+ 'pg_config': pg_config
233
+ }
234
+ else:
235
+ raise ValueError(f"无法获取命名空间 {namespace_name} 的配置: HTTP {resp.status}")
236
+ except Exception as e:
237
+ logger.error(f"通过HTTP获取命名空间 {namespace_name} 配置失败: {e}")
238
+ traceback.print_exc()
239
+ raise
213
240
 
214
241
  async def get_connection(self, namespace_name: str) -> NamespaceConnection:
215
242
  """
@@ -7,6 +7,8 @@ import 'dayjs/locale/zh-cn'
7
7
  import App from './App'
8
8
  import './index.css'
9
9
  import './utils/suppressWarnings'
10
+ // 初始化 API 配置(必须在其他组件之前)
11
+ import './services/api'
10
12
 
11
13
  // 设置 dayjs 为中文
12
14
  dayjs.locale('zh-cn')
@@ -102,8 +102,9 @@ function ScheduledTasks() {
102
102
  offset: (params.current - 1) * params.pageSize,
103
103
  };
104
104
 
105
+ const apiBase = window.JETTASK_API_URL || 'http://localhost:8001';
105
106
  const response = await axios.get(
106
- `http://localhost:8001/api/data/scheduled-tasks/${currentNamespace}`,
107
+ `${apiBase}/api/data/scheduled-tasks/${currentNamespace}`,
107
108
  { params: requestParams }
108
109
  );
109
110
 
@@ -161,7 +161,8 @@ function Settings() {
161
161
  refreshNamespaceList(); // 触发全局命名空间列表刷新
162
162
 
163
163
  // 显示连接URL
164
- const fullUrl = `http://localhost:8001${response.data.connection_url}`;
164
+ const apiBase = window.JETTASK_API_URL || 'http://localhost:8001';
165
+ const fullUrl = `${apiBase}${response.data.connection_url}`;
165
166
  Modal.success({
166
167
  title: '命名空间创建成功',
167
168
  content: (
@@ -2,6 +2,17 @@ import axios from 'axios';
2
2
 
3
3
  // 智能获取 API 基础 URL
4
4
  const getApiBaseUrl = () => {
5
+ // 优先使用注入的 API URL
6
+ if (typeof window !== 'undefined' && window.JETTASK_API_URL !== undefined) {
7
+ const url = window.JETTASK_API_URL;
8
+ // 如果是空字符串,表示使用相对路径(代理模式)
9
+ if (url === '') {
10
+ return ''; // 使用相对路径,不加前缀
11
+ }
12
+ // 否则确保 URL 以 /api 结尾
13
+ return url.endsWith('/api') ? url : `${url}/api`;
14
+ }
15
+
5
16
  // 在开发环境中使用 localhost
6
17
  if (typeof window !== 'undefined') {
7
18
  const hostname = window.location.hostname;
@@ -19,6 +30,19 @@ const getApiBaseUrl = () => {
19
30
 
20
31
  // 智能获取 WebSocket URL
21
32
  const getWsBaseUrl = () => {
33
+ // 优先使用注入的 API URL
34
+ if (typeof window !== 'undefined' && window.JETTASK_API_URL !== undefined) {
35
+ const url = window.JETTASK_API_URL;
36
+ // 如果是空字符串,使用相对路径
37
+ if (url === '') {
38
+ const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
39
+ return `${wsProtocol}//${window.location.host}/ws`;
40
+ }
41
+ // 将 http 转换为 ws,https 转换为 wss
42
+ const wsUrl = url.replace(/^http/, 'ws');
43
+ return wsUrl.endsWith('/ws') ? wsUrl : `${wsUrl}/ws`;
44
+ }
45
+
22
46
  if (typeof window !== 'undefined') {
23
47
  const hostname = window.location.hostname;
24
48
  const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -36,16 +60,37 @@ const getWsBaseUrl = () => {
36
60
  const API_BASE_URL = getApiBaseUrl();
37
61
  const WS_BASE_URL = getWsBaseUrl();
38
62
 
63
+ // 设置 axios 全局默认配置
64
+ // 空字符串时不设置 baseURL,让 axios 使用相对路径
65
+ if (API_BASE_URL !== '') {
66
+ axios.defaults.baseURL = API_BASE_URL;
67
+ }
68
+
39
69
  // 创建 axios 实例
70
+ // 如果 baseURL 为空,设置一个请求拦截器来处理路径
40
71
  const api = axios.create({
41
- baseURL: API_BASE_URL,
72
+ baseURL: API_BASE_URL === '' ? undefined : API_BASE_URL, // 空字符串时不设置 baseURL
42
73
  timeout: 10000,
43
74
  });
44
75
 
76
+ // 如果是代理模式,确保路径正确
77
+ if (API_BASE_URL === '') {
78
+ api.interceptors.request.use(config => {
79
+ // 确保路径以 /api/ 开头
80
+ if (!config.url.startsWith('/api/')) {
81
+ config.url = '/api/' + config.url;
82
+ }
83
+ return config;
84
+ });
85
+ }
86
+
87
+ // 导出配置好的实例和URLs
88
+ export { api, API_BASE_URL, WS_BASE_URL };
89
+
45
90
  // 获取全局统计信息
46
91
  export const fetchGlobalStats = async () => {
47
92
  try {
48
- const response = await api.get('/stats');
93
+ const response = await api.get('stats'); // 不要前导斜杠,axios 会自动处理
49
94
  return response.data.data;
50
95
  } catch (error) {
51
96
  console.error('Failed to fetch global stats:', error);
@@ -56,7 +101,7 @@ export const fetchGlobalStats = async () => {
56
101
  // 获取队列列表
57
102
  export const fetchQueues = async () => {
58
103
  try {
59
- const response = await api.get('/queues');
104
+ const response = await api.get('queues'); // 不要前导斜杠
60
105
  return response.data;
61
106
  } catch (error) {
62
107
  console.error('Failed to fetch queues:', error);
@@ -69,7 +114,7 @@ export const fetchQueueTimeline = async (params) => {
69
114
  try {
70
115
  // 从参数中提取命名空间,如果没有则使用 default
71
116
  const namespace = params.namespace || 'default';
72
- const response = await api.post(`/queue-timeline/${namespace}`, params);
117
+ const response = await api.post(`queue-timeline/${namespace}`, params); // 不要前导斜杠
73
118
  // 处理数据,确保不包含填充的空值
74
119
  const data = response.data.data || response.data || [];
75
120
  return {
@@ -3,6 +3,16 @@ import axios from 'axios';
3
3
  // 在 React 应用中,环境变量需要以 REACT_APP_ 开头
4
4
  // 使用 window.location 作为后备方案
5
5
  const getApiBaseUrl = () => {
6
+ // 优先使用注入的 API URL
7
+ if (typeof window !== 'undefined' && window.JETTASK_API_URL !== undefined) {
8
+ const url = window.JETTASK_API_URL;
9
+ // 如果是空字符串,表示使用相对路径(代理模式)
10
+ if (url === '') {
11
+ return ''; // 使用相对路径
12
+ }
13
+ return url;
14
+ }
15
+
6
16
  // 尝试从环境变量获取(Create React App 会在构建时注入)
7
17
  if (typeof window !== 'undefined' && window.REACT_APP_API_URL) {
8
18
  return window.REACT_APP_API_URL;
@@ -39,7 +49,9 @@ export const fetchQueueTrend = async (timeRange = '1h', queues = [], customTimeR
39
49
  params.end = customTimeRange[1].valueOf();
40
50
  }
41
51
 
42
- const response = await axios.get(`${API_BASE_URL}/api/queue-trend`, { params });
52
+ // API_BASE_URL 为空时,使用相对路径;否则需要加 /api
53
+ const url = API_BASE_URL === '' ? '/api/queue-trend' : `${API_BASE_URL}/api/queue-trend`;
54
+ const response = await axios.get(url, { params });
43
55
 
44
56
  // 处理响应数据 - 后端现在直接返回时间线数据
45
57
  const data = response.data.data || response.data || [];
@@ -132,7 +144,9 @@ const getIntervalByRange = (range) => {
132
144
  */
133
145
  export const fetchQueueStats = async (queueName, timeRange = '1h') => {
134
146
  try {
135
- const response = await axios.get(`${API_BASE_URL}/api/queue/${queueName}/stats`, {
147
+ // API_BASE_URL 为空时,使用相对路径;否则需要加 /api
148
+ const url = API_BASE_URL === '' ? `/api/queue/${queueName}/stats` : `${API_BASE_URL}/api/queue/${queueName}/stats`;
149
+ const response = await axios.get(url, {
136
150
  params: { range: timeRange }
137
151
  });
138
152
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jettask
3
- Version: 0.2.9
3
+ Version: 0.2.13
4
4
  Summary: A high-performance distributed task queue system with web monitoring
5
5
  Author-email: JetTask Team <support@jettask.io>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jettask"
7
- version = "0.2.9"
7
+ version = "0.2.13"
8
8
  authors = [
9
9
  {name = "JetTask Team", email = "support@jettask.io"},
10
10
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes