pytbox 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pytbox might be problematic. Click here for more details.

pytbox/base.py CHANGED
@@ -8,11 +8,11 @@ from pytbox.feishu.client import Client as FeishuClient
8
8
  from pytbox.dida365 import Dida365
9
9
  from pytbox.alert.alert_handler import AlertHandler
10
10
  from pytbox.log.logger import AppLogger
11
-
11
+ from pytbox.win.ad import ADClient
12
+ from pytbox.network.meraki import Meraki
12
13
 
13
14
  config = load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=os.environ.get('oc_vault_id'))
14
15
 
15
-
16
16
  def get_mongo(collection):
17
17
  return Mongo(
18
18
  host=config['mongo']['host'],
@@ -45,4 +45,20 @@ def get_logger(app):
45
45
  feishu=feishu,
46
46
  dida=dida,
47
47
  mongo=get_mongo('alert_program')
48
- )
48
+ )
49
+
50
+ # ad_dev = ADClient(
51
+ # server=config['ad']['dev']['AD_SERVER'],
52
+ # base_dn=config['ad']['dev']['BASE_DN'],
53
+ # username=config['ad']['dev']['AD_USERNAME'],
54
+ # password=config['ad']['dev']['AD_PASSWORD']
55
+ # )
56
+
57
+ # ad_prod = ADClient(
58
+ # server=config['ad']['prod']['AD_SERVER'],
59
+ # base_dn=config['ad']['prod']['BASE_DN'],
60
+ # username=config['ad']['prod']['AD_USERNAME'],
61
+ # password=config['ad']['prod']['AD_PASSWORD']
62
+ # )
63
+
64
+ meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
@@ -41,33 +41,36 @@ class BuildConfig:
41
41
  f.write(render_data)
42
42
 
43
43
  def ping(self):
44
- instances = self.instances['ping']['instance']
45
- render_data = ping_template.render(instances=instances)
46
- target_dir = Path(self.output_dir) / 'input.ping'
47
- if not target_dir.exists():
48
- target_dir.mkdir(parents=True, exist_ok=True)
49
-
50
- with open(Path(self.output_dir) / 'input.ping' / 'ping.toml', 'w', encoding='utf-8') as f:
51
- f.write(render_data)
44
+ if self.instances.get('ping'):
45
+ instances = self.instances['ping']['instance']
46
+ render_data = ping_template.render(instances=instances)
47
+ target_dir = Path(self.output_dir) / 'input.ping'
48
+ if not target_dir.exists():
49
+ target_dir.mkdir(parents=True, exist_ok=True)
50
+
51
+ with open(Path(self.output_dir) / 'input.ping' / 'ping.toml', 'w', encoding='utf-8') as f:
52
+ f.write(render_data)
52
53
 
53
54
  def prometheus(self):
54
- instances = self.instances['prometheus']['urls']
55
- render_data = prometheus_template.render(instances=instances)
56
- target_dir = Path(self.output_dir) / 'input.prometheus'
57
- if not target_dir.exists():
58
- target_dir.mkdir(parents=True, exist_ok=True)
59
- with open(Path(self.output_dir) / 'input.prometheus' / 'prometheus.toml', 'w', encoding='utf-8') as f:
60
- f.write(render_data)
55
+ if self.instances.get('prometheus'):
56
+ instances = self.instances['prometheus']['urls']
57
+ render_data = prometheus_template.render(instances=instances)
58
+ target_dir = Path(self.output_dir) / 'input.prometheus'
59
+ if not target_dir.exists():
60
+ target_dir.mkdir(parents=True, exist_ok=True)
61
+ with open(Path(self.output_dir) / 'input.prometheus' / 'prometheus.toml', 'w', encoding='utf-8') as f:
62
+ f.write(render_data)
61
63
 
62
64
  def vsphere(self):
63
- template = self._get_template('input.vsphere/vsphere.toml.j2')
64
- instances = self.instances['vsphere']['instance']
65
- render_data = template.render(instances=instances)
66
- target_dir = Path(self.output_dir) / 'input.vsphere'
67
- if not target_dir.exists():
68
- target_dir.mkdir(parents=True, exist_ok=True)
69
- with open(Path(self.output_dir) / 'input.vsphere' / 'vsphere.toml', 'w', encoding='utf-8') as f:
70
- f.write(render_data)
65
+ if self.instances.get('vsphere'):
66
+ template = self._get_template('input.vsphere/vsphere.toml.j2')
67
+ instances = self.instances['vsphere']['instance']
68
+ render_data = template.render(instances=instances)
69
+ target_dir = Path(self.output_dir) / 'input.vsphere'
70
+ if not target_dir.exists():
71
+ target_dir.mkdir(parents=True, exist_ok=True)
72
+ with open(Path(self.output_dir) / 'input.vsphere' / 'vsphere.toml', 'w', encoding='utf-8') as f:
73
+ f.write(render_data)
71
74
 
72
75
  def http_response(self):
73
76
  template = self._get_template('input.http_response/http_response.toml.j2')
@@ -5,7 +5,7 @@
5
5
 
6
6
  [prometheus]
7
7
  [[prometheus.urls]]
8
- "http://10.200.12.202:9100" = { name = "x", env = "prod" }
8
+ "http://10.1.1.1:9100" = { name = "x", env = "prod" }
9
9
 
10
10
  [vsphere]
11
11
  [[vsphere.instance]]
@@ -25,6 +25,7 @@
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
+ # 支持 h3c,huawei,cisco,ruijie
28
29
  [[snmp.instances.h3c]]
29
30
  "10.1.1.1:161" = { version = 2, community = "public" }
30
31
  "10.1.1.2:161" = { version = 2, community = "public" }
