--- name: omada-controller-administration description: >- Class-level skill for TP-Link Omada Cloud Controller administration via browser automation. Covers login flow, SPA navigation quirks, iframe-based site management, VLAN configuration, ACL rules, inter-VLAN mDNS limitations, WLAN/channel optimization, device management, and firmware updates. Applies to cloud-based Omada controllers at omada.tplinkcloud.com. device management, and firmware updates. Applies to cloud-based Omada controllers at omada.tplinkcloud.com. tags: - tp-link - omada - wlan - channel-optimization - network-management - browser-automation --- # TP-Link Omada Cloud Controller Administration ## When to Use Any task involving TP-Link Omada Cloud Controller — WLAN configuration, channel optimization, device management, firmware updates, site settings, or viewing logs/alerts. ## Environment - **URL**: `https://omada.tplinkcloud.com/` - **Credentials**: 1Password vault "Hermes", item "omada - Netzwerkverwaltung" - **Controller name**: "Schön" (Cloud Based System) - **Site name**: "Rohr" - **Devices** (all on VLAN 1 / 172.16.1.x management subnet): - Router: DR3650v-4G HW v1.0, FW 1.1.1, IP 172.16.1.1 (also 10.0.30.1 on VLAN 30) - Switch: SG3428MP HW v6.20, FW 6.20.26 (update 6.20.27 avail), IP 172.16.1.2 - AP Erdgeschoss: EAP683 UR v1.0, FW 1.4.0, IP 172.16.1.3 (switch port 16) - AP Obergeschoss: EAP683 UR v1.0, FW 1.4.0, IP 172.16.1.4 (switch port 14) - AP Garage: EAP615-Wall v1.0, FW 1.5.4, IP 172.16.1.5 (switch port 21) - AP Garten: EAP225-Outdoor v3.0, FW 5.2.2, IP 172.16.1.6 - **Local router fallback**: `http://172.16.1.1/webpages/login.html` (login blocked when managed by controller — see "Known Issue" section) - **Local switch UI**: `http://172.16.1.2` (accessible but requires unknown device-specific credentials) - **Monitoring stack**: CT 141 (proxmox7), Docker-based: prometheus (:9090), grafana (:3000), alertmanager, blackbox, pve-exporter, telegram-bridge (:9099). snmp_exporter runs as systemd binary (:9116) — Docker deployment fails on CT 141. - **Grafana dashboard**: `network-infra` (uid: `network-infra`) — Proxmox nodes, guests, HA state, blackbox probes. Omada metrics NOT included (no exporter available for cloud controller). - **Local Omada Controller** (in progress): CT 142 (`omada-controller`, 10.0.30.142/24, VLAN 30, proxmox7, vm_disks storage). Will replace cloud controller to enable SNMP + API exporters. - **Docker hosts in cluster**: Only CT 141 (6 containers: monitoring stack) and CT 121 (1 container: portainer). All other CTs/VMs run services natively without Docker. ## Login Flow ### Steps 1. Navigate to `https://omada.tplinkcloud.com/#/login` 2. Wait 5-8 seconds — the SPA loads slowly, initial snapshot shows `(empty page)` with 0 elements 3. Take snapshot to confirm login form is visible 4. Type TP-Link ID into the "TP-Link ID (Partner ID)" textbox 5. Type password into the "Password" textbox (nested inside a generic wrapper — use the inner textbox ref, not the outer one) 6. Press **Enter** (do NOT click the "Sign In" link — it redirects to tp-link.com marketing site instead of submitting the form) 7. Wait 5-8 seconds for redirect 8. Dismiss cookie banner if present ("Alle Cookies akzeptieren") 9. Dismiss any notice dialogs ("OK" or "Do not remind again") ### Alternative: Console-Based Login Instead of `browser_type` + `browser_press`, you can fill the login form via `browser_console`: ```javascript (async function(){ const sleep = ms => new Promise(r => setTimeout(r, ms)); const inputs = document.querySelectorAll('input'); if (inputs.length >= 2) { inputs[0].focus(); inputs[0].value = 'dominik@familie-schoen.com'; inputs[0].dispatchEvent(new Event('input', {bubbles: true})); const pwInputs = document.querySelectorAll('input[type="password"]'); if (pwInputs.length > 0) { pwInputs[pwInputs.length - 1].focus(); pwInputs[pwInputs.length - 1].value = ''; pwInputs[pwInputs.length - 1].dispatchEvent(new Event('input', {bubbles: true})); } } await sleep(500); // Submit via Enter keydown event document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true})); // Also click Sign In link as backup for (const el of document.querySelectorAll('a, button')) { if (el.textContent.trim() === 'Sign In' && el.tagName === 'A') { el.click(); break; } } return 'login submitted'; })() ``` > Note: This approach dispatches React-compatible `input` events. The `browser_type` + `Enter` approach is simpler and equally reliable for the login form. ### Critical Pitfalls - **Empty page on navigate**: The Omada SPA frequently returns `(empty page)` with `element_count: 0` right after navigation. Always wait 5-8s then re-snapshot before concluding the page failed to load. - **"Sign In" link vs form submit**: The "Sign In" element (`@e9` in observed session) is a **link**, not a submit button. Clicking it navigates to `https://www.tp-link.com/de/support/`. Use `browser_press` with `Enter` while focused in the password field to submit the form. - **Password field nesting**: The password input is wrapped in a generic div containing two textboxes — use the one with aria-label "Password" (the inner ref), not the outer wrapper ref. - **Multiple dismissable dialogs**: After login, expect up to 3 sequential dialogs: cookie consent, regional isolation notice, and firmware/system update announcement. Dismiss each before proceeding. - **Session expiry / white screen**: After extended interaction (especially after clicking buttons inside the iframe), the SPA may go completely blank/white. The iframe disappears from the DOM entirely (`document.querySelector('iframe')` returns null). Recovery: re-navigate to `https://omada.tplinkcloud.com/#/login` and repeat the full login flow. The session does NOT persist across browser navigations. - **`browser_console` variable re-declaration**: Repeated `browser_console` calls that declare variables (e.g., `const fr = document.querySelector(...)`) fail with `SyntaxError: Identifier 'xxx' has already been declared` because the execution context persists between calls. Always wrap code in an IIFE: `(function(){ ... })()` to avoid polluting the persistent scope. - **`browser_click` refs for iframe content**: Refs from `browser_snapshot` for elements inside the iframe (e.g., `@e14` for "Optimize Now") sometimes fail with "Unknown ref". Fall back to `browser_console` with IIFE-scoped DOM queries to click iframe-internal elements directly. ## Navigation: Controller → Site → WLAN ### Structure ``` Omada Cloud Landing └─ Dropdown: "On Premise Systems" / "Cloud Based Systems" → select "Cloud Based Systems" └─ Controller card ("Schön") → click to enter └─ Global View (org overview, site list) └─ Site "Rohr" → click site name cell └─ Site Dashboard with left nav: ├─ Management: Dashboard, Devices, Clients ├─ Monitoring: Map, Logs ├─ Configuration: Network Config, Device Config ├─ Hotspot └─ Maintenance: Network Tools, IntelliRecover ``` ### Switching to Cloud Based Systems The top-left dropdown defaults to "On Premise Systems". Click the combobox, then use `browser_console` to find and click the "Cloud Based Systems" `
  • ` inside `.ant-dropdown-menu`. ### Switching Back to a Site from Global View After performing actions in Global View (e.g., checking org-wide logs), returning to a specific site requires navigating through the Dashboard site list: 1. Click **Dashboard** in the Global View sidebar 2. Find the site name in the Site List table 3. Click the `span.text-link.cursor-pointer` inside the site name cell (NOT the `` itself — it has no click handler) 4. Verify by checking the sidebar: site-level menus (Management, Monitoring, Configuration, Hotspot, Maintenance) should appear instead of Global View menus (Dashboard, Devices, Logs, SD-WAN, Accounts, Settings) **Pitfall: Combobox Does Not List Sites** The top-left combobox (showing controller name "Schön") opens a dropdown with controller/system options (e.g., "SchönEssentials") but does **NOT** list individual sites. It cannot be used to switch between sites. Always use the Dashboard → Site List table to navigate to a specific site. ### Entering a Site Click the site name in the Site List table. **Pitfall**: clicking the `` cell itself does NOT navigate into the site — the cell has `cursor: auto` and no click handler. The actual clickable element is a `span.text-link.cursor-pointer` *inside* the cell. Double-clicking the cell also does not work. Use `browser_console`: ```javascript (function(){ const fr = document.querySelector('iframe'); if (!fr || !fr.contentDocument) return 'no iframe'; const spans = fr.contentDocument.querySelectorAll('span.text-link, .cursor-pointer'); for (const s of spans) { if (s.textContent.trim() === 'Rohr') { s.click(); return 'clicked Rohr text-link'; } } return 'not found'; })() ``` **Verification**: After clicking, the sidebar should show site-level menus (Management, Monitoring, Configuration, Hotspot, Maintenance) instead of Global View menus (Dashboard, Devices, Logs, SD-WAN, Accounts, Settings). If you still see "SD-WAN" or "Accounts" in the sidebar, you're still in Global View — the click didn't work. ### Iframe Navigation > **Reference**: See `references/navigation-css-structure.md` for detailed CSS class hierarchy of the Omada site iframe navigation. Once inside a site, all content is rendered inside an `