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.

Files changed (68) hide show
  1. pytbox/alert/alert_handler.py +139 -0
  2. pytbox/alert/ping.py +24 -0
  3. pytbox/alicloud/sls.py +9 -14
  4. pytbox/base.py +121 -0
  5. pytbox/categraf/build_config.py +143 -0
  6. pytbox/categraf/instances.toml +39 -0
  7. pytbox/categraf/jinja2/__init__.py +6 -0
  8. pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +5 -0
  9. pytbox/categraf/jinja2/input.disk/disk.toml.j2 +11 -0
  10. pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +6 -0
  11. pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +12 -0
  12. pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +9 -0
  13. pytbox/categraf/jinja2/input.mem/mem.toml.j2 +5 -0
  14. pytbox/categraf/jinja2/input.net/net.toml.j2 +11 -0
  15. pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +9 -0
  16. pytbox/categraf/jinja2/input.ping/ping.toml.j2 +11 -0
  17. pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +12 -0
  18. pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +96 -0
  19. pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +41 -0
  20. pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +96 -0
  21. pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +41 -0
  22. pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +96 -0
  23. pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +41 -0
  24. pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +96 -0
  25. pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +41 -0
  26. pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +211 -0
  27. pytbox/cli/__init__.py +7 -0
  28. pytbox/cli/categraf/__init__.py +7 -0
  29. pytbox/cli/categraf/commands.py +55 -0
  30. pytbox/cli/commands/vm.py +22 -0
  31. pytbox/cli/common/__init__.py +6 -0
  32. pytbox/cli/common/options.py +42 -0
  33. pytbox/cli/common/utils.py +269 -0
  34. pytbox/cli/formatters/__init__.py +7 -0
  35. pytbox/cli/formatters/output.py +155 -0
  36. pytbox/cli/main.py +24 -0
  37. pytbox/cli.py +9 -0
  38. pytbox/database/mongo.py +99 -0
  39. pytbox/database/victoriametrics.py +404 -0
  40. pytbox/dida365.py +11 -17
  41. pytbox/excel.py +64 -0
  42. pytbox/feishu/endpoints.py +12 -9
  43. pytbox/{logger.py → log/logger.py} +78 -30
  44. pytbox/{victorialog.py → log/victorialog.py} +2 -2
  45. pytbox/mail/alimail.py +142 -0
  46. pytbox/mail/client.py +171 -0
  47. pytbox/mail/mail_detail.py +30 -0
  48. pytbox/mingdao.py +164 -0
  49. pytbox/network/meraki.py +537 -0
  50. pytbox/notion.py +731 -0
  51. pytbox/pyjira.py +612 -0
  52. pytbox/utils/cronjob.py +79 -0
  53. pytbox/utils/env.py +2 -2
  54. pytbox/utils/load_config.py +132 -0
  55. pytbox/utils/load_vm_devfile.py +45 -0
  56. pytbox/utils/response.py +1 -1
  57. pytbox/utils/richutils.py +31 -0
  58. pytbox/utils/timeutils.py +479 -14
  59. pytbox/vmware.py +120 -0
  60. pytbox/win/ad.py +30 -0
  61. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/METADATA +13 -3
  62. pytbox-0.3.1.dist-info/RECORD +72 -0
  63. pytbox-0.3.1.dist-info/entry_points.txt +2 -0
  64. pytbox/common/base.py +0 -0
  65. pytbox/victoriametrics.py +0 -37
  66. pytbox-0.0.1.dist-info/RECORD +0 -21
  67. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/WHEEL +0 -0
  68. {pytbox-0.0.1.dist-info → pytbox-0.3.1.dist-info}/top_level.txt +0 -0
@@ -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)