@@ -0,0 +1,96 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.table]]
32
+ name = "interface"
33
+ inherit_tags = ["sysName"]
34
+ index_as_tag = true
35
+
36
+ [[instances.table.field]]
37
+ name = "ifIndex"
38
+ oid = "1.3.6.1.2.1.2.2.1.1"
39
+ is_tag = true
40
+
41
+ [[instances.table.field]]
42
+ name = "ifName"
43
+ oid = "1.3.6.1.2.1.31.1.1.1.1"
44
+ is_tag = true
45
+
46
+ [[instances.table.field]]
47
+ name = "ifDescr"
48
+ oid = "1.3.6.1.2.1.2.2.1.2"
49
+ is_tag = true
50
+
51
+ [[instances.table.field]]
52
+ name = "ifSpeed"
53
+ oid = "1.3.6.1.2.1.2.2.1.5"
54
+ # is_tag = true
55
+
56
+ [[instances.table.field]]
57
+ name = "ifAdminStatus"
58
+ oid = "1.3.6.1.2.1.2.2.1.7"
59
+ # is_tag = true
60
+
61
+ [[instances.table.field]]
62
+ name = "ifOperStatus"
63
+ oid = "1.3.6.1.2.1.2.2.1.8"
64
+ # is_tag = true
65
+
66
+ [[instances.table.field]]
67
+ name = "ifInDiscards"
68
+ oid = "1.3.6.1.2.1.2.2.1.13"
69
+
70
+ [[instances.table.field]]
71
+ name = "ifInErrors"
72
+ oid = "1.3.6.1.2.1.2.2.1.14"
73
+
74
+ [[instances.table.field]]
75
+ name = "ifOutDiscards"
76
+ oid = "1.3.6.1.2.1.2.2.1.19"
77
+
78
+ [[instances.table.field]]
79
+ name = "ifOutErrors"
80
+ oid = "1.3.6.1.2.1.2.2.1.20"
81
+
82
+ [[instances.table.field]]
83
+ name = "ifAlias"
84
+ oid = "1.3.6.1.2.1.31.1.1.1.18"
85
+ is_tag = true
86
+
87
+ [[instances.table.field]]
88
+ name = "ifHCInOctets"
89
+ oid = "1.3.6.1.2.1.31.1.1.1.6"
90
+
91
+ [[instances.table.field]]
92
+ name = "ifHCOutOctets"
93
+ oid = "1.3.6.1.2.1.31.1.1.1.10"
94
+
95
+ {% endfor %}
96
+ {% endfor %}
@@ -0,0 +1,41 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.field]]
32
+ oid = "1.3.6.1.2.1.1.1.0"
33
+ name = "sysDescr"
34
+ is_tag = true
35
+
36
+ [[instances.field]]
37
+ oid = "1.3.6.1.2.1.1.3.0"
38
+ name = "sysUpTime"
39
+ conversion = "float(2)"
40
+ {% endfor %}
41
+ {% endfor %}
@@ -0,0 +1,96 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.table]]
32
+ name = "interface"
33
+ inherit_tags = ["sysName"]
34
+ index_as_tag = true
35
+
36
+ [[instances.table.field]]
37
+ name = "ifIndex"
38
+ oid = "1.3.6.1.2.1.2.2.1.1"
39
+ is_tag = true
40
+
41
+ [[instances.table.field]]
42
+ name = "ifName"
43
+ oid = "1.3.6.1.2.1.31.1.1.1.1"
44
+ is_tag = true
45
+
46
+ [[instances.table.field]]
47
+ name = "ifDescr"
48
+ oid = "1.3.6.1.2.1.2.2.1.2"
49
+ is_tag = true
50
+
51
+ [[instances.table.field]]
52
+ name = "ifSpeed"
53
+ oid = "1.3.6.1.2.1.2.2.1.5"
54
+ # is_tag = true
55
+
56
+ [[instances.table.field]]
57
+ name = "ifAdminStatus"
58
+ oid = "1.3.6.1.2.1.2.2.1.7"
59
+ # is_tag = true
60
+
61
+ [[instances.table.field]]
62
+ name = "ifOperStatus"
63
+ oid = "1.3.6.1.2.1.2.2.1.8"
64
+ # is_tag = true
65
+
66
+ [[instances.table.field]]
67
+ name = "ifInDiscards"
68
+ oid = "1.3.6.1.2.1.2.2.1.13"
69
+
70
+ [[instances.table.field]]
71
+ name = "ifInErrors"
72
+ oid = "1.3.6.1.2.1.2.2.1.14"
73
+
74
+ [[instances.table.field]]
75
+ name = "ifOutDiscards"
76
+ oid = "1.3.6.1.2.1.2.2.1.19"
77
+
78
+ [[instances.table.field]]
79
+ name = "ifOutErrors"
80
+ oid = "1.3.6.1.2.1.2.2.1.20"
81
+
82
+ [[instances.table.field]]
83
+ name = "ifAlias"
84
+ oid = "1.3.6.1.2.1.31.1.1.1.18"
85
+ is_tag = true
86
+
87
+ [[instances.table.field]]
88
+ name = "ifHCInOctets"
89
+ oid = "1.3.6.1.2.1.31.1.1.1.6"
90
+
91
+ [[instances.table.field]]
92
+ name = "ifHCOutOctets"
93
+ oid = "1.3.6.1.2.1.31.1.1.1.10"
94
+
95
+ {% endfor %}
96
+ {% endfor %}
@@ -0,0 +1,41 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.field]]
32
+ oid = "1.3.6.1.2.1.1.1.0"
33
+ name = "sysDescr"
34
+ is_tag = true
35
+
36
+ [[instances.field]]
37
+ oid = "1.3.6.1.2.1.1.3.0"
38
+ name = "sysUpTime"
39
+ conversion = "float(2)"
40
+ {% endfor %}
41
+ {% endfor %}
@@ -0,0 +1,96 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.table]]
32
+ name = "interface"
33
+ inherit_tags = ["sysName"]
34
+ index_as_tag = true
35
+
36
+ [[instances.table.field]]
37
+ name = "ifIndex"
38
+ oid = "1.3.6.1.2.1.2.2.1.1"
39
+ is_tag = true
40
+
41
+ [[instances.table.field]]
42
+ name = "ifName"
43
+ oid = "1.3.6.1.2.1.31.1.1.1.1"
44
+ is_tag = true
45
+
46
+ [[instances.table.field]]
47
+ name = "ifDescr"
48
+ oid = "1.3.6.1.2.1.2.2.1.2"
49
+ is_tag = true
50
+
51
+ [[instances.table.field]]
52
+ name = "ifSpeed"
53
+ oid = "1.3.6.1.2.1.2.2.1.5"
54
+ # is_tag = true
55
+
56
+ [[instances.table.field]]
57
+ name = "ifAdminStatus"
58
+ oid = "1.3.6.1.2.1.2.2.1.7"
59
+ # is_tag = true
60
+
61
+ [[instances.table.field]]
62
+ name = "ifOperStatus"
63
+ oid = "1.3.6.1.2.1.2.2.1.8"
64
+ # is_tag = true
65
+
66
+ [[instances.table.field]]
67
+ name = "ifInDiscards"
68
+ oid = "1.3.6.1.2.1.2.2.1.13"
69
+
70
+ [[instances.table.field]]
71
+ name = "ifInErrors"
72
+ oid = "1.3.6.1.2.1.2.2.1.14"
73
+
74
+ [[instances.table.field]]
75
+ name = "ifOutDiscards"
76
+ oid = "1.3.6.1.2.1.2.2.1.19"
77
+
78
+ [[instances.table.field]]
79
+ name = "ifOutErrors"
80
+ oid = "1.3.6.1.2.1.2.2.1.20"
81
+
82
+ [[instances.table.field]]
83
+ name = "ifAlias"
84
+ oid = "1.3.6.1.2.1.31.1.1.1.18"
85
+ is_tag = true
86
+
87
+ [[instances.table.field]]
88
+ name = "ifHCInOctets"
89
+ oid = "1.3.6.1.2.1.31.1.1.1.6"
90
+
91
+ [[instances.table.field]]
92
+ name = "ifHCOutOctets"
93
+ oid = "1.3.6.1.2.1.31.1.1.1.10"
94
+
95
+ {% endfor %}
96
+ {% endfor %}
@@ -0,0 +1,41 @@
1
+ {% for instance in instances %}
2
+ {% for agent, detail in instance.items() %}
3
+ [[instances]]
4
+ agents = [
5
+ "udp://{{agent}}"
6
+ ]
7
+ {% if detail.version == 2 %}
8
+ version = {{detail.version}}
9
+ community = "{{detail.community}}"
10
+ {% endif %}
11
+ {% if detail.version == 3 %}
12
+ version = {{detail.version}}
13
+ sec_name = "{{detail.sec_name}}"
14
+ auth_protocol = "{{detail.auth_protocol}}"
15
+ auth_password = "{{detail.auth_password}}"
16
+ priv_protocol = "{{detail.priv_protocol}}"
17
+ priv_password = "{{detail.priv_password}}"
18
+ sec_level = "{{detail.sec_level}}"
19
+ {% endif %}
20
+ timeout = "5s"
21
+ retries = 3
22
+ path = ["/usr/share/snmp/mibs"]
23
+ translator = "gosmi"
24
+ max_repetitions = 50
25
+
26
+ [[instances.field]]
27
+ name = "sysName"
28
+ oid = "1.3.6.1.2.1.1.5.0"
29
+ is_tag = true
30
+
31
+ [[instances.field]]
32
+ oid = "1.3.6.1.2.1.1.1.0"
33
+ name = "sysDescr"
34
+ is_tag = true
35
+
36
+ [[instances.field]]
37
+ oid = "1.3.6.1.2.1.1.3.0"
38
+ name = "sysUpTime"
39
+ conversion = "float(2)"
40
+ {% endfor %}
41
+ {% endfor %}
pytbox/mail/alimail.py ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Literal
4
+ from datetime import datetime, timedelta
5
+
6
+ import requests
7
+
8
+
9
+
10
+ class AliMail:
11
+ def __init__(self, email_address: str=None, client_id: str=None, client_secret: str=None):
12
+
13
+ self.email_address = email_address
14
+ self.client_id = auth['username']
15
+ self.client_secret = auth['password']
16
+ self.base_url = "https://alimail-cn.aliyuncs.com/v2"
17
+ self.headers = {
18
+ "Content-Type": "application/json",
19
+ "Authorization": f"bearer {self._get_access_token()}"
20
+ }
21
+
22
+ def _get_access_token(self):
23
+ current_time = datetime.now()
24
+ # stored_token = mongo_alimail_token.find_one({"token_type": "bearer"})
25
+ if stored_token and stored_token.get('expiration_time'):
26
+ expiration_time = stored_token['expiration_time']
27
+ if current_time < expiration_time:
28
+ return stored_token['access_token']
29
+ else:
30
+ return self._get_access_token_by_request()
31
+ else:
32
+ return self._get_access_token_by_request()
33
+
34
+ def _get_access_token_by_request(self):
35
+ '''
36
+ https://mailhelp.aliyun.com/openapi/index.html#/markdown/authorization.md
37
+
38
+ Raises:
39
+ ValueError: _description_
40
+
41
+ Returns:
42
+ _type_: _description_
43
+ '''
44
+ # 定义接口URL
45
+ interface_url = "https://alimail-cn.aliyuncs.com/oauth2/v2.0/token"
46
+ # 设置请求头,指定内容类型
47
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
48
+ # 准备请求数据
49
+ data = {
50
+ "grant_type": "client_credentials",
51
+ "client_id": self.client_id,
52
+ "client_secret": self.client_secret
53
+ }
54
+ try:
55
+ response = requests.post(interface_url, headers=headers, data=data, timeout=3)
56
+ response_json = response.json()
57
+ current_time = datetime.now()
58
+ data = {
59
+ 'token_type': response_json["token_type"],
60
+ 'access_token': response_json["access_token"],
61
+ 'expires_in': response_json["expires_in"],
62
+ 'expiration_time': current_time + timedelta(seconds=response_json["expires_in"])
63
+ }
64
+ mongo_alimail_token.update_one(
65
+ {"token_type": "bearer"},
66
+ {"$set": data},
67
+ upsert=True
68
+ )
69
+ return data.get("access_token")
70
+ except requests.RequestException as e:
71
+ # 处理请求失败异常
72
+ print(f"请求失败:{e}")
73
+ except (KeyError, ValueError) as e:
74
+ # 处理解析响应失败异常
75
+ print(f"解析响应失败: {e}")
76
+
77
+ def list_mail_folders(self):
78
+ response = requests.get(
79
+ url=f"{self.base_url}/users/{self.email_address}/mailFolders",
80
+ headers=self.headers
81
+ )
82
+ return response.json().get('folders')
83
+
84
+ def query_folder_id(self, folder_name: Literal['inbox']='inbox'):
85
+ folders = self.list_mail_folders()
86
+ for folder in folders:
87
+ if folder.get('displayName') == folder_name:
88
+ return folder.get('id')
89
+ return None
90
+
91
+ def get_mail_detail(self, mail_id: str):
92
+ params = {
93
+ "$select": "body,toRecipients,internetMessageId,internetMessageHeaders"
94
+ }
95
+ response = requests.get(
96
+ url=f"{self.base_url}/users/{self.email_address}/messages/{mail_id}",
97
+ headers=self.headers,
98
+ params=params,
99
+ timeout=3
100
+ )
101
+ return response.json().get('message')
102
+
103
+ def list_mail(self, folder_name: str='inbox', size: int=100):
104
+ folder_id = self.query_folder_id(folder_name=folder_name)
105
+ params = {
106
+ "size": size,
107
+ # "$select": "toRecipients"
108
+ }
109
+ response = requests.get(
110
+ url=f"{self.base_url}/users/{self.email_address}/mailFolders/{folder_id}/messages",
111
+ headers=self.headers,
112
+ params=params,
113
+ timeout=3
114
+ )
115
+ messages = response.json().get("messages")
116
+ for message in messages:
117
+ mail_id = message.get("id")
118
+ detail = self.get_mail_detail(mail_id=mail_id)
119
+ message.update(detail)
120
+ yield message
121
+
122
+
123
+ if __name__ == '__main__':
124
+ ali_mail = AliMail()
125
+ # print(ali_mail.query_folder_id())
126
+ # for i in ali_mail.list_mail(size=1):
127
+ # print(i)
128
+ # print(ali_mail.access_token)
129
+ print(ali_mail.get_mail_detail(mail_id='DzzzzzzMeuY'))
pytbox/mail/client.py ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Generator, Literal
6
+
7
+ import yagmail
8
+ from imbox import Imbox
9
+ from imap_tools import MailBox, AND, MailMessageFlags
10
+ from nb_log import get_logger
11
+
12
+ from src.library.onepassword import my1p
13
+
14
+
15
+ log = get_logger('src.library.mail.client')
16
+
17
+
18
+ @dataclass
19
+ class MailDetail:
20
+ """
21
+ 邮件详情数据类。
22
+
23
+ Attributes:
24
+ uid: 邮件唯一标识符
25
+ send_from: 发件人邮箱地址
26
+ send_to: 收件人邮箱地址列表
27
+ cc: 抄送人邮箱地址列表
28
+ subject: 邮件主题
29
+ body_plain: 纯文本正文
30
+ body_html: HTML格式正文
31
+ attachment: 附件完整保存路径列表
32
+ """
33
+ uid: str=None
34
+ send_from: str=None
35
+ send_to: list=None
36
+ date: str=None
37
+ cc: list=None
38
+ subject: str=None
39
+ body_plain: str=None
40
+ body_html: str=None
41
+ attachment: list=None
42
+ has_attachments: bool=False
43
+
44
+
45
+ class MailClient:
46
+ '''
47
+ _summary_
48
+ '''
49
+ def __init__(self,
50
+ send_mail_address: Literal['service@tyun.cn', 'alert@tyun.cn', 'houmingming@tyun.cn', 'houmdream@163.com', 'houm01@foxmail.com'] = 'service@tyun.cn',
51
+ authorization_code: bool=False,
52
+ password: str=None
53
+ ):
54
+
55
+ self.mail_address = send_mail_address
56
+
57
+ if password:
58
+ self.password = password
59
+ else:
60
+ if authorization_code:
61
+ self.password = my1p.get_item_by_title(title=send_mail_address)['授权码']
62
+ else:
63
+ self.password = my1p.get_item_by_title(title=send_mail_address)['password']
64
+
65
+ if '163.com' in send_mail_address:
66
+ self.smtp_address = 'smtp.163.com'
67
+ self.imap_address = 'imap.163.com'
68
+ self.imbox_client = self._create_imbox_object(vendor='163')
69
+ elif 'foxmail.com' in send_mail_address:
70
+ self.smtp_address = 'smtp.qq.com'
71
+ self.imap_address = 'imap.qq.com'
72
+ self.imbox_client = self._create_imbox_object(vendor='qq')
73
+ else:
74
+ raise ValueError(f'不支持的邮箱地址: {send_mail_address}')
75
+
76
+ self.mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
77
+
78
+ def _create_imbox_object(self, vendor: Literal['aliyun', 'qq', '163']):
79
+ return Imbox(self.imap_address,
80
+ username=self.mail_address,
81
+ password=self.password,
82
+ ssl=True,
83
+ ssl_context=None,
84
+ starttls=False)
85
+
86
+ def send_mail(self, receiver: list=[], cc: list=['houmingming@tyun.cn'], subject: str='', contents: str='', attachments: list=[], tips: str=None):
87
+ '''
88
+ _summary_
89
+
90
+ Args:
91
+ receiver (list, optional): _description_. Defaults to [].
92
+ cc (list, optional): _description_. Defaults to ['houmingming@tyun.cn'].
93
+ subject (str, optional): _description_. Defaults to ''.
94
+ contents (str, optional): _description_. Defaults to ''.
95
+ attachments (list, optional): _description_. Defaults to [].
96
+ '''
97
+ email_signature = """
98
+ ————————————————————————————————————————
99
+ 以专业成就客户
100
+ 钛信(上海)信息科技有限公司
101
+ Tai Xin(ShangHai) Information Technology Co.,Ltd.
102
+ 中国上海市浦东新区达尔文路88号半岛科技园21栋2楼
103
+ Tel:400-920-0057
104
+ Web:www.tyun.cn
105
+ ————————————————————————————————————————
106
+ 信息安全声明:本邮件包含信息归发件人所在组织所有,发件人所在组织对该邮件拥有所有权利。请接收者注意保密,未经发件人书面许可,不得向任何第三方组织和个人透露本邮件所含信息的全部或部分。以上声明仅适用于工作邮件。
107
+ Information Security Notice: The information contained in this mail is solely property of the sender's organization. This mail communication is confidential. Recipients named above are obligated to maintain secrecy and are not permitted to disclose the contents of this communication to others.
108
+ """
109
+ with yagmail.SMTP(user=self.mail_address, password=my1p.get_item_by_title(self.mail_address)['password'], port=465, host=self.smtp_address) as yag:
110
+ log.info(f'receiver: {receiver}, cc: {cc}, subject: {subject}, contents: {contents}, attachments: {attachments}')
111
+ try:
112
+ if tips:
113
+ contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送, 如有问题, 请联系 houmingming@tyun.cn</p>'
114
+
115
+ contents = contents + email_signature
116
+ yag.send(to=receiver, cc=cc, subject=subject, contents=contents, attachments=attachments)
117
+ log.info('发送成功!!!')
118
+ return True
119
+ except Exception as e:
120
+ log.error(f'发送失败, 报错: {e}')
121
+ return False
122
+
123
+ def get_mail_list(self, is_read: bool=False) -> Generator:
124
+ with MailBox(self.imap_address).login(self.mail_address, self.password) as mailbox:
125
+ for msg in mailbox.fetch(criteria=AND(seen=is_read)):
126
+ # 处理附件
127
+ attachment_list = []
128
+ for att in msg.attachments:
129
+ att_dict = {}
130
+ att_dict['filename'] = att.filename
131
+ att_dict['payload'] = att.payload
132
+ att_dict['size'] = att.size
133
+ att_dict['content_id'] = att.content_id
134
+ att_dict['content_type'] = att.content_type
135
+ att_dict['content_disposition'] = att.content_disposition
136
+ # att_dict['part'] = att.part
137
+ att_dict['size'] = att.size
138
+ attachment_list.append(att_dict)
139
+
140
+ if attachment_list:
141
+ is_has_attachments = True
142
+ else:
143
+ is_has_attachments = False
144
+
145
+ yield MailDetail(
146
+ uid=msg.uid,
147
+ send_from=msg.from_,
148
+ send_to=msg.to,
149
+ date=msg.date,
150
+ cc=msg.cc,
151
+ subject=msg.subject,
152
+ body_plain=msg.text,
153
+ body_html=msg.html,
154
+ has_attachments=is_has_attachments,
155
+ attachment=attachment_list
156
+ )
157
+
158
+ def mark_as_read(self, uid):
159
+ """
160
+ 标记邮件为已读。
161
+
162
+ Args:
163
+ uid (str): 邮件的唯一标识符
164
+ """
165
+ try:
166
+ # 使用 imap_tools 的 flag 方法标记邮件为已读
167
+ # 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
168
+ self.mailbox.flag(uid, [MailMessageFlags.SEEN], True)
169
+ # log.info(f'标注邮件{uid}为已读')
170
+ except Exception as e:
171
+ log.error(f'标记邮件{uid}为已读失败: {e}')
172
+ raise
173
+
174
+ def delete(self, uid):
175
+ """
176
+ 删除邮件。
177
+
178
+ Args:
179
+ uid (str): 邮件的唯一标识符
180
+ """
181
+ try:
182
+ # 使用 imap_tools 的 delete 方法删除邮件
183
+ self.mailbox.delete(uid)
184
+ log.info(f'删除邮件{uid}')
185
+ except Exception as e:
186
+ log.error(f'删除邮件{uid}失败: {e}')
187
+ raise
188
+
189
+ def move(self, uid: str, destination_folder: str) -> None:
190
+ """
191
+ 移动邮件到指定文件夹。
192
+
193
+ 注意:部分 IMAP 服务商(如 QQ 邮箱)在移动邮件时,实际上是"复制到目标文件夹并从原文件夹删除",
194
+ 这会导致邮件在原文件夹中消失,表现为"被删除"。但邮件会在目标文件夹中存在,并未彻底丢失。
195
+
196
+ Args:
197
+ uid (str): 邮件的唯一标识符。
198
+ destination_folder (str): 目标文件夹名称。
199
+
200
+ Returns:
201
+ None
202
+
203
+ Raises:
204
+ Exception: 移动过程中底层 imap 库抛出的异常。
205
+ """
206
+ try:
207
+ # 使用 imap_tools 的 move 方法移动邮件
208
+ self.mailbox.move(uid, destination_folder)
209
+ log.info(f'邮件{uid}已移动到{destination_folder}')
210
+ except Exception as e:
211
+ log.error(f'移动邮件{uid}到{destination_folder}失败: {e}')
212
+ raise
213
+
214
+ if __name__ == '__main__':
215
+ # ali_mail = MailClient(send_mail_address='houmingming@tyun.cn')
216
+ # mail = MailClient(send_mail_address='houmingming@tyun.cn')
217
+ # mail = MailClient(send_mail_address='houmdream@163.com', authorization_code=True)
218
+ # mail = MailClient(send_mail_address='houm01@foxmail.com', password=my1p.get_item_by_title('QQ'))
219
+ # 对于阿里云邮箱,使用兼容方法
220
+ pass
221
+ # mail.get_mail_list(attachment=True, attachment_path='/tmp')
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Any, Literal
4
+ import requests
5
+ from ..utils.response import ReturnResponse
6
+
7
+ class Meraki:
8
+ '''
9
+ Meraki Client
10
+ '''
11
+ def __init__(self, api_key: str=None, organization_id: str=None, timeout: int=10):
12
+ if not api_key:
13
+ raise ValueError("api_key is required")
14
+ if not organization_id:
15
+ raise ValueError("organization_id is required")
16
+ self.base_url = 'https://api.meraki.cn/api/v1'
17
+ self.headers = {
18
+ "Authorization": f"Bearer {api_key}",
19
+ "Accept": "application/json"
20
+ }
21
+ self.organization_id = organization_id
22
+ self.timeout = timeout
23
+
24
+ def get_networks(self, tags: list[str]=None) -> ReturnResponse:
25
+ '''
26
+ https://developer.cisco.com/meraki/api-v1/get-organization-networks/
27
+
28
+ Args:
29
+ tags (list): PROD STG NSO
30
+
31
+ Returns:
32
+ list: _description_
33
+ '''
34
+ params = {}
35
+ if tags:
36
+ params['tags[]'] = tags
37
+
38
+ r = requests.get(
39
+ f"{self.base_url}/organizations/{self.organization_id}/networks",
40
+ headers=self.headers,
41
+ params=params,
42
+ timeout=self.timeout
43
+ )
44
+ if r.status_code == 200:
45
+ return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个网络", data=r.json())
46
+ return ReturnResponse(code=1, msg=f"获取网络失败: {r.status_code} {r.text}")
47
+
48
+ def get_network_id_by_name(self, name: str) -> str | None:
49
+ '''
50
+ name 必须是唯一值,否则仅反馈第一个匹配到的 network
51
+
52
+ Args:
53
+ name (str): 网络名称, 是包含的关系, 例如实际 name 是 main office, 传入的 name 可以是 office
54
+
55
+ Returns:
56
+ str | None: 网络ID
57
+ '''
58
+ r = self.get_networks()
59
+ if r.code == 0:
60
+ for network in r.data:
61
+ if name in network['name']:
62
+ return network['id']
63
+ return None
64
+
65
+ def get_devices(self, network_ids: Any = []) -> ReturnResponse:
66
+ '''
67
+ 获取设备信息
68
+ https://developer.cisco.com/meraki/api-v1/get-organization-inventory-devices/
69
+
70
+ Args:
71
+ network_ids (list 或 str, 可选): 可以传入网络ID的列表,也可以直接传入单个网络ID字符串。默认为空列表,表示不指定网络ID。
72
+
73
+ Returns:
74
+ 返回示例(部分敏感信息已隐藏):
75
+ [
76
+ {
77
+ 'mac': '00:00:00:00:00:00',
78
+ 'serial': 'Q3AL-****-****',
79
+ 'name': 'OFFICE-AP01',
80
+ 'model': 'MR44',
81
+ 'networkId': 'L_***************0076',
82
+ 'orderNumber': None,
83
+ 'claimedAt': '2025-02-26T02:20:00.853251Z',
84
+ 'tags': ['MR44'],
85
+ 'productType': 'wireless',
86
+ 'countryCode': 'CN',
87
+ 'details': []
88
+ }
89
+ ]
90
+ '''
91
+
92
+ params = {}
93
+ if network_ids:
94
+ if isinstance(network_ids, str):
95
+ params['networkIds[]'] = [network_ids]
96
+ else:
97
+ params['networkIds[]'] = network_ids
98
+
99
+ r = requests.get(
100
+ f"{self.base_url}/organizations/{self.organization_id}/inventory/devices",
101
+ headers=self.headers,
102
+ params=params,
103
+ timeout=self.timeout
104
+ )
105
+ if r.status_code == 200:
106
+ return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个设备", data=r.json())
107
+ return ReturnResponse(code=1, msg=f"获取设备失败: {r.status_code} {r.text}")
108
+
109
+ def get_device_detail(self, serial: str) -> ReturnResponse:
110
+ '''
111
+ 获取指定序列号(serial)的 Meraki 设备详细信息
112
+
113
+ Args:
114
+ serial (str): 设备的序列号
115
+
116
+ Returns:
117
+ ReturnResponse:
118
+ code: 0 表示成功,1 表示失败,3 表示设备未添加
119
+ msg: 结果说明
120
+ data: 设备详细信息,示例(部分敏感信息已隐藏):
121
+ {
122
+ 'lat': 1.1,
123
+ 'lng': 2.2,
124
+ 'address': '(1.1, 2.2)',
125
+ 'serial': 'Q3AL-****-****',
126
+ 'mac': '00:00:00:00:00:00',
127
+ 'lanIp': '10.1.1.1',
128
+ 'tags': ['MR44'],
129
+ 'url': 'https://n3.meraki.cn/xxx',
130
+ 'networkId': '00000',
131
+ 'name': 'OFFICE-AP01',
132
+ 'details': [],
133
+ 'model': 'MR44',
134
+ 'firmware': 'wireless-31-1-6',
135
+ 'floorPlanId': '00000'
136
+ }
137
+ '''
138
+ r = requests.get(
139
+ f"{self.base_url}/devices/{serial}",
140
+ headers=self.headers,
141
+ timeout=self.timeout
142
+ )
143
+ if r.status_code == 200:
144
+ return ReturnResponse(code=0, msg=f"获取设备详情成功: {r.json()}", data=r.json())
145
+ elif r.status_code == 404:
146
+ return ReturnResponse(code=3, msg=f"设备 {serial} 还未添加过", data=None)
147
+ return ReturnResponse(code=1, msg=f"获取设备详情失败: {r.status_code} - {r.text}", data=None)
148
+
149
+ def get_device_availabilities(self, network_id: str=None,
150
+ status: Literal['online', 'offline', 'dormant', 'alerting']=None,
151
+ serial: str=None) -> ReturnResponse:
152
+ params = {}
153
+
154
+ if status:
155
+ params["statuses[]"] = status
156
+
157
+ if serial:
158
+ params["serials[]"] = serial
159
+
160
+ if network_id:
161
+ params["networkIds[]"] = network_id
162
+
163
+ r = requests.get(
164
+ url=f"{self.base_url}/organizations/{self.organization_id}/devices/availabilities",
165
+ headers=self.headers,
166
+ params=params,
167
+ timeout=self.timeout
168
+ )
169
+ return ReturnResponse(code=0, msg=f"获取设备健康状态成功", data=r.json())
pytbox/win/ad.py ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from ldap3 import Server, Connection, ALL, SUBTREE, MODIFY_REPLACE, SUBTREE, MODIFY_ADD, MODIFY_DELETE
5
+
6
+
7
+ class ADClient:
8
+ '''
9
+ _summary_
10
+ '''
11
+ def __init__(self, server, base_dn, username, password):
12
+ self.server = Server(server, get_info=ALL)
13
+ self.conn = Connection(self.server, user=username, password=password, auto_bind=True)
14
+ self.base_dn = base_dn
15
+
16
+ def list_user(self):
17
+ '''
18
+ 查询所有用户
19
+
20
+ Yields:
21
+ dict: 返回的是生成器, 字典类型
22
+ '''
23
+ # 搜索过滤条件
24
+ secarch_filter = '(objectCategory=person)' # 过滤所有用户
25
+ # SEARCH_ATTRIBUTES = ['cn', 'sAMAccountName', 'mail', 'userPrincipalName'] # 需要的用户属性
26
+ search_attributes = ["*"] # 需要的用户属性
27
+ # 搜索用户
28
+ if self.conn.search(search_base=self.base_dn, search_filter=secarch_filter, search_scope=SUBTREE, attributes=search_attributes):
29
+ for entry in self.conn.entries:
30
+ yield {k: v[0] if isinstance(v, list) else v for k, v in entry.entry_attributes_as_dict.items()}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytbox
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -13,15 +13,16 @@ Requires-Dist: loguru>=0.7.3
13
13
  Requires-Dist: chinese_calendar>=1.10.0
