pytbox 0.1.1__tar.gz → 0.1.3__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.
- {pytbox-0.1.1/src/pytbox.egg-info → pytbox-0.1.3}/PKG-INFO +3 -1
- {pytbox-0.1.1 → pytbox-0.1.3}/pyproject.toml +3 -1
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/base.py +23 -1
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/build_config.py +1 -6
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/instances.toml +3 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +2 -2
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +2 -2
- pytbox-0.1.3/src/pytbox/cli/commands/vm.py +22 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/main.py +2 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/database/victoriametrics.py +46 -10
- pytbox-0.1.3/src/pytbox/excel.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/feishu/endpoints.py +6 -6
- pytbox-0.1.3/src/pytbox/mail/client.py +167 -0
- pytbox-0.1.3/src/pytbox/mail/mail_detail.py +30 -0
- pytbox-0.1.3/src/pytbox/pyjira.py +558 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/utils/env.py +2 -2
- pytbox-0.1.3/src/pytbox/utils/load_config.py +132 -0
- pytbox-0.1.3/src/pytbox/utils/load_vm_devfile.py +45 -0
- pytbox-0.1.3/src/pytbox/vmware.py +120 -0
- {pytbox-0.1.1 → pytbox-0.1.3/src/pytbox.egg-info}/PKG-INFO +3 -1
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox.egg-info/SOURCES.txt +8 -1
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox.egg-info/requires.txt +2 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_victoriametrics.py +7 -1
- pytbox-0.1.3/tests/test_vmware.py +6 -0
- pytbox-0.1.1/src/pytbox/mail/client.py +0 -221
- pytbox-0.1.1/src/pytbox/utils/load_config.py +0 -86
- {pytbox-0.1.1 → pytbox-0.1.3}/MANIFEST.in +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/README.md +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/setup.cfg +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/alert/alert_handler.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/alert/ping.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/alicloud/sls.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.disk/disk.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.mem/mem.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.net/net.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.ping/ping.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/categraf/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/categraf/commands.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/common/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/common/options.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/common/utils.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/formatters/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli/formatters/output.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/cli.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/common/__init__.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/database/mongo.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/dida365.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/feishu/client.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/feishu/errors.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/feishu/helpers.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/feishu/typing.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/log/logger.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/log/victorialog.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/mail/alimail.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/network/meraki.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/onepassword_connect.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/onepassword_sa.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/utils/ping_checker.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/utils/response.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/utils/richutils.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/utils/timeutils.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/win/ad.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox.egg-info/dependency_links.txt +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox.egg-info/entry_points.txt +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox.egg-info/top_level.txt +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_base.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_feishu.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_logger.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_onepassword_connect.py +0 -0
- {pytbox-0.1.1 → pytbox-0.1.3}/tests/test_onepassword_sa.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytbox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)
|
|
5
5
|
Author-email: mingming hou <houm01@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,6 +16,8 @@ Requires-Dist: rich>=12.0.0
|
|
|
16
16
|
Requires-Dist: jinja2>=3.0.0
|
|
17
17
|
Requires-Dist: toml>=0.10.0
|
|
18
18
|
Requires-Dist: ldap3>=2.9.1
|
|
19
|
+
Requires-Dist: imap-tools>=1.11.0
|
|
20
|
+
Requires-Dist: yagmail>=0.15.293
|
|
19
21
|
Provides-Extra: dev
|
|
20
22
|
Requires-Dist: pytest; extra == "dev"
|
|
21
23
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytbox"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)"
|
|
9
9
|
authors = [{ name = "mingming hou", email = "houm01@foxmail.com" }]
|
|
10
10
|
license = "MIT"
|
|
@@ -24,6 +24,8 @@ dependencies = [
|
|
|
24
24
|
"jinja2>=3.0.0", # 模板渲染
|
|
25
25
|
"toml>=0.10.0", # TOML 输出支持
|
|
26
26
|
"ldap3>=2.9.1", # LDAP 支持
|
|
27
|
+
"imap-tools>=1.11.0",
|
|
28
|
+
"yagmail>=0.15.293"
|
|
27
29
|
]
|
|
28
30
|
|
|
29
31
|
[project.scripts]
|
|
@@ -10,9 +10,14 @@ from pytbox.alert.alert_handler import AlertHandler
|
|
|
10
10
|
from pytbox.log.logger import AppLogger
|
|
11
11
|
from pytbox.win.ad import ADClient
|
|
12
12
|
from pytbox.network.meraki import Meraki
|
|
13
|
+
from pytbox.utils.env import get_env_by_os_environment
|
|
14
|
+
from pytbox.vmware import VMwareClient
|
|
15
|
+
from pytbox.pyjira import PyJira
|
|
16
|
+
from pytbox.mail.client import MailClient
|
|
13
17
|
|
|
14
18
|
config = load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=os.environ.get('oc_vault_id'))
|
|
15
19
|
|
|
20
|
+
|
|
16
21
|
def get_mongo(collection):
|
|
17
22
|
return Mongo(
|
|
18
23
|
host=config['mongo']['host'],
|
|
@@ -61,4 +66,21 @@ def get_logger(app):
|
|
|
61
66
|
# password=config['ad']['prod']['AD_PASSWORD']
|
|
62
67
|
# )
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
env = get_env_by_os_environment(check_key='ENV')
|
|
70
|
+
meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
|
|
71
|
+
|
|
72
|
+
vmware_test = VMwareClient(
|
|
73
|
+
host=config['vmware']['test']['host'],
|
|
74
|
+
username=config['vmware']['test']['username'],
|
|
75
|
+
password=config['vmware']['test']['password'],
|
|
76
|
+
version=config['vmware']['test']['version'],
|
|
77
|
+
proxies=config['vmware']['test']['proxies']
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
pyjira = PyJira(
|
|
81
|
+
base_url=config['jira']['base_url'],
|
|
82
|
+
token=config['jira']['token']
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
mail_163 = MailClient(mail_address=config['mail']['163']['mail_address'], password=config['mail']['163']['password'])
|
|
86
|
+
mail_qq = MailClient(mail_address=config['mail']['qq']['mail_address'], password=config['mail']['qq']['password'])
|
|
@@ -114,16 +114,11 @@ class BuildConfig:
|
|
|
114
114
|
device_templates = glob(str(jinja2_dir / f'{device_type}_*.toml.j2'))
|
|
115
115
|
if not device_templates:
|
|
116
116
|
continue
|
|
117
|
-
|
|
118
117
|
for tmpl_path in device_templates:
|
|
119
118
|
tmpl_name = os.path.basename(tmpl_path)
|
|
120
|
-
# 例如 h3c_system.toml.j2 -> h3c_system
|
|
121
119
|
base_name = tmpl_name.replace('.toml.j2', '')
|
|
122
|
-
|
|
123
120
|
template = self._get_template(f'input.snmp/{tmpl_name}')
|
|
124
|
-
|
|
125
|
-
# instances 是 [{"udp://10.1.1.1:161": {...}, "udp://10.1.1.2:161": {...}}, ...]
|
|
126
|
-
render_data = template.render(instances=instances)
|
|
121
|
+
render_data = template.render(instances=instances, config=self.instances['snmp']['config'])
|
|
127
122
|
|
|
128
123
|
target_dir = Path(self.output_dir) / 'input.snmp'
|
|
129
124
|
if not target_dir.exists():
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"119.29.29.29_baidu.com" = { dns_server = "119.29.29.29", domains = "www.baidu.com", labels = { name = "x", env = "prod" } }
|
|
26
26
|
|
|
27
27
|
[snmp]
|
|
28
|
+
[snmp.config]
|
|
29
|
+
timeout = '120s'
|
|
30
|
+
path = "/usr/share/snmp/mibs"
|
|
28
31
|
# 支持 h3c,huawei,cisco,ruijie
|
|
29
32
|
[[snmp.instances.h3c]]
|
|
30
33
|
"10.1.1.1:161" = { version = 2, community = "public" }
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
{pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2
RENAMED
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
{pytbox-0.1.1 → pytbox-0.1.3}/src/pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2
RENAMED
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
|
|
|
17
17
|
priv_password = "{{detail.priv_password}}"
|
|
18
18
|
sec_level = "{{detail.sec_level}}"
|
|
19
19
|
{% endif %}
|
|
20
|
-
timeout = "
|
|
20
|
+
timeout = "{{config.timeout}}"
|
|
21
21
|
retries = 3
|
|
22
|
-
path = ["
|
|
22
|
+
path = ["{{config.path}}"]
|
|
23
23
|
translator = "gosmi"
|
|
24
24
|
max_repetitions = 50
|
|
25
25
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pytbox.database.victoriametrics import VictoriaMetrics
|
|
5
|
+
from pytbox.utils.richutils import RichUtils
|
|
6
|
+
|
|
7
|
+
rich_utils = RichUtils()
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def vm_group():
|
|
11
|
+
"""VictoriaMetrics 查询工具"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@vm_group.command('query')
|
|
16
|
+
@click.option('--url', '-u', type=str, required=True)
|
|
17
|
+
@click.option('--query', '-q', type=str, required=True)
|
|
18
|
+
def query(url, query):
|
|
19
|
+
"""查询 VM 数据"""
|
|
20
|
+
vm_client = VictoriaMetrics(url=url)
|
|
21
|
+
r = vm_client.query(query, output_format='json')
|
|
22
|
+
rich_utils.print(msg=r)
|
|
@@ -5,6 +5,7 @@ Pytbox 主命令行入口
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
from .categraf import categraf_group
|
|
8
|
+
from .commands.vm import vm_group
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@click.group()
|
|
@@ -16,6 +17,7 @@ def main():
|
|
|
16
17
|
|
|
17
18
|
# 注册子命令组
|
|
18
19
|
main.add_command(categraf_group, name='categraf')
|
|
20
|
+
main.add_command(vm_group, name='vm')
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
if __name__ == "__main__":
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import Literal, Optional
|
|
4
5
|
import requests
|
|
5
6
|
from ..utils.response import ReturnResponse
|
|
7
|
+
from ..utils.load_vm_devfile import load_dev_file
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class VictoriaMetrics:
|
|
@@ -16,13 +18,13 @@ class VictoriaMetrics:
|
|
|
16
18
|
'Accept': 'application/json'
|
|
17
19
|
})
|
|
18
20
|
|
|
19
|
-
def query(self, query: str) -> ReturnResponse:
|
|
21
|
+
def query(self, query: str, output_format: Literal['json']=None) -> ReturnResponse:
|
|
20
22
|
'''
|
|
21
23
|
查询指标数据
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
Args:
|
|
24
26
|
query (str): 查询语句
|
|
25
|
-
|
|
27
|
+
|
|
26
28
|
Returns:
|
|
27
29
|
dict: 查询结果
|
|
28
30
|
'''
|
|
@@ -32,13 +34,32 @@ class VictoriaMetrics:
|
|
|
32
34
|
timeout=self.timeout,
|
|
33
35
|
params={"query": query}
|
|
34
36
|
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
res_json = r.json()
|
|
38
|
+
status = res_json.get("status")
|
|
39
|
+
result = res_json.get("data", {}).get("result", [])
|
|
40
|
+
is_json = output_format == 'json'
|
|
41
|
+
|
|
42
|
+
if status == "success":
|
|
43
|
+
if result:
|
|
44
|
+
code = 0
|
|
45
|
+
msg = f"[{query}] 查询成功!"
|
|
46
|
+
data = result
|
|
38
47
|
else:
|
|
39
|
-
|
|
48
|
+
code = 2
|
|
49
|
+
msg = f"[{query}] 没有查询到结果"
|
|
50
|
+
data = res_json
|
|
40
51
|
else:
|
|
41
|
-
|
|
52
|
+
code = 1
|
|
53
|
+
msg = f"[{query}] 查询失败: {res_json.get('error')}"
|
|
54
|
+
data = res_json
|
|
55
|
+
|
|
56
|
+
resp = ReturnResponse(code=code, msg=msg, data=data)
|
|
57
|
+
|
|
58
|
+
if is_json:
|
|
59
|
+
json_result = json.dumps(resp.__dict__, ensure_ascii=False)
|
|
60
|
+
return json_result
|
|
61
|
+
else:
|
|
62
|
+
return resp
|
|
42
63
|
|
|
43
64
|
def get_labels(self, metric_name: str) -> ReturnResponse:
|
|
44
65
|
url = f"{self.url}/api/v1/series?match[]={metric_name}"
|
|
@@ -49,9 +70,19 @@ class VictoriaMetrics:
|
|
|
49
70
|
else:
|
|
50
71
|
return ReturnResponse(code=1, msg=f"metric name: {metric_name} 查询失败")
|
|
51
72
|
|
|
52
|
-
def check_ping_result(self, target: str, last_minute: int=10) -> ReturnResponse:
|
|
73
|
+
def check_ping_result(self, target: str, last_minute: int=10, env: str='prod', dev_file: str='') -> ReturnResponse:
|
|
53
74
|
'''
|
|
54
75
|
检查ping结果
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
target (str): 目标地址
|
|
79
|
+
last_minute (int, optional): 最近多少分钟. Defaults to 10.
|
|
80
|
+
env (str, optional): 环境. Defaults to 'prod'.
|
|
81
|
+
dev_file (str, optional): 开发文件. Defaults to ''.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ReturnResponse:
|
|
85
|
+
code = 0 正常, code = 1 异常, code = 2 没有查询到数据, 建议将其判断为正常
|
|
55
86
|
'''
|
|
56
87
|
if target:
|
|
57
88
|
# 这里需要在字符串中保留 {},同时插入 target,可以用双大括号转义
|
|
@@ -62,13 +93,18 @@ class VictoriaMetrics:
|
|
|
62
93
|
if last_minute:
|
|
63
94
|
query = query + f"[{last_minute}m]"
|
|
64
95
|
|
|
65
|
-
|
|
96
|
+
if env == 'dev':
|
|
97
|
+
r = load_dev_file(dev_file)
|
|
98
|
+
else:
|
|
99
|
+
r = self.query(query=query)
|
|
100
|
+
|
|
66
101
|
if r.code == 0:
|
|
67
102
|
values = r.data[0]['values']
|
|
68
103
|
if len(values) == 2 and values[1] == "0":
|
|
69
104
|
code = 0
|
|
70
105
|
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
71
106
|
else:
|
|
107
|
+
|
|
72
108
|
if all(str(item[1]) == "1" for item in values):
|
|
73
109
|
code = 1
|
|
74
110
|
msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
|
|
File without changes
|
|
@@ -254,9 +254,9 @@ class MessageEndpoint(Endpoint):
|
|
|
254
254
|
body=payload
|
|
255
255
|
)
|
|
256
256
|
if r.code == 0:
|
|
257
|
-
return ReturnResponse(code=0,
|
|
257
|
+
return ReturnResponse(code=0, msg=f"{message_id} 回复 emoji [{emoji_type}] 成功")
|
|
258
258
|
else:
|
|
259
|
-
return ReturnResponse(code=1,
|
|
259
|
+
return ReturnResponse(code=1, msg=f"{message_id} 回复 emoji [{emoji_type}] 失败")
|
|
260
260
|
|
|
261
261
|
class BitableEndpoint(Endpoint):
|
|
262
262
|
|
|
@@ -432,10 +432,10 @@ class BitableEndpoint(Endpoint):
|
|
|
432
432
|
resp = self.parent.request(path=f'/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}',
|
|
433
433
|
method='PUT',
|
|
434
434
|
body=payload)
|
|
435
|
-
return ReturnResponse(code=resp.code,
|
|
435
|
+
return ReturnResponse(code=resp.code, msg=f"记录已存在, 进行更新", data=resp.data)
|
|
436
436
|
else:
|
|
437
437
|
resp = self.add_record(app_token, table_id, fields)
|
|
438
|
-
return ReturnResponse(code=resp.code,
|
|
438
|
+
return ReturnResponse(code=resp.code, msg=f"记录不存在, 进行创建", data=resp.data)
|
|
439
439
|
|
|
440
440
|
def query_name_by_record_id(self, app_token: str=None, table_id: str=None, field_names: list=None, record_id: str='', name: str=''):
|
|
441
441
|
response = self.query_record(app_token=app_token, table_id=table_id, field_names=field_names)
|
|
@@ -967,9 +967,9 @@ class ExtensionsEndpoint(Endpoint):
|
|
|
967
967
|
body=payload)
|
|
968
968
|
if response.code == 0:
|
|
969
969
|
if get == 'open_id':
|
|
970
|
-
return ReturnResponse(code=0,
|
|
970
|
+
return ReturnResponse(code=0, msg=f'根据用户输入的 {user_input}, 获取用户信息成功', data=response.data['user_list'][0]['user_id'])
|
|
971
971
|
else:
|
|
972
|
-
return ReturnResponse(code=response.code,
|
|
972
|
+
return ReturnResponse(code=response.code, msg=f"获取时失败, 报错请见 data 字段", data=response.data)
|
|
973
973
|
|
|
974
974
|
def format_rich_text(self, text: str, color: Literal['red', 'green', 'yellow', 'blue'], bold: bool=False):
|
|
975
975
|
if bold:
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
import yagmail
|
|
6
|
+
from imap_tools import MailBox, MailMessageFlags, AND
|
|
7
|
+
from .mail_detail import MailDetail
|
|
8
|
+
from ..utils.response import ReturnResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MailClient:
|
|
12
|
+
'''
|
|
13
|
+
_summary_
|
|
14
|
+
'''
|
|
15
|
+
def __init__(self,
|
|
16
|
+
mail_address: str=None,
|
|
17
|
+
password: str=None
|
|
18
|
+
):
|
|
19
|
+
|
|
20
|
+
self.mail_address = mail_address
|
|
21
|
+
self.password = password
|
|
22
|
+
|
|
23
|
+
if '163.com' in mail_address:
|
|
24
|
+
self.smtp_address = 'smtp.163.com'
|
|
25
|
+
self.imap_address = 'imap.163.com'
|
|
26
|
+
# self.imbox_client = self._create_imbox_object()
|
|
27
|
+
elif 'foxmail.com' in mail_address:
|
|
28
|
+
self.smtp_address = 'smtp.qq.com'
|
|
29
|
+
self.imap_address = 'imap.qq.com'
|
|
30
|
+
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(f'不支持的邮箱地址: {mail_address}')
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def get_mailbox(self, readonly=False):
|
|
36
|
+
"""
|
|
37
|
+
创建并返回一个已登录的 MailBox 上下文管理器。
|
|
38
|
+
|
|
39
|
+
使用方式:
|
|
40
|
+
with self.get_mailbox() as mailbox:
|
|
41
|
+
# 使用 mailbox 进行操作
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
readonly (bool): 是否以只读模式打开邮箱,只读模式下不会将邮件标记为已读
|
|
46
|
+
|
|
47
|
+
Yields:
|
|
48
|
+
MailBox: 已登录的邮箱对象
|
|
49
|
+
"""
|
|
50
|
+
mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
|
|
51
|
+
if readonly:
|
|
52
|
+
# 以只读模式选择收件箱,防止邮件被标记为已读
|
|
53
|
+
mailbox.folder.set('INBOX', readonly=True)
|
|
54
|
+
try:
|
|
55
|
+
yield mailbox
|
|
56
|
+
finally:
|
|
57
|
+
mailbox.logout()
|
|
58
|
+
|
|
59
|
+
def send_mail(self, receiver: list=None, cc: list=None, subject: str='', contents: str='', attachments: list=None, tips: str=None):
|
|
60
|
+
'''
|
|
61
|
+
_summary_
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
receiver (list, optional): _description_. Defaults to None.
|
|
65
|
+
cc (list, optional): _description_. Defaults to None.
|
|
66
|
+
subject (str, optional): _description_. Defaults to ''.
|
|
67
|
+
contents (str, optional): _description_. Defaults to ''.
|
|
68
|
+
attachments (list, optional): _description_. Defaults to None.
|
|
69
|
+
'''
|
|
70
|
+
with yagmail.SMTP(user=self.mail_address, password=self.password, port=465, host=self.smtp_address) as yag:
|
|
71
|
+
try:
|
|
72
|
+
if tips:
|
|
73
|
+
contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送</p>'
|
|
74
|
+
yag.send(to=receiver, cc=cc, subject=subject, contents=contents, attachments=attachments)
|
|
75
|
+
return True
|
|
76
|
+
except Exception as e:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def get_mail_list(self, seen: bool=False, readonly: bool=True):
|
|
80
|
+
'''
|
|
81
|
+
获取邮件
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
seen (bool, optional): 默认获取未读邮件, 如果为 True, 则获取已读邮件
|
|
85
|
+
readonly (bool, optional): 是否以只读模式获取邮件,默认为 True,防止邮件被标记为已读
|
|
86
|
+
|
|
87
|
+
Yields:
|
|
88
|
+
MailDetail: 邮件详情
|
|
89
|
+
'''
|
|
90
|
+
with self.get_mailbox(readonly=readonly) as mailbox:
|
|
91
|
+
for msg in mailbox.fetch(AND(seen=seen)):
|
|
92
|
+
yield MailDetail(
|
|
93
|
+
uid=msg.uid,
|
|
94
|
+
sent_from=msg.from_,
|
|
95
|
+
sent_to=msg.to,
|
|
96
|
+
date=msg.date,
|
|
97
|
+
cc=msg.cc,
|
|
98
|
+
subject=msg.subject,
|
|
99
|
+
body_plain=msg.text,
|
|
100
|
+
body_html=msg.html
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def mark_as_read(self, uid):
|
|
104
|
+
"""
|
|
105
|
+
标记邮件为已读。
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
uid (str): 邮件的唯一标识符
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
with self.get_mailbox() as mailbox:
|
|
112
|
+
# 使用 imap_tools 的 flag 方法标记邮件为已读
|
|
113
|
+
# 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
|
|
114
|
+
mailbox.flag(uid, [MailMessageFlags.SEEN], True)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return ReturnResponse(code=1, msg='邮件删除失败', data=e)
|
|
117
|
+
|
|
118
|
+
def delete(self, uid):
|
|
119
|
+
"""
|
|
120
|
+
删除邮件。
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
uid (str): 邮件的唯一标识符
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
with self.get_mailbox() as mailbox:
|
|
127
|
+
# 使用 imap_tools 的 delete 方法删除邮件
|
|
128
|
+
mailbox.delete(uid)
|
|
129
|
+
# log.info(f'删除邮件{uid}')
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# log.error(f'删除邮件{uid}失败: {e}')
|
|
132
|
+
raise
|
|
133
|
+
|
|
134
|
+
def move(self, uid: str, destination_folder: str) -> ReturnResponse:
|
|
135
|
+
"""
|
|
136
|
+
移动邮件到指定文件夹。
|
|
137
|
+
|
|
138
|
+
注意:部分 IMAP 服务商(如 QQ 邮箱)在移动邮件时,实际上是"复制到目标文件夹并从原文件夹删除",
|
|
139
|
+
这会导致邮件在原文件夹中消失,表现为"被删除"。但邮件会在目标文件夹中存在,并未彻底丢失。
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
uid (str): 邮件的唯一标识符。
|
|
143
|
+
destination_folder (str): 目标文件夹名称。
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
ReturnResponse: 移动邮件结果
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
Exception: 移动过程中底层 imap 库抛出的异常。
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
with self.get_mailbox() as mailbox:
|
|
153
|
+
# 使用 imap_tools 的 move 方法移动邮件
|
|
154
|
+
mailbox.move(uid, destination_folder)
|
|
155
|
+
return ReturnResponse(code=0, msg=f'邮件 {uid} 移动到 {destination_folder} 成功', data=None)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
return ReturnResponse(code=1, msg=f'邮件 {uid} 移动到 {destination_folder} 失败', data=e)
|
|
158
|
+
|
|
159
|
+
def get_folder_list(self):
|
|
160
|
+
'''
|
|
161
|
+
获取文件夹列表
|
|
162
|
+
'''
|
|
163
|
+
with self.get_mailbox() as mailbox:
|
|
164
|
+
return ReturnResponse(code=0, msg='获取文件夹列表成功', data=mailbox.folder.list())
|
|
165
|
+
|
|
166
|
+
if __name__ == '__main__':
|
|
167
|
+
pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class MailDetail:
|
|
8
|
+
"""
|
|
9
|
+
邮件详情数据类。
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
uid: 邮件唯一标识符
|
|
13
|
+
send_from: 发件人邮箱地址
|
|
14
|
+
send_to: 收件人邮箱地址列表
|
|
15
|
+
cc: 抄送人邮箱地址列表
|
|
16
|
+
subject: 邮件主题
|
|
17
|
+
body_plain: 纯文本正文
|
|
18
|
+
body_html: HTML格式正文
|
|
19
|
+
attachment: 附件完整保存路径列表
|
|
20
|
+
"""
|
|
21
|
+
uid: str=None
|
|
22
|
+
sent_from: str=None
|
|
23
|
+
sent_to: list=None
|
|
24
|
+
date: str=None
|
|
25
|
+
cc: list=None
|
|
26
|
+
subject: str=None
|
|
27
|
+
body_plain: str=None
|
|
28
|
+
body_html: str=None
|
|
29
|
+
attachment: list=None
|
|
30
|
+
has_attachments: bool=False
|