A web-based management tool for Netgear M4350 series managed switches. It provides a visual dashboard for monitoring and configuring multiple switches through the Netgear REST API, with a Rust CLI backend that handles all direct switch communication.
┌──────────────────────────────────────────────────────┐
│ Browser (JavaScript SPA) │
│ - Port visualization with VLAN colors │
│ - Live PoE status, neighbor tables, LAG, QoS │
│ - Inline editing with push confirmation modal │
│ - Auto-refresh (5s timer) │
└──────────────┬───────────────────────────────────────┘
│ HTTP (Flask REST API)
┌──────────────▼───────────────────────────────────────┐
│ Python / Flask (web/app.py) │
│ - Serves the SPA and static assets │
│ - Proxies requests to the Rust CLI via subprocess │
│ - Manages switch inventory (config/switches.json) │
│ - Logs all writes to config/pushlog.json │
└──────────────┬───────────────────────────────────────┘
│ subprocess (stdin/stdout JSON)
┌──────────────▼───────────────────────────────────────┐
│ Rust CLI (netgear-cli) │
│ - Authenticates with the switch REST API │
│ - Performs GET and POST requests │
│ - Outputs structured JSON to stdout │
└──────────────┬───────────────────────────────────────┘
│ HTTP (Netgear REST API)
┌──────────────▼───────────────────────────────────────┐
│ Netgear M4350 Switch(es) │
│ - REST API on port 80 │
│ - Session-based authentication │
└──────────────────────────────────────────────────────┘
├── config/
│ ├── switches.json # Switch inventory (hosts, names, models)
│ ├── pushlog.json # Audit log of all configuration changes
│ └── <SwitchName>.json # Cached query results per switch
├── netgear-cli/
│ └── src/
│ ├── main.rs # CLI commands (device-info, ports, poe, etc.)
│ └── api.rs # HTTP client (login, get, post)
├── web/
│ ├── app.py # Flask backend
│ ├── templates/
│ │ └── index.html # SPA shell
│ └── static/
│ ├── css/styles.css
│ └── js/
│ ├── state.js # Global state, auto-refresh
│ ├── api.js # API call wrapper with caching
│ ├── nav.js # Tab navigation, switch selection
│ ├── push.js # Push confirmation modal
│ ├── tables.js # Table toolbar (search, export)
│ ├── search.js # Global search across cached data
│ ├── switches.js # Add/remove switch UI
│ ├── utils.js # Formatting helpers
│ ├── tabs/ # One file per tab (visualize, ports, poe, etc.)
│ └── editors/ # Inline edit dialogs (device name, port desc, poe toggle)
└── .env # SWITCH_USERNAME and SWITCH_PASSWORD
- Python 3.8+
- Rust toolchain (for building the CLI)
# 1. Build the Rust CLI
cd netgear-cli
cargo build --release
cd ..
# 2. Create .env with switch credentials
echo "SWITCH_USERNAME=admin" > .env
echo "SWITCH_PASSWORD=yourpassword" >> .env
# 3. Install Python dependencies
pip install -r web/requirements.txt
# 4. Run the web server
python web/app.py
# Opens http://127.0.0.1:5000 in your browserClick + Add in the top bar, enter the switch IP address and a friendly name. The manager will discover the model, serial number, and firmware version on first query.
| Tab | Description |
|---|---|
| Visualize | Color-coded port map showing VLAN assignments, link state, PoE status, and connected devices. Click a port to copy its details. |
| Ports | Full port configuration table with descriptions, VLAN profiles, link speed, admin state. Inline editing for descriptions and profile assignments. |
| MAC / Neighbors | Combined LLDP and ARP neighbor table. Shows connected device hostnames, MACs, IPs. Flush ARP button to clear stale entries. |
| PoE | Power over Ethernet status per port. Toggle PoE on/off. Shows power draw, class, priority, mode. |
| LAG | Link Aggregation Group configuration. Shows member ports, LACP mode, balance algorithm. |
| VLANs | VLAN/profile definitions and per-port assignments. Summary view of how many ports per VLAN. |
| Trunks | Trunk port analysis — identifies uplinks carrying multiple VLANs. |
| QoS | Quality of Service profiles and DSCP mappings. |
| Push Log | Audit trail of every configuration change pushed to switches. |
| Manage Switches | Add or remove switches from the inventory. |
| Switch Status | Device overview: model, firmware, serial, uptime, CPU, memory, fan RPM, sensor temperatures. |
- Switch toggles — Click a switch name to select it; click All Switches to view all switches side-by-side.
- Refresh — Clears the cache and reloads the current tab from the switch.
- Auto-refresh (5s) — Checkbox that polls the current tab every 5 seconds.
- Search — Searches across all cached data for IPs, MACs, VLANs, or port numbers.
All endpoints below are served by the switch itself on HTTP port 80. The base URL is http://<switch-ip>.
Every API session begins with a login. All subsequent requests must include the session token in the Session HTTP header.
Authenticates and returns a session token.
Request:
{
"user": {
"name": "admin",
"password": "yourpassword"
}
}Response (success):
{
"resp": {
"respCode": 0,
"respMsg": "Login successful"
},
"user": {
"session": "a1b2c3d4e5f6..."
}
}Response (failure):
{
"resp": {
"respCode": -1,
"respMsg": "Invalid credentials"
}
}Headers for all subsequent requests:
Session: <token from login>
Accept: application/json
Content-Type: application/json (for POST requests)
Every Netgear API response wraps the result in a resp object:
{
"resp": {
"respCode": 0,
"respMsg": "OK"
},
...endpoint-specific data...
}respCode: 0= successrespCode: non-zero= error (checkrespMsgfor details)
Some POST endpoints (such as ARP flush) return an empty HTTP body on success instead of JSON.
All GET endpoints require the Session header.
Returns switch identity, hardware details, health metrics.
CLI command: netgear-cli device-info
Response fields:
deviceInfo
├── name # Switch hostname
├── mac # Management MAC address
├── poe # PoE capability (boolean)
├── cpu[]
│ └── usage # CPU utilization string
├── memory[]
│ └── usage # Memory utilization string
├── details[]
│ ├── model # Hardware model (e.g. "M4350-44M4X4V")
│ ├── fwVer # Firmware version
│ ├── sn # Serial number
│ └── upTime # System uptime string
├── fan[]
│ └── details[]
│ ├── desc # Fan name
│ ├── speed # RPM
│ └── dutyLevel # Duty cycle percentage
└── sensor[]
└── details[]
├── desc # Sensor name
├── temp # Current temperature (C)
└── maxTemp # Maximum rated temperature (C)
Returns the full port configuration for every port on the switch.
CLI command: netgear-cli ports
Response fields:
switchPortConfig
└── unit[]
└── slot[]
└── port[]
├── id # Port number
├── portName # Port label (e.g. "0/1")
├── portNum # Port number (same as id)
├── adminState # 1 = Enabled, 0 = Disabled
├── linkState # 0 = Up, 1 = Down
├── linkSpeed # Speed string (e.g. "1000")
├── switchportMode # 0=Access, 1=Trunk, 2=General, 3=Hybrid
├── vlan[] # Assigned VLAN IDs
└── poeIsValid # 3 = PoE capable, 0 = Not PoE
Returns port status including descriptions and profile assignments. Supports pagination.
CLI command: netgear-cli port-status
Query parameters:
| Parameter | Description |
|---|---|
indexPage |
Page number (starts at 1) |
pageSize |
Results per page (max 200) |
Response fields:
switchPortStatus
└── rows[]
├── portNum # Port number
├── portName # Port label
├── portStr # Port string identifier
├── description # User-configured port description
├── profileName # Assigned VLAN/network profile name
├── profileTemplate # Profile template name
├── physicalStatus # Physical link status string
├── speed # Negotiated speed
├── stpMode # Spanning Tree Protocol mode
├── flowControl # Flow control setting
└── frameSize # Maximum frame size
Returns the combined LLDP and ARP neighbor table. This is where you find connected devices — their MAC addresses, IP addresses, and hostnames.
CLI command: netgear-cli neighbors
Query parameters:
| Parameter | Description |
|---|---|
indexPage |
Page number (starts at 1) |
pageSize |
Results per page |
Response fields:
lldpRemoteDevice
└── rows[]
├── port # Local port number
├── portNum # Local port number (alternate field)
├── portName # Local port label
├── hostMacAddress # Remote device MAC address
├── hostIpAddress # Remote device IP address
├── hostName # Remote device hostname
├── source # Discovery source: "lldp" or "arp"
├── remotePortId # Remote port identifier
└── systemDescription # Remote device system description
Returns PoE status and configuration for every PoE-capable port.
CLI command: netgear-cli poe
Response fields:
poePortConfig[]
├── port # Port number
├── enable # 1 = Enabled, 0 = Disabled
├── status # See status codes below
├── currentPower # Current draw in tenths of watts (divide by 100 for W)
├── powerLimit # Max power in tenths of watts (divide by 1000 for W)
├── classification # PoE device class (0-8)
├── priority # 0=Low, 1=Medium, 2=High, 3=Critical
├── powerMode # See power mode codes below
└── schedule # Schedule name or empty
PoE status codes:
| Code | Status |
|---|---|
| 0 | Disabled |
| 1 | Searching |
| 2 | Delivering |
| 3 | Fault |
| 4 | Test |
| 5 | Other Fault |
PoE power mode codes:
| Code | Standard |
|---|---|
| 1 | PoE (802.3af) — up to 15.4W |
| 2 | PoE+ (802.3at) — up to 30W |
| 3 | PoE++ (802.3bt Type 3) — up to 60W |
| 4 | PoE++ (802.3bt Type 4) — up to 90W |
| 7 | Auto |
Returns Link Aggregation Group (LAG / port channel) configuration.
CLI command: netgear-cli lag
Response fields:
switchConfigLagGroup[]
├── groupId # LAG group ID
├── name # LAG name
├── portNum # LAG logical port number
├── lagMode # 1=Static, 2=LACP Passive, 3=LACP Active
├── members[] # Member port numbers
├── memberName[] # Member port labels
├── balanceMode # 1=Src/Dst MAC, 2=Src/Dst IP, 3=Src/Dst MAC+IP
├── vlanId # VLAN ID
├── trunkMode # Trunk mode enabled
└── staticMode # Static mode enabled
Returns VLAN/network profile definitions. Each profile maps to a VLAN ID and defines tagged/untagged port membership.
CLI command: netgear-cli vlans (fetches this along with port-status)
Response fields:
profileList[]
├── id # Profile ID
├── name # Profile name
├── color # Hex color code for UI display
├── profileType # Profile type identifier
└── vlans[]
├── vlanId # VLAN ID (e.g. 100)
└── ports
│ ├── tagged[] # Ports carrying this VLAN tagged
│ └── untagged[] # Ports carrying this VLAN untagged
└── routing
├── ipAddr # SVI IP address
└── ipMask # SVI subnet mask
Returns QoS profile templates — the building blocks that define DSCP mappings, multicast, STP, MTU, and other per-profile settings.
CLI command: netgear-cli qos (fetches this along with profile/list and port-status)
Response fields:
profile[]
├── name # Profile name
├── description # Profile description
├── color
│ └── value # Hex color code
├── custom # Is user-defined (boolean)
├── activation # Is active (boolean)
├── PTP # Precision Time Protocol enabled
├── AVB # Audio Video Bridging enabled
├── MTU # Jumbo frame support
├── LLDP # LLDP enabled
├── STP # Spanning Tree enabled
├── multicast
│ └── igmp # IGMP snooping enabled
└── qos[]
├── type # 1=DSCP mapping, 2=Scheduler
├── codePoint # DSCP value (for type 1)
├── priority # Queue priority
└── schedulerType # 1=Weighted, 2=Strict Priority
All POST endpoints require Session, Content-Type: application/json, and Accept: application/json headers.
Changes the switch hostname.
Request:
{
"deviceName": "New Switch Name"
}Response: Standard envelope with respCode: 0 on success.
Updates port configuration — descriptions and profile/VLAN assignments.
Change port description:
{
"switchPortStatus": {
"rows": [
{
"portNum": 1,
"description": "Lobby AP"
}
]
}
}Change port VLAN/profile:
{
"switchPortStatus": {
"rows": [
{
"portNum": 1,
"profileName": "VLAN100-Data"
}
]
}
}Multiple ports can be updated in a single request by adding more objects to the rows array.
Response: Standard envelope with respCode: 0 on success.
Enables or disables PoE on specific ports.
Request:
{
"poePortConfig": [
{
"port": "1",
"enable": 1
}
]
}enable: 1— Enable PoEenable: 0— Disable PoE
Multiple ports can be toggled in a single request by adding more objects to the array.
Response: Standard envelope with respCode: 0 on success.
Flushes the ARP table to clear stale neighbor entries. Useful when devices have moved ports or been disconnected.
Request:
{
"flush": true
}Response: Empty body (HTTP 200). The Rust CLI synthesizes a success response:
{
"status": "ok",
"httpStatus": 200,
"endpoint": "/api/v1/arp_table_flush"
}The CLI is the bridge between the Flask web server and the switch. It handles authentication, makes the API call, and outputs JSON to stdout.
# Test connectivity and credentials
netgear-cli login -H 10.0.0.1 -u admin -p password
# Read operations
netgear-cli device-info -H 10.0.0.1 -u admin -p password
netgear-cli ports -H 10.0.0.1 -u admin -p password
netgear-cli port-status -H 10.0.0.1 -u admin -p password
netgear-cli neighbors -H 10.0.0.1 -u admin -p password
netgear-cli poe -H 10.0.0.1 -u admin -p password
netgear-cli lag -H 10.0.0.1 -u admin -p password
netgear-cli vlans -H 10.0.0.1 -u admin -p password
netgear-cli qos -H 10.0.0.1 -u admin -p password
netgear-cli config -H 10.0.0.1 -u admin -p password # all of the above combined
# Write operations (POST any endpoint with arbitrary JSON)
netgear-cli set -H 10.0.0.1 -u admin -p password \
--endpoint /api/v1/swcfg_poe \
--data '{"poePortConfig":[{"port":"1","enable":1}]}'| CLI Command | Switch Endpoint | Method |
|---|---|---|
login |
/api/v1/login |
POST |
device-info |
/api/v1/device_info |
GET |
ports |
/api/v1/swcfg_port |
GET |
port-status |
/api/v1/swcfg_ports_status?indexPage=1&pageSize=200 |
GET |
neighbors |
/api/v1/neighbor?indexPage=1&pageSize=9999 |
GET |
poe |
/api/v1/swcfg_poe |
GET |
lag |
/api/v1/sw_lag_cfg |
GET |
vlans |
/api/v1/profile/list + /api/v1/swcfg_ports_status |
GET |
qos |
/api/v1/profile + /api/v1/profile/list + /api/v1/swcfg_ports_status |
GET |
config |
All GET endpoints combined | GET |
set |
Any endpoint (user-specified) | POST |
- Success: JSON on stdout, exit code 0
- Error: JSON on stderr (
{"error": "message"}), exit code 1
These are the internal endpoints served by the Python backend. The browser talks to these; they invoke the Rust CLI under the hood.
Returns the switch inventory from config/switches.json.
Adds a new switch. Body: {"host": "10.0.0.1", "name": "Lobby Switch"}. Auto-assigns an ID.
Removes a switch from the inventory.
Runs a CLI read command against a switch. Valid actions: login, device-info, ports, port-status, neighbors, poe, lag, config, qos, vlans.
Pushes a configuration change to a switch. Logs the change to pushlog.json.
Request:
{
"endpoint": "/api/v1/swcfg_poe",
"data": {"poePortConfig": [{"port": "1", "enable": 1}]},
"old_value": {"poePortConfig": [{"port": "1", "enable": 0}]}
}The old_value is stored in the push log for audit purposes but is not sent to the switch.
Returns the audit log of all configuration changes.
Response:
{
"entries": [
{
"timestamp": "2026-06-15T11:34:14.311592",
"switch_id": "switch-1",
"switch_host": "10.201.100.164",
"endpoint": "/api/v1/swcfg_poe",
"old_value": {"poePortConfig": [{"port": "1", "enable": 0}]},
"new_value": {"poePortConfig": [{"port": "1", "enable": 1}]},
"result": "success"
}
]
}