14
14
  Requires-Dist: click>=8.0.0
15
15
  Requires-Dist: rich>=12.0.0
16
+ Requires-Dist: jinja2>=3.0.0
17
+ Requires-Dist: toml>=0.10.0
18
+ Requires-Dist: ldap3>=2.9.1
16
19
  Provides-Extra: dev
17
20
  Requires-Dist: pytest; extra == "dev"
18
21
  Requires-Dist: black; extra == "dev"
19
22
  Requires-Dist: ruff; extra == "dev"
20
23
  Requires-Dist: python-dotenv; extra == "dev"
21
24
  Provides-Extra: cli
22
- Requires-Dist: jinja2>=3.0.0; extra == "cli"
23
25
  Requires-Dist: pyyaml>=6.0; extra == "cli"
24
- Requires-Dist: toml>=0.10.0; extra == "cli"
25
26
 
26
27
  # PytBox
27
28
 
@@ -1,4 +1,4 @@
1
- pytbox/base.py,sha256=_SpfeIiJE4xbQMsYghnMehcNzHP-mBfrKONX43b0OQk,1490
1
+ pytbox/base.py,sha256=rt2vs_h6bLXggnXp6WTywEBgq_Wg0-hdTEG5BzxPJWU,2116
2
2
  pytbox/cli.py,sha256=N775a0GK80IT2lQC2KRYtkZpIiu9UjavZmaxgNUgJhQ,160
