ezKit 1.0.0__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.
- ezKit/__init__.py +1 -0
- ezKit/bottle.py +3809 -0
- ezKit/cipher.py +74 -0
- ezKit/database.py +168 -0
- ezKit/files.py +348 -0
- ezKit/http.py +92 -0
- ezKit/mongo.py +62 -0
- ezKit/plots.py +155 -0
- ezKit/redis.py +51 -0
- ezKit/reports.py +274 -0
- ezKit/sendemail.py +146 -0
- ezKit/utils.py +1257 -0
- ezKit/weixin.py +148 -0
- ezKit/xftp.py +194 -0
- ezKit/zabbix.py +866 -0
- ezKit-1.0.0.dist-info/LICENSE +674 -0
- ezKit-1.0.0.dist-info/METADATA +10 -0
- ezKit-1.0.0.dist-info/RECORD +20 -0
- ezKit-1.0.0.dist-info/WHEEL +5 -0
- ezKit-1.0.0.dist-info/top_level.txt +1 -0
ezKit/mongo.py
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
from loguru import logger
|
2
|
+
from pymongo import MongoClient
|
3
|
+
|
4
|
+
from . import utils
|
5
|
+
|
6
|
+
|
7
|
+
class Mongo():
|
8
|
+
|
9
|
+
mongo = MongoClient()
|
10
|
+
|
11
|
+
def __init__(self, mongo_url=None):
|
12
|
+
''' Initiation '''
|
13
|
+
if mongo_url != None:
|
14
|
+
self.mongo = MongoClient(mongo_url)
|
15
|
+
|
16
|
+
def close(self):
|
17
|
+
try:
|
18
|
+
self.mongo.close()
|
19
|
+
except Exception as e:
|
20
|
+
logger.exception(e)
|
21
|
+
|
22
|
+
def connect_test(self, debug: bool = False):
|
23
|
+
info = 'MongoDB连接测试'
|
24
|
+
try:
|
25
|
+
logger.info(f'{info}[执行]')
|
26
|
+
self.mongo.server_info()
|
27
|
+
logger.success(f'{info}[成功]')
|
28
|
+
return True
|
29
|
+
except Exception as e:
|
30
|
+
logger.error(f'{info}[失败]')
|
31
|
+
logger.exception(e) if utils.v_true(debug, bool) else next
|
32
|
+
return False
|
33
|
+
|
34
|
+
def collection(self, database, name):
|
35
|
+
return self.mongo[database][name]
|
36
|
+
|
37
|
+
def collection_insert(self, database, collection, data, drop=None):
|
38
|
+
db_collection = self.mongo[database][collection]
|
39
|
+
info = '插入数据'
|
40
|
+
try:
|
41
|
+
logger.info(f'{info}[执行]')
|
42
|
+
# 是否删除 collection
|
43
|
+
if drop == True:
|
44
|
+
# 删除 collection
|
45
|
+
db_collection.drop()
|
46
|
+
# 插入数据
|
47
|
+
if utils.v_true(data, dict):
|
48
|
+
# 插入一条数据
|
49
|
+
result = db_collection.insert_one(data)
|
50
|
+
elif utils.v_true(data, list):
|
51
|
+
# 插入多条数据
|
52
|
+
result = db_collection.insert_many(data)
|
53
|
+
else:
|
54
|
+
logger.error(f'{info}[失败]')
|
55
|
+
logger.error('数据类型错误')
|
56
|
+
return False
|
57
|
+
logger.success(f'{info}[成功]')
|
58
|
+
return result
|
59
|
+
except Exception as e:
|
60
|
+
logger.error(f'{info}[失败]')
|
61
|
+
logger.exception(e)
|
62
|
+
return False
|
ezKit/plots.py
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
import matplotlib.patches as mpatches
|
2
|
+
import matplotlib.pyplot as plt
|
3
|
+
import numpy as np
|
4
|
+
import pandas as pd
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
|
8
|
+
def bar(data={}, index=[], image={}, **kwargs):
|
9
|
+
'''
|
10
|
+
标准条形图
|
11
|
+
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html
|
12
|
+
https://matplotlib.org/stable/api/pyplot_summary.html
|
13
|
+
'''
|
14
|
+
try:
|
15
|
+
_df = pd.DataFrame(data, index=index)
|
16
|
+
'''
|
17
|
+
title: 标题
|
18
|
+
kind: 类型
|
19
|
+
figsize: 图片大小, 以 100 为基数, 这里输出的图片为 2000 x 2000
|
20
|
+
width: 图形条宽度
|
21
|
+
dpi: 清晰度
|
22
|
+
bbox_inches='tight' 去除图片周边空白
|
23
|
+
'''
|
24
|
+
_ax = _df.plot(title=image.get('title'), kind=image.get('kind', 'bar'), figsize=image.get('size', (10, 10)), width=image.get('width', 0.8))
|
25
|
+
_ax.set_xlim(image.get('xlim'))
|
26
|
+
_ax.set_ylim(image.get('ylim'))
|
27
|
+
_ax.figure.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
28
|
+
return True
|
29
|
+
|
30
|
+
except Exception as e:
|
31
|
+
logger.exception(e)
|
32
|
+
return False
|
33
|
+
|
34
|
+
def bar_cover(data=[], index=[], image={}, **kwargs):
|
35
|
+
'''
|
36
|
+
重叠条形图
|
37
|
+
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html
|
38
|
+
https://matplotlib.org/stable/api/pyplot_summary.html
|
39
|
+
df.plot.bar() 和 df.plot.barh() 中 stacked=True 的效果是叠加, 即所有图层堆积拼接在一起
|
40
|
+
这里需要的效果是图层重叠, 即按从大到小的顺序依次重叠
|
41
|
+
但是 pandas 没有好的处理方法, 为了实现重叠效果, 以下使用设置 ax 处理
|
42
|
+
'''
|
43
|
+
|
44
|
+
try:
|
45
|
+
|
46
|
+
# 创建数据实例
|
47
|
+
_df = pd.DataFrame({item['key']: item['data'] for item in data}, index)
|
48
|
+
|
49
|
+
'''
|
50
|
+
设置图形类型、颜色、宽度
|
51
|
+
注意顺序: 从大到小排列
|
52
|
+
因为图层是叠加的, 所以后面的图层会覆盖前面的图层
|
53
|
+
如果后面图层的较大, 就会前面较小的图层, 所以把图层较大的放在前面, 图层较小的放在后面
|
54
|
+
_, ax = plt.subplots()
|
55
|
+
df.MAX.plot(kind='barh', ax=ax, color='#E74C3C', width=0.8)
|
56
|
+
df.MID.plot(kind='barh', ax=ax, color='#3498DB', width=0.8)
|
57
|
+
df.MIN.plot(kind='barh', ax=ax, color='#2ECC71', width=0.8)
|
58
|
+
'''
|
59
|
+
_, _ax = plt.subplots()
|
60
|
+
for i in data:
|
61
|
+
_df[i['key']].plot(kind=i.get('kind', 'bar'), ax=_ax, color=i.get('color'), width=i.get('width', 0.8))
|
62
|
+
|
63
|
+
'''
|
64
|
+
设置 Label
|
65
|
+
即图片右上角的说明信息, 这里也有顺序, 会按照 handles 中的顺序显示
|
66
|
+
https://stackoverflow.com/a/69897921
|
67
|
+
patch_max = mpatches.Patch(color='#E74C3C', label='Max')
|
68
|
+
patch_mid = mpatches.Patch(color='#3498DB', label='Mid')
|
69
|
+
patch_min = mpatches.Patch(color='#2ECC71', label='Min')
|
70
|
+
plt.legend(handles=[patch_max, patch_mid, patch_min])
|
71
|
+
'''
|
72
|
+
plt.legend(handles=[mpatches.Patch(color=i.get('color'), label=i.get('label')) for i in data])
|
73
|
+
|
74
|
+
# 设置标题
|
75
|
+
plt.title(image.get('title'))
|
76
|
+
|
77
|
+
# 设置上下限
|
78
|
+
plt.xlim(image.get('xlim'))
|
79
|
+
plt.ylim(image.get('ylim'))
|
80
|
+
|
81
|
+
# 创建图片实例
|
82
|
+
_fig = plt.gcf()
|
83
|
+
|
84
|
+
# 设置图片大小
|
85
|
+
# https://www.zhihu.com/question/37221233
|
86
|
+
_fig.set_size_inches(image.get('size', (10, 10)))
|
87
|
+
|
88
|
+
# 保存图片
|
89
|
+
# bbox_inches='tight' 去除图片周边空白
|
90
|
+
_fig.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
91
|
+
|
92
|
+
# Close a figure window
|
93
|
+
plt.close()
|
94
|
+
|
95
|
+
# Return
|
96
|
+
return True
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
logger.exception(e)
|
100
|
+
return False
|
101
|
+
|
102
|
+
def bar_extend(data=[], index=[], image={}, **kwargs):
|
103
|
+
'''
|
104
|
+
扩展的条形图
|
105
|
+
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.plot.html
|
106
|
+
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bar.html
|
107
|
+
https://matplotlib.org/stable/gallery/lines_bars_and_markers/barchart.html
|
108
|
+
https://stackoverflow.com/a/68107816
|
109
|
+
'''
|
110
|
+
try:
|
111
|
+
|
112
|
+
_x = np.arange(len(index))
|
113
|
+
|
114
|
+
_bar_width = image.get('width', 0.8)
|
115
|
+
|
116
|
+
_fig, _ax = plt.subplots()
|
117
|
+
|
118
|
+
_rects = _ax.bar(_x, data, _bar_width, label=image.get('label'), color=image.get('color'))
|
119
|
+
|
120
|
+
# Set base
|
121
|
+
_ax.set_title(image.get('title'))
|
122
|
+
_ax.set_xlabel(image.get('xlabel'))
|
123
|
+
_ax.set_ylabel(image.get('ylabel'))
|
124
|
+
_ax.set_xticks(_x, index)
|
125
|
+
|
126
|
+
# Set view limits
|
127
|
+
_ax.set_xlim(image.get('xlim'))
|
128
|
+
_ax.set_ylim(image.get('ylim'))
|
129
|
+
|
130
|
+
# 设置图例
|
131
|
+
_ax.legend()
|
132
|
+
|
133
|
+
# bar 顶部显示 value
|
134
|
+
_ax.bar_label(_rects, padding=3)
|
135
|
+
|
136
|
+
# 自动调整参数, 使图形适合图形区域
|
137
|
+
_fig.tight_layout()
|
138
|
+
|
139
|
+
# 图片大小
|
140
|
+
# 默认 300px 为 1 个单位
|
141
|
+
# 默认 1920px * 1440px, 即 6.4 * 4.8
|
142
|
+
_fig.set_size_inches(image.get('size', (6.4, 4.8)))
|
143
|
+
|
144
|
+
# 生成图片文件
|
145
|
+
# bbox_inches='tight' 去除图片周边空白
|
146
|
+
_fig.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
147
|
+
|
148
|
+
# Close a figure window
|
149
|
+
plt.close()
|
150
|
+
|
151
|
+
return True
|
152
|
+
|
153
|
+
except Exception as e:
|
154
|
+
logger.exception(e)
|
155
|
+
return False
|
ezKit/redis.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import redis as RedisClient
|
2
|
+
from loguru import logger
|
3
|
+
|
4
|
+
from . import utils
|
5
|
+
|
6
|
+
|
7
|
+
class Redis(object):
|
8
|
+
|
9
|
+
# https://redis.readthedocs.io/en/stable/_modules/redis/client.html#Redis
|
10
|
+
# https://github.com/redis/redis-py#client-classes-redis-and-strictredis
|
11
|
+
# redis-py 3.0 drops support for the legacy Redis client class.
|
12
|
+
# StrictRedis has been renamed to Redis and an alias named StrictRedis is provided so that users previously using StrictRedis can continue to run unchanged.
|
13
|
+
# redis-py 3.0 之后只有一个 Redis, StrictRedis 是 Redis 的别名
|
14
|
+
# 这里修改以下参数: host, port, socket_timeout, socket_connect_timeout, charset
|
15
|
+
redis = RedisClient.Redis()
|
16
|
+
|
17
|
+
def __init__(self, arguments=None):
|
18
|
+
'''Initiation'''
|
19
|
+
if utils.v_true(arguments, str):
|
20
|
+
self.redis = RedisClient.from_url(arguments)
|
21
|
+
elif utils.v_true(arguments, dict):
|
22
|
+
self.redis = RedisClient.Redis(**arguments)
|
23
|
+
else:
|
24
|
+
pass
|
25
|
+
|
26
|
+
def connect_test(self):
|
27
|
+
info = 'Redis连接测试'
|
28
|
+
try:
|
29
|
+
logger.info(f'{info}......')
|
30
|
+
self.redis.ping()
|
31
|
+
logger.success(f'{info}[成功]')
|
32
|
+
return True
|
33
|
+
except Exception as e:
|
34
|
+
logger.error(f'{info}[失败]')
|
35
|
+
logger.exception(e)
|
36
|
+
return False
|
37
|
+
|
38
|
+
def flush(self, all=None):
|
39
|
+
info = 'Redis数据清理'
|
40
|
+
try:
|
41
|
+
logger.info(f'{info}......')
|
42
|
+
if all == True:
|
43
|
+
self.redis.flushall()
|
44
|
+
else:
|
45
|
+
self.redis.flushdb()
|
46
|
+
logger.success(f'{info}[成功]')
|
47
|
+
return True
|
48
|
+
except Exception as e:
|
49
|
+
logger.error(f'{info}[失败]')
|
50
|
+
logger.exception(e)
|
51
|
+
return False
|
ezKit/reports.py
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
from . import files, utils
|
2
|
+
|
3
|
+
'''
|
4
|
+
reports.logout()
|
5
|
+
|
6
|
+
生成报告完成以后, 退出 Zabbix
|
7
|
+
|
8
|
+
return _image
|
9
|
+
|
10
|
+
返回图片信息, 发邮件时使用
|
11
|
+
'''
|
12
|
+
|
13
|
+
class Reports(object):
|
14
|
+
|
15
|
+
# Zabbix Instance
|
16
|
+
_zabbix = None
|
17
|
+
|
18
|
+
# Files Instance
|
19
|
+
_files = None
|
20
|
+
|
21
|
+
# Image Object
|
22
|
+
_image_dir = '.'
|
23
|
+
_image_name_prefix = 'image'
|
24
|
+
|
25
|
+
def __init__(self, zabbix, markdown_file, html_file, image_dir, image_name_prefix):
|
26
|
+
''' Initiation '''
|
27
|
+
self._zabbix = zabbix
|
28
|
+
self._files = files.files(markdown_file, html_file)
|
29
|
+
self._image_dir = image_dir
|
30
|
+
self._image_name_prefix = image_name_prefix
|
31
|
+
|
32
|
+
def generic(
|
33
|
+
self,
|
34
|
+
pieces=None,
|
35
|
+
hosts=None,
|
36
|
+
time_from=None,
|
37
|
+
time_till=None,
|
38
|
+
item_keys=None,
|
39
|
+
data_type=None,
|
40
|
+
data_proc=None,
|
41
|
+
title=None,
|
42
|
+
description=None,
|
43
|
+
number_type=None,
|
44
|
+
number_unit=None,
|
45
|
+
number_handling=None,
|
46
|
+
table_header_title='Host',
|
47
|
+
table_header_data='Data',
|
48
|
+
sort_by_ip=None,
|
49
|
+
image_cid=None,
|
50
|
+
image_label=None,
|
51
|
+
image_kind=None
|
52
|
+
):
|
53
|
+
|
54
|
+
_history = []
|
55
|
+
|
56
|
+
if utils.v_true(item_keys, str):
|
57
|
+
_history = utils.retry(10, self._zabbix.get_history_by_item_key, hosts, time_from, time_till, item_keys, data_type)
|
58
|
+
|
59
|
+
if utils.v_true(item_keys, list):
|
60
|
+
for _item_key in item_keys:
|
61
|
+
_history_slice = utils.retry(10, self._zabbix.get_history_by_item_key, hosts, time_from, time_till, _item_key, data_type)
|
62
|
+
if _history_slice != None:
|
63
|
+
if callable(data_proc) == True:
|
64
|
+
_history_slice = data_proc(_history_slice)
|
65
|
+
_history += _history_slice
|
66
|
+
|
67
|
+
if _history != None:
|
68
|
+
|
69
|
+
_files_func = self._files.multiple_pieces
|
70
|
+
|
71
|
+
if pieces == 'single':
|
72
|
+
|
73
|
+
_files_func = self._files.single_piece
|
74
|
+
|
75
|
+
for _data in _history:
|
76
|
+
if len(_data['history']) > 0:
|
77
|
+
_history_last = max(_data['history'], key=lambda i: i['clock'])
|
78
|
+
_data['history'] = _history_last
|
79
|
+
|
80
|
+
_image = {
|
81
|
+
'cid': '{}'.format(image_cid),
|
82
|
+
'path': '{}/{}_{}.png'.format(self._image_dir, self._image_name_prefix, image_cid),
|
83
|
+
'label': image_label,
|
84
|
+
'kind': image_kind
|
85
|
+
}
|
86
|
+
|
87
|
+
_files_result = _files_func(
|
88
|
+
title=title,
|
89
|
+
description=description,
|
90
|
+
data=_history,
|
91
|
+
image=_image,
|
92
|
+
number_type=number_type,
|
93
|
+
number_unit=number_unit,
|
94
|
+
number_handling=number_handling,
|
95
|
+
table_header_title=table_header_title,
|
96
|
+
table_header_data=table_header_data,
|
97
|
+
sort_by_ip=sort_by_ip
|
98
|
+
)
|
99
|
+
|
100
|
+
if _files_result == True:
|
101
|
+
return _image
|
102
|
+
else:
|
103
|
+
return None
|
104
|
+
|
105
|
+
else:
|
106
|
+
|
107
|
+
return None
|
108
|
+
|
109
|
+
def system_interface(self, hosts, interfaces, time_from, time_till, direction='in'):
|
110
|
+
''' System Interface '''
|
111
|
+
|
112
|
+
_direction_name = 'Received'
|
113
|
+
_direction_alias = 'received'
|
114
|
+
_direction_info = '接收数据'
|
115
|
+
|
116
|
+
if direction == 'out':
|
117
|
+
_direction_name = 'Sent'
|
118
|
+
_direction_alias = 'sent'
|
119
|
+
_direction_info = '发送数据'
|
120
|
+
|
121
|
+
_history = utils.retry(10, self._zabbix.get_history_by_interface, hosts, interfaces, time_from, time_till, direction)
|
122
|
+
|
123
|
+
if utils.v_true(_history, list):
|
124
|
+
|
125
|
+
_image = {
|
126
|
+
'cid': 'system_interface_'.format(_direction_alias),
|
127
|
+
'path': '{}/{}_system_interface_{}.png'.format(self._image_dir, self._image_name_prefix, _direction_alias)
|
128
|
+
}
|
129
|
+
|
130
|
+
_ = self._files.multiple_pieces(
|
131
|
+
title='System Interface {}'.format(_direction_name),
|
132
|
+
description='说明: 网卡**{}**的速度'.format(_direction_info),
|
133
|
+
data=_history,
|
134
|
+
image=_image,
|
135
|
+
number_type='int',
|
136
|
+
number_unit='Kbps',
|
137
|
+
number_handling=utils.divisor_1000,
|
138
|
+
sort_by_ip=True
|
139
|
+
)
|
140
|
+
|
141
|
+
if _ == True:
|
142
|
+
return _image
|
143
|
+
else:
|
144
|
+
return None
|
145
|
+
|
146
|
+
else:
|
147
|
+
|
148
|
+
return None
|
149
|
+
|
150
|
+
def base_system(self, hosts, time_from, time_till, interfaces=None):
|
151
|
+
|
152
|
+
# Images
|
153
|
+
_images = []
|
154
|
+
|
155
|
+
# System CPU utilization
|
156
|
+
_image = self.generic(
|
157
|
+
hosts=hosts,
|
158
|
+
time_from=time_from,
|
159
|
+
time_till=time_till,
|
160
|
+
item_keys='system.cpu.util',
|
161
|
+
data_type=0,
|
162
|
+
title='System CPU utilization',
|
163
|
+
description='说明: 系统 CPU 使用率',
|
164
|
+
number_type='float',
|
165
|
+
number_unit='%',
|
166
|
+
sort_by_ip=True,
|
167
|
+
image_cid='system_cpu_utilization'
|
168
|
+
)
|
169
|
+
if _image != None:
|
170
|
+
_images.append(_image)
|
171
|
+
|
172
|
+
# System Memory utilization
|
173
|
+
_image = self.generic(
|
174
|
+
hosts=hosts,
|
175
|
+
time_from=time_from,
|
176
|
+
time_till=time_till,
|
177
|
+
item_keys='vm.memory.utilization',
|
178
|
+
data_type=0,
|
179
|
+
title='System Memory utilization',
|
180
|
+
description='说明: 系统 内存 使用率',
|
181
|
+
number_type='float',
|
182
|
+
number_unit='%',
|
183
|
+
sort_by_ip=True,
|
184
|
+
image_cid='system_memory_utilization'
|
185
|
+
)
|
186
|
+
if _image != None:
|
187
|
+
_images.append(_image)
|
188
|
+
|
189
|
+
# System root partition utilization
|
190
|
+
_image = self.generic(
|
191
|
+
pieces='single',
|
192
|
+
hosts=hosts,
|
193
|
+
time_from=time_from,
|
194
|
+
time_till=time_till,
|
195
|
+
item_keys='vfs.fs.size[/,pused]',
|
196
|
+
data_type=0,
|
197
|
+
title='System root partition utilization',
|
198
|
+
description='说明: 系统 根目录(/) 使用率',
|
199
|
+
number_type='float',
|
200
|
+
number_unit='%',
|
201
|
+
table_header_data='Used',
|
202
|
+
sort_by_ip=True,
|
203
|
+
image_cid='system_root_partition_utilization',
|
204
|
+
image_label='Used (%)',
|
205
|
+
image_kind='barh'
|
206
|
+
)
|
207
|
+
if _image != None:
|
208
|
+
_images.append(_image)
|
209
|
+
|
210
|
+
if interfaces != None:
|
211
|
+
|
212
|
+
# System Interface Received
|
213
|
+
_image = self.system_interface(hosts, interfaces, time_from, time_till, 'in')
|
214
|
+
if _image != None:
|
215
|
+
_images.append(_image)
|
216
|
+
|
217
|
+
# System Interface Sent
|
218
|
+
_image = self.system_interface(hosts, interfaces, time_from, time_till, 'out')
|
219
|
+
if _image != None:
|
220
|
+
_images.append(_image)
|
221
|
+
|
222
|
+
return _images
|
223
|
+
|
224
|
+
def base_generic(self, hosts, time_from, time_till, items=None):
|
225
|
+
|
226
|
+
# Images
|
227
|
+
_images = []
|
228
|
+
|
229
|
+
if items != None:
|
230
|
+
|
231
|
+
for _item in items:
|
232
|
+
|
233
|
+
# CPU utilization
|
234
|
+
_image = self.generic(
|
235
|
+
hosts=hosts,
|
236
|
+
time_from=time_from,
|
237
|
+
time_till=time_till,
|
238
|
+
item_keys=_item['keys'][0],
|
239
|
+
data_type=_item['types'][0],
|
240
|
+
data_proc=_item.get('data_proc'),
|
241
|
+
title='{} CPU utilization'.format(_item['name']),
|
242
|
+
description='说明: {} CPU 使用率'.format(_item['name']),
|
243
|
+
number_type='float',
|
244
|
+
number_unit='%',
|
245
|
+
table_header_title=_item.get('table_header_title', 'Host'),
|
246
|
+
table_header_data=_item.get('table_header_data', 'Data'),
|
247
|
+
sort_by_ip=True,
|
248
|
+
image_cid='{}_cpu_utilization'.format(_item['alias'])
|
249
|
+
)
|
250
|
+
if _image != None:
|
251
|
+
_images.append(_image)
|
252
|
+
|
253
|
+
# Memory used (RSS)
|
254
|
+
_image = self.generic(
|
255
|
+
hosts=hosts,
|
256
|
+
time_from=time_from,
|
257
|
+
time_till=time_till,
|
258
|
+
item_keys=_item['keys'][1],
|
259
|
+
data_type=_item['types'][1],
|
260
|
+
data_proc=_item.get('data_proc'),
|
261
|
+
title='{} Memory used (RSS)'.format(_item['name']),
|
262
|
+
description='说明: {} 内存 使用量'.format(_item['name']),
|
263
|
+
number_type='int',
|
264
|
+
number_unit='MB',
|
265
|
+
number_handling=utils.divisor_square_1024,
|
266
|
+
table_header_title=_item.get('table_header_title', 'Host'),
|
267
|
+
table_header_data=_item.get('table_header_data', 'Data'),
|
268
|
+
sort_by_ip=True,
|
269
|
+
image_cid='{}_memory_used_rss'.format(_item['alias'])
|
270
|
+
)
|
271
|
+
if _image != None:
|
272
|
+
_images.append(_image)
|
273
|
+
|
274
|
+
return _images
|
ezKit/sendemail.py
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
'''
|
2
|
+
https://stackoverflow.com/questions/882712/sending-html-email-using-python
|
3
|
+
'''
|
4
|
+
import smtplib
|
5
|
+
from email.header import Header
|
6
|
+
from email.mime.image import MIMEImage
|
7
|
+
from email.mime.multipart import MIMEMultipart
|
8
|
+
from email.mime.text import MIMEText
|
9
|
+
from email.utils import formataddr, parseaddr
|
10
|
+
|
11
|
+
from loguru import logger
|
12
|
+
|
13
|
+
from . import utils
|
14
|
+
|
15
|
+
|
16
|
+
def format_parse(s):
|
17
|
+
_name, _addr = parseaddr(s)
|
18
|
+
return formataddr((Header(_name, 'utf-8').encode(), _addr))
|
19
|
+
|
20
|
+
def related_html(smtp=None, sender=None, recipients=None, subject=None, html_file=None, images=None):
|
21
|
+
'''
|
22
|
+
smtp SMTP信息
|
23
|
+
|
24
|
+
server SMTP地址
|
25
|
+
port SMTP端口
|
26
|
+
ssl 是否使用SSL
|
27
|
+
|
28
|
+
sender 发件人信息
|
29
|
+
|
30
|
+
name 发件人名称
|
31
|
+
address 发件人邮箱地址
|
32
|
+
password 发件人邮箱密码(SMTP)
|
33
|
+
|
34
|
+
recipients 收件人列表
|
35
|
+
subject 邮件主题
|
36
|
+
html_file HTML文件
|
37
|
+
|
38
|
+
images 图片列表(可选)
|
39
|
+
|
40
|
+
cid 图片CID
|
41
|
+
path 图片路径
|
42
|
+
'''
|
43
|
+
|
44
|
+
# 参数判断
|
45
|
+
# match True:
|
46
|
+
# case True if utils.vTrue(smtp, dict) == False:
|
47
|
+
# logger.error('ERROR!! {} is not dictionary or none'.format('smtp'))
|
48
|
+
# return False
|
49
|
+
# case True if utils.vTrue(sender, dict) == False:
|
50
|
+
# logger.error('ERROR!! {} is not dictionary or none'.format('sender'))
|
51
|
+
# return False
|
52
|
+
# case True if (utils.vTrue(recipients, str) == False) and (utils.vTrue(recipients, list) == False):
|
53
|
+
# logger.error('ERROR!! {} is not list or none'.format('recipients'))
|
54
|
+
# return False
|
55
|
+
# case True if utils.vTrue(subject, str) == False:
|
56
|
+
# logger.error('ERROR!! {} is not string or none'.format('subject'))
|
57
|
+
# return False
|
58
|
+
# case True if utils.vTrue(html_file, str) == False:
|
59
|
+
# logger.error('ERROR!! {} is not string or none'.format('html_file'))
|
60
|
+
# return False
|
61
|
+
|
62
|
+
logger.success('sendemail start')
|
63
|
+
|
64
|
+
try:
|
65
|
+
|
66
|
+
_message = MIMEMultipart('related')
|
67
|
+
|
68
|
+
with open(html_file, 'r') as _html_file:
|
69
|
+
|
70
|
+
_message.attach(MIMEText(_html_file.read(), 'html', 'utf-8'))
|
71
|
+
|
72
|
+
if utils.v_true(images, list):
|
73
|
+
|
74
|
+
for _image in images:
|
75
|
+
|
76
|
+
try:
|
77
|
+
|
78
|
+
if utils.check_file_type(_image.get('path', ''), 'file'):
|
79
|
+
|
80
|
+
'''
|
81
|
+
添加图片
|
82
|
+
with open(image_path, 'rb') as image_file:
|
83
|
+
mime_image = MIMEImage(image_file.read())
|
84
|
+
# Define the image's ID as referenced above
|
85
|
+
mime_image.add_header('Content-ID', '<CID>')
|
86
|
+
message.attach(mime_image)
|
87
|
+
'''
|
88
|
+
|
89
|
+
with open(_image['path'], 'rb') as _image_file:
|
90
|
+
_mime_image = MIMEImage(_image_file.read())
|
91
|
+
_mime_image.add_header('Content-ID', '<{}>'.format(_image['cid']))
|
92
|
+
_message.attach(_mime_image)
|
93
|
+
|
94
|
+
else:
|
95
|
+
|
96
|
+
next
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
logger.exception(e)
|
100
|
+
next
|
101
|
+
|
102
|
+
# 发件人
|
103
|
+
_message['From'] = formataddr([sender.get('name'), sender.get('address')])
|
104
|
+
|
105
|
+
# 收件人
|
106
|
+
if utils.v_true(recipients, str):
|
107
|
+
_message['To'] = format_parse(recipients)
|
108
|
+
elif utils.v_true(recipients, list):
|
109
|
+
_message['To'] = ", ".join(list(map(format_parse, recipients)))
|
110
|
+
else:
|
111
|
+
logger.error('recipients error')
|
112
|
+
return False
|
113
|
+
|
114
|
+
# 主题
|
115
|
+
_message['Subject'] = subject
|
116
|
+
|
117
|
+
'''
|
118
|
+
发送邮件
|
119
|
+
|
120
|
+
SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
|
121
|
+
|
122
|
+
to_addrs = sender_to + sender_cc
|
123
|
+
https://docs.python.org/3/library/smtplib.html#smtplib.SMTP.sendmail
|
124
|
+
https://gist.github.com/AO8/c5a6f747eeeca02351152ae8dc79b537
|
125
|
+
'''
|
126
|
+
|
127
|
+
if smtp.get('ssl', False) == True:
|
128
|
+
|
129
|
+
with smtplib.SMTP_SSL(smtp.get('server'), smtp.get('port')) as _smtp:
|
130
|
+
_smtp.login(sender.get('address'), sender.get('password'))
|
131
|
+
_smtp.sendmail(sender.get('address'), recipients, _message.as_string())
|
132
|
+
|
133
|
+
else:
|
134
|
+
|
135
|
+
with smtplib.SMTP(smtp.get('server'), smtp.get('port')) as _smtp:
|
136
|
+
_smtp.login(sender.get('address'), sender.get('password'))
|
137
|
+
_smtp.sendmail(sender.get('address'), recipients, _message.as_string())
|
138
|
+
|
139
|
+
logger.success('sendemail success')
|
140
|
+
|
141
|
+
return True
|
142
|
+
|
143
|
+
except Exception as e:
|
144
|
+
logger.error('sendemail error')
|
145
|
+
logger.exception(e)
|
146
|
+
return False
|