e5d6d843c3
修复: - 环路检测正则 \s+(Onu\S+)\s+ → \s+(Onu\S+) (splitlines移除换行后尾随\s无法匹配) - 权限中间件 Header(...) → Header(None) 避免缺失Auth头返回422而非401 - 环路检测请求超时30s→120s (SSH连接30+台OLT实测需58秒) 重构 (ssh_service.py): - 提取 _send_and_wait 为私有方法,消除3处重复内部函数 - 添加 __enter__/__exit__ 上下文管理器支持 - 加固 execute_command prompt检测 (按行匹配<DEVICE_NAME>) - 移除未使用的settings import - olt.py/devices.py 调用方改用 with 语法 新功能: - 侧边栏退出登录上方显示当前用户名和角色 - 版本号从VERSION文件自动读取 (后端/health返回,前端动态显示) - 基于广西南宁经纬度计算日落时间,自动切换深色/浅色主题 - /api/olt/loopback-detection 响应增加raw字段便于排查 基础设施: - CLAUDE.md 加入 .gitignore - 新增 .claude/rules/07-remote-operations.md (远程部署操作) - 新增 .claude/rules/08-frp-notes.md (frp隧道注意事项) - 新增 VERSION 文件 (版本号 0.10.0) - 新增环路检测解析测试用例 (5个) Co-Authored-By: Claude <noreply@anthropic.com>
593 lines
21 KiB
Python
593 lines
21 KiB
Python
"""OLT 设备管理 API"""
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import distinct
|
|
from pydantic import BaseModel
|
|
from app.core.database import get_db
|
|
from app.core.config import settings
|
|
from app.middleware.permission_middleware import require_permission
|
|
from app.models.device import OLTDevice
|
|
import pandas as pd
|
|
import io
|
|
|
|
router = APIRouter(prefix="/api/olt", tags=["OLT设备"])
|
|
|
|
|
|
class OLTCreate(BaseModel):
|
|
ip_address: str
|
|
username: str
|
|
password: str
|
|
slot_command: str = "display onu slot"
|
|
region: str = "城区"
|
|
location: str = ""
|
|
description: str = ""
|
|
|
|
|
|
class OLTEdit(BaseModel):
|
|
username: str
|
|
password: str = None
|
|
slot_command: str = "display onu slot"
|
|
region: str = "城区"
|
|
location: str = ""
|
|
description: str = ""
|
|
|
|
|
|
@router.get("/regions")
|
|
def get_olt_regions(
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.view')),
|
|
):
|
|
"""获取 OLT 设备的所有区域(受角色数据范围限制)"""
|
|
query = db.query(distinct(OLTDevice.region)).filter(
|
|
OLTDevice.region.isnot(None),
|
|
OLTDevice.region != ''
|
|
)
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
if areas:
|
|
query = query.filter(OLTDevice.region.in_(areas))
|
|
else:
|
|
return []
|
|
return sorted([r[0] for r in query.all()])
|
|
|
|
|
|
@router.get("/devices")
|
|
def get_devices(
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.view')),
|
|
):
|
|
q = db.query(OLTDevice)
|
|
# 区域管理员只能看自己区域的 OLT
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
if areas:
|
|
q = q.filter(OLTDevice.region.in_(areas))
|
|
else:
|
|
return []
|
|
return q.all()
|
|
|
|
|
|
@router.post("/devices")
|
|
def create_device(
|
|
device: OLTCreate,
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
# 区域管理员只能创建自己区域的 OLT
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
if device.region not in areas:
|
|
raise HTTPException(status_code=403, detail="只能管理本区域的 OLT")
|
|
db_device = OLTDevice(**device.dict())
|
|
db.add(db_device)
|
|
db.commit()
|
|
return {"message": "创建成功"}
|
|
|
|
|
|
@router.put("/devices/{ip_address}")
|
|
def update_device(
|
|
ip_address: str,
|
|
device: OLTEdit,
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
db_device = db.query(OLTDevice).filter(OLTDevice.ip_address == ip_address).first()
|
|
if not db_device:
|
|
raise HTTPException(status_code=404, detail="设备不存在")
|
|
# 区域管理员只能管理自己区域的 OLT
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
if db_device.region not in areas:
|
|
raise HTTPException(status_code=403, detail="只能管理本区域的 OLT")
|
|
|
|
db_device.username = device.username
|
|
if device.password:
|
|
db_device.password = device.password
|
|
db_device.slot_command = device.slot_command
|
|
db_device.region = device.region
|
|
db_device.location = device.location
|
|
db_device.description = device.description
|
|
|
|
db.commit()
|
|
return {"message": "更新成功"}
|
|
|
|
|
|
@router.delete("/devices/{ip_address}")
|
|
def delete_device(
|
|
ip_address: str,
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
from app.models.device import ONUDevice
|
|
|
|
device = db.query(OLTDevice).filter(OLTDevice.ip_address == ip_address).first()
|
|
if not device:
|
|
raise HTTPException(status_code=404, detail="设备不存在")
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
if device.region not in areas:
|
|
raise HTTPException(status_code=403, detail="只能管理本区域的 OLT")
|
|
|
|
onu_count = db.query(ONUDevice).filter(ONUDevice.olt_id == device.id).count()
|
|
if onu_count > 0:
|
|
raise HTTPException(status_code=400, detail=f"该 OLT 设备下还有 {onu_count} 个 ONU 设备,无法删除")
|
|
|
|
db.delete(device)
|
|
db.commit()
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
@router.post("/import")
|
|
async def import_devices(
|
|
file: UploadFile = File(...),
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
try:
|
|
content = await file.read()
|
|
df = pd.read_excel(io.BytesIO(content))
|
|
# 标准化列名
|
|
df.columns = [str(c).strip() for c in df.columns]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"文件解析失败: {str(e)}")
|
|
|
|
required_cols = ['IP地址', '用户名', '密码']
|
|
missing = [c for c in required_cols if c not in df.columns]
|
|
if missing:
|
|
raise HTTPException(status_code=400, detail=f"缺少必填列: {', '.join(missing)},当前列: {', '.join(df.columns.tolist())}")
|
|
|
|
success, failed = 0, []
|
|
for idx, row in df.iterrows():
|
|
try:
|
|
ip = str(row['IP地址']).strip()
|
|
if not ip or ip == 'nan':
|
|
continue
|
|
existing = db.query(OLTDevice).filter(OLTDevice.ip_address == ip).first()
|
|
if existing:
|
|
failed.append({"row": idx + 2, "ip": ip, "reason": "IP 已存在"})
|
|
continue
|
|
device = OLTDevice(
|
|
ip_address=ip,
|
|
username=str(row['用户名']).strip(),
|
|
password=str(row['密码']).strip(),
|
|
slot_command=str(row['槽位命令']).strip() if '槽位命令' in df.columns and str(row['槽位命令']) != 'nan' else 'display onu slot',
|
|
region=str(row['区域']).strip() if '区域' in df.columns and str(row['区域']) != 'nan' else '城区',
|
|
location=str(row['安装位置']).strip() if '安装位置' in df.columns and str(row['安装位置']) != 'nan' else '',
|
|
description=str(row['描述']).strip() if '描述' in df.columns and str(row['描述']) != 'nan' else '',
|
|
)
|
|
db.add(device)
|
|
success += 1
|
|
except Exception as e:
|
|
failed.append({"row": idx + 2, "ip": str(row.get('IP地址', '')), "reason": str(e)})
|
|
|
|
db.commit()
|
|
return {"message": f"成功导入 {success} 条记录", "success": success, "failed": failed}
|
|
|
|
|
|
@router.get("/template")
|
|
def download_template():
|
|
from fastapi.responses import FileResponse
|
|
import os
|
|
# __file__ is at <root>/app/api/v1/olt.py → go up 4 levels to reach <root>
|
|
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
path = os.path.join(base, "templates", "OLT设备导入模板.xlsx")
|
|
return FileResponse(path=path, filename="OLT设备导入模板.xlsx")
|
|
|
|
|
|
@router.get("/duplicate-macs")
|
|
def get_duplicate_macs(
|
|
olt_id: int = None,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.view')),
|
|
):
|
|
"""查询重复 MAC 地址记录"""
|
|
from app.models.device import DuplicateMac
|
|
query = db.query(DuplicateMac)
|
|
if olt_id:
|
|
query = query.filter(DuplicateMac.olt_id == olt_id)
|
|
records = query.order_by(DuplicateMac.last_seen_at.desc()).all()
|
|
return [
|
|
{
|
|
"id": r.id,
|
|
"olt_id": r.olt_id,
|
|
"mac_address": r.mac_address,
|
|
"ports": r.ports,
|
|
"first_seen_at": r.first_seen_at,
|
|
"last_seen_at": r.last_seen_at,
|
|
}
|
|
for r in records
|
|
]
|
|
|
|
|
|
@router.delete("/duplicate-macs/{record_id}")
|
|
def delete_duplicate_mac(
|
|
record_id: int,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
"""删除重复 MAC 记录(已处理后清除)"""
|
|
from app.models.device import DuplicateMac
|
|
record = db.query(DuplicateMac).filter(DuplicateMac.id == record_id).first()
|
|
if not record:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
db.delete(record)
|
|
db.commit()
|
|
return {"message": "已删除"}
|
|
|
|
|
|
class ClearPortRequest(BaseModel):
|
|
port_id: str # 如 "1/0/1:3"
|
|
|
|
|
|
@router.post("/duplicate-macs/{record_id}/clear-port")
|
|
def clear_onu_port(
|
|
record_id: int,
|
|
body: ClearPortRequest,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
"""通过 SSH 清除指定端口的 ONU 配置,并从 ports 列表中移除该端口"""
|
|
from app.models.device import DuplicateMac
|
|
from app.services.ssh_service import SSHService
|
|
|
|
record = db.query(DuplicateMac).filter(DuplicateMac.id == record_id).first()
|
|
if not record:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
|
|
olt = db.query(OLTDevice).filter(OLTDevice.id == record.olt_id).first()
|
|
if not olt:
|
|
raise HTTPException(status_code=404, detail="OLT 设备不存在")
|
|
|
|
# 验证 port_id 在记录中
|
|
port_ids = [p["port_id"] for p in (record.ports or [])]
|
|
if body.port_id not in port_ids:
|
|
raise HTTPException(status_code=400, detail="端口不在重复记录中")
|
|
|
|
try:
|
|
with SSHService(olt.ip_address, olt.username, olt.password) as ssh:
|
|
ssh.clear_onu_port(body.port_id)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"清除失败: {str(e)}")
|
|
|
|
# 从 ports 列表移除已清除的端口
|
|
remaining = [p for p in record.ports if p["port_id"] != body.port_id]
|
|
if remaining:
|
|
record.ports = remaining
|
|
else:
|
|
# 所有端口都清除了,删除整条记录
|
|
db.delete(record)
|
|
db.commit()
|
|
|
|
return {"message": f"端口 Onu{body.port_id} 已清除", "remaining_ports": remaining}
|
|
|
|
|
|
@router.get("/new-devices")
|
|
def get_new_devices(
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.view')),
|
|
):
|
|
"""查询新发现的设备列表(待补全信息)"""
|
|
from app.models.device import NewDevice, ONUDevice
|
|
rows = (
|
|
db.query(NewDevice, ONUDevice)
|
|
.join(ONUDevice, NewDevice.onu_device_id == ONUDevice.id)
|
|
.all()
|
|
)
|
|
return [
|
|
{
|
|
"id": nd.id,
|
|
"onu_device_id": nd.onu_device_id,
|
|
"olt_id": nd.olt_id,
|
|
"discovered_at": nd.discovered_at,
|
|
"mac_address": onu.mac_address,
|
|
"port_id": f"{onu.slot_number}/{onu.port_number}" if onu.slot_number else None,
|
|
"loid": onu.loid,
|
|
"model": onu.model,
|
|
"region": onu.region,
|
|
"school_name": onu.school_name,
|
|
"building": onu.building,
|
|
"room_number": onu.room_number,
|
|
}
|
|
for nd, onu in rows
|
|
]
|
|
|
|
|
|
class NewDeviceUpdate(BaseModel):
|
|
region: str
|
|
school_name: str
|
|
building: str = ""
|
|
place_type: str = ""
|
|
room_number: str = ""
|
|
notes: str = ""
|
|
|
|
|
|
@router.put("/new-devices/{record_id}")
|
|
def update_new_device(
|
|
record_id: int,
|
|
body: NewDeviceUpdate,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
"""补全新设备信息,完成后从 new_devices 移除"""
|
|
from app.models.device import NewDevice, ONUDevice
|
|
record = db.query(NewDevice).filter(NewDevice.id == record_id).first()
|
|
if not record:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
|
|
onu = db.query(ONUDevice).filter(ONUDevice.id == record.onu_device_id).first()
|
|
if not onu:
|
|
raise HTTPException(status_code=404, detail="ONU 设备不存在")
|
|
|
|
onu.region = body.region
|
|
onu.school_name = body.school_name
|
|
onu.building = body.building or None
|
|
onu.place_type = body.place_type or None
|
|
onu.room_number = body.room_number or None
|
|
onu.notes = body.notes or None
|
|
|
|
db.delete(record)
|
|
db.commit()
|
|
return {"message": "信息已补全"}
|
|
|
|
|
|
@router.delete("/new-devices/{record_id}")
|
|
def dismiss_new_device(
|
|
record_id: int,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
"""忽略新设备(不补全信息,仅从待处理列表移除)"""
|
|
from app.models.device import NewDevice
|
|
record = db.query(NewDevice).filter(NewDevice.id == record_id).first()
|
|
if not record:
|
|
raise HTTPException(status_code=404, detail="记录不存在")
|
|
db.delete(record)
|
|
db.commit()
|
|
return {"message": "已忽略"}
|
|
|
|
|
|
@router.post("/quick-scan")
|
|
def quick_scan(
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.discover')),
|
|
):
|
|
"""多线程对所有 OLT 同时执行扫描,更新已有设备状态"""
|
|
from app.services.check_service import CheckService
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
olts = db.query(OLTDevice).all()
|
|
|
|
def scan_one(olt):
|
|
from app.core.database import SessionLocal
|
|
thread_db = SessionLocal()
|
|
try:
|
|
service = CheckService(thread_db)
|
|
result = service.scan_and_discover(olt.id)
|
|
return {"olt_id": olt.id, "olt_location": olt.location or olt.ip_address,
|
|
"online": result.get("online", 0), "offline": result.get("offline", 0),
|
|
"new_discovered": result.get("new_discovered", 0),
|
|
"error": None}
|
|
except Exception as e:
|
|
return {"olt_id": olt.id, "olt_location": olt.location or olt.ip_address,
|
|
"online": 0, "offline": 0, "new_discovered": 0, "error": str(e)}
|
|
finally:
|
|
thread_db.close()
|
|
|
|
olt_results = {}
|
|
with ThreadPoolExecutor(max_workers=len(olts) or 1) as executor:
|
|
futures = {executor.submit(scan_one, olt): olt.id for olt in olts}
|
|
for future in as_completed(futures):
|
|
r = future.result()
|
|
olt_results[r["olt_id"]] = r
|
|
|
|
results = []
|
|
errors = []
|
|
total_online = 0
|
|
total_offline = 0
|
|
total_new = 0
|
|
for olt in olts:
|
|
r = olt_results.get(olt.id, {})
|
|
if r.get("error"):
|
|
errors.append({"olt_location": r["olt_location"], "error": r["error"]})
|
|
else:
|
|
total_online += r.get("online", 0)
|
|
total_offline += r.get("offline", 0)
|
|
total_new += r.get("new_discovered", 0)
|
|
results.append({
|
|
"olt_location": r.get("olt_location", olt.location or olt.ip_address),
|
|
"online": r.get("online", 0),
|
|
"offline": r.get("offline", 0),
|
|
"new_discovered": r.get("new_discovered", 0),
|
|
"success": not r.get("error"),
|
|
"error": r.get("error"),
|
|
})
|
|
|
|
return {
|
|
"total_online": total_online,
|
|
"total_offline": total_offline,
|
|
"total_new": total_new,
|
|
"results": results,
|
|
"errors": errors,
|
|
}
|
|
|
|
|
|
@router.post("/loopback-detection")
|
|
def loopback_detection(
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.loopback')),
|
|
):
|
|
"""对所有 OLT 并发执行环路检测,返回有环路的端口及对应设备信息"""
|
|
from app.models.device import ONUDevice
|
|
from app.services.ssh_service import SSHService
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
olts = db.query(OLTDevice).all()
|
|
|
|
# 预加载所有 ONU 设备,按 (olt_id, port_id) 索引,避免多线程操作 Session
|
|
all_onus = db.query(ONUDevice).all()
|
|
onu_map = {(o.olt_id, o.port_id): o for o in all_onus if o.port_id}
|
|
|
|
def check_one(olt):
|
|
try:
|
|
with SSHService(olt.ip_address, olt.username, olt.password) as ssh:
|
|
detection = ssh.detect_loopback()
|
|
except Exception as e:
|
|
return {
|
|
"olt_id": olt.id,
|
|
"olt_ip": olt.ip_address,
|
|
"olt_location": olt.location or olt.ip_address,
|
|
"error": str(e),
|
|
"has_loop": False,
|
|
"loop_interfaces": [],
|
|
}
|
|
|
|
loop_interfaces = []
|
|
for iface in detection.get("interfaces", []):
|
|
port_id = iface.removeprefix("Onu")
|
|
onu = onu_map.get((olt.id, port_id))
|
|
loop_interfaces.append({
|
|
"interface": iface,
|
|
"port_id": port_id,
|
|
"mac_address": onu.mac_address if onu else None,
|
|
"region": onu.region if onu else None,
|
|
"school_name": onu.school_name if onu else None,
|
|
"building": onu.building if onu else None,
|
|
"room_number": onu.room_number if onu else None,
|
|
})
|
|
|
|
return {
|
|
"olt_id": olt.id,
|
|
"olt_ip": olt.ip_address,
|
|
"olt_location": olt.location or olt.ip_address,
|
|
"has_loop": detection["has_loop"],
|
|
"loop_interfaces": loop_interfaces,
|
|
"error": None,
|
|
"raw": detection.get("raw", ""),
|
|
}
|
|
|
|
results_map = {}
|
|
with ThreadPoolExecutor(max_workers=len(olts) or 1) as executor:
|
|
futures = {executor.submit(check_one, olt): olt.id for olt in olts}
|
|
for future in as_completed(futures):
|
|
olt_id = futures[future]
|
|
results_map[olt_id] = future.result()
|
|
|
|
# 按原始顺序返回
|
|
return [results_map[olt.id] for olt in olts]
|
|
|
|
|
|
class SyncNTPRequest(BaseModel):
|
|
old_server: str = settings.NTP_OLD_SERVER
|
|
new_server: str = settings.NTP_NEW_SERVER
|
|
|
|
|
|
@router.post("/sync-ntp")
|
|
def sync_ntp(
|
|
body: SyncNTPRequest,
|
|
db: Session = Depends(get_db),
|
|
current: dict = Depends(require_permission('olt.manage')),
|
|
):
|
|
"""对所有 OLT 并发执行 NTP 时间服务器同步"""
|
|
from app.services.ssh_service import SSHService
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
olts = db.query(OLTDevice).all()
|
|
if current.get('role') == 'area_admin' and current.get('assigned_area'):
|
|
areas = [a.strip() for a in current['assigned_area'].split(',') if a.strip()]
|
|
olts = [o for o in olts if o.region in areas] if areas else []
|
|
|
|
def sync_one(olt):
|
|
try:
|
|
with SSHService(olt.ip_address, olt.username, olt.password) as ssh:
|
|
ssh.sync_ntp(body.old_server, body.new_server)
|
|
return {
|
|
"olt_ip": olt.ip_address,
|
|
"olt_location": olt.location or olt.ip_address,
|
|
"success": True,
|
|
"error": None,
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"olt_ip": olt.ip_address,
|
|
"olt_location": olt.location or olt.ip_address,
|
|
"success": False,
|
|
"error": str(e),
|
|
}
|
|
|
|
results_map = {}
|
|
with ThreadPoolExecutor(max_workers=len(olts) or 1) as executor:
|
|
futures = {executor.submit(sync_one, olt): olt.id for olt in olts}
|
|
for future in as_completed(futures):
|
|
r = future.result()
|
|
results_map[r["olt_ip"]] = r
|
|
|
|
results = [results_map[olt.ip_address] for olt in olts]
|
|
success_count = sum(1 for r in results if r["success"])
|
|
return {
|
|
"total": len(results),
|
|
"success": success_count,
|
|
"failed": len(results) - success_count,
|
|
"results": results,
|
|
}
|
|
|
|
|
|
class TogglePortRequest(BaseModel):
|
|
action: str # "shutdown" 或 "undo shutdown"
|
|
|
|
|
|
@router.get("/devices/{olt_id}/ports")
|
|
def get_olt_ports(
|
|
olt_id: int,
|
|
db: Session = Depends(get_db),
|
|
_: dict = Depends(require_permission('olt.port_manage')),
|
|
):
|
|
"""获取指定 OLT 的所有 Olt 端口状态"""
|
|
from app.services.ssh_service import SSHService
|
|
olt = db.query(OLTDevice).filter(OLTDevice.id == olt_id).first()
|
|
if not olt:
|
|
raise HTTPException(status_code=404, detail="OLT 不存在")
|
|
try:
|
|
with SSHService(olt.ip_address, olt.username, olt.password) as ssh:
|
|
ports = ssh.get_olt_ports()
|
|
return {"ports": ports}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/devices/{olt_id}/ports/toggle")
|
|
def toggle_olt_port(olt_id: int, body: TogglePortRequest, port_name: str, db: Session = Depends(get_db), _: dict = Depends(require_permission('olt.port_manage'))):
|
|
"""开启或关闭指定 OLT 端口"""
|
|
from app.services.ssh_service import SSHService
|
|
if body.action not in ("shutdown", "undo shutdown"):
|
|
raise HTTPException(status_code=400, detail="action 必须为 shutdown 或 undo shutdown")
|
|
olt = db.query(OLTDevice).filter(OLTDevice.id == olt_id).first()
|
|
if not olt:
|
|
raise HTTPException(status_code=404, detail="OLT 不存在")
|
|
try:
|
|
with SSHService(olt.ip_address, olt.username, olt.password) as ssh:
|
|
ssh.toggle_olt_port(port_name, body.action)
|
|
return {"message": f"端口 {port_name} 已{'关闭' if body.action == 'shutdown' else '开启'}"}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|