3
3
  pytbox/dida365.py,sha256=pUMPB9AyLZpTTbaz2LbtzdEpyjvuGf4YlRrCvM5sbJo,10545
4
4
  pytbox/onepassword_connect.py,sha256=nD3xTl1ykQ4ct_dCRRF138gXCtk-phPfKYXuOn-P7Z8,3064
@@ -6,8 +6,8 @@ pytbox/onepassword_sa.py,sha256=08iUcYud3aEHuQcUsem9bWNxdXKgaxFbMy9yvtr-DZQ,6995
6
6
  pytbox/alert/alert_handler.py,sha256=FePPQS4LyGphSJ0QMv0_pLWaXxEqsRlcTKMfUjtsNfk,5048
7
7
  pytbox/alert/ping.py,sha256=g36X0U3U8ndZqfpVIcuoxJJ0X5gST3I_IwjTQC1roHA,779
8
8
  pytbox/alicloud/sls.py,sha256=UR4GdI86dCKAFI2xt_1DELu7q743dpd3xrYtuNpfC5A,4065
9
- pytbox/categraf/build_config.py,sha256=sm1knLDN2PAuOj4ljxfFCESDa2a4Orcj0x8P1-wTK1c,6323
10
- pytbox/categraf/instances.toml,sha256=1fTjEZUDGb600RPdBYof_XtkTuUUePmhPB-Tn7u_3G0,1165
9
+ pytbox/categraf/build_config.py,sha256=Php9Y0K_5RBr3jtgZuJYaBVpx0Uo-kXK9VvCNQkQR_Q,6541
10
+ pytbox/categraf/instances.toml,sha256=Nq3yQIeCa2fZw4jr1g9B9WjUexiURk0tK02SHPwTei0,1193
11
11
  pytbox/categraf/jinja2/__init__.py,sha256=Epm01j8Oujeg4Sk5GgHMvgKIZ6h3BTx-MGmuMgIjUMo,150
