Building a Lightweight EXFO Tester Admin Panel with FastAPI and Alpine.js
In this post, I’ll walk you through how to build a lightweight web application that integrates with EXFO tester APIs, allows for user authentication, and fetches test results using FastAPI and Alpine.js.
Why FastAPI + Alpine.js?
- FastAPI is a modern, async-ready web framework perfect for building APIs and backend systems.
- Alpine.js is a minimal JavaScript framework that allows you to add interactivity without needing a heavy frontend like React or Vue.
- Together, they create a clean, fast, and powerful stack ideal for internal admin panels or embedded control apps.
Key Features
- Session-based login system (username + password)
- Authentication-protected API endpoints
- A single HTML page that dynamically fetches test results after login
- EXFO integration placeholder for real test results
Tech Stack
- Backend: FastAPI
- Frontend: HTML + TailwindCSS + Alpine.js
- Auth: Session-based with cookie storage
- API: Mock EXFO test result endpoint
Basic Fiber Optic Terms
Before diving into EXFO testing, it’s helpful to understand some key fiber optic terms:
- OLT (Optical Line Terminal): The central device at the telecom provider that sends data to multiple ONUs.
- ONU/ONT (Optical Network Unit/Terminal): The device at the user’s location that receives data from the OLT.
- PON (Passive Optical Network): A point-to-multipoint fiber network that uses unpowered splitters.
- GPON/XGS-PON: Standards for PONs offering different speeds (GPON = 2.5 Gbps down; XGS-PON = 10 Gbps symmetrically).
- dBm: Measurement unit for optical signal power.
How to Configure an EXFO PON Tester (Basics)
1.Connect the Tester
- Plug the fiber cable from the splitter or drop cable into the EXFO tester’s "ONT" port.
- Optionally connect the ONU to the tester’s "PON" output port for inline testing.
2.Select PON Mode
- Choose between GPON, XGS-PON, or dual-mode depending on your deployment.
3.Power Measurement
- Start a power test to check downstream (OLT) and upstream (ONT) signal levels.
- Typical acceptable range: –8 dBm to –27 dBm.
4.Pass/Fail Results
- The tester will display whether levels are within thresholds.
- Adjust thresholds manually if needed to match your network standard.
5.Save/Export Results
- Use USB or API integration to export results to your backend or PC.
System Deployment Diagram (Mermaid.js)
graph TD
A["EXFO Tester"] --> B["FastAPI Server"]
B --> C["Session Auth API"]
B --> D["Test Results API"]
D --> E["MongoDB (optional)"]
B --> F["Static HTML + Alpine.js"]
F --> G["User Browser"]
G -->|login| C
G -->|view data| D
ONT + EXFO Tester Configuration Flow (Mermaid.js)
graph LR
Start["Start"] --> Splitter["Connect Drop Fiber to EXFO Tester (ONT Port)"]
Splitter --> InlineTest["(Optional) Connect ONU to PON Port for Inline Test"]
InlineTest --> Mode["Select PON Mode: GPON / XGS-PON"]
Mode --> PowerTest["Run Optical Power Test"]
PowerTest --> Result["Check Signal Levels: OLT RX / ONT TX"]
Result --> Decision{"Within Acceptable Range?"}
Decision -- Yes --> Save["Save / Export Result"]
Decision -- No --> Adjust["Adjust Fiber or Ports and Retest"]
Save --> Done["Done"]
Adjust --> PowerTest
EXFO Tester Physical Setup Diagram (Mermaid.js)
graph TD
A["OLT (Central Office)"] --> B["Splitter"]
B --> C["Drop Fiber"]
C --> D["EXFO Tester (ONT Port)"]
D -->|optional| E["PON Port to ONU"]
E --> F["User Premises / ONU Device"]
This diagram shows the standard setup for inline testing using an EXFO tester. The drop fiber is connected to the ONT port, and optionally, the ONU can be connected to the PON port so the tester sits inline and does not interrupt service.
graph LR
Start["Start"] --> Splitter["Connect Drop Fiber to EXFO Tester (ONT Port)"]
Splitter --> InlineTest["(Optional) Connect ONU to PON Port for Inline Test"]
InlineTest --> Mode["Select PON Mode: GPON / XGS-PON"]
Mode --> PowerTest["Run Optical Power Test"]
PowerTest --> Result["Check Signal Levels: OLT RX / ONT TX"]
Result --> Decision{"Within Acceptable Range?"}
Decision -- Yes --> Save["Save / Export Result"]
Decision -- No --> Adjust["Adjust Fiber or Ports and Retest"]
Save --> Done["Done"]
Adjust --> PowerTest
graph TD
A["EXFO Tester"] --> B["FastAPI Server"]
B --> C["Session Auth API"]
B --> D["Test Results API"]
D --> E["MongoDB (optional)"]
B --> F["Static HTML + Alpine.js"]
F --> G["User Browser"]
G -->|login| C
G -->|view data| D
1. Project Setup
Install dependencies:
pip install fastapi uvicorn python-multipart jinja2
Directory structure:
project/
├── main.py
└── templates/
└── index.html
2. FastAPI Backend Code (main.py)
from fastapi import FastAPI, Request, Form
from fastapi.responses import RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="supersecret")
templates = Jinja2Templates(directory="templates")
users = {"admin": {"password": "1234"}}
fake_data = [
{"id": 1, "onu": "ONU123", "status": "PASS"},
{"id": 2, "onu": "ONU456", "status": "FAIL"},
]
def get_current_user(request: Request):
return request.session.get("user")
@app.get("/")
def index(request: Request):
user = get_current_user(request)
return templates.TemplateResponse("index.html", {"request": request, "user": user})
@app.post("/login")
def login(request: Request, username: str = Form(...), password: str = Form(...)):
if username in users and users[username]["password"] == password:
request.session["user"] = username
return RedirectResponse("/", status_code=303)
return RedirectResponse("/?error=1", status_code=303)
@app.get("/logout")
def logout(request: Request):
request.session.clear()
return RedirectResponse("/", status_code=303)
@app.get("/api/test-results")
def get_test_data(request: Request):
if not get_current_user(request):
return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
return fake_data
3. HTML + Alpine.js Frontend (templates/index.html)
<!DOCTYPE html>
<html lang="en" x-data="testApp()" x-init="init()">
<head>
<script src="https://cdn.jsdelivr.net/npm/alpinejs" defer></script>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<title>EXFO Admin Panel</title>
</head>
<body class="p-6 bg-gray-100">
<div class="max-w-xl mx-auto bg-white p-6 rounded shadow text-center">
<template x-if="!loggedIn">
<form method="POST" action="/login" class="space-y-4">
<h2 class="text-xl font-bold">Login</h2>
<input type="text" name="username" placeholder="Username" class="border p-2 w-full">
<input type="password" name="password" placeholder="Password" class="border p-2 w-full">
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">Login</button>
</form>
</template>
<template x-if="loggedIn">
<div>
<h2 class="text-xl font-bold mb-4">Test Results</h2>
<a href="/logout" class="text-sm text-blue-600 underline">Logout</a>
<ul class="mt-4 space-y-2 text-left">
<template x-for="item in testResults" :key="item.id">
<li class="p-2 border rounded">
<strong x-text="item.onu"></strong> - <span x-text="item.status"></span>
</li>
</template>
</ul>
</div>
</template>
</div>
<script>
function testApp() {
return {
loggedIn: {{ 'true' if user else 'false' }},
testResults: [],
async init() {
if (this.loggedIn) {
try {
const res = await fetch('/api/test-results');
if (res.ok) {
this.testResults = await res.json();
}
} catch (err) {
console.error("Error fetching test results", err);
}
}
}
}
}
</script>
</body>
</html>
4. Run It
uvicorn main:app --reload
Visit http://localhost:8000 and log in with:
- Username:
admin - Password:
1234
Conclusion
This is a great starting point for building an EXFO-integrated dashboard with:
- Lightweight UI using Tailwind + Alpine.js
- FastAPI backend with full async support
- Session-based login
- API-protected data views
You can expand this to include:
- MongoDB support
- Real EXFO tester API calls
- Role-based access control
- Export to CSV/PDF
Need help upgrading it? Drop a comment!
Get in Touch with us
Related Posts
- AI Security in Production: What Enterprise Teams Must Know in 2026
- 弹性无人机蜂群设计:具备安全通信的无领导者容错网状网络
- Designing Resilient Drone Swarms: Leaderless-Tolerant Mesh Networks with Secure Communications
- NumPy广播规则详解:为什么`(3,)`和`(3,1)`行为不同——以及它何时会悄悄给出错误答案
- NumPy Broadcasting Rules: Why `(3,)` and `(3,1)` Behave Differently — and When It Silently Gives Wrong Answers
- 关键基础设施遭受攻击:从乌克兰电网战争看工业IT/OT安全
- Critical Infrastructure Under Fire: What IT/OT Security Teams Can Learn from Ukraine’s Energy Grid
- LM Studio代码开发的系统提示词工程:`temperature`、`context_length`与`stop`词详解
- LM Studio System Prompt Engineering for Code: `temperature`, `context_length`, and `stop` Tokens Explained
- LlamaIndex + pgvector: Production RAG for Thai and Japanese Business Documents
- simpliShop:专为泰国市场打造的按需定制多语言电商平台
- simpliShop: The Thai E-Commerce Platform for Made-to-Order and Multi-Language Stores
- ERP项目为何失败(以及如何让你的项目成功)
- Why ERP Projects Fail (And How to Make Yours Succeed)
- Payment API幂等性设计:用Stripe、支付宝、微信支付和2C2P防止重复扣款
- Idempotency in Payment APIs: Prevent Double Charges with Stripe, Omise, and 2C2P
- Agentic AI in SOC Workflows: Beyond Playbooks, Into Autonomous Defense (2026 Guide)
- 从零构建SOC:Wazuh + IRIS-web 真实项目实战报告
- Building a SOC from Scratch: A Real-World Wazuh + IRIS-web Field Report
- 中国品牌出海东南亚:支付、物流与ERP全链路集成技术方案













