Pass4Sure HP HP

本文档列举了大量 HP 公司提供的专业认证考试,涵盖服务器解决方案、存储解决方案、网络解决方案等多个领域,旨在帮助 IT 专业人士提升技能并获得行业认可。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Pass4Sure HP HP
HP0-648 also called ProCurve Adaptive Edge Fundamentals is a HP exam of HP company.
HP0-S16 also called Implementing HP BladeSystem is a HP exam of HP company.
HP0-Y18 also called ProCurve Secure Mobility Solutions is a HP exam of HP company.
HP0-J15 also called HP StorageWorks MASE 2088 Delta Exam is a HP exam of HP company.
HP0-Y17 also called ProCurve Secure Mobility Solutions is a HP exam of HP company.
HP0-J24 also called Designing and Implementing HP Enterprise Backup Solutions is a HP exam of HP company.
HP0-A25 also called NonStop SQL/MX is a HP exam of HP company.
HP0-P18 also called HP Integrity o-Class BladeSystem is a HP exam of HP company.
HP0-003 also called HP OpenView Service Desk 5.x is a HP exam of HP company.
hp0-y16 also called ProCurve Network Immunity Solutions is a HP exam of HP company.
hp0-y13 also called ProCurve Network Management is a HP exam of HP company.
hp0-j16 also called Introduction to HP SANs is a HP exam of HP company.
HP0-J17 also called Designing and Implementing the HP StorageWorks EVA4400 is a HP exam of HP company.
HP0-S15 also called Planning and Designing ProLiant Solutions for the Enterprise is a HP exam of HP company.
HP0-J23 also called Implementing MSA Storage Solutions is a HP exam of HP company.
HP0-J18 also called Implementing HP StorageWorks EVA Solutions is a HP exam of HP company.
HP0-S14 also called Design & Implementation of HP SIM for ISS Solutions is a HP exam of HP company.
HP0-G11 also called CCI Fundamentals for Solution Architects is a HP exam of HP company.
HP0-M21 also called HP Universal Configuration Management Database Foundation is a HP exam of HP company.
HP0-J21 also called Designing and Implementing HP Storage Essentials SRM is a HP exam of HP company.
HP0-J19 also called HP Storage Essentials SRM Standard Edition is a HP exam of HP company.
HP0-M22 also called Implementing HP Service Manager Software is a HP exam of HP company.
hp0-y15 also called ProCurve Network Access Control is a HP exam of HP company.
hp0-j20 also called HP Storage Essentials SRM Enterprise Edition is a HP exam of HP company.
HP0-S13 also called HP BladeSystems c-Class Solutions 1 is a HP exam of HP company.
HP0-M19 also called HP Virtual User Generator Software is a HP exam of HP company.
HP0-M18 also called HP LoadRunner Software is a HP exam of HP company.
HP0-090 also called HP-UX Virtual Server Environment is a HP exam of HP company.
HP0-Y12 also called Building ProCurve Resilient, Adaptive Networks is a HP exam of HP company.
HP0-J12 also called Supporting ESL E-Series and EML E-Series Libraries is a HP exam of HP company.
HP0-286 also called HP OpenView ServiceCenter 6.X is a HP exam of HP company.
HP0-P17 also called HP-UX 11i v3 Security Administration is a HP exam of HP company.
HP0-M20 also called HP Business Availability Center 6.5 Software is a HP exam of HP company.
HP0-M16 also called HP QuickTest Professional 9.2 Software is a HP exam of HP company.
HP0-M15 also called HP Quality Center 9.2 Software is a HP exam of HP company.
HP0-M14 also called HP Project and Portfolio Management 7.1 Software is a HP exam of HP company.
HP0-M12 also called HP WinRunner 9.2 Software is a HP exam of HP company.
HP0-S12 also called Implementing HP ProLiant ML/DL Servers. is a HP exam of HP company.
HP0-S11 also called Integrating & Managing HP ProLiant ML/DL in the Enterprise is a HP exam of HP company.
HP0-K03 also called Integrating and Managing HP BladeSystem in the Enterprise is a HP exam of HP company.
HP0-K02 also called Implementing HP BladeSystem is a HP exam of HP company.
HP0-A03 also called HP-UX 11 to 11i v3 Differences is a HP exam of HP company.
HP0-A02 also called HP-UX 11i v3 Advanced System Administration is a HP exam of HP company.
HP0-A01 also called HP-UX 11i v3 System Administration is a HP exam of HP company.
HP0-A20 also called Install, Maintain and Upgrade NS-Series Hardware is a HP exam of HP company.
HP0-Y11 also called ProCure Security 7.31 is a HP exam of HP company.
HP0-E01 also called Planning and Designing HP Enterprise Solutions is a HP exam of HP company.
HP0-J10 also called Implementing HP StorageWorks XP Disk Array Solutions is a HP exam of HP company.
HP0-W02 also called Implementing HP StorageWorks EVA Solutions is a HP exam of HP company.
HP0-A16 also called NonStop Himalaya Problem Management and Resolution is a HP exam of HP company.
HP0-X02 also called Designing & Implementing HP Enterprise Backup Solutions is a HP exam of HP company.
HP0-X01 also called HP Storage Essentials Enterprise Edition. is a HP exam of HP company.
HP0-W03 also called Advanced SAN Architecture is a HP exam of HP company.
hp0-t01 also called HP Performance Insight v5.x Software is a HP exam of HP company.
HP0-S01 also called HP AssetCenter v5.x Software Exam is a HP exam of HP company.
HP0-A17 also called Install.Maintain and Upgrade NonStop Himalaya Hardware is a HP exam of HP company.
HP0-085 also called Planning and Designing HP Superdome Server Solutions is a HP exam of HP company.
HP0-P16 also called Planning & Design of HP 9000/HP Integrity Server Solutions is a HP exam of HP company.
HP0-P11 also called HP Integrity Entry-Level Server Technologies is a HP exam of HP company.
HP0-P10 also called HP Integrity Server Multi-OS Installation & Deployment is a HP exam of HP company.
HP0-J11 also called Replication Solutions for the HP StorageWorks EVA is a HP exam of HP company.
HP0-922 also called Implementing & Supporting HP Storage Essentials v5.1 is a HP exam of HP company.
HP0-775 also called HP Data Protector Software Application Integration-Windows is a HP exam of HP company.
HP0-086 also called HP BladeSystem p-Class Solutions I is a HP exam of HP company.
HP0-084 also called HP Integrity Server Multi-OS Installation & Deployment is a HP exam of HP company.
HP0-069 also called HP Integrity Mid-Range Server Technologies is a HP exam of HP company.
HP0-068 also called HP Integrity Entry-Level Server Technologies is a HP exam of HP company.
HP0-065 also called Planning and Designing ProLiant Solutions for the Enterprise is a HP exam of HP company.
HP0-058 also called Configuration & Management of HP Integrity Mid-range Servers is a HP exam of HP company.
HP0-052 also called Planning & Design of HP 9000/HP Integrity Server Solutions is a HP exam of HP company.
HP0-045 also called Supporting the ESL E-Series Libraries is a HP exam of HP company.
HP0-921 also called Supporting the Enterprise Modular Library (EML) is a HP exam of HP company.
HP0-055 also called Implementing HP ProLiant Servers is a HP exam of HP company.
HP0-053 also called Enterprise Integration and Management of HP ProLiant Servers is a HP exam of HP company.
HP0-W01 also called HP Storage Essentials Standard Edition is a HP exam of HP company.
HP0-823 also called Implementing MSA Storage Solutions is a HP exam of HP company.
HP0-821 also called HP Data Protector Software Applicatn Integratn-Windows/UNIX is a HP exam of HP company.
HP0-920 also called HP Storage Data Protector 6.0 Fundamentals for UNIX is a HP exam of HP company.
HP0-919 also called HP Data Protector Software Application Integration-UNIX is a HP exam of HP company.
HP0-918 also called HP Storage Data Protector 6.0 Fundamentals for Windows is a HP exam of HP company.
HP0-505 also called Planning and Designing HP Enterprise Solutions is a HP exam of HP company.
HP0-207 also called Procurve Adaptive EDGE Fundamentals is a HP exam of HP company.
HP0-429 also called Installling & Supporting Standard HP SAN Environments is a HP exam of HP company.
HP0-276 also called OpenVMS Security Administration is a HP exam of HP company.
HP0-066 also called Advanced Lights-Out is a HP exam of HP company.
HP0-064 also called HP BladeSystems C-Class Solutions 1 is a HP exam of HP company.
HP0-145 also called CCI Fundamentals for Solution Architects Exam is a HP exam of HP company.
HP0-787 also called NonStop Advanced Application Development is a HP exam of HP company.
HP0-791 also called HP ProCurve Convergence is a HP exam of HP company.
HP0-176 also called Design & Implementation of HP SIM for ISS Solutions is a HP exam of HP company.
HP0-512 also called Replication Solutions for the HP StorageWorks EVA is a HP exam of HP company.
HP0-678 also called Implementing HP Enterprise Virtual Array Solutions is a HP exam of HP company.
HP0-766 also called Nonstop Security is a HP exam of HP company.
HP0-782 also called NonStop Remote Database Facility (RDF) is a HP exam of HP company.
HP0-780 also called NonStop Structured Query Language (SQL) is a HP exam of HP company.
HP0-277 also called OpenVMS Version 7.x to 8.2 Migrarion is a HP exam of HP company.
HP0-244 also called Advanced Backup Troubleshooting & Tuning is a HP exam of HP company.
HP0-427 also called Implementing & Supporting HP Storage Essentials v5.0 is a HP exam of HP company.
HP0-771 also called Designing & Implementing HP Enterprise Backup Solutions is a HP exam of HP company.
HP0-490 also called HP BladeSystem p-Class Solutions I is a HP exam of HP company.
HP0-417 also called Implementing & Supporting HP Storage Essentials v5.0 is a HP exam of HP company.
HP0-409 also called OVIS/OVTA is a HP exam of HP company.
HP0-382 also called Servicing HP Mid-Range Integrity Servers is a HP exam of HP company.
HP0-381 also called Configuration & Management of HP Integrity Mid-range Servers is a HP exam of HP company.
HP0-380 also called Planning & Design of HP Integrity Entry-Level Srvr Solutions is a HP exam of HP company.
HP0-255 also called Planning & Design of HP Integrity Mid-Range Server Solutions is a HP exam of HP company.
HP0-063 also called LF PROFESSIONAL COLOR WORKFLOW MANAGEMENT is a HP exam of HP company.
HP0-891 also called Implementing HP XP1024/128 Array Solution Fundamentals is a HP exam of HP company.
HP0-876 also called Implementing HP ProLiant Cluster Solutions for NetWare 6 is a HP exam of HP company.
HP0-874 also called Implementing Windows 2000 on HP ProLiant Clusters is a HP exam of HP company.
HP0-866 also called Implementing Compaq ProLiant Cluster Solutions for NetWare is a HP exam of HP company.
HP0-864 also called Implementing Windows 2000 on Compaq ProLiant Clusters is a HP exam of HP company.
HP0-850 also called Integrating Oracle9i with hp Platforms is a HP exam of HP company.
HP0-803 also called Implementing MSA Storage Solutions is a HP exam of HP company.
HP0-802 also called Alpha/Linux Integration and Performance is a HP exam of HP company.
HP0-797 also called Enterprise Integration and Management of HP ProLiant Servers is a HP exam of HP company.
HP0-786 also called NonStop Advanced Application Design is a HP exam of HP company.
HP0-785 also called NonStop Hardware Install, Maintain, Upgrade is a HP exam of HP company.
HP0-784 also called NonStop Problem Management and Resolution is a HP exam of HP company.
HP0-781 also called NonStop Performance Analysis and Tuning is a HP exam of HP company.
HP0-773 also called Installing & Supporting Standard HP SAN Environments is a HP exam of HP company.
HP0-768 also called NonStop Transaction Management Facility (TMF) is a HP exam of HP company.
HP0-767 also called NonStop Pathway TS and TS/MP is a HP exam of HP company.
HP0-765 also called NonStop Web Enabling is a HP exam of HP company.
HP0-764 also called NonStop Guardian Architecture is a HP exam of HP company.
HP0-763 also called NonStop Kemel Systems Management and Operations is a HP exam of HP company.
HP0-762 also called NonStop Kemel Platform Support is a HP exam of HP company.
HP0-761 also called NonStop Advanced Networking and Comms is a HP exam of HP company.
HP0-760 also called NonStop Kemel Advanced (Level 2) is a HP exam of HP company.
HP0-759 also called HP ProCurve Combined Security and Mobility Exam is a HP exam of HP company.
HP0-758 also called HP ProCurve Mobility is a HP exam of HP company.
HP0-757 also called HP ProCurve Security is a HP exam of HP company.
HP0-756 also called HP ProCurve Secure Mobility Solutions is a HP exam of HP company.
HP0-753 also called HP OpenView Service Desk 4.5 is a HP exam of HP company.
HP0-750 also called Advanced OpenVMS Support is a HP exam of HP company.
HP0-742 also called OpenView Storage Area Manager Fundamentals is a HP exam of HP company.
HP0-733 also called AlphaServer GS80/160/320 System Technologies is a HP exam of HP company.
HP0-727 also called HP OpenView Operations (7.x) Windows is a HP exam of HP company.
HP0-720 also called HP OpenView Omniback II is a HP exam of HP company.
HP0-719 also called OpenView Operations(7.x)- Windows is a HP exam of HP company.
HP0-710 also called Alpha Platforms Support is a HP exam of HP company.
HP0-704 also called TruCluster v5 Implementation and Support is a HP exam of HP company.
HP0-703 also called Tru64 UNIX V5 Network Administration is a HP exam of HP company.
HP0-702 also called Tru64 UNIX V5 Advanced Admin. Support & Performance is a HP exam of HP company.
HP0-698 also called Supporting the HP ProLiant Storage Server Product Family is a HP exam of HP company.
HP0-685 also called Implementing HP Virtual Array Solutions is a HP exam of HP company.
HP0-671 also called Intermediate Customer Service Skills Level 2 is a HP exam of HP company.
HP0-664 also called NonStop Application Design and Development is a HP exam of HP company.
HP0-663 also called NonStop Data Communication Basics is a HP exam of HP company.
HP0-662 also called NonStop Configuration and Planning is a HP exam of HP company.
HP0-661 also called NonStop Systems and Technologies is a HP exam of HP company.
HP0-660 also called NonStop Kemal Basics (Level 1) is a HP exam of HP company.
HP0-656 also called DATA PROTECTOR 5.5 BASICS FOR UNIX is a HP exam of HP company.
HP0-655 also called Data Protector 5.5 Basics for Windows is a HP exam of HP company.
HP0-651 also called OpenVMS System Administration is a HP exam of HP company.
HP0-645 also called Implementing HP ProLiant Servers is a HP exam of HP company.
HP0-638 also called Building Proactive Networks is a HP exam of HP company.
HP0-634 also called NNM III(7.x) Advanced is a HP exam of HP company.
HP0-633 also called NNM II(7.x) Customization is a HP exam of HP company.
HP0-632 also called NNM 1(7.x) Essentials is a HP exam of HP company.
HP0-628 also called Implementing HSx80 Compag Storage Solutions for UNIX is a HP exam of HP company.
HP0-626 also called Hardcopy Solutions Technical Training Test v7.1 (Wds) is a HP exam of HP company.
HP0-603 also called HP Routing Switch Essentials version 3.0 is a HP exam of HP company.
HP0-601 also called Tru64 UNIX V5.0 System Admin. Support & Integration is a HP exam of HP company.
HP0-538 also called Network Printing Solutions v3 is a HP exam of HP company.
HP0-517 also called HP Integrity Server Multi-OS Installation and Deployment is a HP exam of HP company.
HP0-513 also called AlphaServer System Maintenance (formerlt #330-610) is a HP exam of HP company.
HP0-509 also called Compag Alpha Sales Training is a HP exam of HP company.
HP0-461 also called Supporting the Enterprise Virtual Array (EVA) Storage Family is a HP exam of HP company.
HP0-460 also called Implementing HP XP12000/10000 Solution Fundamentals is a HP exam of HP company.
HP0-451 also called Radia v4 Foundation Exam is a HP exam of HP company.
HP0-450 also called OpenVMS v7 Advanced Administration. Performance.and Support is a HP exam of HP company.
HP0-449 also called Total Print Management Technical Professional is a HP exam of HP company.
HP0-447 also called Business Process Document Solutions Technical Professional is a HP exam of HP company.
HP0-446 also called High Impact Color Imaging and Printing Technical Professional is a HP exam of HP company.
HP0-438 also called Advanced SAN Architecture is a HP exam of HP company.
HP0-436 also called OpenVMS v7 Network Administration is a HP exam of HP company.
HP0-425 also called OpenCall Service Developer is a HP exam of HP company.
HP0-401 also called Tru64 UNIX v.4 to v.5 Update is a HP exam of HP company.
HP0-390 also called Planning & Deployment of HP BladeSystem Solutions is a HP exam of HP company.
HP0-345 also called HP OpenView Operations (OVO) II8.x UNIX is a HP exam of HP company.
HP0-336 also called Identity Management Exam is a HP exam of HP company.
HP0-335 also called HP OpenView Operations (OVO) I8.x UNIX is a HP exam of HP company.
HP0-324 also called Power On with HP for Technical Professionals- Multi- OS is a HP exam of HP company.
HP0-311 also called HP OpenView TeMIP is a HP exam of HP company.
HP0-310 also called OpenView Performance Insight is a HP exam of HP company.
HP0-302 also called Planning and Designing HP Superdome Server Solutions is a HP exam of HP company.
HP0-273 also called Power On Sales Professional Server Solutions Certification is a HP exam of HP company.
HP0-266 also called Servicing HP Monochrome LaserJet MFP Printers,High-End is a HP exam of HP company.
HP0-265 also called Servicing HP Color LaserJet MFP Printers,High-End is a HP exam of HP company.
HP0-263 also called Servicing HP Color LaserJet Printers, High-End is a HP exam of HP company.
HP0-262 also called Servicing HP Large Format Printers & MFP is a HP exam of HP company.
HP0-243 also called Supporting OVSAM(OpenView Storage Area Manager) is a HP exam of HP company.
HP0-242 also called Supporting the ESL9000 is a HP exam of HP company.
HP0-241 also called Supporting the Enterprise Virtual Array (EVA) Storage Family is a HP exam of HP company.
HP0-240 also called Supporting the Virtual Array(VA) Storage Family is a HP exam of HP company.
HP0-239 also called Supporting the Modular Array(MA) Storage Family is a HP exam of HP company.
HP0-238 also called Supporting the MSA1000 and SCSI JBODS is a HP exam of HP company.
HP0-236 also called Supporting SAN Infrastructure & Solutions is a HP exam of HP company.
HP0-230 also called HP Crisis Management Level 1 Certification is a HP exam of HP company.
HP0-210 also called HP Mission Critical Technical Level 1 Certification is a HP exam of HP company.
HP0-205 also called Supporting the Enterprise Modular Library is a HP exam of HP company.
HP0-198 also called HP SuperDome Technical Presales Always on Gold Certification is a HP exam of HP company.
HP0-197 also called HP SuperDome Sales Always on Gold Certification is a HP exam of HP company.
HP0-195 also called HP Modular Disk Array and SureStore is a HP exam of HP company.
HP0-173 also called HP SuperDome Technical Pre-Sales Service is a HP exam of HP company.
HP0-171 also called HP Networked Storage Sales Professional is a HP exam of HP company.
HP0-136 also called HP Large Format Printing Technical Professional is a HP exam of HP company.
HP0-120 also called HP HA Cluster/Enterprise Certification is a HP exam of HP company.
HP0-087 also called Planning and Designing HP Enterprise Solutions is a HP exam of HP company.
HP0-081 also called OpenVMS System Administration is a HP exam of HP company.
HP0-080 also called HP Multi-User System Product T500/T600 Certification is a HP exam of HP company.
HP0-075 also called HP Multi-User System Product K-SERIES Certification is a HP exam of HP company.
HP0-072 also called Storage Subsystems for Open VMS is a HP exam of HP company.
HP0-071 also called Storage Subsystems for Window NT is a HP exam of HP company.
HP0-070 also called Storage Subsystems for UNIX is a HP exam of HP company.
HP0-054 also called Compaq/Oracle 8i I&P with Windows NT is a HP exam of HP company.
HP0-051 also called HP UNIX 10x/11.x Operating System Certification is a HP exam of HP company.
HP0-050 also called Selling Compaq Enterprise StorareWorks Solutions is a HP exam of HP company.
HP0-022 also called Essential Service Skills is a HP exam of HP company.

jupyter报以下错误Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions. https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html Please note that updating to Notebook 7 might break some of your extensions. [W 19:23:44.080 NotebookApp] Loading JupyterLab as a classic notebook (v6) extension. [W 2025-05-31 19:23:44.085 LabApp] 'notebook_dir' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release. [W 2025-05-31 19:23:44.085 LabApp] 'notebook_dir' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release. [I 2025-05-31 19:23:44.085 LabApp] JupyterLab extension loaded from E:\anaconda\Lib\site-packages\jupyterlab [I 2025-05-31 19:23:44.085 LabApp] JupyterLab application directory is E:\anaconda\share\jupyter\lab [I 19:23:45.179 NotebookApp] Serving notebooks from local directory: E:\data [I 19:23:45.179 NotebookApp] Jupyter Notebook 6.5.4 is running at: [I 19:23:45.179 NotebookApp] http://localhost:8888/?token=8a4e3c786fabbd199ffbddb6aacd0fb20ecaaa71ecef1762 [I 19:23:45.179 NotebookApp] or http://127.0.0.1:8888/?token=8a4e3c786fabbd199ffbddb6aacd0fb20ecaaa71ecef1762 [I 19:23:45.179 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). [C 19:23:45.225 NotebookApp] To access the notebook, open this file in a browser: file:///C:/Users/hp/AppData/Roaming/jupyter/runtime/nbserver-6052-open.html Or copy and paste one of these URLs: http://localhost:8888/?token=8a4e3c786fabbd199ffbddb6aacd0fb20ecaaa71ecef1762 or http://127.0.0.1:8888/?token=8a4e3c786fabbd199ffbddb6aacd0fb20ecaaa71ecef1762 0.00s - Debugger warning: It seems that frozen modules are being used, which may 0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off 0.00s - to python to disable frozen modules. 0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation. [E 19:23:48.491 NotebookApp] Uncaught exception GET /notebooks/1.%E6%B7%98%E5%AE%9D%E7%94%A8%E6%88%B7%E8%A1%8C%E4%B8%BA%E5%88%86%E6%9E%90%E9%A2%84%E6%B5%8B.ipynb (::1) HTTPServerRequest(protocol='http', host='localhost:8888', method='GET', uri='/notebooks/1.%E6%B7%98%E5%AE%9D%E7%94%A8%E6%88%B7%E8%A1%8C%E4%B8%BA%E5%88%86%E6%9E%90%E9%A2%84%E6%B5%8B.ipynb', version='HTTP/1.1', remote_ip='::1') Traceback (most recent call last): File "E:\anaconda\Lib\site-packages\tornado\web.py", line 1786, in _execute result = await result ^^^^^^^^^^^^ File "E:\anaconda\Lib\site-packages\tornado\gen.py", line 786, in run yielded = self.gen.send(value) ^^^^^^^^^^^^^^^^^^^^ File "E:\anaconda\Lib\site-packages\notebook\notebook\handlers.py", line 94, in get self.write(self.render_template('notebook.html', ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "E:\anaconda\Lib\site-packages\notebook\base\handlers.py", line 515, in render_template return template.render(**ns) ^^^^^^^^^^^^^^^^^^^^^ File "E:\anaconda\Lib\site-packages\jinja2\environment.py", line 1304, in render self.environment.handle_exception() File "E:\anaconda\Lib\site-packages\jinja2\environment.py", line 939, in handle_exception raise rewrite_traceback_stack(source=source) File "E:\anaconda\Lib\site-packages\notebook\templates\notebook.html", line 1, in top-level template code {% extends "page.html" %} File "E:\anaconda\Lib\site-packages\notebook\templates\page.html", line 187, in top-level template code {% block header %} File "E:\anaconda\Lib\site-packages\notebook\templates\notebook.html", line 115, in block 'header' {% for exporter in get_frontend_exporters() %} ^^^^^^^^^^^^^^^^^^^^^^^^^ File "E:\anaconda\Lib\site-packages\notebook\notebook\handlers.py", line 23, in get_frontend_exporters from nbconvert.exporters.base import get_export_names, get_exporter File "E:\anaconda\Lib\site-packages\nbconvert\__init__.py", line 5, in <module> from .exporters import * File "E:\anaconda\Lib\site-packages\nbconvert\exporters\__init__.py", line 1, in <module> from .asciidoc import ASCIIDocExporter File "E:\anaconda\Lib\site-packages\nbconvert\exporters\asciidoc.py", line 9, in <module> from .templateexporter import TemplateExporter File "E:\anaconda\Lib\site-packages\nbconvert\exporters\templateexporter.py", line 25, in <module> from lxml.html.clean import clean_html File "E:\anaconda\Lib\site-packages\lxml\html\clean.py", line 18, in <module> raise ImportError( ImportError: lxml.html.clean module is now a separate project lxml_html_clean. Install lxml[html_clean] or lxml_html_clean directly. [E 19:23:48.491 NotebookApp] { "Host": "localhost:8888", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": "http://localhost:8888/tree", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0" } [E 19:23:48.491 NotebookApp] 500 GET /notebooks/1.%E6%B7%98%E5%AE%9D%E7%94%A8%E6%88%B7%E8%A1%8C%E4%B8%BA%E5%88%86%E6%9E%90%E9%A2%84%E6%B5%8B.ipynb (::1) 132.330000ms referer=http://localhost:8888/tree
06-01
现在我的功能都能实现,但是最近上了一台h3c V5版本的接入交换机,绑定命令和其他接入交换机不一样,绑定步骤是这样的,首先登录到这个交换机,输入display mac-address xxxx-xxxx-xxxx 回显如下<CDSL_SW201.28_LBG>dis mac-add 1c69-7a56-b2fb MAC ADDR VLAN ID STATE PORT INDEX AGING TIME(s) 1c69-7a56-b2fb 116 LEARNED GigabitEthernet1/0/7 AGING --- 1 MAC address(es) found --- 然后进入全局配置模式sys 再进入到具体的端口,如上是GigabitEthernet1/0/7 进入之后执行绑定命令 user-bind ip-address ip地址 mac-address xxxx-xxxx-xxxx(mac地址)然后再输入dis user-bind mac-address xxxx-xxxx-xxxx验证是否绑定成功,然后输入save force保存即可 因为V5的交换机不多,可以直接在代码里面写一个列表,如果在这个列表里面,就执行V5的命令 我现在的代码如下import logging import re import time import threading import tkinter as tk from tkinter import ttk, scrolledtext, messagebox from netmiko import ConnectHandler # ----------------------- 日志配置 ----------------------- logging.basicConfig( filename='ip_mac_bind.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8' ) # ----------------------- 配置参数 ----------------------- DEVICES = { 'oa_core_switch': { 'device_type': 'hp_comware', 'host': '172.17.21.1', 'username': 'user', 'password': 'CD5', 'timeout': 30 }, 'aggregation_switch': { 'device_type': 'hp_comware', 'username': 'user', 'password': 'CD345', 'timeout': 30 }, 'access_switch': { 'device_type': 'hp_comware', 'username': 'python', 'password': 'C345', 'timeout': 30, 'global_delay_factor': 3 } } # 已知汇聚交换机列表 AGGREGATION_SWITCHES = ["172.17.21.2", "172.17.21.3", "172.17.21.4", "172.17.21.22"] # ----------------------- 工具函数 ----------------------- def format_mac_address(raw_mac): """格式化MAC地址为H3C格式:xxxx-xxxx-xxxx""" cleaned = re.sub(r'[^0-9a-fA-F]', '', raw_mac) if len(cleaned) != 12: raise ValueError("无效的MAC地址格式") return f"{cleaned[:4]}-{cleaned[4:8]}-{cleaned[8:12]}".lower() def connect_with_retry(device_info, retries=3, delay=5): """带重试机制的设备连接""" for attempt in range(retries): try: # 确保设置了合理的延迟因子 if 'global_delay_factor' not in device_info: device_info['global_delay_factor'] = 2 conn = ConnectHandler(**device_info) logging.info(f"成功连接 {device_info['host']}") # 确保禁用分页 conn.send_command_timing("screen-length disable", delay_factor=2) time.sleep(0.5) # 确保命令执行完成 return conn except Exception as e: logging.error(f"连接失败({attempt + 1}/{retries}) {device_info['host']}: {str(e)}") time.sleep(delay) raise ConnectionError(f"无法连接 {device_info['host']}") def get_device_credentials(ip): """根据IP地址确定设备类型""" if ip == DEVICES['oa_core_switch']['host']: return DEVICES['oa_core_switch'].copy() if ip in AGGREGATION_SWITCHES: # 汇聚交换机使用自己的凭据 creds = DEVICES['aggregation_switch'].copy() creds['host'] = ip return creds else: # 默认使用接入交换机凭据 creds = DEVICES['access_switch'].copy() creds['host'] = ip return creds def parse_switch_name(switch_name): """将交换机名称转换为IP地址""" # 支持格式: SW201.55 或 sw201.55 pattern = r'^[sS][wW](\d{1,3})\.(\d{1,3})$' match = re.match(pattern, switch_name) if not match: raise ValueError("交换机名称格式错误,请使用 SW交换机编号 的格式") building = match.group(1) switch_num = match.group(2) # 验证数据在合理范围内 if not 0 <= int(building) <= 255: raise ValueError(f"数据超出范围: {building}") # 验证交换机编号在合理范围内 if not 0 <= int(switch_num) <= 255: raise ValueError(f"交换机编号超出范围: {switch_num}") return f"172.17.{building}.{switch_num}" # ----------------------- 核心功能 ----------------------- def find_port_with_mac(conn, mac): """查找MAC地址所在的端口(支持单链路和聚合端口)""" try: output = conn.send_command_timing(f"display mac-address {mac}", delay_factor=4) logging.info(f"MAC地址查询结果 ({conn.host}):\n{output}") # 查找端口(可能是聚合端口或普通端口) found_port = None # 正则表达式匹配端口 port_pattern = r'(BAGG\d+|GE\S+|XGE\S+|Ten-GigabitEthernet\S+)' for line in output.splitlines(): # 查找Learned状态的端口 if 'Learned' in line: # 优先查找聚合端口 if 'BAGG' in line: match = re.search(r'BAGG\d+', line) if match: found_port = match.group() logging.info(f"在{conn.host}上找到聚合端口: {found_port}") return found_port, "AGG" # 查找物理端口 elif 'GE' in line or 'XGE' in line or 'Ten-GigabitEthernet' in line: match = re.search(port_pattern, line) if match: found_port = match.group() logging.info(f"在{conn.host}上找到物理端口: {found_port}") return found_port, "PHY" # 如果没找到明确的端口,尝试普通查询 for line in output.splitlines(): if 'GE' in line or 'XGE' in line or 'BAGG' in line or 'Ten-GigabitEthernet' in line: match = re.search(port_pattern, line) if match: found_port = match.group() if 'BAGG' in found_port: logging.info(f"在{conn.host}上找到聚合端口: {found_port}") return found_port, "AGG" else: logging.info(f"在{conn.host}上找到物理端口: {found_port}") return found_port, "PHY" logging.warning(f"在{conn.host}上未找到明确的端口") return None, None except Exception as e: logging.error(f"MAC地址查询失败 ({conn.host}): {str(e)}") return None, None def parse_lldp_neighbor(output): """解析LLDP输出获取管理IP""" for line in output.splitlines(): if 'Management address' in line and ':' in line: ip_match = re.search(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', line) if ip_match: return ip_match.group() return None def get_lldp_neighbor(conn, port): """获取LLDP邻居信息""" try: # 确保端口格式正确,移除多余空格和括号 port = re.sub(r'[()]', '', port).strip() # H3C端口格式转换 if port.startswith('XGE'): converted_port = port.replace("XGE", "Ten-GigabitEthernet") elif port.startswith('GE'): converted_port = port.replace("GE", "GigabitEthernet") else: converted_port = port logging.info(f"在{conn.host}上查询接口 {converted_port} 的LLDP邻居信息") output = conn.send_command_timing( f"display lldp neighbor-information interface {converted_port} verbose", read_timeout=90, delay_factor=4 ) return parse_lldp_neighbor(output) except Exception as e: logging.error(f"在{conn.host}上LLDP查询失败: {str(e)}") return None def get_agg_members(conn, agg_port): """获取聚合组的成员端口(改进的解析逻辑)""" try: # 将聚合端口转换为标准格式(如BAGG12 -> Bridge-Aggregation12) if agg_port.startswith('BAGG'): bridge_agg = "Bridge-Aggregation" + agg_port[4:] else: bridge_agg = agg_port logging.info(f"查询聚合组: {bridge_agg}") # 使用正确的命令格式 output = conn.send_command_timing( f"display link-aggregation verbose {bridge_agg}", delay_factor=4 ) logging.info(f"聚合组查询结果:\n{output}") # 解析成员端口 - 改进的解析逻辑 members = [] in_local_section = False for line in output.splitlines(): # 查找Local部分标题 if "Local:" in line: in_local_section = True continue if in_local_section: # 检查是否进入Remote部分 if "Remote:" in line: break # 忽略空行 if not line.strip(): continue # 提取物理端口(行中第一个元素) parts = line.split() if parts and any(port_type in parts[0] for port_type in ['GE', 'XGE', 'Ten']): port_name = parts[0] # 移除可能的参考端口标识 (R) if '(' in port_name: port_name = port_name.split('(')[0] members.append(port_name) logging.info(f"找到成员端口: {port_name}") if not members: # 尝试备用解析方法 logging.warning("使用备用方法解析成员端口") for line in output.splitlines(): if any(port_type in line for port_type in ['GE', 'XGE', 'Ten']): match = re.search(r'(GE\S+|XGE\S+|Ten-GigabitEthernet\S+)', line) if match: port_name = match.group() if '(' in port_name: port_name = port_name.split('(')[0] if port_name not in members: members.append(port_name) logging.info(f"找到成员端口: {port_name}") if not members: logging.warning(f"未能在聚合组 {bridge_agg} 中找到成员端口") else: logging.info(f"找到 {len(members)} 个成员端口") return members except Exception as e: logging.error(f"聚合组查询失败: {str(e)}") return [] def get_next_hop_from_port(conn, port, port_type): """根据端口类型获取下一跳设备""" if port_type == "AGG": logging.info(f"处理聚合端口: {port}") # 获取聚合组中的所有物理端口 physical_ports = get_agg_members(conn, port) if not physical_ports: logging.warning(f"在{conn.host}上未找到聚合端口 {port} 的成员端口") return None # 尝试每个物理端口查找LLDP邻居 for physical_port in physical_ports: logging.info(f"在物理端口 {physical_port} 上查询LLDP邻居") neighbor_ip = get_lldp_neighbor(conn, physical_port) if neighbor_ip: logging.info(f"在端口 {physical_port} 上找到邻居: {neighbor_ip}") return neighbor_ip return None else: logging.info(f"处理物理端口: {port}") return get_lldp_neighbor(conn, port) def trace_next_hop(switch_ip, mac): """追踪下一跳设备(改进的聚合端口处理)""" conn = None try: # 获取设备凭据 device_info = get_device_credentials(switch_ip) conn = connect_with_retry(device_info) # 查找MAC地址所在的端口及其类型 port, port_type = find_port_with_mac(conn, mac) if not port: logging.warning(f"在{switch_ip}上未找到MAC地址 {mac} 对应的端口") return None # 查找邻居设备IP neighbor_ip = get_next_hop_from_port(conn, port, port_type) if neighbor_ip: logging.info(f"在{switch_ip}上端口 {port} 找到邻居设备: {neighbor_ip}") return neighbor_ip else: logging.warning(f"在{switch_ip}上端口 {port} 未找到LLDP邻居") return None except Exception as e: logging.error(f"在{switch_ip}上追踪失败: {str(e)}") return None finally: if conn: conn.disconnect() def trace_final_device(mac): """追踪到最终设备""" formatted_mac = format_mac_address(mac) logging.info(f"开始追踪 MAC: {formatted_mac}") # 第一跳:核心交换机 core_ip = DEVICES['oa_core_switch']['host'] first_hop = trace_next_hop(core_ip, formatted_mac) if not first_hop: logging.warning("无法从核心交换机找到第一跳设备") return None # 检查第一跳设备是否是汇聚交换机 if first_hop in AGGREGATION_SWITCHES: logging.info(f"{first_hop} 是已知汇聚交换机,继续追踪") # 第二跳:从汇聚交换机追踪 final_device = trace_next_hop(first_hop, formatted_mac) if not final_device: logging.info(f"在汇聚交换机{first_hop}上未找到下层设备,使用该交换机作为最终设备") return first_hop return final_device else: logging.info(f"{first_hop} 不是已知汇聚交换机,作为最终设备") return first_hop def configure_device(device_ip, ip, mac): """在最终设备上绑定IP-MAC""" conn = None try: # 获取设备凭据 device_info = get_device_credentials(device_ip) # 为接入交换机设置更高的延迟因子 device_info['global_delay_factor'] = 3 conn = connect_with_retry(device_info) # 进入系统视图 - 使用send_command_timing避免期望字符串问题 conn.send_command_timing("system-view", delay_factor=3) time.sleep(1) # 确保进入系统视图 # 绑定IP-MAC cmd = f"ip source binding ip-address {ip} mac-address {mac}" output = conn.send_command_timing(cmd, delay_factor=3) logging.info(f"在{device_ip}上执行命令: {cmd}\n输出: {output}") # 保存配置 - 使用更稳健的方式处理保存确认 save_output = conn.send_command_timing("save force", delay_factor=3) if "Are you sure" in save_output or "(Y/N)" in save_output: save_output += conn.send_command_timing("y", delay_factor=3) time.sleep(2) # 给保存操作足够的时间 logging.info(f"在{device_ip}上保存配置结果: {save_output}") # 检查是否成功绑定 verify_cmd = f"display ip source binding ip-address {ip}" verify_output = conn.send_command_timing(verify_cmd, delay_factor=3) logging.info(f"验证绑定: {verify_output}") # 确认MAC地址是否在输出中 if mac.lower() in verify_output.lower(): logging.info(f"在{device_ip}上成功绑定验证") return True, device_ip else: logging.warning(f"在{device_ip}上未找到绑定记录") return False, device_ip except Exception as e: logging.error(f"在{device_ip}上配置失败: {str(e)}") return False, device_ip finally: if conn: try: conn.disconnect() except: pass def bind_ip_mac(ip, mac): """主绑定流程(自动发现)""" try: formatted_mac = format_mac_address(mac) logging.info(f"开始处理 自动发现: IP: {ip}, MAC: {formatted_mac}") # 查找最终设备 device_ip = trace_final_device(formatted_mac) if not device_ip: return f"❌ 错误: 无法找到MAC {formatted_mac} 对应的网络设备" # 在最终设备上配置 success, target_ip = configure_device(device_ip, ip, formatted_mac) return f"✅ 成功: 在 {target_ip} 上绑定 {ip} - {formatted_mac}" if success else \ f"❌ 错误: 无法在 {target_ip} 上绑定 {ip} - {formatted_mac}" except Exception as e: error_msg = f"⚠️ 异常: {str(e)}" logging.error(error_msg) return error_msg def manual_bind(ip, mac, switch_name): """手动绑定流程""" try: formatted_mac = format_mac_address(mac) switch_ip = parse_switch_name(switch_name) logging.info(f"开始处理 手动绑定: IP: {ip}, MAC: {formatted_mac}, 交换机: {switch_name} -> {switch_ip}") # 在指定交换机上配置 success, target_ip = configure_device(switch_ip, ip, formatted_mac) return f"✅ 成功: 在 {target_ip} 上手动绑定 {ip} - {formatted_mac}" if success else \ f"❌ 错误: 无法在 {target_ip} 上手动绑定 {ip} - {formatted_mac}" except ValueError as ve: error_msg = f"⚠️ 输入错误: {str(ve)}" logging.error(error_msg) return error_msg except Exception as e: error_msg = f"⚠️ 异常: {str(e)}" logging.error(error_msg) return error_msg # ----------------------- GUI界面 ----------------------- class IPMacBindApp: def __init__(self, root): self.root = root self.root.title("IP-MAC绑定工具") self.root.geometry("900x750") # 调整尺寸以适应新输入区域 self.root.resizable(True, True) # 设置现代主题 style = ttk.Style() style.theme_use('clam') # 配置颜色 style.configure("TFrame", background="#f0f0f0") style.configure("TLabel", background="#f0f0f0", foreground="#333333") style.configure("TButton", background="#4a86e8", foreground="white", font=("Arial", 10)) style.map("TButton", background=[('active', '#3a76d8')]) style.configure("Accent.TButton", background="#28a745", foreground="white", font=("Arial", 10, "bold")) style.map("Accent.TButton", background=[('active', '#218838')]) style.configure("Treeview", background="white", fieldbackground="white", foreground="#333333") style.configure("Treeview.Heading", background="#e9ecef", foreground="#333333", font=("Arial", 9, "bold")) style.configure("TLabelframe", background="#f0f0f0", foreground="#333333") style.configure("TLabelframe.Label", background="#f0f0f0", foreground="#333333") style.configure("TScrollbar", background="#e9ecef") # 配置操作按钮样式 style.configure("Delete.TButton", background="#e74c3c", foreground="white", font=("Arial", 9, "bold")) style.map("Delete.TButton", background=[('active', '#c0392b')]) # 创建主框架 main_frame = ttk.Frame(root) main_frame.pack(fill="both", expand=True, padx=15, pady=15) # 标题区域 title_frame = ttk.Frame(main_frame) title_frame.pack(fill="x", pady=(0, 10)) title_label = ttk.Label( title_frame, text="IP-MAC绑定工具", font=("Arial", 16, "bold"), foreground="#2c3e50" ) title_label.pack(pady=5) # ========== 新增手动绑定区域 ========== manual_frame = ttk.LabelFrame(main_frame, text="手动交换机绑定") manual_frame.pack(fill="x", pady=10, padx=5) # 使用网格布局 manual_grid = ttk.Frame(manual_frame) manual_grid.pack(fill="x", padx=10, pady=10) # 交换机名称标签和输入框 ttk.Label(manual_grid, text="交换机名称:").grid(row=0, column=0, padx=5, pady=5, sticky="e") self.switch_entry = ttk.Entry(manual_grid, width=20) self.switch_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w") self.switch_entry.insert(0, "SW201.55") # 提示文本 tip_label = ttk.Label( manual_grid, text="格式: SW交换机号 (示例: SW201.55 -> 172.17.201.55)", foreground="#666666", font=("Arial", 8) ) tip_label.grid(row=0, column=2, padx=10, pady=5, sticky="w", columnspan=3) # IP地址标签和输入框 ttk.Label(manual_grid, text="IP地址:").grid(row=1, column=0, padx=5, pady=5, sticky="e") self.manual_ip_entry = ttk.Entry(manual_grid, width=20) self.manual_ip_entry.grid(row=1, column=1, padx=5, pady=5, sticky="w") # MAC地址标签和输入框 ttk.Label(manual_grid, text="MAC地址:").grid(row=1, column=2, padx=5, pady=5, sticky="e") self.manual_mac_entry = ttk.Entry(manual_grid, width=20) self.manual_mac_entry.grid(row=1, column=3, padx=5, pady=5, sticky="w") # 添加手动绑定按钮 manual_add_btn = ttk.Button( manual_grid, text="添加手动绑定任务", command=self.add_manual_to_queue, width=18, style="Accent.TButton" ) manual_add_btn.grid(row=1, column=4, padx=10, pady=5) # 添加分隔线 separator = ttk.Separator(main_frame, orient="horizontal") separator.pack(fill="x", pady=10) # ========== 自动绑定区域 ========== auto_frame = ttk.LabelFrame(main_frame, text="自动发现绑定 (通过LLDP)") auto_frame.pack(fill="x", pady=10, padx=5) # 使用网格布局 auto_grid = ttk.Frame(auto_frame) auto_grid.pack(fill="x", padx=10, pady=10) ttk.Label(auto_grid, text="IP地址:").grid(row=0, column=0, padx=5, pady=5, sticky="e") self.auto_ip_entry = ttk.Entry(auto_grid, width=20) self.auto_ip_entry.grid(row=0, column=1, padx=5, pady=5, sticky="w") ttk.Label(auto_grid, text="MAC地址:").grid(row=0, column=2, padx=5, pady=5, sticky="e") self.auto_mac_entry = ttk.Entry(auto_grid, width=20) self.auto_mac_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w") auto_add_btn = ttk.Button( auto_grid, text="添加到队列 (自动发现)", command=self.add_auto_to_queue, width=18, style="TButton" ) auto_add_btn.grid(row=0, column=4, padx=10, pady=5) # 分隔线下方的队列区域 separator2 = ttk.Separator(main_frame, orient="horizontal") separator2.pack(fill="x", pady=5) # 队列区域 queue_frame = ttk.LabelFrame(main_frame, text="绑定任务队列") queue_frame.pack(fill="both", expand=True, pady=5) # 创建队列树视图(添加操作列) columns = ("type", "ip", "mac", "switch", "status", "actions") self.queue_tree = ttk.Treeview( queue_frame, columns=columns, show="headings", height=8, selectmode="browse" ) self.queue_tree.heading("type", text="类型") self.queue_tree.heading("ip", text="IP地址") self.queue_tree.heading("mac", text="MAC地址") self.queue_tree.heading("switch", text="目标交换机") self.queue_tree.heading("status", text="状态") self.queue_tree.heading("actions", text="操作") self.queue_tree.column("type", width=80, anchor="center") self.queue_tree.column("ip", width=120, anchor="center") self.queue_tree.column("mac", width=150, anchor="center") self.queue_tree.column("switch", width=150, anchor="center") self.queue_tree.column("status", width=100, anchor="center") self.queue_tree.column("actions", width=80, anchor="center") # 配置状态颜色 self.queue_tree.tag_configure('waiting', foreground='#6c757d') self.queue_tree.tag_configure('processing', foreground='#0d6efd') self.queue_tree.tag_configure('manual', foreground='#e67e22') self.queue_tree.tag_configure('auto', foreground='#3498db') self.queue_tree.tag_configure('success', foreground='#198754') self.queue_tree.tag_configure('failed', foreground='#dc3545') # 添加滚动条 tree_scroll = ttk.Scrollbar( queue_frame, orient="vertical", command=self.queue_tree.yview ) self.queue_tree.configure(yscrollcommand=tree_scroll.set) # 布局 self.queue_tree.pack(side="left", fill="both", expand=True, padx=5, pady=5) tree_scroll.pack(side="right", fill="y", padx=5, pady=5) # 操作按钮区域 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill="x", pady=10) # 添加开始绑定按钮 self.start_btn = ttk.Button( btn_frame, text="开始绑定", command=self.start_binding, width=12, style="Accent.TButton" ) self.start_btn.pack(side="left", padx=5) # 添加重试失败按钮 self.retry_btn = ttk.Button( btn_frame, text="重试失败", command=self.retry_failed, width=12, style="TButton" ) self.retry_btn.pack(side="left", padx=5) # 其他操作按钮 clear_queue_btn = ttk.Button( btn_frame, text="清空队列", command=self.clear_queue, width=12, style="TButton" ) clear_queue_btn.pack(side="left", padx=5) export_btn = ttk.Button( btn_frame, text="导出日志", command=self.export_log, width=12, style="TButton" ) export_btn.pack(side="right", padx=5) # 日志区域 log_frame = ttk.LabelFrame(main_frame, text="操作日志") log_frame.pack(fill="both", expand=True, pady=(0, 5)) self.log_area = scrolledtext.ScrolledText( log_frame, height=15, bg="white", fg="#333333" ) self.log_area.pack(fill="both", expand=True, padx=5, pady=5) self.log_area.configure(state="disabled") # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label( root, textvariable=self.status_var, relief="sunken", anchor="w", background="#e9ecef", foreground="#6c757d", font=("Arial", 9) ) status_bar.pack(side="bottom", fill="x", padx=5, pady=2) # 状态变量 self.running = False self.queue = [] # 存储任务信息:((type, ip, mac, switch_name), status) self.task_ids = {} # 跟踪任务ID,防止重复添加未完成的任务 self.item_ids = {} # 跟踪树项ID,防止重复添加 # 绑定双击事件 self.queue_tree.bind("<Double-1>", self.on_tree_double_click) # 绑定右键菜单 self.context_menu = tk.Menu(root, tearoff=0) self.context_menu.add_command(label="删除任务", command=self.delete_selected_task) self.queue_tree.bind("<Button-3>", self.show_context_menu) self.log_message("✅ 应用程序已启动") self.log_message("手动绑定区域: 输入交换机名(如SW201.55)、IP和MAC,点'添加手动绑定任务'") self.log_message("自动绑定区域: 输入IP和MAC,点'添加到队列(自动发现)'") def log_message(self, message): """添加消息到日志区域""" self.log_area.configure(state="normal") self.log_area.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n") self.log_area.see(tk.END) self.log_area.configure(state="disabled") self.status_var.set(message[:60] + "..." if len(message) > 60 else message) def add_auto_to_queue(self): """添加自动绑定任务到队列""" ip = self.auto_ip_entry.get().strip() mac = self.auto_mac_entry.get().strip() if not ip or not mac: messagebox.showwarning("输入错误", "请输入IP和MAC地址") return try: # 验证MAC格式 formatted_mac = format_mac_address(mac) # 检查IP格式 if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip): raise ValueError("无效的IP地址格式") # 创建任务标识符 task_type = "auto" task_id = f"{task_type}|{ip}|{mac}" # 检查任务是否已存在且未完成 if task_id in self.task_ids: existing_status = self.queue_tree.item(self.task_ids[task_id], "values")[4] if existing_status in ["等待中", "处理中..."]: self.log_message(f"⚠️ 任务已存在: {ip} ↔ {formatted_mac} (自动发现)") return # 添加到队列 self.queue.append((task_type, ip, mac, "")) # 添加到队列树 item_id = self.queue_tree.insert( "", "end", values=("自动发现", ip, formatted_mac, "待确定", "等待中", "删除"), tags=('auto', 'waiting') ) self.task_ids[task_id] = item_id self.item_ids[item_id] = task_id # 添加反向映射 # 清空输入框 self.auto_ip_entry.delete(0, tk.END) self.auto_mac_entry.delete(0, tk.END) self.log_message(f"✅ 已添加绑定任务: {ip} ↔ {formatted_mac} (自动发现)") self.status_var.set(f"队列: {len(self.queue)} 个任务等待处理") except Exception as e: self.log_message(f"❌ 错误: {str(e)}") def add_manual_to_queue(self): """添加手动绑定任务到队列""" switch_name = self.switch_entry.get().strip() ip = self.manual_ip_entry.get().strip() mac = self.manual_mac_entry.get().strip() if not switch_name or not ip or not mac: messagebox.showwarning("输入错误", "请输入交换机名称、IP和MAC地址") return try: # 验证MAC格式 formatted_mac = format_mac_address(mac) # 检查IP格式 if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip): raise ValueError("无效的IP地址格式") # 解析交换机名称 switch_ip = parse_switch_name(switch_name) # 创建任务标识符 task_type = "manual" task_id = f"{task_type}|{switch_ip}|{ip}|{formatted_mac}" # 检查任务是否已存在且未完成 if task_id in self.task_ids: existing_status = self.queue_tree.item(self.task_ids[task_id], "values")[4] if existing_status in ["等待中", "处理中..."]: self.log_message(f"⚠️ 任务已存在: {switch_name} ↔ {ip} ↔ {formatted_mac}") return # 添加到队列 self.queue.append((task_type, ip, mac, switch_name)) # 添加到队列树 item_id = self.queue_tree.insert( "", "end", values=("手动指定", ip, formatted_mac, f"{switch_name} ({switch_ip})", "等待中", "删除"), tags=('manual', 'waiting') ) self.task_ids[task_id] = item_id self.item_ids[item_id] = task_id # 添加反向映射 # 清空输入框(只清空IP和MAC,保留交换机名称) self.manual_ip_entry.delete(0, tk.END) self.manual_mac_entry.delete(0, tk.END) self.log_message(f"✅ 已添加绑定任务: {ip} ↔ {formatted_mac} (交换机: {switch_name} -> {switch_ip})") self.status_var.set(f"队列: {len(self.queue)} 个任务等待处理") except Exception as e: self.log_message(f"❌ 错误: {str(e)}") def on_tree_double_click(self, event): """处理树状视图的双击事件""" region = self.queue_tree.identify("region", event.x, event.y) if region != "cell": return column = self.queue_tree.identify_column(event.x) item_id = self.queue_tree.identify_row(event.y) if not item_id: return # 如果双击的是操作列(第6列) if column == "#6": self.confirm_delete_task(item_id) def confirm_delete_task(self, item_id): """确认删除任务""" values = self.queue_tree.item(item_id, "values") if not values: return if values[4] == "处理中...": # 第5列是状态 messagebox.showwarning("无法删除", "任务正在处理中,无法删除") return task_type = values[0] # 第1列是类型 ip = values[1] # 第2列是IP mac = values[2] # 第3列是MAC if messagebox.askyesno("确认删除", f"确定要删除 {task_type} 绑定任务吗?\nIP: {ip}\nMAC: {mac}"): self.delete_task(item_id) def delete_task(self, item_id): """从队列中删除任务""" if item_id not in self.item_ids: self.log_message(f"❌ 错误: 树项 {item_id} 不存在") return # 获取任务ID task_id = self.item_ids[item_id] # 从树视图中删除 self.queue_tree.delete(item_id) # 从task_ids字典中移除 if task_id in self.task_ids: del self.task_ids[task_id] # 从item_ids字典中移除 if item_id in self.item_ids: del self.item_ids[item_id] # 从队列中移除任务 task_parts = task_id.split("|") if len(task_parts) > 1: task_type = task_parts[0] ip = task_parts[1] mac = task_parts[2] if len(task_parts) >= 3 else "" switch = task_parts[3] if len(task_parts) >= 4 else "" # 从队列中删除相应的任务 for task in self.queue[:]: if ( task[0] == task_type and task[1] == ip and task[2] == mac and (len(task) < 4 or task[3] == switch) ): self.queue.remove(task) break self.log_message("♻️ 任务已删除") self.status_var.set(f"队列: {len(self.queue)} 个任务等待处理") def show_context_menu(self, event): """显示右键菜单""" item_id = self.queue_tree.identify_row(event.y) if not item_id: return values = self.queue_tree.item(item_id, "values") if not values or len(values) < 6: return self.queue_tree.selection_set(item_id) # 选中该行 # 创建带删除选项的菜单 self.context_menu.delete(0, tk.END) if values[4] == "处理中...": # 状态在第5列 self.context_menu.add_command( label="任务处理中(不可删除)" ) else: self.context_menu.add_command( label="删除任务", command=lambda id=item_id: self.confirm_delete_task(id) ) self.context_menu.tk_popup(event.x_root, event.y_root) def delete_selected_task(self): """删除当前选中的任务""" selected = self.queue_tree.selection() if selected: self.confirm_delete_task(selected[0]) # 在 add_manual_to_queue 方法中修改任务ID生成方式 def add_manual_to_queue(self): """添加手动绑定任务到队列""" switch_name = self.switch_entry.get().strip() ip = self.manual_ip_entry.get().strip() mac = self.manual_mac_entry.get().strip() if not switch_name or not ip or not mac: messagebox.showwarning("输入错误", "请输入交换机名称、IP和MAC地址") return try: # 验证MAC格式 formatted_mac = format_mac_address(mac) # 检查IP格式 if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip): raise ValueError("无效的IP地址格式") # 解析交换机名称 switch_ip = parse_switch_name(switch_name) # 创建任务标识符 - 使用交换机IP而不是名称 task_type = "manual" task_id = f"{task_type}|{switch_ip}|{ip}|{formatted_mac}" # 关键修改 # 检查任务是否已存在且未完成 if task_id in self.task_ids: existing_status = self.queue_tree.item(self.task_ids[task_id], "values")[4] if existing_status in ["等待中", "处理中..."]: self.log_message(f"⚠️ 任务已存在: {switch_name} ↔ {ip} ↔ {formatted_mac}") return # 添加到队列 self.queue.append((task_type, ip, mac, switch_name)) # 添加到队列树 item_id = self.queue_tree.insert( "", "end", values=("手动指定", ip, formatted_mac, f"{switch_name} ({switch_ip})", "等待中", "删除"), tags=('manual', 'waiting') ) self.task_ids[task_id] = item_id self.item_ids[item_id] = task_id # 添加反向映射 # 清空输入框(只清空IP和MAC,保留交换机名称) self.manual_ip_entry.delete(0, tk.END) self.manual_mac_entry.delete(0, tk.END) self.log_message(f"✅ 已添加绑定任务: {ip} ↔ {formatted_mac} (交换机: {switch_name} -> {switch_ip})") self.status_var.set(f"队列: {len(self.queue)} 个任务等待处理") except Exception as e: self.log_message(f"❌ 错误: {str(e)}") # 在 start_binding 方法中修改任务ID生成方式 # 修改 add_auto_to_queue 方法 def add_auto_to_queue(self): """添加自动绑定任务到队列""" ip = self.auto_ip_entry.get().strip() mac = self.auto_mac_entry.get().strip() if not ip or not mac: messagebox.showwarning("输入错误", "请输入IP和MAC地址") return try: # 验证MAC格式 formatted_mac = format_mac_address(mac) # 检查IP格式 if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip): raise ValueError("无效的IP地址格式") # 创建任务标识符 task_type = "auto" task_id = f"{task_type}|{ip}|{formatted_mac}" # 使用格式化后的MAC # 检查任务是否已存在且未完成 if task_id in self.task_ids: existing_status = self.queue_tree.item(self.task_ids[task_id], "values")[4] if existing_status in ["等待中", "处理中..."]: self.log_message(f"⚠️ 任务已存在: {ip} ↔ {formatted_mac} (自动发现)") return # 添加到队列 - 只存储3个元素 self.queue.append((task_type, ip, mac)) # 添加到队列树 item_id = self.queue_tree.insert( "", "end", values=("自动发现", ip, formatted_mac, "待确定", "等待中", "删除"), tags=('auto', 'waiting') ) self.task_ids[task_id] = item_id self.item_ids[item_id] = task_id # 添加反向映射 # 清空输入框 self.auto_ip_entry.delete(0, tk.END) self.auto_mac_entry.delete(0, tk.END) self.log_message(f"✅ 已添加绑定任务: {ip} ↔ {formatted_mac} (自动发现)") self.status_var.set(f"队列: {len(self.queue)} 个任务等待处理") except Exception as e: self.log_message(f"❌ 错误: {str(e)}") # 修改 start_binding 方法中的任务处理部分 def start_binding(self): """开始绑定流程(多线程)""" if not self.queue: messagebox.showinfo("操作提示", "绑定队列为空") return if self.running: self.log_message("⚠️ 绑定任务已在执行中") return self.running = True self.start_btn.configure(state="disabled") self.retry_btn.configure(state="disabled") self.log_message(" 开始绑定任务处理...") # 创建线程执行绑定 def worker(): try: # 创建队列副本避免修改时冲突 queue_copy = self.queue.copy() for task_info in queue_copy: # 使用[:]创建副本避免修改冲突 if not self.running: self.log_message("❌ 绑定任务被中断") break # 解包任务信息 task_type = task_info[0] ip = task_info[1] mac = task_info[2] # 格式化MAC地址 formatted_mac = format_mac_address(mac) # 创建任务标识符 if task_type == "manual": switch_name = task_info[3] switch_ip = parse_switch_name(switch_name) task_id = f"{task_type}|{switch_ip}|{ip}|{formatted_mac}" else: # auto task_id = f"{task_type}|{ip}|{formatted_mac}" item_id = self.task_ids.get(task_id) # 检查任务是否已被删除 if not item_id or not self.queue_tree.exists(item_id): self.log_message(f"⏩ 跳过已删除任务: {ip} ↔ {formatted_mac}") continue # 更新任务状态 self.queue_tree.item( item_id, values=(task_type.capitalize(), ip, formatted_mac, self.queue_tree.item(item_id, "values")[3], "处理中...", "删除"), tags=('processing',) ) self.log_message(f"▶ 正在处理: {task_type}任务 - {ip} ↔ {formatted_mac}") # 执行绑定 start_time = time.time() if task_type == "manual": result = manual_bind(ip, mac, switch_name) switch_ip = parse_switch_name(switch_name) # 更新交换机显示 self.queue_tree.item( item_id, values=("手动指定", ip, formatted_mac, f"{switch_name} ({switch_ip})", "处理中...", "删除"), tags=('processing',) ) else: # auto # 调用自动绑定函数 result = bind_ip_mac(ip, mac) elapsed = time.time() - start_time self.log_message(f"⏱ 处理耗时: {elapsed:.2f}秒") self.log_message(result) # 解析结果并更新状态 if "成功" in result: status = "✅ 已完成" tag = 'success' # 自动任务需要更新交换机信息 if task_type == "auto": # 尝试从结果中提取设备IP (例如 "✅ 成功: 在 172.17.201.55 上绑定...") match = re.search(r'在\s*(\d+\.\d+\.\d+\.\d+)\s*上', result) if match: device_ip = match.group(1) self.queue_tree.item( item_id, values=("自动发现", ip, formatted_mac, device_ip, status, "删除"), tags=(tag,) ) else: self.queue_tree.item( item_id, values=("自动发现", ip, formatted_mac, "已发现", status, "删除"), tags=(tag,) ) else: # manual self.queue_tree.item( item_id, values=("手动指定", ip, formatted_mac, self.queue_tree.item(item_id, "values")[3], status, "删除"), tags=(tag,) ) else: status = "❌ 失败" tag = 'failed' self.queue_tree.item( item_id, values=(task_type.capitalize(), ip, formatted_mac, self.queue_tree.item(item_id, "values")[3], status, "删除"), tags=(tag,) ) # 处理完成后从主队列中移除 if task_info in self.queue: self.queue.remove(task_info) # 短暂暂停避免设备过载 time.sleep(1) self.log_message("🏁 所有绑定任务处理完成") except Exception as e: self.log_message(f"⚠️ 绑定过程中断: {str(e)}") finally: self.start_btn.configure(state="normal") self.retry_btn.configure(state="normal") self.running = False self.status_var.set("就绪 | 绑定任务完成") threading.Thread(target=worker, daemon=True).start() def retry_failed(self): """重试所有失败的任务""" # 收集所有失败的任务 failed_tasks = [] for item_id in self.queue_tree.get_children(): values = self.queue_tree.item(item_id, "values") if values and values[4] == "❌ 失败": # 第5列是状态 ip, mac = values[1], values[2] task_type = values[0] if task_type == "手动指定": switch_name_match = re.search(r'(\S+)\s*\(', values[3]) # 从"SW201.55 (172.17.201.55)"提取"SW201.55" if switch_name_match: switch_name = switch_name_match.group(1) failed_tasks.append((item_id, "manual", ip, mac, switch_name)) else: failed_tasks.append((item_id, "auto", ip, mac, "")) if not failed_tasks: messagebox.showinfo("操作提示", "没有失败的任务") return self.log_message(f"🔄 开始重试 {len(failed_tasks)} 个失败任务") # 直接开始绑定,不修改队列 self.running = True self.start_btn.configure(state="disabled") self.retry_btn.configure(state="disabled") # 创建线程执行重试 def worker(): try: for task_info in failed_tasks: item_id, task_type, ip, mac, switch_name = task_info if not self.running: self.log_message("❌ 重试任务被中断") break # 检查树项是否存在 if not self.queue_tree.exists(item_id): self.log_message(f"⏩ 跳过不存在的任务: {ip} ↔ {mac}") continue # 更新任务状态 self.queue_tree.item( item_id, values=(self.queue_tree.item(item_id, "values")[0], ip, format_mac_address(mac), self.queue_tree.item(item_id, "values")[3], "处理中...", "删除"), tags=('processing',) ) self.log_message(f"▶ 正在重试: {task_type}任务 - {ip} ↔ {format_mac_address(mac)}") # 执行绑定 start_time = time.time() if task_type == "manual": result = manual_bind(ip, mac, switch_name) else: # auto result = bind_ip_mac(ip, mac) elapsed = time.time() - start_time self.log_message(f"⏱ 处理耗时: {elapsed:.2f}秒") self.log_message(result) # 更新任务状态 if "成功" in result: status = "✅ 已完成" tag = 'success' else: status = "❌ 失败" tag = 'failed' self.queue_tree.item( item_id, values=(task_type, ip, format_mac_address(mac), self.queue_tree.item(item_id, "values")[3], status, "删除"), tags=(tag,) ) # 短暂暂停避免设备过载 time.sleep(1) self.log_message("🏁 所有重试任务处理完成") except Exception as e: self.log_message(f"⚠️ 重试过程中断: {str(e)}") finally: self.start_btn.configure(state="normal") self.retry_btn.configure(state="normal") self.running = False self.status_var.set("就绪 | 重试任务完成") threading.Thread(target=worker, daemon=True).start() def clear_queue(self): """清除整个队列""" if not self.queue: return if messagebox.askyesno("确认", "确定要清空所有绑定任务吗?"): self.queue = [] self.task_ids = {} self.item_ids = {} for item in self.queue_tree.get_children(): self.queue_tree.delete(item) self.log_message("♻️ 绑定队列已清空") self.status_var.set("就绪") def export_log(self): """导出日志到文件""" try: timestamp = time.strftime("%Y%m%d-%H%M%S") filename = f"ip_mac_bind_{timestamp}.log" with open(filename, "w", encoding="utf-8") as f: f.write(self.log_area.get("1.0", tk.END)) self.log_message(f"📄 日志已导出到 {filename}") messagebox.showinfo("导出成功", f"日志已保存到 {filename}") except Exception as e: self.log_message(f"❌ 日志导出失败: {str(e)}") messagebox.showerror("导出失败", f"无法保存日志: {str(e)}") # ----------------------- 主程序 ----------------------- if __name__ == "__main__": root = tk.Tk() app = IPMacBindApp(root) # 居中显示窗口 window_width = 900 window_height = 750 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() position_x = int((screen_width - window_width) / 2) position_y = int((screen_height - window_height) / 2) root.geometry(f"{window_width}x{window_height}+{position_x}+{position_y}") root.mainloop()
07-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值