12
12
  pytbox/categraf/jinja2/input.cpu/cpu.toml.j2,sha256=wxpyLDNvz2ViZK-0a4EgH35Zsg82CwMwijOtT0nBfTI,92
13
13
  pytbox/categraf/jinja2/input.disk/disk.toml.j2,sha256=kOWwVF6u7x2huLVa4eBIOhC8R13DXmw0PD9o3HXwEZo,420
@@ -19,8 +19,14 @@ pytbox/categraf/jinja2/input.net/net.toml.j2,sha256=yodVoT1f7bcuNMFFmAvc7WhORUsT
19
19
  pytbox/categraf/jinja2/input.net_response/net_response.toml.j2,sha256=Qp1EOxx7TC7_cH69KqMVwbfCKrD5xbbVO2drVdkvo6s,317
20
20
  pytbox/categraf/jinja2/input.ping/ping.toml.j2,sha256=UCRy_IVoI_FDQxfsDpGS-aZJasFG1sf67ScN9U8YVIo,344
21
21
  pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2,sha256=6ax30L1sAUreojlzf0v26GzOAiheCP0Lj7n5IIjgco0,384
22
+ pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2,sha256=pamihAFrsnoKgeR6gw6i7fJYX9VnNmxiRcBdYnRsy9Y,1930
23
+ pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2,sha256=W0zYf1OZ_oil8n9Pe1Bhl3NtVJQPvLETv6GAp1sRVl0,914
22
24
  pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2,sha256=pamihAFrsnoKgeR6gw6i7fJYX9VnNmxiRcBdYnRsy9Y,1930
