pytbox 0.0.1__py3-none-any.whl → 0.3.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/alert/alert_handler.py +139 -0
- pytbox/alert/ping.py +24 -0
- pytbox/alicloud/sls.py +9 -14
- pytbox/base.py +121 -0
- pytbox/categraf/build_config.py +143 -0
- pytbox/categraf/instances.toml +39 -0
- pytbox/categraf/jinja2/__init__.py +6 -0
- pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +5 -0
- pytbox/categraf/jinja2/input.disk/disk.toml.j2 +11 -0
- pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +6 -0
- pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +12 -0
- pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +9 -0
- pytbox/categraf/jinja2/input.mem/mem.toml.j2 +5 -0
- pytbox/categraf/jinja2/input.net/net.toml.j2 +11 -0
- pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +9 -0
- pytbox/categraf/jinja2/input.ping/ping.toml.j2 +11 -0
- pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +12 -0
- pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +211 -0
- pytbox/cli/__init__.py +7 -0
- pytbox/cli/categraf/__init__.py +7 -0
- pytbox/cli/categraf/commands.py +55 -0
- pytbox/cli/commands/vm.py +22 -0
- pytbox/cli/common/__init__.py +6 -0
- pytbox/cli/common/options.py +42 -0
- pytbox/cli/common/utils.py +269 -0
- pytbox/cli/formatters/__init__.py +7 -0
- pytbox/cli/formatters/output.py +155 -0
- pytbox/cli/main.py +24 -0
- pytbox/cli.py +9 -0
- pytbox/database/mongo.py +99 -0
- pytbox/database/victoriametrics.py +404 -0
- pytbox/dida365.py +11 -17
- pytbox/excel.py +64 -0
- pytbox/feishu/endpoints.py +12 -9
- pytbox/{logger.py → log/logger.py} +78 -30
- pytbox/{victorialog.py → log/victorialog.py} +2 -2
- pytbox/mail/alimail.py +142 -0
- pytbox/mail/client.py +171 -0
- pytbox/mail/mail_detail.py +30 -0
- pytbox/mingdao.py +164 -0
- pytbox/network/meraki.py +537 -0
- pytbox/notion.py +731 -0
- pytbox/pyjira.py +612 -0
- pytbox/utils/cronjob.py +79 -0
- pytbox/utils/env.py +2 -2
- pytbox/utils/load_config.py +132 -0
- pytbox/utils/load_vm_devfile.py +45 -0
- pytbox/utils/response.py +1 -1
- pytbox/utils/richutils.py +31 -0
- pytbox/utils/timeutils.py +479 -14
- pytbox/vmware.py +120 -0
- pytbox/win/ad.py +30 -0
- {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/METADATA +13 -3
- pytbox-0.3.1.dist-info/RECORD +72 -0
- pytbox-0.3.1.dist-info/entry_points.txt +2 -0
- pytbox/common/base.py +0 -0
- pytbox/victoriametrics.py +0 -37
- pytbox-0.0.1.dist-info/RECORD +0 -21
- {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/WHEEL +0 -0
- {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/top_level.txt +0 -0
pytbox/network/meraki.py
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
import requests
|
|
5
|
+
from ..utils.response import ReturnResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Meraki:
|
|
9
|
+
'''
|
|
10
|
+
Meraki Client
|
|
11
|
+
'''
|
|
12
|
+
def __init__(self, api_key: str=None, organization_id: str=None, timeout: int=10, region: Literal['global', 'china']='china'):
|
|
13
|
+
if not api_key:
|
|
14
|
+
raise ValueError("api_key is required")
|
|
15
|
+
if not organization_id:
|
|
16
|
+
raise ValueError("organization_id is required")
|
|
17
|
+
if region == 'china':
|
|
18
|
+
self.base_url = 'https://api.meraki.cn/api/v1'
|
|
19
|
+
else:
|
|
20
|
+
self.base_url = 'https://api.meraki.com/api/v1'
|
|
21
|
+
|
|
22
|
+
self.headers = {
|
|
23
|
+
"Authorization": f"Bearer {api_key}",
|
|
24
|
+
"Accept": "application/json"
|
|
25
|
+
}
|
|
26
|
+
self.organization_id = organization_id
|
|
27
|
+
self.timeout = timeout
|
|
28
|
+
|
|
29
|
+
def get_organizations(self) -> ReturnResponse:
|
|
30
|
+
'''
|
|
31
|
+
https://developer.cisco.com/meraki/api-v1/get-organizations/
|
|
32
|
+
'''
|
|
33
|
+
r = requests.get(
|
|
34
|
+
f"{self.base_url}/organizations",
|
|
35
|
+
headers=self.headers,
|
|
36
|
+
timeout=self.timeout
|
|
37
|
+
)
|
|
38
|
+
if r.status_code == 200:
|
|
39
|
+
return ReturnResponse(code=0, msg=f"获取组织成功", data=r.json())
|
|
40
|
+
return ReturnResponse(code=1, msg=f"获取组织失败: {r.status_code} {r.text}")
|
|
41
|
+
|
|
42
|
+
def get_api_requests(self, timespan: int=5*60) -> ReturnResponse:
|
|
43
|
+
|
|
44
|
+
params = {}
|
|
45
|
+
params['timespan'] = timespan
|
|
46
|
+
|
|
47
|
+
r = requests.get(
|
|
48
|
+
url=f"{self.base_url}/organizations/{self.organization_id}/apiRequests",
|
|
49
|
+
headers=self.headers,
|
|
50
|
+
params=params,
|
|
51
|
+
timeout=self.timeout
|
|
52
|
+
)
|
|
53
|
+
if r.status_code == 200:
|
|
54
|
+
return ReturnResponse(code=0, msg='获取 API 请求数量成功', data=r.json())
|
|
55
|
+
return ReturnResponse(code=1, msg=f"获取 API 请求失败: {r.status_code} - {r.text}", data=None)
|
|
56
|
+
|
|
57
|
+
def get_networks(self, tags: list[str]=None) -> ReturnResponse:
|
|
58
|
+
'''
|
|
59
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-networks/
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
tags (list): PROD STG NSO
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
list: _description_
|
|
66
|
+
'''
|
|
67
|
+
params = {}
|
|
68
|
+
if tags:
|
|
69
|
+
params['tags[]'] = tags
|
|
70
|
+
|
|
71
|
+
r = requests.get(
|
|
72
|
+
f"{self.base_url}/organizations/{self.organization_id}/networks",
|
|
73
|
+
headers=self.headers,
|
|
74
|
+
params=params,
|
|
75
|
+
timeout=self.timeout
|
|
76
|
+
)
|
|
77
|
+
if r.status_code == 200:
|
|
78
|
+
return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个网络", data=r.json())
|
|
79
|
+
return ReturnResponse(code=1, msg=f"获取网络失败: {r.status_code} {r.text}")
|
|
80
|
+
|
|
81
|
+
def get_network_id_by_name(self, name: str) -> str | None:
|
|
82
|
+
'''
|
|
83
|
+
name 必须是唯一值,否则仅反馈第一个匹配到的 network
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name (str): 网络名称, 是包含的关系, 例如实际 name 是 main office, 传入的 name 可以是 office
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
str | None: 网络ID
|
|
90
|
+
'''
|
|
91
|
+
r = self.get_networks()
|
|
92
|
+
if r.code == 0:
|
|
93
|
+
for network in r.data:
|
|
94
|
+
if name in network['name']:
|
|
95
|
+
return network['id']
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def get_devices(self, network_ids: Any = []) -> ReturnResponse:
|
|
99
|
+
'''
|
|
100
|
+
获取设备信息
|
|
101
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-inventory-devices/
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
network_ids (list 或 str, 可选): 可以传入网络ID的列表,也可以直接传入单个网络ID字符串。默认为空列表,表示不指定网络ID。
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
返回示例(部分敏感信息已隐藏):
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
'mac': '00:00:00:00:00:00',
|
|
111
|
+
'serial': 'Q3AL-****-****',
|
|
112
|
+
'name': 'OFFICE-AP01',
|
|
113
|
+
'model': 'MR44',
|
|
114
|
+
'networkId': 'L_***************0076',
|
|
115
|
+
'orderNumber': None,
|
|
116
|
+
'claimedAt': '2025-02-26T02:20:00.853251Z',
|
|
117
|
+
'tags': ['MR44'],
|
|
118
|
+
'productType': 'wireless',
|
|
119
|
+
'countryCode': 'CN',
|
|
120
|
+
'details': []
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
'''
|
|
124
|
+
|
|
125
|
+
params = {}
|
|
126
|
+
if network_ids:
|
|
127
|
+
if isinstance(network_ids, str):
|
|
128
|
+
params['networkIds[]'] = [network_ids]
|
|
129
|
+
else:
|
|
130
|
+
params['networkIds[]'] = network_ids
|
|
131
|
+
|
|
132
|
+
r = requests.get(
|
|
133
|
+
f"{self.base_url}/organizations/{self.organization_id}/inventory/devices",
|
|
134
|
+
headers=self.headers,
|
|
135
|
+
params=params,
|
|
136
|
+
timeout=self.timeout
|
|
137
|
+
)
|
|
138
|
+
if r.status_code == 200:
|
|
139
|
+
return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个设备", data=r.json())
|
|
140
|
+
return ReturnResponse(code=1, msg=f"获取设备失败: {r.status_code} {r.text}")
|
|
141
|
+
|
|
142
|
+
def get_device_detail(self, serial: str) -> ReturnResponse:
|
|
143
|
+
'''
|
|
144
|
+
获取指定序列号(serial)的 Meraki 设备详细信息
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
serial (str): 设备的序列号
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
ReturnResponse:
|
|
151
|
+
code: 0 表示成功,1 表示失败,3 表示设备未添加
|
|
152
|
+
msg: 结果说明
|
|
153
|
+
data: 设备详细信息,示例(部分敏感信息已隐藏):
|
|
154
|
+
{
|
|
155
|
+
'lat': 1.1,
|
|
156
|
+
'lng': 2.2,
|
|
157
|
+
'address': '(1.1, 2.2)',
|
|
158
|
+
'serial': 'Q3AL-****-****',
|
|
159
|
+
'mac': '00:00:00:00:00:00',
|
|
160
|
+
'lanIp': '10.1.1.1',
|
|
161
|
+
'tags': ['MR44'],
|
|
162
|
+
'url': 'https://n3.meraki.cn/xxx',
|
|
163
|
+
'networkId': '00000',
|
|
164
|
+
'name': 'OFFICE-AP01',
|
|
165
|
+
'details': [],
|
|
166
|
+
'model': 'MR44',
|
|
167
|
+
'firmware': 'wireless-31-1-6',
|
|
168
|
+
'floorPlanId': '00000'
|
|
169
|
+
}
|
|
170
|
+
'''
|
|
171
|
+
r = requests.get(
|
|
172
|
+
f"{self.base_url}/devices/{serial}",
|
|
173
|
+
headers=self.headers,
|
|
174
|
+
timeout=self.timeout
|
|
175
|
+
)
|
|
176
|
+
if r.status_code == 200:
|
|
177
|
+
return ReturnResponse(code=0, msg=f"获取设备详情成功: {r.json()}", data=r.json())
|
|
178
|
+
elif r.status_code == 404:
|
|
179
|
+
return ReturnResponse(code=3, msg=f"设备 {serial} 还未添加过", data=None)
|
|
180
|
+
return ReturnResponse(code=1, msg=f"获取设备详情失败: {r.status_code} - {r.text}", data=None)
|
|
181
|
+
|
|
182
|
+
def get_device_availability(self, network_id: list=None,
|
|
183
|
+
status: Literal['online', 'offline', 'dormant', 'alerting']=None,
|
|
184
|
+
serial: str=None,
|
|
185
|
+
tags: list=None,
|
|
186
|
+
get_all: bool=False) -> ReturnResponse:
|
|
187
|
+
'''
|
|
188
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-devices-availabilities/
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
network_id (str, optional): 如果是列表, 不能传太多 network_id
|
|
192
|
+
status (Literal['online', 'offline', 'dormant', 'alerting'], optional): _description_. Defaults to None.
|
|
193
|
+
serial (str, optional): _description_. Defaults to None.
|
|
194
|
+
get_all (bool, optional): _description_. Defaults to False.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ReturnResponse: _description_
|
|
198
|
+
'''
|
|
199
|
+
params = {}
|
|
200
|
+
|
|
201
|
+
if status:
|
|
202
|
+
params["statuses[]"] = status
|
|
203
|
+
|
|
204
|
+
if serial:
|
|
205
|
+
if isinstance(serial, str):
|
|
206
|
+
params["serials[]"] = [serial]
|
|
207
|
+
else:
|
|
208
|
+
params["serials[]"] = serial
|
|
209
|
+
|
|
210
|
+
if network_id:
|
|
211
|
+
if isinstance(network_id, str):
|
|
212
|
+
params["networkIds[]"] = [network_id]
|
|
213
|
+
else:
|
|
214
|
+
params["networkIds[]"] = network_id
|
|
215
|
+
|
|
216
|
+
if tags:
|
|
217
|
+
params["tags[]"] = tags
|
|
218
|
+
|
|
219
|
+
# 如果需要获取所有数据,设置每页最大数量
|
|
220
|
+
if get_all:
|
|
221
|
+
params['perPage'] = 1000
|
|
222
|
+
|
|
223
|
+
all_data = []
|
|
224
|
+
url = f"{self.base_url}/organizations/{self.organization_id}/devices/availabilities"
|
|
225
|
+
|
|
226
|
+
while url:
|
|
227
|
+
r = requests.get(
|
|
228
|
+
url=url,
|
|
229
|
+
headers=self.headers,
|
|
230
|
+
params=params if url == f"{self.base_url}/organizations/{self.organization_id}/devices/availabilities" else {},
|
|
231
|
+
timeout=self.timeout
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if r.status_code != 200:
|
|
235
|
+
return ReturnResponse(code=1, msg=f"获取设备健康状态失败: {r.status_code} - {r.text}", data=None)
|
|
236
|
+
|
|
237
|
+
data = r.json()
|
|
238
|
+
all_data.extend(data)
|
|
239
|
+
|
|
240
|
+
# 如果不需要获取所有数据,只返回第一页
|
|
241
|
+
if not get_all:
|
|
242
|
+
return ReturnResponse(code=0, msg=f"获取设备健康状态成功,共 {len(data)} 条", data=data)
|
|
243
|
+
|
|
244
|
+
# 解析 Link header 获取下一页 URL
|
|
245
|
+
url = None
|
|
246
|
+
link_header = r.headers.get('Link', '')
|
|
247
|
+
if link_header:
|
|
248
|
+
# 解析 Link header,格式如: '<url>; rel=next, <url>; rel=prev'
|
|
249
|
+
for link in link_header.split(','):
|
|
250
|
+
link = link.strip()
|
|
251
|
+
if 'rel=next' in link or 'rel="next"' in link:
|
|
252
|
+
# 提取 URL (在 < > 之间)
|
|
253
|
+
url = link.split(';')[0].strip('<> ')
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
return ReturnResponse(code=0, msg=f"获取设备健康状态成功,共 {len(all_data)} 条", data=all_data)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_device_availabilities_change_history(self, network_id: str=None, serial: str=None) -> ReturnResponse:
|
|
260
|
+
'''
|
|
261
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-devices-availabilities/
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
network_id (str, optional): _description_. Defaults to None.
|
|
265
|
+
serial (str, optional): _description_. Defaults to None.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
ReturnResponse: _description_
|
|
269
|
+
'''
|
|
270
|
+
params = {}
|
|
271
|
+
if network_id:
|
|
272
|
+
params['networkId'] = network_id
|
|
273
|
+
if serial:
|
|
274
|
+
params['serial'] = serial
|
|
275
|
+
|
|
276
|
+
r = requests.get(
|
|
277
|
+
url=f"{self.base_url}/organizations/{self.organization_id}/devices/availabilities/changeHistory",
|
|
278
|
+
headers=self.headers,
|
|
279
|
+
params=params,
|
|
280
|
+
timeout=self.timeout
|
|
281
|
+
)
|
|
282
|
+
if r.status_code == 200:
|
|
283
|
+
return ReturnResponse(code=0, msg=f"获取设备健康状态变化历史成功", data=r.json())
|
|
284
|
+
return ReturnResponse(code=1, msg=f"获取设备健康状态变化历史失败: {r.status_code} - {r.text}", data=None)
|
|
285
|
+
|
|
286
|
+
def reboot_device(self, serial: str) -> ReturnResponse:
|
|
287
|
+
'''
|
|
288
|
+
该接口 60s 只能执行一次
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
serial (str): _description_
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
ReturnResponse: _description_
|
|
295
|
+
'''
|
|
296
|
+
r = requests.post(
|
|
297
|
+
url=f"{self.base_url}/devices/{serial}/reboot",
|
|
298
|
+
headers=self.headers,
|
|
299
|
+
timeout=self.timeout
|
|
300
|
+
)
|
|
301
|
+
if r.status_code == 202 and r.json()['success'] == True:
|
|
302
|
+
return ReturnResponse(code=0, msg=f"重启 {serial} 成功", data=r.json())
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
error_msg = r.json()['error']
|
|
306
|
+
except KeyError:
|
|
307
|
+
error_msg = r.json()
|
|
308
|
+
return ReturnResponse(code=1, msg=f"重启 {serial} 失败, 报错 {error_msg}", data=None)
|
|
309
|
+
|
|
310
|
+
def get_alerts(self):
|
|
311
|
+
# from datetime import datetime, timedelta
|
|
312
|
+
params = {}
|
|
313
|
+
params['tsStart'] = "2025-10-20T00:00:00Z"
|
|
314
|
+
params['tsEnd'] = "2025-10-30T00:00:00Z"
|
|
315
|
+
# # 获取昨天0:00的时间戳(秒)
|
|
316
|
+
# yesterday = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=1)
|
|
317
|
+
# ts_start = int(yesterday.timestamp()) * 1000
|
|
318
|
+
# params['tsStart'] = str(ts_start)
|
|
319
|
+
# print(params)
|
|
320
|
+
r = requests.get(
|
|
321
|
+
url=f"{self.base_url}/organizations/{self.organization_id}/assurance/alerts",
|
|
322
|
+
headers=self.headers,
|
|
323
|
+
timeout=self.timeout,
|
|
324
|
+
params=params
|
|
325
|
+
)
|
|
326
|
+
for i in r.json():
|
|
327
|
+
print(i)
|
|
328
|
+
# return ReturnResponse(code=0, msg="获取告警成功", data=r.json())
|
|
329
|
+
|
|
330
|
+
def get_network_events(self, network_id):
|
|
331
|
+
params = {}
|
|
332
|
+
params['productType'] = "wireless"
|
|
333
|
+
|
|
334
|
+
print(params)
|
|
335
|
+
r = requests.get(
|
|
336
|
+
url=f"{self.base_url}/networks/{network_id}/events",
|
|
337
|
+
headers=self.headers,
|
|
338
|
+
timeout=self.timeout,
|
|
339
|
+
params=params
|
|
340
|
+
)
|
|
341
|
+
if r.status_code == 200:
|
|
342
|
+
return ReturnResponse(code=0, msg=f"获取网络事件成功", data=r.json())
|
|
343
|
+
return ReturnResponse(code=1, msg=f"获取网络事件失败: {r.status_code} - {r.text}", data=None)
|
|
344
|
+
|
|
345
|
+
def get_wireless_failcounter(self, network_id: str, timespan: int=5*60, serial: str=None):
|
|
346
|
+
'''
|
|
347
|
+
https://developer.cisco.com/meraki/api-v1/get-network-wireless-failed-connections/
|
|
348
|
+
'''
|
|
349
|
+
params = {}
|
|
350
|
+
params['timespan'] = timespan
|
|
351
|
+
if serial:
|
|
352
|
+
params['serial'] = serial
|
|
353
|
+
|
|
354
|
+
r = requests.get(
|
|
355
|
+
url=f"{self.base_url}/networks/{network_id}/wireless/failedConnections",
|
|
356
|
+
headers=self.headers,
|
|
357
|
+
timeout=self.timeout,
|
|
358
|
+
params=params
|
|
359
|
+
)
|
|
360
|
+
if r.status_code == 200:
|
|
361
|
+
return ReturnResponse(code=0, msg=f"获取无线失败连接成功", data=r.json())
|
|
362
|
+
return ReturnResponse(code=1, msg=f"获取无线失败连接失败: {r.status_code} - {r.text}", data=None)
|
|
363
|
+
|
|
364
|
+
def claim_network_devices(self, network_id: str, serials: list[str]) -> ReturnResponse:
|
|
365
|
+
'''
|
|
366
|
+
https://developer.cisco.com/meraki/api-v1/claim-network-devices/
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
network_id (_type_): _description_
|
|
370
|
+
serials (list): _description_
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
ReturnResponse: _description_
|
|
374
|
+
'''
|
|
375
|
+
new_serials = []
|
|
376
|
+
already_claimed_serials = []
|
|
377
|
+
|
|
378
|
+
for serial in serials:
|
|
379
|
+
r = self.get_device_detail(serial=serial)
|
|
380
|
+
if r.code == 0:
|
|
381
|
+
already_claimed_serials.append(serial)
|
|
382
|
+
elif r.code == 3:
|
|
383
|
+
new_serials.append(serial)
|
|
384
|
+
else:
|
|
385
|
+
new_serials.append(serial)
|
|
386
|
+
|
|
387
|
+
body = {
|
|
388
|
+
"serials": new_serials,
|
|
389
|
+
"addAtomically": True
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
r = requests.post(
|
|
393
|
+
url=f"{self.base_url}/networks/{network_id}/devices/claim",
|
|
394
|
+
headers=self.headers,
|
|
395
|
+
json=body,
|
|
396
|
+
timeout=self.timeout + 10
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if len(already_claimed_serials) == len(serials):
|
|
400
|
+
code = 0
|
|
401
|
+
msg = f"All {len(already_claimed_serials)} devices are already claimed"
|
|
402
|
+
elif len(already_claimed_serials) > 0:
|
|
403
|
+
code = 0
|
|
404
|
+
msg = f"Some {len(already_claimed_serials)} devices are already claimed"
|
|
405
|
+
else:
|
|
406
|
+
code = 0
|
|
407
|
+
msg = f"Claim network devices successfully, claimed {len(new_serials)} devices"
|
|
408
|
+
|
|
409
|
+
return ReturnResponse(code=code, msg=msg)
|
|
410
|
+
|
|
411
|
+
def update_device(self, serial: str, name: str=None, tags: list=None, address: str=None, lat: float=None, lng: float=None) -> ReturnResponse:
|
|
412
|
+
'''
|
|
413
|
+
https://developer.cisco.com/meraki/api-v1/update-device/
|
|
414
|
+
'''
|
|
415
|
+
body = {}
|
|
416
|
+
if name:
|
|
417
|
+
body['name'] = name
|
|
418
|
+
if tags:
|
|
419
|
+
body['tags'] = tags
|
|
420
|
+
if address:
|
|
421
|
+
body['address'] = address
|
|
422
|
+
if lat:
|
|
423
|
+
body['lat'] = lat
|
|
424
|
+
if lng:
|
|
425
|
+
body['lng'] = lng
|
|
426
|
+
r = requests.put(
|
|
427
|
+
url=f"{self.base_url}/devices/{serial}",
|
|
428
|
+
headers=self.headers,
|
|
429
|
+
json=body,
|
|
430
|
+
timeout=self.timeout
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def update_device(self,
|
|
435
|
+
config_template_id: str=None,
|
|
436
|
+
serial: str=None,
|
|
437
|
+
name: str=None,
|
|
438
|
+
tags: list=None,
|
|
439
|
+
address: str=None,
|
|
440
|
+
lat: float=None,
|
|
441
|
+
lng: float=None,
|
|
442
|
+
switch_profile_id: str=None
|
|
443
|
+
) -> ReturnResponse:
|
|
444
|
+
|
|
445
|
+
body = {
|
|
446
|
+
"name": name,
|
|
447
|
+
"tags": tags,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if address:
|
|
451
|
+
body['address'] = address
|
|
452
|
+
body["moveMapMarker"] = True
|
|
453
|
+
|
|
454
|
+
if lat:
|
|
455
|
+
body['Lat'] = lat
|
|
456
|
+
|
|
457
|
+
if lng:
|
|
458
|
+
body['Lng'] = lng
|
|
459
|
+
|
|
460
|
+
if not switch_profile_id:
|
|
461
|
+
model = self.get_device_detail(serial=serial).data.get('model')
|
|
462
|
+
for switch_profile in self.get_switch_profiles(config_template_id=config_template_id).data:
|
|
463
|
+
if switch_profile.get('model') == model:
|
|
464
|
+
switch_profile_id = switch_profile.get('switchProfileId')
|
|
465
|
+
body['switchProfileId'] = switch_profile_id
|
|
466
|
+
else:
|
|
467
|
+
body['switchProfileId'] = switch_profile_id
|
|
468
|
+
|
|
469
|
+
response = requests.put(
|
|
470
|
+
url=f"{self.base_url}/devices/{serial}",
|
|
471
|
+
headers=self.headers,
|
|
472
|
+
json=body,
|
|
473
|
+
timeout=3
|
|
474
|
+
)
|
|
475
|
+
if response.status_code == 200:
|
|
476
|
+
return ReturnResponse(code=0, msg=f"更新设备 {serial} 成功", data=response.json())
|
|
477
|
+
else:
|
|
478
|
+
return ReturnResponse(code=1, msg=f"更新设备 {serial} 失败: {response.status_code} - {response.text}", data=None)
|
|
479
|
+
|
|
480
|
+
def get_switch_ports(self, serial: str) -> ReturnResponse:
|
|
481
|
+
'''
|
|
482
|
+
https://developer.cisco.com/meraki/api-v1/get-device-switch-ports/
|
|
483
|
+
'''
|
|
484
|
+
r = requests.get(
|
|
485
|
+
url=f"{self.base_url}/devices/{serial}/switch/ports/statuses",
|
|
486
|
+
headers=self.headers,
|
|
487
|
+
timeout=self.timeout
|
|
488
|
+
)
|
|
489
|
+
if r.status_code == 200:
|
|
490
|
+
return ReturnResponse(code=0, msg=f"获取交换机端口状态成功", data=r.json())
|
|
491
|
+
return ReturnResponse(code=1, msg=f"获取交换机端口状态失败: {r.status_code} - {r.text}", data=None)
|
|
492
|
+
|
|
493
|
+
def get_ssids(self, network_id):
|
|
494
|
+
'''
|
|
495
|
+
https://developer.cisco.com/meraki/api-v1/get-network-wireless-ssids/
|
|
496
|
+
'''
|
|
497
|
+
r = requests.get(
|
|
498
|
+
url=f"{self.base_url}/networks/{network_id}/wireless/ssids",
|
|
499
|
+
headers=self.headers,
|
|
500
|
+
timeout=self.timeout
|
|
501
|
+
)
|
|
502
|
+
if r.status_code == 200:
|
|
503
|
+
return ReturnResponse(code=0, msg="获取 SSID 成功", data=r.json())
|
|
504
|
+
return ReturnResponse(code=1, msg=f"获取 SSID 失败: {r.status_code} - {r.text}", data=None)
|
|
505
|
+
|
|
506
|
+
def get_ssid_by_number(self, network_id, ssid_number):
|
|
507
|
+
'''
|
|
508
|
+
https://developer.cisco.com/meraki/api-v1/get-network-wireless-ssid-by-number/
|
|
509
|
+
'''
|
|
510
|
+
r = requests.get(
|
|
511
|
+
url=f"{self.base_url}/networks/{network_id}/wireless/ssids/{ssid_number}",
|
|
512
|
+
headers=self.headers,
|
|
513
|
+
timeout=self.timeout
|
|
514
|
+
)
|
|
515
|
+
if r.status_code == 200:
|
|
516
|
+
return r.json()['name']
|
|
517
|
+
|
|
518
|
+
def update_ssid(self, network_id, ssid_number, body):
|
|
519
|
+
'''
|
|
520
|
+
https://developer.cisco.com/meraki/api-v1/update-network-wireless-ssid/
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
network_id (_type_): _description_
|
|
524
|
+
ssid_number (_type_): _description_
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
_type_: _description_
|
|
528
|
+
'''
|
|
529
|
+
r = requests.put(
|
|
530
|
+
url=f"{self.base_url}/networks/{network_id}/wireless/ssids/{ssid_number}",
|
|
531
|
+
headers=self.headers,
|
|
532
|
+
timeout=self.timeout,
|
|
533
|
+
json=body
|
|
534
|
+
)
|
|
535
|
+
if r.status_code == 200:
|
|
536
|
+
return ReturnResponse(code=0, msg=f"更新 SSID 成功", data=r.json())
|
|
537
|
+
return ReturnResponse(code=1, msg=f"更新 SSID 失败: {r.status_code} - {r.text}", data=None)
|