kibana-reader-mcp 0.1.0__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.
- kibana_reader_mcp-0.1.0/.gitignore +71 -0
- kibana_reader_mcp-0.1.0/LICENSE +21 -0
- kibana_reader_mcp-0.1.0/PKG-INFO +155 -0
- kibana_reader_mcp-0.1.0/README.md +124 -0
- kibana_reader_mcp-0.1.0/pyproject.toml +62 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/__init__.py +3 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/__main__.py +6 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/auth.py +196 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/config.py +84 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/kibana.py +395 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/server.py +179 -0
- kibana_reader_mcp-0.1.0/src/kibana_reader_mcp/url_parser.py +201 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class*
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
env.bak/
|
|
60
|
+
venv.bak/
|
|
61
|
+
|
|
62
|
+
# IDE
|
|
63
|
+
.idea/
|
|
64
|
+
.vscode/
|
|
65
|
+
*.swp
|
|
66
|
+
*.swo
|
|
67
|
+
|
|
68
|
+
# Session files
|
|
69
|
+
session.json
|
|
70
|
+
config.json
|
|
71
|
+
.claude/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yuyangfan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kibana-reader-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for reading Kibana pages with DingTalk scan login
|
|
5
|
+
Project-URL: Homepage, https://gitee.com/yuyangfan/kibana-reader-mcp
|
|
6
|
+
Project-URL: Repository, https://gitee.com/yuyangfan/kibana-reader-mcp
|
|
7
|
+
Project-URL: Documentation, https://gitee.com/yuyangfan/kibana-reader-mcp#readme
|
|
8
|
+
Author-email: yuyangfan <yuyangfan@example.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: elasticsearch,kibana,logs,mcp,model-context-protocol
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: mcp>=1.0.0
|
|
23
|
+
Requires-Dist: playwright>=1.40.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Kibana Reader MCP
|
|
33
|
+
|
|
34
|
+
一个用于读取 Kibana 页面内容的 MCP Server,支持钉钉扫码登录。
|
|
35
|
+
|
|
36
|
+
## 功能
|
|
37
|
+
|
|
38
|
+
- 读取 Kibana Discover 页面数据
|
|
39
|
+
- 读取 Kibana Dashboard 页面数据
|
|
40
|
+
- 支持短 URL 自动重定向
|
|
41
|
+
- 自动管理 Cookie/Session
|
|
42
|
+
- 支持钉钉扫码认证
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install kibana-reader-mcp
|
|
48
|
+
playwright install chromium
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 配置
|
|
52
|
+
|
|
53
|
+
### 方式一:配置文件
|
|
54
|
+
|
|
55
|
+
创建配置文件 `~/.kibana-reader-mcp/config.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"kibana_base_url": "https://your-kibana.example.com",
|
|
60
|
+
"session_file": "~/.kibana-reader-mcp/session.json",
|
|
61
|
+
"playwright_headless": false,
|
|
62
|
+
"scan_timeout_seconds": 120,
|
|
63
|
+
"default_max_rows": 100
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 方式二:环境变量
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
export KIBANA_BASE_URL="https://your-kibana.example.com"
|
|
71
|
+
export PLAYWRIGHT_HEADLESS=false
|
|
72
|
+
export SCAN_TIMEOUT_SECONDS=120
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 客户端配置
|
|
76
|
+
|
|
77
|
+
### Claude Desktop
|
|
78
|
+
|
|
79
|
+
编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"mcpServers": {
|
|
84
|
+
"kibana-reader": {
|
|
85
|
+
"command": "uvx",
|
|
86
|
+
"args": ["kibana-reader-mcp"],
|
|
87
|
+
"env": {
|
|
88
|
+
"KIBANA_BASE_URL": "https://your-kibana.example.com"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Claude Code
|
|
96
|
+
|
|
97
|
+
编辑 `~/.claude/settings.json`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"kibana-reader": {
|
|
103
|
+
"command": "uvx",
|
|
104
|
+
"args": ["kibana-reader-mcp"],
|
|
105
|
+
"env": {
|
|
106
|
+
"KIBANA_BASE_URL": "https://your-kibana.example.com"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Cursor / Windsurf / Codex
|
|
114
|
+
|
|
115
|
+
在 MCP 设置中添加服务器配置,格式同上。
|
|
116
|
+
|
|
117
|
+
## 使用示例
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
AI: 调用 read_kibana_url
|
|
121
|
+
参数:
|
|
122
|
+
- url: "https://your-kibana/goto/abc123"
|
|
123
|
+
- max_rows: 100
|
|
124
|
+
|
|
125
|
+
返回: Kibana 页面的结构化数据
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 认证流程
|
|
129
|
+
|
|
130
|
+
1. 首次使用时,MCP 会打开浏览器显示钉钉扫码页面
|
|
131
|
+
2. 用户扫码登录后,MCP 自动保存 Cookie
|
|
132
|
+
3. 后续调用直接使用保存的 Cookie
|
|
133
|
+
4. Cookie 过期时自动提示重新扫码
|
|
134
|
+
|
|
135
|
+
## URL 类型支持
|
|
136
|
+
|
|
137
|
+
| 类型 | 示例 | 处理方式 |
|
|
138
|
+
|------|------|---------|
|
|
139
|
+
| 短 URL | `/goto/abc123` | Playwright 重定向 |
|
|
140
|
+
| Discover | `/app/kibana#/discover` | API 调用 |
|
|
141
|
+
| Dashboard | `/app/kibana#/dashboard` | API 调用 |
|
|
142
|
+
|
|
143
|
+
## 开发
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 安装开发依赖
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
|
|
149
|
+
# 运行测试
|
|
150
|
+
pytest
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Kibana Reader MCP
|
|
2
|
+
|
|
3
|
+
一个用于读取 Kibana 页面内容的 MCP Server,支持钉钉扫码登录。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 读取 Kibana Discover 页面数据
|
|
8
|
+
- 读取 Kibana Dashboard 页面数据
|
|
9
|
+
- 支持短 URL 自动重定向
|
|
10
|
+
- 自动管理 Cookie/Session
|
|
11
|
+
- 支持钉钉扫码认证
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install kibana-reader-mcp
|
|
17
|
+
playwright install chromium
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 配置
|
|
21
|
+
|
|
22
|
+
### 方式一:配置文件
|
|
23
|
+
|
|
24
|
+
创建配置文件 `~/.kibana-reader-mcp/config.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"kibana_base_url": "https://your-kibana.example.com",
|
|
29
|
+
"session_file": "~/.kibana-reader-mcp/session.json",
|
|
30
|
+
"playwright_headless": false,
|
|
31
|
+
"scan_timeout_seconds": 120,
|
|
32
|
+
"default_max_rows": 100
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 方式二:环境变量
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export KIBANA_BASE_URL="https://your-kibana.example.com"
|
|
40
|
+
export PLAYWRIGHT_HEADLESS=false
|
|
41
|
+
export SCAN_TIMEOUT_SECONDS=120
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 客户端配置
|
|
45
|
+
|
|
46
|
+
### Claude Desktop
|
|
47
|
+
|
|
48
|
+
编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"kibana-reader": {
|
|
54
|
+
"command": "uvx",
|
|
55
|
+
"args": ["kibana-reader-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"KIBANA_BASE_URL": "https://your-kibana.example.com"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Claude Code
|
|
65
|
+
|
|
66
|
+
编辑 `~/.claude/settings.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"kibana-reader": {
|
|
72
|
+
"command": "uvx",
|
|
73
|
+
"args": ["kibana-reader-mcp"],
|
|
74
|
+
"env": {
|
|
75
|
+
"KIBANA_BASE_URL": "https://your-kibana.example.com"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Cursor / Windsurf / Codex
|
|
83
|
+
|
|
84
|
+
在 MCP 设置中添加服务器配置,格式同上。
|
|
85
|
+
|
|
86
|
+
## 使用示例
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
AI: 调用 read_kibana_url
|
|
90
|
+
参数:
|
|
91
|
+
- url: "https://your-kibana/goto/abc123"
|
|
92
|
+
- max_rows: 100
|
|
93
|
+
|
|
94
|
+
返回: Kibana 页面的结构化数据
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 认证流程
|
|
98
|
+
|
|
99
|
+
1. 首次使用时,MCP 会打开浏览器显示钉钉扫码页面
|
|
100
|
+
2. 用户扫码登录后,MCP 自动保存 Cookie
|
|
101
|
+
3. 后续调用直接使用保存的 Cookie
|
|
102
|
+
4. Cookie 过期时自动提示重新扫码
|
|
103
|
+
|
|
104
|
+
## URL 类型支持
|
|
105
|
+
|
|
106
|
+
| 类型 | 示例 | 处理方式 |
|
|
107
|
+
|------|------|---------|
|
|
108
|
+
| 短 URL | `/goto/abc123` | Playwright 重定向 |
|
|
109
|
+
| Discover | `/app/kibana#/discover` | API 调用 |
|
|
110
|
+
| Dashboard | `/app/kibana#/dashboard` | API 调用 |
|
|
111
|
+
|
|
112
|
+
## 开发
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 安装开发依赖
|
|
116
|
+
pip install -e ".[dev]"
|
|
117
|
+
|
|
118
|
+
# 运行测试
|
|
119
|
+
pytest
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "kibana-reader-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for reading Kibana pages with DingTalk scan login"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "yuyangfan", email = "yuyangfan@example.com" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["mcp", "kibana", "elasticsearch", "logs", "model-context-protocol"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"mcp>=1.0.0",
|
|
24
|
+
"playwright>=1.40.0",
|
|
25
|
+
"httpx>=0.27.0",
|
|
26
|
+
"pydantic>=2.0.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0.0",
|
|
32
|
+
"pytest-asyncio>=0.23.0",
|
|
33
|
+
"build>=1.0.0",
|
|
34
|
+
"twine>=5.0.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
kibana-reader-mcp = "kibana_reader_mcp.server:run"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://gitee.com/yuyangfan/kibana-reader-mcp"
|
|
42
|
+
Repository = "https://gitee.com/yuyangfan/kibana-reader-mcp"
|
|
43
|
+
Documentation = "https://gitee.com/yuyangfan/kibana-reader-mcp#readme"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["hatchling"]
|
|
47
|
+
build-backend = "hatchling.build"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/kibana_reader_mcp"]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
include = [
|
|
54
|
+
"src/kibana_reader_mcp/",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE",
|
|
57
|
+
"pyproject.toml",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_mode = "auto"
|
|
62
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Authentication management for Kibana with DingTalk scan login."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from playwright.async_api import async_playwright, Browser, Page
|
|
10
|
+
|
|
11
|
+
from .config import Config, get_session_path, ensure_config_dir
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SessionData:
|
|
15
|
+
"""Session 数据模型"""
|
|
16
|
+
|
|
17
|
+
cookies: dict
|
|
18
|
+
created_at: float
|
|
19
|
+
expires_at: Optional[float] = None
|
|
20
|
+
|
|
21
|
+
def __init__(self, cookies: dict, created_at: float = None):
|
|
22
|
+
self.cookies = cookies
|
|
23
|
+
self.created_at = created_at or time.time()
|
|
24
|
+
self.expires_at = None
|
|
25
|
+
|
|
26
|
+
def is_expired(self, max_age_seconds: int = 3600 *24) -> bool:
|
|
27
|
+
"""检查 Session 是否过期"""
|
|
28
|
+
if self.expires_at:
|
|
29
|
+
return time.time() > self.expires_at
|
|
30
|
+
# 默认24小时过期
|
|
31
|
+
return time.time() > self.created_at + max_age_seconds
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> dict:
|
|
34
|
+
return {
|
|
35
|
+
"cookies": self.cookies,
|
|
36
|
+
"created_at": self.created_at,
|
|
37
|
+
"expires_at": self.expires_at,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls, data: dict) -> "SessionData":
|
|
42
|
+
return cls(
|
|
43
|
+
cookies=data.get("cookies", {}),
|
|
44
|
+
created_at=data.get("created_at"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AuthManager:
|
|
49
|
+
"""认证管理器"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, config: Config):
|
|
52
|
+
self.config = config
|
|
53
|
+
self.session: Optional[SessionData] = None
|
|
54
|
+
self._browser: Optional[Browser] = None
|
|
55
|
+
|
|
56
|
+
def load_session(self) -> Optional[SessionData]:
|
|
57
|
+
"""从文件加载 Session"""
|
|
58
|
+
session_path = get_session_path(self.config)
|
|
59
|
+
|
|
60
|
+
if session_path.exists():
|
|
61
|
+
try:
|
|
62
|
+
with open(session_path, "r") as f:
|
|
63
|
+
data = json.load(f)
|
|
64
|
+
self.session = SessionData.from_dict(data)
|
|
65
|
+
return self.session
|
|
66
|
+
except Exception:
|
|
67
|
+
return None
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def save_session(self):
|
|
71
|
+
"""保存 Session 到文件"""
|
|
72
|
+
ensure_config_dir()
|
|
73
|
+
session_path = get_session_path(self.config)
|
|
74
|
+
|
|
75
|
+
if self.session:
|
|
76
|
+
with open(session_path, "w") as f:
|
|
77
|
+
json.dump(self.session.to_dict(), f)
|
|
78
|
+
|
|
79
|
+
async def ensure_authenticated(self) -> dict:
|
|
80
|
+
"""确保已认证,返回有效的 cookies"""
|
|
81
|
+
# 检查是否有有效 Session
|
|
82
|
+
if self.session and not self.session.is_expired():
|
|
83
|
+
return self.session.cookies
|
|
84
|
+
|
|
85
|
+
# 尝试加载已保存的 Session
|
|
86
|
+
loaded = self.load_session()
|
|
87
|
+
if loaded and not loaded.is_expired():
|
|
88
|
+
self.session = loaded
|
|
89
|
+
return self.session.cookies
|
|
90
|
+
|
|
91
|
+
# 需要重新登录(扫码)
|
|
92
|
+
cookies = await self._login_with_scan()
|
|
93
|
+
self.session = SessionData(cookies)
|
|
94
|
+
self.save_session()
|
|
95
|
+
return cookies
|
|
96
|
+
|
|
97
|
+
async def _login_with_scan(self) -> dict:
|
|
98
|
+
"""使用钉钉扫码登录"""
|
|
99
|
+
login_url = f"{self.config.kibana_base_url}/login"
|
|
100
|
+
|
|
101
|
+
async with async_playwright() as p:
|
|
102
|
+
browser = await p.chromium.launch(
|
|
103
|
+
headless=self.config.playwright_headless
|
|
104
|
+
)
|
|
105
|
+
page = await browser.new_page()
|
|
106
|
+
|
|
107
|
+
# 打开登录页
|
|
108
|
+
await page.goto(login_url)
|
|
109
|
+
print(f"请在浏览器中扫码登录: {login_url}")
|
|
110
|
+
print("等待扫码...")
|
|
111
|
+
|
|
112
|
+
# 等待登录成功(检测页面跳转或特定元素)
|
|
113
|
+
timeout = self.config.scan_timeout_seconds * 1000
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
# 等待登录成功后的页面变化
|
|
117
|
+
await page.wait_for_url("**/app/kibana**", timeout=timeout)
|
|
118
|
+
print("登录成功!")
|
|
119
|
+
|
|
120
|
+
# 提取 cookies
|
|
121
|
+
cookies = {}
|
|
122
|
+
for cookie in await page.context.cookies():
|
|
123
|
+
cookies[cookie["name"]] = cookie["value"]
|
|
124
|
+
|
|
125
|
+
await browser.close()
|
|
126
|
+
return cookies
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
await browser.close()
|
|
130
|
+
raise Exception(f"扫码登录超时或失败: {e}")
|
|
131
|
+
|
|
132
|
+
async def resolve_short_url(self, short_url: str) -> str:
|
|
133
|
+
"""使用浏览器解析短 URL 重定向"""
|
|
134
|
+
async with async_playwright() as p:
|
|
135
|
+
browser = await p.chromium.launch(
|
|
136
|
+
headless=self.config.playwright_headless
|
|
137
|
+
)
|
|
138
|
+
context = await browser.new_context()
|
|
139
|
+
|
|
140
|
+
# 设置 cookies
|
|
141
|
+
if self.session:
|
|
142
|
+
cookies_list = []
|
|
143
|
+
for name, value in self.session.cookies.items():
|
|
144
|
+
cookies_list.append({
|
|
145
|
+
"name": name,
|
|
146
|
+
"value": value,
|
|
147
|
+
"domain": self.config.kibana_base_url.split("//")[1].split("/")[0],
|
|
148
|
+
"path": "/"
|
|
149
|
+
})
|
|
150
|
+
await context.add_cookies(cookies_list)
|
|
151
|
+
|
|
152
|
+
page = await context.new_page()
|
|
153
|
+
|
|
154
|
+
# 用于捕获 _msearch 请求
|
|
155
|
+
captured_request = None
|
|
156
|
+
|
|
157
|
+
async def capture_request(request):
|
|
158
|
+
if "_msearch" in request.url:
|
|
159
|
+
try:
|
|
160
|
+
body = request.post_data
|
|
161
|
+
if body:
|
|
162
|
+
captured_request = body
|
|
163
|
+
except:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
page.on("request", capture_request)
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
# 访问短 URL,设置超时
|
|
170
|
+
await page.goto(short_url, timeout=30000, wait_until="domcontentloaded")
|
|
171
|
+
|
|
172
|
+
# 等待 URL 变化(短 URL 重定向)
|
|
173
|
+
await page.wait_for_url("**/app/kibana**", timeout=15000)
|
|
174
|
+
|
|
175
|
+
# 等待 _msearch 请求完成
|
|
176
|
+
await page.wait_for_timeout(2000)
|
|
177
|
+
|
|
178
|
+
# 获取最终 URL
|
|
179
|
+
final_url = page.url
|
|
180
|
+
|
|
181
|
+
await browser.close()
|
|
182
|
+
return final_url, captured_request
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
await browser.close()
|
|
186
|
+
# 如果解析失败,返回原始 URL
|
|
187
|
+
return short_url, None
|
|
188
|
+
|
|
189
|
+
def get_http_client(self) -> httpx.AsyncClient:
|
|
190
|
+
"""获取带认证的 HTTP 客户端"""
|
|
191
|
+
cookies = self.session.cookies if self.session else {}
|
|
192
|
+
return httpx.AsyncClient(
|
|
193
|
+
base_url=self.config.kibana_base_url,
|
|
194
|
+
cookies=cookies,
|
|
195
|
+
timeout=30.0,
|
|
196
|
+
)
|