23
25
  pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2,sha256=W0zYf1OZ_oil8n9Pe1Bhl3NtVJQPvLETv6GAp1sRVl0,914
26
+ pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2,sha256=pamihAFrsnoKgeR6gw6i7fJYX9VnNmxiRcBdYnRsy9Y,1930
27
+ pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2,sha256=W0zYf1OZ_oil8n9Pe1Bhl3NtVJQPvLETv6GAp1sRVl0,914
28
+ pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2,sha256=pamihAFrsnoKgeR6gw6i7fJYX9VnNmxiRcBdYnRsy9Y,1930
29
+ pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2,sha256=W0zYf1OZ_oil8n9Pe1Bhl3NtVJQPvLETv6GAp1sRVl0,914
24
30
  pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2,sha256=7SCo8DSh5Uuy-7MeWw-soy6sqblK54k6K2WWSlimELk,8396
25
31
  pytbox/cli/__init__.py,sha256=5ID4-oXrMsHFcfDsQeXDYeThPOuQ1Fl2x2kHWfgfOEw,67
26
32
  pytbox/cli/main.py,sha256=S-DBp-1d0BCpvZ7jRE3bYmhKSiPpCJHFGsbdVF485BY,322
@@ -41,14 +47,18 @@ pytbox/feishu/helpers.py,sha256=jhSkHiUw4822QBXx2Jw8AksogZdakZ-3QqvC3lB3qEI,201
41
47
  pytbox/feishu/typing.py,sha256=3hWkJgOi-v2bt9viMxkyvNHsPgrbAa0aZOxsZYg2vdM,122
