jettask 0.2.8__py3-none-any.whl → 0.2.13__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.
- jettask/core/cli.py +175 -34
- jettask/webui/backend/namespace_data_access.py +52 -25
- jettask/webui/frontend/src/main.jsx +2 -0
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +2 -1
- jettask/webui/frontend/src/pages/Settings.jsx +2 -1
- jettask/webui/frontend/src/services/api.js +49 -4
- jettask/webui/frontend/src/services/queueTrend.js +16 -2
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/METADATA +1 -1
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/RECORD +13 -13
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/WHEEL +0 -0
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.8.dist-info → jettask-0.2.13.dist-info}/top_level.txt +0 -0
jettask/core/cli.py
CHANGED
@@ -475,28 +475,32 @@ def scheduler(task_center, interval, batch_size, check_interval, debug):
|
|
475
475
|
click.echo("\nShutdown complete")
|
476
476
|
|
477
477
|
@cli.command()
|
478
|
-
@click.option('--port', default=
|
479
|
-
@click.option('--host', default='0.0.0.0', help='
|
478
|
+
@click.option('--port', default=8080, help='前端服务器端口')
|
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
|
-
@click.option('--build', is_flag=True, help='
|
483
|
-
def frontend(port, host, auto_install, force_install,
|
483
|
+
@click.option('--build-only', is_flag=True, help='仅构建生产版本,不启动服务器')
|
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
|
+
# 启动生产版本服务器(默认)
|
493
494
|
jettask frontend
|
494
495
|
|
495
496
|
# 指定端口
|
496
497
|
jettask frontend --port 3000
|
497
498
|
|
498
|
-
#
|
499
|
-
jettask frontend --
|
499
|
+
# 指定后端API地址
|
500
|
+
jettask frontend --api-url http://192.168.1.100:8001
|
501
|
+
|
502
|
+
# 仅构建生产版本
|
503
|
+
jettask frontend --build-only
|
500
504
|
|
501
505
|
# 强制重新安装依赖
|
502
506
|
jettask frontend --force-install
|
@@ -541,7 +545,7 @@ def frontend(port, host, auto_install, force_install, build):
|
|
541
545
|
|
542
546
|
# 切换到前端目录
|
543
547
|
os.chdir(frontend_dir)
|
544
|
-
click.echo(f"
|
548
|
+
click.echo(f"前端目录: {frontend_dir}")
|
545
549
|
|
546
550
|
# 检查 package.json 是否存在
|
547
551
|
package_json = frontend_dir / "package.json"
|
@@ -583,48 +587,185 @@ def frontend(port, host, auto_install, force_install, build):
|
|
583
587
|
|
584
588
|
# 构建或启动
|
585
589
|
try:
|
586
|
-
|
587
|
-
|
590
|
+
# 构建生产版本
|
591
|
+
# 注意:根据 vite.config.js,构建输出到 ../static/dist
|
592
|
+
dist_dir = frontend_dir.parent / "static" / "dist"
|
593
|
+
|
594
|
+
# 检查是否需要构建
|
595
|
+
need_build = not dist_dir.exists() or build_only
|
596
|
+
|
597
|
+
# 如果 dist 不存在或明确要求构建,则进行构建
|
598
|
+
if need_build:
|
588
599
|
click.echo("\n正在构建生产版本...")
|
589
600
|
subprocess.run([npm_cmd, 'run', 'build'], check=True)
|
590
601
|
|
591
|
-
# 显示构建结果
|
592
|
-
dist_dir = frontend_dir.parent / "static" / "dist"
|
593
602
|
if dist_dir.exists():
|
594
|
-
click.echo(f"\n✓ 构建完成!输出目录: {dist_dir}")
|
595
603
|
# 统计文件
|
596
604
|
files = list(dist_dir.rglob('*'))
|
597
605
|
file_count = len([f for f in files if f.is_file()])
|
598
606
|
total_size = sum(f.stat().st_size for f in files if f.is_file())
|
607
|
+
click.echo(f"\n✓ 构建完成!")
|
608
|
+
click.echo(f" - 输出目录: {dist_dir}")
|
599
609
|
click.echo(f" - 文件数量: {file_count}")
|
600
610
|
click.echo(f" - 总大小: {total_size / 1024 / 1024:.2f} MB")
|
601
611
|
else:
|
602
|
-
click.echo("
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
612
|
+
click.echo("错误:构建失败,未生成输出目录", err=True)
|
613
|
+
sys.exit(1)
|
614
|
+
|
615
|
+
# 如果不是仅构建模式,启动生产服务器
|
616
|
+
if not build_only:
|
617
|
+
if not dist_dir.exists():
|
618
|
+
click.echo("错误:未找到构建输出,请先运行构建", err=True)
|
619
|
+
sys.exit(1)
|
609
620
|
|
610
|
-
|
611
|
-
|
621
|
+
click.echo(f"\n正在启动生产版本服务器...")
|
622
|
+
click.echo(f" - 访问地址: http://localhost:{port}")
|
612
623
|
if host != 'localhost':
|
613
|
-
|
614
|
-
|
615
|
-
|
624
|
+
click.echo(f" - 网络地址: http://{host}:{port}")
|
625
|
+
click.echo(f" - 静态文件: {dist_dir}")
|
626
|
+
click.echo(f" - API 地址: {api_url}")
|
627
|
+
click.echo("\n按 Ctrl+C 停止服务器\n")
|
628
|
+
|
629
|
+
# 使用 Python 内置的 HTTP 服务器提供静态文件
|
630
|
+
import http.server
|
631
|
+
import socketserver
|
632
|
+
import urllib.request
|
633
|
+
import urllib.parse
|
634
|
+
import json
|
635
|
+
|
636
|
+
class ProxyHandler(http.server.SimpleHTTPRequestHandler):
|
637
|
+
def __init__(self, *args, **kwargs):
|
638
|
+
super().__init__(*args, directory=str(dist_dir), **kwargs)
|
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
|
+
|
680
|
+
def do_GET(self):
|
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
|
+
# 其他文件正常处理
|
715
|
+
return super().do_GET()
|
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
|
+
|
741
|
+
def log_message(self, format, *args):
|
742
|
+
# 自定义日志格式
|
743
|
+
click.echo(f"[{self.log_date_time_string()}] {format % args}")
|
744
|
+
|
745
|
+
# 创建服务器
|
746
|
+
class ReuseAddrTCPServer(socketserver.TCPServer):
|
747
|
+
allow_reuse_address = True
|
748
|
+
allow_reuse_port = True
|
616
749
|
|
617
|
-
|
618
|
-
|
619
|
-
|
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
|
620
764
|
except subprocess.CalledProcessError as e:
|
621
765
|
click.echo(f"错误:命令执行失败 - {e}", err=True)
|
622
766
|
sys.exit(1)
|
623
767
|
except KeyboardInterrupt:
|
624
768
|
click.echo("\n停止前端服务器")
|
625
|
-
if 'process' in locals():
|
626
|
-
process.terminate()
|
627
|
-
process.wait()
|
628
769
|
|
629
770
|
def main():
|
630
771
|
"""主入口函数"""
|
@@ -181,35 +181,62 @@ class NamespaceDataAccessManager:
|
|
181
181
|
|
182
182
|
async def get_namespace_config(self, namespace_name: str) -> dict:
|
183
183
|
"""从任务中心API获取命名空间配置"""
|
184
|
-
|
185
|
-
|
184
|
+
# 直接从数据库获取,而不是通过HTTP调用自己
|
185
|
+
# 这避免了循环依赖和网络问题
|
186
186
|
try:
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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':
|
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"
|
209
|
-
except Exception as e:
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
"""
|
@@ -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
|
-
|
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
|
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('
|
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('
|
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(
|
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
|
-
|
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
|
-
|
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
|
|
@@ -7,7 +7,7 @@ jettask/config/performance.py,sha256=bOdLEskfB_6cRfS10IRgmtKEsJw_CaIZsPHbXxaHwbU
|
|
7
7
|
jettask/core/__init__.py,sha256=CvBoBCERXCo-jgnkPqAuIgT4uC7oQMnSi7okRxMi6Vc,181
|
8
8
|
jettask/core/app.py,sha256=D6wqdGIVR8E1a7RNevNDaICfKGDsea4RqS9aRlMU810,74906
|
9
9
|
jettask/core/app_importer.py,sha256=B8WiSUz5_O5jlFIBr1CxI_F2gqFYK6ItpONiY_4AiXI,10266
|
10
|
-
jettask/core/cli.py,sha256=
|
10
|
+
jettask/core/cli.py,sha256=HagyhlJcp_KspuzJ6NhAmweD8N5qzh8ks-s0F0t91uA,31580
|
11
11
|
jettask/core/consumer_manager.py,sha256=7z3IBvH85YD61ZkVNfOVsuP5NaAwV2Ki8aVz9TCplAM,79870
|
12
12
|
jettask/core/context.py,sha256=XI4Q1s75NIWImcrHIkCLgGo_kpuJIk8MlBoNIJuEfF0,993
|
13
13
|
jettask/core/delay_scanner.py,sha256=rwbIA7SFyOxAV14FW7NB40_djFrrqJJpNkpOri7XcZI,8394
|
@@ -92,7 +92,7 @@ jettask/webui/backend/main_unified.py,sha256=4owlt7n1Axg2zwzmaACHkuIZTj-KJonXxjK
|
|
92
92
|
jettask/webui/backend/main_v2.py,sha256=XU7LZriOuTcMHt8rZTeNUAtY7HBIu50vzJT-f8_AbCs,11603
|
93
93
|
jettask/webui/backend/namespace_api.py,sha256=Q-VmwszGjJXrGI_GlNM356l1TSprRENE-p1vtR-WUt0,11310
|
94
94
|
jettask/webui/backend/namespace_api_old.py,sha256=lGmkWurtt5LTsTDzjkG8q3rhyMrzzm73VUFkCmafI5Y,11240
|
95
|
-
jettask/webui/backend/namespace_data_access.py,sha256=
|
95
|
+
jettask/webui/backend/namespace_data_access.py,sha256=GB4mzL1SGObnk7iW2ns6OC94NRifiFKZZLmvrBcKw2I,27134
|
96
96
|
jettask/webui/backend/queue_backlog_api.py,sha256=HQphpAj4gFRxw7S4h1LC3HxM6eWWMleNr7cDSaxKnyI,26681
|
97
97
|
jettask/webui/backend/queue_stats_v2.py,sha256=VeI46WQREFAQgqW330eKs0jO7ZBehLIEKsxAAu907T8,24402
|
98
98
|
jettask/webui/backend/redis_monitor_api.py,sha256=fmDCzHAm2jtx1IhKZ299W5IS2CSI1RS7EC9V3rDtELs,18287
|
@@ -118,7 +118,7 @@ jettask/webui/frontend/vite.config.js,sha256=WqgVesX8iqlLFtWq7u4L33hdJXaDqy9EOJJ
|
|
118
118
|
jettask/webui/frontend/src/App.css,sha256=TLwSOdKNsUtfAtnTyCxeLZBkRhhw34zWeA8zhEP7dDs,1753
|
119
119
|
jettask/webui/frontend/src/App.jsx,sha256=wyTfHb3hp0dThO9fhn4r8JLc9RIkaxnwOieY0o8e7tg,2497
|
120
120
|
jettask/webui/frontend/src/index.css,sha256=s2mxkzEDoY10XqRcV_5_32r4GZk22cvZoBMh6_NKVq0,2074
|
121
|
-
jettask/webui/frontend/src/main.jsx,sha256=
|
121
|
+
jettask/webui/frontend/src/main.jsx,sha256=GCb7lGMl--XLnsJz2PQX2MoXHR5hMS-mcbojnkyvGYY,582
|
122
122
|
jettask/webui/frontend/src/components/NamespaceSelector.jsx,sha256=xUIvLPhY_KdUQEHJD7sxim5ZCprk6lK2RRyI3qIs9UQ,5341
|
123
123
|
jettask/webui/frontend/src/components/QueueBacklogChart.jsx,sha256=fzwI4QBUN8lBuE89Sf-7fM3kFgH5J8mbDCPyHjkL8ao,8114
|
124
124
|
jettask/webui/frontend/src/components/QueueBacklogTrend.jsx,sha256=SPwEeGWDoxemeIrcRqgEA0Yx59VUO4cNvOx_EHCIp_I,21255
|
@@ -151,22 +151,22 @@ jettask/webui/frontend/src/pages/Dashboard.jsx,sha256=uMD5hIpgFgapACkz_exQI-tz5u
|
|
151
151
|
jettask/webui/frontend/src/pages/QueueDetail.jsx,sha256=04LlmiIf5qJiPh6ilTIBb1x9mAQHkza7vA2QeM75Nqo,36054
|
152
152
|
jettask/webui/frontend/src/pages/QueueMonitor.jsx,sha256=zp5eR0jBiqZktzsP6NsbTbIo7J7Vr2pwdyozMakB1gE,17749
|
153
153
|
jettask/webui/frontend/src/pages/Queues.jsx,sha256=FRE5EMaW3ln_yU9K1_LyFDLgfwmR7jUqIcC_n6mVPVk,201
|
154
|
-
jettask/webui/frontend/src/pages/ScheduledTasks.jsx,sha256=
|
155
|
-
jettask/webui/frontend/src/pages/Settings.jsx,sha256=
|
154
|
+
jettask/webui/frontend/src/pages/ScheduledTasks.jsx,sha256=QvFVE2gTMxhtkso8pjXlNT1apwW3KR6aTz-FHs8lZRs,24034
|
155
|
+
jettask/webui/frontend/src/pages/Settings.jsx,sha256=Zn-YiOlEWKHrJTqR4PIYoyJ_HSycVFeiMUfpp9vpFmc,26570
|
156
156
|
jettask/webui/frontend/src/pages/Workers.jsx,sha256=S3JI-gDfkBuAIeHQdBCyFg8OuwOa970Kz4cgBGqaAmo,227
|
157
157
|
jettask/webui/frontend/src/pages/Dashboard/index.css,sha256=DNzBEusK0LtDgcVie_Tg-bcKHs_0EtLUPtqnBfYdERs,511
|
158
158
|
jettask/webui/frontend/src/pages/Dashboard/index.jsx,sha256=h-852VjLYcuoDGlkmJ0odrnLU554NsFh332lK99AzWI,7873
|
159
|
-
jettask/webui/frontend/src/services/api.js,sha256=
|
160
|
-
jettask/webui/frontend/src/services/queueTrend.js,sha256=
|
159
|
+
jettask/webui/frontend/src/services/api.js,sha256=Oui7OeiRCCjHXR6ULeVDovpDPEySdUmK2j6qjxJjtMk,4641
|
160
|
+
jettask/webui/frontend/src/services/queueTrend.js,sha256=ljFAxuO2-JTqM_C0zZuF6Wa8edUCu4QdtsYT6bv0dLQ,5207
|
161
161
|
jettask/webui/frontend/src/utils/suppressWarnings.js,sha256=L2_mnKbkh7_5l0JkLDOxXe6smgMsv7I3sB0ANGrS2JA,678
|
162
162
|
jettask/webui/frontend/src/utils/userPreferences.js,sha256=u9VC47XrG0fl0tfx4dY5BBs4ztmGQR0oS-V8LYVw8a4,4685
|
163
163
|
jettask/webui/models/__init__.py,sha256=5cv0oZksj1B3_rzCqsPmF3Gn9NRZLwzMnaJ8bOKGB4I,25
|
164
164
|
jettask/webui/models/namespace.py,sha256=jDj-hZF_-wXzrWAWVDyZVU0JUWDax9apb4Gyykwg-rE,2006
|
165
165
|
jettask/webui/sql/batch_upsert_functions.sql,sha256=5eWOhOD8gWHhtcop_BrCpZTxPFeyBHtt_leNQZO89Cs,6615
|
166
166
|
jettask/webui/sql/init_database.sql,sha256=CSjhBZldtfV0SGBLTFf576ALaRfCFe3wywpezzkX1TQ,22369
|
167
|
-
jettask-0.2.
|
168
|
-
jettask-0.2.
|
169
|
-
jettask-0.2.
|
170
|
-
jettask-0.2.
|
171
|
-
jettask-0.2.
|
172
|
-
jettask-0.2.
|
167
|
+
jettask-0.2.13.dist-info/licenses/LICENSE,sha256=sKR8OPWvnqxzcHAmnaVSANpRpeM0Z52PNLCB0ZlFN6c,1062
|
168
|
+
jettask-0.2.13.dist-info/METADATA,sha256=KbsHMYpOfvTjraDeGQdFNDNqMoiDbZKFK3EdO_n_rNY,2956
|
169
|
+
jettask-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
170
|
+
jettask-0.2.13.dist-info/entry_points.txt,sha256=VC3byRkkSfRHu_QzczGtpGjcFERkJUlCrD__TFLVqxI,153
|
171
|
+
jettask-0.2.13.dist-info/top_level.txt,sha256=uymyRUF87-OsSurk5NhpeTW0jy3Wltnn91Zoa6jmAaw,8
|
172
|
+
jettask-0.2.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|