42
48
  pytbox/log/logger.py,sha256=7ZisXRxLb_MVbIqlYHWoTbj1EA0Z4G5SZvITlt1IKW8,7416
43
49
  pytbox/log/victorialog.py,sha256=gffEiq38adv9sC5oZeMcyKghd3SGfRuqtZOFuqHQF6E,4139
50
+ pytbox/mail/alimail.py,sha256=ap6K6kmKTjqbHlfecYBi-EZtOY1iQ8tCilP8oqUz21Q,4621
51
+ pytbox/mail/client.py,sha256=zr51tW3URhaA1EzrXZ85Go3QIHGz_0aMszmGSFcJFLk,9065
52
+ pytbox/network/meraki.py,sha256=054E3C5KzAuXs9aPalvdAOUo6Hc5aOKZSWUaVbPquy4,6112
44
53
  pytbox/utils/env.py,sha256=jO_-BKbGuDU7lIL9KYkcxGCzQwTXfxD4mIYtSAjREmI,622
45
54
  pytbox/utils/load_config.py,sha256=wNCDPLH7xet5b9pUlTz6VsBejRsJZ7LP85wWMaITBYg,3042
46
55
  pytbox/utils/ping_checker.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
47
56
  pytbox/utils/response.py,sha256=kXjlwt0WVmLRam2eu1shzX2cQ7ux4cCQryaPGYwle5g,1247
48
57
  pytbox/utils/richutils.py,sha256=OT9_q2Q1bthzB0g1GlhZVvM4ZAepJRKL6a_Vsr6vEqo,487
49
58
  pytbox/utils/timeutils.py,sha256=XbK2KB-SVi7agNqoQN7i40wysrZvrGuwebViv1Cw-Ok,20226
50
- pytbox-0.1.0.dist-info/METADATA,sha256=JdN0lGZwixetlK_LpIuAU23ABRFjtyZv40lb05DraWo,6256
51
- pytbox-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- pytbox-0.1.0.dist-info/entry_points.txt,sha256=YaTOJ2oPjOiv2SZwY0UC-UA9QS2phRH1oMvxGnxO0Js,43
53
- pytbox-0.1.0.dist-info/top_level.txt,sha256=YADgWue-Oe128ptN3J2hS3GB0Ncc5uZaSUM3e1rwswE,7
54
- pytbox-0.1.0.dist-info/RECORD,,
59
+ pytbox/win/ad.py,sha256=-3pWfL3dElz-XoO4j4M9lrgu3KJtlhrS9gCWJBpafAU,1147
60
+ pytbox-0.1.1.dist-info/METADATA,sha256=0MJBLQ3fP2OlkoGgdLPtZjmryaB8vDZ2-YbzO3f9mt4,6252
61
+ pytbox-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
62
+ pytbox-0.1.1.dist-info/entry_points.txt,sha256=YaTOJ2oPjOiv2SZwY0UC-UA9QS2phRH1oMvxGnxO0Js,43
63
+ pytbox-0.1.1.dist-info/top_level.txt,sha256=YADgWue-Oe128ptN3J2hS3GB0Ncc5uZaSUM3e1rwswE,7
64
+ pytbox-0.1.1.dist-info/RECORD,,
File without changes