Knowledge Base · MSP Operations
FortiGate
Operations Console
Complete reference for the FortiGate MSP Operations Console — a multi-tenant, single-file HTML dashboard covering real-time FortiOS threat events, firewall policy management, VPN tunnel status, SD-WAN, HA clustering, quarantine enforcement, and per-client operational data. Built for MSP security engineers and NOC staff managing FortiGate deployments across multiple client tenants.
FG · CMD FORTIOS 7.x REST API 5 CLIENT TENANTS 30s AUTO-REFRESH DEMO MODE FortiManager · FortiAnalyzer · PSA PROXY REQUIRED FOR LIVE

The FortiGate Operations Console (rail brand: FG · CMD) is a self-contained single-file HTML dashboard for MSP security teams managing FortiGate firewall deployments across multiple client tenants. It provides unified visibility into threat events, VPN status, firewall policies, active sessions, HA cluster state, FortiGuard license health, and per-client operational data from a single browser window.

The console runs as a static HTML file — no server, no build process, no dependencies beyond Google Fonts. In demo mode all data is generated by mock functions using randomized but realistic FortiOS data structures. In live mode, a proxy service relays authenticated requests to each client's FortiGate instance or FortiManager.

Primary Use Cases
  • NOC Wall Display: Rail stats and event table give shift leads a live security posture summary across all clients.
  • Threat Response: Click any event → inspection panel surfaces src/dst IPs, policy context, and one-click Block IP / Create Ticket actions.
  • VPN Operations: VPN tab in the center column + ssl.root and ipsec0 interface cards for at-a-glance tunnel health.
  • Compliance Review: Policies tab, FortiGuard subscription status, and HA sync state for audit evidence gathering.
  • Per-Client Drill-Down: Client selector row filters all panels to a single tenant instantly.
What It Replaces
  • Individual FortiGate web UIs — no more logging into each client's HTTPS management interface separately.
  • FortiManager device views — device inventory, alert feeds, and policy tables are surfaced directly in the console.
  • Manual VPN status checks — ipsec and ssl-vpn tunnel counts are visible in the rail and interface cards.
  • FortiGuard expiry spreadsheets — the FortiGuard tab shows subscription status, version, and days remaining per service.
  • PSA context switching — Create Ticket fires directly to the PSA API (live mode) from the inspection panel without leaving the console.
Demo vs Live All data is currently generated by mock functions: mockSummary(), mockAlerts(), mockDevices(), mockRoutes(), mockPolicies(), mockQuarantine(), mockUsers(), mockTraffic(), mockFortiGuard(), and mockHA(). No network requests are made in demo mode. See Section 14 for the proxy activation checklist.

Each data source has a different API availability story. The table below reflects the actual FortiOS 7.x REST API as verified against Fortinet documentation — not the theoretical ideal. Features marked "proxy required" are implemented and wired in the code but will return no live data until the proxy service is deployed and DEMO_MODE is set to false.

Data SourceStatusEndpoint / Notes
System status & resource usagePROXY REQUIRED/api/v2/monitor/system/status
/api/v2/monitor/system/resource/usage
Alert / event streamPROXY REQUIRED/api/v2/log/memory/event?logtype=event&rows=200 — memory log only. FortiAnalyzer REST API required for persistent log queries.
Device inventory (multi-device)FORTIMGR ONLYPOST /json/rpc to FortiManager. Standalone FortiOS has no device-list endpoint. See Section 12.
Firewall routing tablePROXY REQUIRED/api/v2/monitor/router/ipv4
Firewall policiesPROXY REQUIRED/api/v2/cmdb/firewall/policy — supports ?filter, ?vdom, ?start, ?count
Quarantine (banned IP list)PROXY REQUIRED/api/v2/monitor/user/banned — FortiOS calls this "banned" not "quarantine". See Section 12.
SSL-VPN active usersPROXY REQUIRED/api/v2/monitor/vpn/ssl
IPSec VPN tunnelsPROXY REQUIRED/api/v2/monitor/vpn/ipsec
Traffic / session dataPROXY REQUIRED/api/v2/monitor/firewall/session + /api/v2/monitor/system/interface. Per-app breakdown requires FortiAnalyzer. See Section 12.
FortiGuard license statusPROXY REQUIRED/api/v2/monitor/license/status
HA cluster statisticsPROXY REQUIRED/api/v2/monitor/system/ha-statistics — note: /api/v2/monitor/ha does NOT exist. See Section 12.
FortiAnalyzer connectivityPROXY REQUIRED/api/v2/monitor/log/fortianalyzer-connectivity-status
Incident objectsNO FORTIOS ENDPOINTFortiOS has no native incident object endpoint. Incidents are PSA records (ConnectWise / Autotask) or FortiAnalyzer incident views. Console currently uses mock incident data.
Action: Block IPPROXY REQUIREDPUT /api/v2/monitor/user/banned — adds IP to banned list
Action: Create TicketPROXY REQUIREDPOST to PSA proxy endpoint — ConnectWise or Autotask
Action: Reset VPNPROXY REQUIREDPOST /api/v2/monitor/vpn/ipsec/tunnel_reset or DELETE on ssl session
🔌
All Features Are Implemented — Proxy Activates Them Every endpoint, every action button, and every data panel is wired in the source code. The fgFetch() function has the live fetch block pre-written and commented out. Setting DEMO_MODE = false and deploying a proxy is all that is required to transition from simulated to live data. No UI changes are needed.

The console is a single HTML file organized as a vertical stack of fixed-height chrome layers above a three-column body. The layout uses height:100vh with overflow:hidden so the body columns scroll independently without the rail or ticker moving.

Rail · 46px · position:fixed-like via flex
FG · CMD Rail
Brand · 6 stat counters · DEMO badge · clock · Refresh button
Ticker · 20px
FORTIOS Alert Ticker
Scrolling incident feed · hover to pause
Client Selector · 36px
Multi-Tenant Client Row
All Clients + 5 per-client chips · severity dots · event counts
Refresh Progress Bar · 2px
30s Auto-Refresh Indicator
Depleting orange bar · resets on each refresh cycle
KPI Row · 7 tiles · grid
Key Performance Indicators
CPU · Memory · IPS Blocked · App Control · Web Blocked · VPN Tunnels · HA Status — all clickable
Left Column · 272px
Monitor
Alerts · API Health · FortiGuard · HA tabs
Center Column · flex 1
Operations
Interface cards · Events · Routing · Policies · Quarantine · Traffic · Users
Right Column · 300px
Inspection
Selected item detail · quick actions · progress bars
Data Flow — Demo Mode
1
DOMContentLoaded → loadDashboard()
On page load loadDashboard() is called immediately. It calls refreshAll() which runs 11 parallel mock data promises via Promise.allSettled().
2
refreshAll() → 11 fgFetch() calls
Each fgFetch(key) call resolves from a corresponding mock function: mockSummary, mockAlerts, mockDevices, mockIncidents, mockRoutes, mockPolicies, mockQuarantine, mockUsers, mockTraffic, mockFortiGuard, mockHA. Each simulates a random 40–200ms latency.
3
FG.data populated → fgRenderAll()
After all promises settle, results are stored in the FG.data object keyed by data type. fgRenderAll() is called to update every panel.
4
renderClientRow() → client chips built
The full unfiltered event set is tallied per client. Each chip gets a severity dot color (red/orange/green) and an event count. The active client filter is then applied before any other rendering occurs.
5
Rail, KPI, panels, ticker updated
Rail counters animate via animCount(). KPI tiles are rebuilt from scratch. Left/center/right panels render from the active client–filtered data set. The ticker rebuilds its scrolling event list.
6
fgStartRefreshTimer() → 30s countdown
A depleting orange bar drains over 30 seconds. At zero, loadDashboard() is called again, restarting the cycle. Ctrl+R or the Refresh button resets the timer immediately.
Rail — Live Stats Strip

The 46px rail is the top-most chrome element. It holds the brand identity on the left, six clickable stat counters in the center, and operational controls on the right. Stats animate from zero on every refresh using animCount().

StatElement IDColorSourceClick Action
Threatsrb-critREDCombined crit count from incidents + alerts + devicesfgRailClick('crit') — filters event table to CRIT severity
Highrb-highORANGECombined high countfgRailClick('high') — filters event table to HIGH severity
Policiesrb-policiesACCENTsummary.activePoliciesfgOpenKpi('policies') — switches center tab to Policies
VPN Uprb-vpnTEALsummary.vpnUpfgOpenKpi('vpn') — switches center to Events, filters VPN type
Sessionsrb-sessionsGREENsummary.activeSessionsfgOpenKpi('sessions') — switches center tab to Users
Interfacesrb-intfBLUEsummary.ifUpfgOpenKpi('interfaces') — switches center tab to Traffic
Ticker

The 20px ticker sits immediately below the rail. It contains all current events (incidents + alerts + devices) in a CSS-animated continuous scroll. Color coding follows severity: red for crit, orange for high, green for low.

  • Hover pauses the scroll — CSS animation-play-state:paused on hover for closer inspection.
  • Format per item: HH:MM · EVENT-ID · TITLE
  • Ticker content is rebuilt via buildTicker() on every refresh cycle.
  • Animation duration is 70 seconds for a full cycle — adjust in .tscroll CSS for faster or slower display contexts.

The 36px client selector row is the multi-tenant control layer. It sits between the ticker and the refresh progress bar. Every panel below it — the KPI tiles, event table, alert stream, and inspection panel — responds to the active client selection.

Chip Behavior
All Clients chipDefault state. Shows the aggregate event count across all tenants. All panels display combined data. Element ID: cc-all. Calls fgSelectClient(null).
Per-client chipsOne chip per entry in FG_CLIENTS. Clicking calls fgSelectClient(clientName), stores the name in FG.activeClient, and triggers fgRenderAll(). The active chip highlights using the client's configured color.
Severity dotSmall colored circle on each chip. RED = client has crit events. ORANGE = high events only. GREEN = no crit or high. Updates on every refresh.
Event count badgeMonospace count to the right of the client name showing total events for that tenant in the current refresh cycle.
Filter scopeFiltering is applied via fgGetFiltered() which filters FG.data.incidents + FG.data.alerts + FG.data.devices by a.client === FG.activeClient. Routing, policy, quarantine, traffic, and user data are not per-client filtered in demo mode — those panels reflect the single connected FortiGate in live mode.
Client names must match the data Each alert, incident, and device object has a client string field. The selector filters by exact string match. In demo mode the values are: Acme Corp, BlueSky Logistics, Cortex Financial, Delta Medical, Ember Analytics. In live mode these must match values returned by your API data mapping layer.

Seven KPI tiles sit in a full-width grid below the refresh bar. Each tile has a colored 2px accent bar at the top. Clicking any tile populates the right column inspection panel with expanded detail and quick actions.

TileColorSource FieldSub-LabelClick → Right Panel
CPU UsageORANGEsummary.cpuPct⚠ Elevated if >70%, else NormalCPU bar + CPU Diagnostics action if >70%
MemoryBLUEsummary.memPct⚠ High if >80%, else NormalMemory usage bar + Performance Report action
IPS BlockedREDsummary.ipsBlockedLast 24hBlock count + View IPS Events + IPS Report
App ControlACCENTsummary.appCtrlEventsEvents todayCount detail + View Report action
Web BlockedYELLOWsummary.webFilterBlockedDNS + URLCount detail + View Report action
VPN TunnelsTEALsummary.vpnUp / vpnDown⚠ N down if any downUp/Down counts + Reset VPN + View VPN Events
HA StatusGREENsummary.haStatesummary.fwVersionHA State, Sync, Version, Uptime + View HA Detail

The 272px left column is the monitor/triage panel. It has a fixed header showing the column title and a live event count, followed by four tabs. Only one tab panel is visible at a time.

Alerts Tab (Default)

Two filter rows sit above a scrollable alert stream. The top row filters by event type; the second row filters by severity. Both filters stack — active type and active severity both apply simultaneously.

Filter RowOptionsState Variable
TypeAll · Threat · VPN · Policy · Intf · SysFG.filter — set by fgSetFilter(f)
SeverityCRIT · HIGH · MED · LOW · ALLFG.sev — set by fgSetSev(s)

The alert stream below shows compact event rows (colored severity dot · title · client name · age). Clicking any row calls fgSelectRow(id) which populates the right column inspection panel and highlights the matching row in the center event table.

API Tab

Lists all FortiOS REST API endpoints used by the console with live latency and status. In demo mode latencies are randomized. In live mode they reflect actual round-trip times from the proxy.

  • Green dot = OK. Orange = WARN (>300ms or intermittent). Red = FAIL (no response).
  • Clicking an endpoint row populates the right panel with the endpoint path, latency, status, and a Re-test + Log Service Issue action.
  • Endpoints verified against FortiOS 7.x REST API documentation are listed in Section 11.
FortiGuard Tab

Shows all FortiGuard subscription services with current signature version, days until expiry, and status badge. Data source: mockFortiGuard() in demo mode, /api/v2/monitor/license/status in live mode.

  • Services tracked: AntiVirus, IPS, Web Filter, DNS Filter, App Control, AntiSpam, Outbreak Prevention, FortiSandbox Cloud.
  • Status colors: OK = within expiry. EXPIRING = approaching expiry (random threshold in demo). EXPIRED = past expiry date.
  • Clicking a service row opens an inspection panel with version detail and a Force Update action.
  • Expiring services also surface a Renewal Ticket action button.
HA Tab

Shows the HA cluster configuration and per-member stats. Data source: mockHA() in demo mode, /api/v2/monitor/system/ha-statistics in live mode.

  • Cluster fields: Mode (Active-Passive), Group name, Group ID, Heartbeat state, Last failover timestamp.
  • Per-member rows show: name, serial, role (Primary / Secondary), CPU %, memory %, session count, sync state, uptime.
  • Clicking a member row opens the inspection panel with progress bars for CPU and memory and Force Failover / Force HA Sync actions.

The center column takes up all remaining horizontal space. It is composed of two sub-regions: an interface card strip at the top and a tabbed content area below.

Interface Card Strip

Seven interface/service cards scroll horizontally above the center tabs. Each card has a colored 2px top bar (green = OK, orange = degraded/standby, red flashing = critical), an icon, interface name, status label, and info line.

CardTypeInfo LineClick Action
wan1WANISP-A · 1Gbps · bandwidth MbpsView Traffic + SD-WAN Rules
wan2WANISP-B · 500Mbps · Active or DegradedView Traffic + SD-WAN Rules
ipsec0TunnelN tunnels activeReset Tunnel + View Users
ssl.rootVPNN active usersReset Tunnel + View Users
internalLANCore LAN · bandwidth MbpsView Traffic
dmzDMZDMZ Zone · bandwidth MbpsView Traffic
FortiGuardServiceSynced or Updating · signature versionInspection detail
Events Tab (Default)

A scrollable table of all current events filtered by the active client, type filter, and severity filter. Column layout:

ColumnWidthContent
ID70pxEvent ID — FGT-XXXX for alerts, INC-XXXX for incidents, DEV-XXXX for devices
SEV46pxColored severity badge: CRIT / HIGH / MED / LOW
TITLE / DEVICEflex 1Primary title bold + device name in monospace below
CLIENT110pxTenant name — populated from a.client field
STATUS78pxopen (red) · investigating (orange) · resolved (dim) · online (green)
AGE52pxTime since event: Xm or Xh Ym
ASSIGNED82pxEngineer name or Unassigned

A 2px left border on each row is colored by severity: red = crit, orange = high, yellow = med, blue = low. Row hover highlights orange-tinted. Selected row (after clicking) highlights with a stronger orange background.

Routing Tab

Displays the FortiGate IPv4 routing table. Data source: mockRoutes() / /api/v2/monitor/router/ipv4. Columns: Type (BGP/OSPF/Static/Connected) · Destination · Gateway · Interface · Admin Distance · Status badge.

Clicking any row populates the right panel with route detail and Flush Route / Traceroute actions.

Policies Tab

Lists all firewall policies. Data source: mockPolicies() / /api/v2/cmdb/firewall/policy. Columns: ID · Name · Src→Dst · Service · Action (ACCEPT / DENY badge) · Hit Count · Status (enabled / disabled badge).

Clicking any row opens a detail panel with IPS, AV, and web filter profile state and Edit Policy / Disable Policy actions.

Quarantine Tab

Lists IPs in the FortiGate banned (quarantine) list. Data source: mockQuarantine() / /api/v2/monitor/user/banned. Columns: IP Address · Reason · Source engine · Added time · Expires · Release button.

Clicking a row opens a detail panel with Release from Quarantine / Extend Ban / Create Ticket actions.

Endpoint name mismatch The UI labels this panel "Quarantine" but the correct FortiOS API endpoint is /api/v2/monitor/user/banned. The endpoint /api/v2/monitor/user/quarantine does NOT exist in FortiOS. Any proxy mapping must use user/banned.
Traffic Tab

Displays WAN1 and WAN2 bandwidth charts (stacked bar, 20 samples), Top Applications by percentage, and Top Source IPs by bytes and session count. Data source: mockTraffic() / /api/v2/monitor/firewall/session + /api/v2/monitor/system/interface.

Clicking a source IP fires a toast: "🔍 Investigating X.X.X.X". In live mode this should trigger a lookup action.

Users Tab

Active VPN user sessions. Data source: mockUsers() / /api/v2/monitor/vpn/ssl. Columns: Username · Auth Type · IP Address · Duration · Rx/Tx bytes · Status badge.

Clicking any row surfaces Disconnect User and Block IP actions.

The 300px right column is the inspection and action panel. It starts in a prompt state and populates when any row, tile, card, or API endpoint is clicked. The column header shows the selected item ID and the content area scrolls independently.

Inspection Panel Content — Alert / Device / Incident

When a row is clicked via fgSelectRow(id), the panel renders:

  • Detail grid: Severity, Status, Age, Assigned — 2×2 card grid with large colored values.
  • Description: Full detail text from the event's detail field — FortiOS log context, IPS rule matched, affected hosts, action taken.
  • Identifiers list: Event ID, Device, Source, Client, Type, Policy, Src IP, Dst IP, Action, Model, Firmware, CPU%, Mem%, Sessions — only fields that exist for the selected object are rendered.
  • Quick Actions: Context-sensitive buttons based on event type. See action table below.
All Available Actions

All action buttons call fgAct(type) which triggers the action modal with a multi-step execution sequence. In demo mode steps animate but no real API calls are made. In live mode each step maps to an actual API call via the proxy.

ActionfgAct() KeyContextSteps (Live Mode Intent)
Block Source IPblockThreat events, VPN usersIdentify threat context → Look up existing policies → Add IP to block list via FortiOS API → Verify rule active → Log to FortiAnalyzer
Edit Firewall PolicypolicyPolicy events, policy rowsFetch current policy config → Analyze hit counts → Apply modification via API → Verify policy compile
Disable Policypolicy-disablePolicy rowsFetch policy state → Set status disabled → Confirm disabled
Reset VPN TunnelvpnVPN events, ipsec/ssl cardsIdentify affected tunnel → Clear IKE/IPSec SAs → Trigger Phase 1 renegotiation → Confirm tunnel Up → Verify BGP routes restored
Create PSA TicketticketAll event typesCollect event metadata → Check for duplicates → Create PSA ticket → Notify assigned engineer
Generate ReportreportKPI tiles, system eventsCompile event timeline → Gather policy + threat summary → Render via FortiAnalyzer → Send to stakeholders
Resolve EventresolveAll event typesVerify threat neutralized → Log resolution notes → Notify client
Reboot DevicerebootDevice eventsBackup config → Send reboot command → Wait for device online → Verify reachable
Flush Routeroute-flushRouting rowsIdentify route entry → Clear from RIB → Confirm re-learned
Tracerouteroute-traceRouting rowsExecute traceroute from FortiGate → Resolve hops → Display path results
Update SD-WAN RulessdwanWAN interface cardsFetch SD-WAN rules → Check interface SLA scores → Apply rule update → Verify traffic steering
Force HA Failoverha-failoverHA secondary memberConfirm intent → Send failover command → Wait for secondary to assume primary → Verify new primary active
Force HA Syncha-syncHA membersInitiate config sync → Wait for completion → Confirm In-Sync state
Force FortiGuard Updatefortiguard-updateFortiGuard tabConnect to FortiGuard Distribution Network → Download latest signatures → Apply update → Verify version updated
Test API Endpointapi-testAPI tab rowsSend test request → Validate response payload → Confirm reachable
CPU Diagnosticsdiag-cpuCPU KPI tile (if >70%)Fetch top process list → Analyze CPU spikes → Log diagnostics to FortiAnalyzer
Release from QuarantineunquarantineQuarantine rowsIdentify quarantine entry → Release IP from banned list → Log release action
Disconnect VPN Userdisconnect-userUsers tab rowsLocate active session → Terminate session via SSL-VPN API → Confirm session closed

All actions execute through a full-screen modal overlay that shows each step sequentially. The modal is driven by fgRunModal(title, steps) where each step object has an id, icon, label, and dur (simulated duration in ms).

  • Step states: waiting (dim) → running… (yellow pulsing) → ✓ done (green). Steps execute sequentially with 80–200ms gaps between them.
  • Progress bar: Orange bar fills proportionally as each step completes. Reaches 100% when the final step is done.
  • Close button: Hidden during execution. Appears only after all steps complete.
  • Keyboard: Escape closes the modal at any time via the global keydown handler.
  • Live mode: Each step's dur can be replaced with a real async API call. The modal's sequential step pattern is designed to accommodate real network latency gracefully.
Detail Pane vs Action Modal The slide-out detail pane (#dpane) is separate from the action modal. The detail pane opens from event table rows and shows deeper investigation context — IPS rule details, connection logs, related events. It slides in from the right edge. The action modal covers the full screen and is only for executing operational actions.

All endpoints below are verified against FortiOS 7.x REST API documentation. Auth header for all requests: Authorization: Bearer <api_token> or cookie-based: Cookie: APSCOOKIE=...

System
FortiOS REST API
# System status + firmware version + serial + uptime
GET /api/v2/monitor/system/status

# CPU, memory, disk utilization
GET /api/v2/monitor/system/resource/usage

# HA cluster statistics — members, role, sync state
GET /api/v2/monitor/system/ha-statistics

# Available firmware versions (NOT IPS engine status)
GET /api/v2/monitor/system/available-firmware
Logs & Events
FortiOS REST API
# Memory log — event stream (no persistent storage)
GET /api/v2/log/memory/event?logtype=event&rows=200

# FortiAnalyzer connectivity status
GET /api/v2/monitor/log/fortianalyzer-connectivity-status

# FortiGuard license & subscription status
GET /api/v2/monitor/license/status
Network & Routing
FortiOS REST API
# IPv4 routing table — BGP, OSPF, Static, Connected
GET /api/v2/monitor/router/ipv4

# IPv6 routing table (if needed)
GET /api/v2/monitor/router/ipv6

# Interface list and status
GET /api/v2/monitor/system/interface

# Active firewall sessions
GET /api/v2/monitor/firewall/session
VPN
FortiOS REST API
# SSL-VPN active user sessions
GET /api/v2/monitor/vpn/ssl

# IPSec VPN tunnel status
GET /api/v2/monitor/vpn/ipsec
Policies & Security
FortiOS REST API
# Firewall policy list — supports ?filter, ?vdom, ?start, ?count
GET /api/v2/cmdb/firewall/policy

# Banned IP list (called "quarantine" in UI)
GET /api/v2/monitor/user/banned

# Add IP to banned list (Block IP action)
PUT /api/v2/monitor/user/banned
Body: { "ip_address": "x.x.x.x", "expiry": 3600 }
FortiManager — Device Inventory (Multi-Tenant)
FortiManager JSON-RPC API
# Device list per ADOM — requires FortiManager, not standalone FortiOS
POST /json/rpc
Body: {
  "method": "get",
  "params": [{ "url": "/dvmdb/adom/<adom>/device" }],
  "session": "<session_token>",
  "id": 1
}

# Per-ADOM policy read via FortiManager proxy
GET /api/v2/cmdb/firewall/policy?vdom=<vdom>&adom=<adom>

These limitations are documented verbatim from the source code API audit block. They reflect the actual state of the FortiOS 7.x REST API as of the build date — not oversights in the console design.

No single /alerts endpoint FortiOS has no unified /alerts monitor endpoint. Events must be read from /api/v2/log/memory/event (memory log — not persisted across reboots) or forwarded via the FortiAnalyzer REST API. For managed deployments, the FortiAnalyzer REST API should be the primary event source. LIMITATION
No device-list endpoint on FortiOS Standalone FortiOS has no endpoint to list multiple managed devices. The device inventory view (interface cards and device events) requires FortiManager as the upstream data source via the JSON-RPC API. The "Devices" data key in FG.data.devices is populated from FortiManager only. LIMITATION
No native incident objects FortiOS has no native incident object endpoint. Incidents visible in the console (INC-XXXX series) are either PSA records (ConnectWise / Autotask) or FortiAnalyzer incident views — not fulfillable from FortiOS alone. In live mode, the incidents fetch key must route to a PSA or SIEM source, not FortiOS. LIMITATION
/monitor/user/quarantine does not exist The FortiOS API endpoint for banned IPs is /api/v2/monitor/user/banned. The path /api/v2/monitor/user/quarantine does NOT exist and will return a 404. Any proxy configuration must map to user/banned. The UI label "Quarantine" is user-facing terminology only. LIMITATION
/monitor/firewall/stats does not exist There is no /api/v2/monitor/firewall/stats endpoint. Traffic data must be assembled from /api/v2/monitor/firewall/session and /api/v2/monitor/system/interface. Per-application traffic breakdown requires FortiAnalyzer or FortiView data — it is not available directly from FortiOS. LIMITATION
/monitor/ha does not exist The HA endpoint is /api/v2/monitor/system/ha-statistics. The path /api/v2/monitor/ha does NOT exist. Any proxy or automation that references the shorter path will fail. LIMITATION
/monitor/log/fortianalyzer does not exist The FortiAnalyzer connectivity status endpoint is /api/v2/monitor/log/fortianalyzer-connectivity-status. The path /api/v2/monitor/log/fortianalyzer does NOT exist. LIMITATION
/monitor/system/firmware is not IPS engine status The endpoint /api/v2/monitor/system/available-firmware returns available firmware versions, not IPS engine status. IPS engine version is returned inside the /api/v2/monitor/system/status response. Do not use the firmware endpoint to check IPS signature versions — use /api/v2/monitor/license/status instead. LIMITATION
Memory log is not persistent /api/v2/log/memory/event reads from the FortiGate's volatile memory log buffer. This log is cleared on reboot and has limited capacity. For reliable event history, FortiAnalyzer (or a SIEM receiving syslog from the FortiGate) must be the authoritative log source. The console's Events tab reflects this when wired to live data. LIMITATION
CORS blocks direct browser API calls FortiOS does not return CORS headers that allow browser-originated cross-origin requests. A proxy service (Azure Function, Cloudflare Worker, Express) is mandatory in live mode — direct fetch() from the browser to the FortiGate management IP will fail with a CORS error regardless of API token validity. ARCHITECTURAL

All configurable values are located at the top of the <script> block. No config file, no environment variables — edit these constants directly in the HTML source.

Primary Config Constants
ConstantDefaultPurpose
DEMO_MODEtrueMaster toggle. true = all mock data, no network calls. false = live API mode via proxy.
FG_BASE_URL'https://your-fortigate-hostname'FQDN or IP of the FortiGate management interface. Used as the base URL for all API calls in live mode. In multi-tenant mode, overridden per-client by FG_CLIENTS.
FG_API_TOKEN''FortiOS REST API token. Generate from System > Admin > Administrators > Create API user. Token must have read access to monitor endpoints and write access for action endpoints (block, policy, VPN reset).
FG_CLIENTS — Multi-Tenant Client Config

The FG_CLIENTS array defines all managed tenants. One entry per client. Each entry drives the client selector chip UI and (in live mode) provides the credentials for routing API calls to that specific FortiGate instance.

JavaScript — FG_CLIENTS array
var FG_CLIENTS = [
  {
    id:       'acme',              // unique slug — used for chip DOM ID
    name:     'Acme Corp',         // display name — must match a.client in data
    color:    '#4dabff',           // chip accent color when selected
    baseUrl:  'https://fg.acme.corp', // FortiGate FQDN for this client (live mode)
    apiToken: ''                  // API token for this client's FortiGate
  },
  // ... one entry per client
];
FieldRequiredNotes
idYesUnique slug. Used for chip element ID (cc-{id}) and CSS scoping. Lowercase, no spaces.
nameYesMust exactly match the client string field in alert/incident/device data objects for filtering to work.
colorYesAny valid CSS color. Used for the chip's active border, text, and background tint.
baseUrlLive onlyFortiGate management FQDN including protocol. No trailing slash. In FortiManager deployments, this may be the FortiManager host with ADOM routing handled at the proxy layer.
apiTokenLive onlyPer-client FortiOS API token. In live mode, fgFetch() reads the active client's token via FG_CLIENTS.find(c => c.name === FG.activeClient).
FG State Object

Runtime state is stored in var FG = { ... } at the top of the script. Do not edit this — it is initialized programmatically. Key properties for reference:

FG.dataAll fetched data keyed by type: summary, alerts, devices, incidents, routes, policies, quarantine, users, traffic, fortiguard, ha.
FG.activeClientnull for All Clients view. String matching a FG_CLIENTS[n].name value when a client chip is active. Used by fgGetFiltered().
FG.filterActive event type filter: 'all' | 'threat' | 'vpn' | 'policy' | 'interface' | 'system'.
FG.sevActive severity filter: 'all' | 'crit' | 'high' | 'med' | 'low'.
FG.ltabActive left column tab: 'alerts' | 'api' | 'fortiguard' | 'ha'.
FG.ctabActive center column tab: 'events' | 'routing' | 'policies' | 'quarantine' | 'traffic' | 'users'.
FG.engineersArray of engineer names assigned to events: ['J. Torres','K. Patel','M. Chen','S. Adams','R. Nguyen','Unassigned']. Replace with your actual team.

Because FortiOS does not return CORS headers, a proxy service is required for all live API calls. The proxy sits between the browser and the FortiGate, injecting the Authorization header and relaying responses. The console's fgFetch() function has the live block pre-written — it just needs the proxy URL and the DEMO_MODE flag changed.

🔌
Proxy Options Any HTTP proxy that can forward requests and inject auth headers works: Azure Function App (Consumption plan — effectively free at MSP volumes), Cloudflare Worker, Express.js on a small VM, or an nginx reverse proxy co-located with the FortiGate. The proxy does not need to run on the FortiGate itself.
1
Create a FortiOS API user
On the FortiGate: System > Admin > Administrators > Create New > REST API Admin. Assign a trusted host (your proxy's IP). Assign a profile with Read access to Log & Report, Network, Policy & Objects, User & Device, System and Write access to User & Device (for block IP) and Network (for VPN reset). Copy the generated API token.
2
Deploy the proxy service
Create a proxy endpoint for each API route: GET /proxy/system/statusGET {FG_BASE_URL}/api/v2/monitor/system/status with header Authorization: Bearer {FG_API_TOKEN}. The proxy strips the Authorization header from the browser request and injects the real token server-side, keeping the token out of browser DevTools.
3
Update FG_BASE_URL and FG_API_TOKEN
In the HTML source, set FG_BASE_URL to your proxy's base URL (not the FortiGate directly). Set FG_API_TOKEN if the proxy uses token pass-through, or leave blank if the proxy handles credential injection internally.
4
Fill in FG_CLIENTS for each tenant
For each client, set baseUrl to that client's FortiGate FQDN and apiToken to that client's API token. The proxy reads these per-request to route to the correct device. If using FortiManager as an upstream aggregator, set all clients to the FortiManager host and differentiate by ADOM at the proxy layer.
5
Set DEMO_MODE = false
In the HTML source, change var DEMO_MODE = true; to var DEMO_MODE = false;. Uncomment the live fetch block inside fgFetch(). The EP_MAP object inside that block maps each data key to the correct proxy endpoint path.
6
Test each endpoint via the API tab
Load the console and switch to the API tab in the left column. Click each endpoint row and use the Re-test Endpoint action. Green dots confirm the proxy is routing correctly. Any FAIL state indicates a routing, auth, or CORS configuration issue at the proxy layer.
7
Verify action endpoints (optional)
Test Block IP with a known-safe IP, then verify it appears in the Quarantine tab and release it. Test Create PSA Ticket with a test event and confirm it appears in your PSA. This validates both the GET (read) and PUT/POST (write) proxy routes end-to-end.
8
Remove the DEMO badge (optional)
The yellow DEMO badge in the rail's right section is rendered by a span.demo-badge element inside #rail. Remove that element from the HTML to clear the demo indicator from the live deployment.

The multi-tenant layer was added to the console as a full feature layer. The client selector row, FG_CLIENTS config, fgSelectClient(), renderClientRow(), and fgGetFiltered() are all implemented. In demo mode client filtering works on the mock data set. In live mode it requires per-tenant API routing at the proxy layer.

Two Multi-Tenant Architecture Patterns
Pattern A — Direct Per-Client

Each client has their own FortiGate. The console routes to each device directly. One FG_CLIENTS entry per FortiGate. The proxy uses the selected client's baseUrl and apiToken to target the correct device on each request.

Best for: small MSP with <10 clients, each with standalone FortiGate.

Pattern B — FortiManager ADOM

FortiManager is the single upstream. All baseUrl values point to FortiManager. The proxy uses the client's ADOM name (stored in FG_CLIENTS[n].id or a custom field) to scope FortiManager JSON-RPC queries per request. Policy and device data is per-ADOM. Event data routes through FortiAnalyzer.

Best for: MSPs managing 10+ clients through a central FortiManager instance.

What the Client Filter Affects
  • Events tab: Fully filtered — only events where a.client === FG.activeClient are rendered.
  • Alert stream (left panel): Fully filtered — mini event rows respect the active client.
  • Rail stat counters: Filtered — crit and high counts reflect the selected client's events only.
  • KPI tiles: In demo mode, summary data is not per-client (single mock summary object). In live mode, fgFetch('summary') routes to the selected client's FortiGate, so KPI tiles will be client-specific.
  • Routing, Policies, Quarantine, Traffic, Users tabs: In live mode these are inherently per-client (the proxy routes to the selected FortiGate). In demo mode they show shared mock data regardless of client selection.
Common Issues
Rail stats show "–" on loadExpected — the initial render shows dashes before the first fetch completes. animCount() is called in fgRenderAll() after data arrives and animates from 0 to the target value. Wait 200–400ms for the initial mock delay to resolve.
Client chip event count shows 0The client field in the data objects must exactly match the name value in FG_CLIENTS. Check for extra spaces, different capitalization, or abbreviations. In demo mode the mock values are: Acme Corp, BlueSky Logistics, Cortex Financial, Delta Medical, Ember Analytics.
API tab shows all FAIL in live modeCORS is blocking direct browser-to-FortiGate requests. Confirm the proxy is deployed and FG_BASE_URL points to the proxy (not the FortiGate). Verify the proxy returns permissive CORS headers: Access-Control-Allow-Origin: *.
Block IP action completes but IP not quarantinedIn demo mode, the action modal animates but makes no real API call. In live mode, verify the API user profile has Write access to User & Device. Confirm the proxy routes PUT /api/v2/monitor/user/banned correctly. Check the FortiGate's quarantine (banned) list via GUI under Monitor > Quarantine Monitor.
Policies tab shows no results in live modeThe policies endpoint /api/v2/cmdb/firewall/policy requires the correct VDOM context. If the FortiGate is in multi-VDOM mode, append ?vdom=root (or the correct VDOM name) to the endpoint. Verify the API user has policy-read access in that VDOM.
HA tab shows "No HA data"This renders when FG.data.ha.members is undefined. In live mode, confirm the FortiGate is actually in an HA configuration — standalone devices return a minimal or empty response from /api/v2/monitor/system/ha-statistics. The console handles this gracefully by showing the placeholder message.
Ticker is not scrollingThe ticker uses CSS animation: tscrl 70s linear infinite. If the animation appears frozen, check that no parent element has overflow:hidden cutting the animation mid-frame. On iOS Safari, add -webkit-animation prefix to the .tscroll rule. Also verify FG.data.alerts is populated — an empty data set produces an empty ticker with nothing to scroll.
Layout breaks below 1200pxThe three-column body grid (272px 1fr 300px) requires at least ~700px of content width after the left column. The rail and ticker remain functional at any width. On displays under 1024px, consider hiding the right column and using the detail pane instead.
Refresh timer stopsThe countdown interval is stored in FG.countTimer. If the page tab becomes inactive (backgrounded), browsers throttle or suspend JavaScript timers. The auto-refresh will resume when the tab is foregrounded. For wall display use, keep the tab active and visible at all times. startAutoRefresh(30000) runs on a separate 30s interval and will also restart on tab refocus.
Escape key not closing modal or paneThe global keydown listener calls fgCloseModal() and fgClosePane() on Escape. If another element has captured focus (an input field inside the modal), the keydown may not bubble. Click outside the input first, then press Escape.
Customization Quick Reference
What to ChangeWhere in SourceHow
Refresh intervalstartAutoRefresh(30000) at bottom of scriptChange 30000 to desired milliseconds. Also update FG.countdown = 30 in fgStartRefreshTimer().
Client names + colorsvar FG_CLIENTS = [...]Edit name and color fields. Names must match client values in mock data or API responses.
Mock client names in datamockAlerts(), mockDevices(), mockIncidents()Edit the pick([...]) array in the client assignment line in each mock function.
Engineer namesFG.engineers = [...] in FG objectReplace with your team members. Last entry should remain 'Unassigned'.
FortiGate accent color:root { --accent: #ff6600 } in CSSChange hex value. Also update --accent2 for hover states.
Interface cardsvar ifaces = [...] in fgRenderAll()Edit names, types, status, and info lines. Each entry needs ic, nm, type, st, sst, n, bw, key.
Startup toast messageEnd of DOMContentLoadedModify or remove the fgToast(...) call.
Action stepsvar ACT_STEPS = { ... }Each action key maps to an array of step objects. Add, remove, or reorder steps. Each step needs id, icon, label, dur.
KB // FORTIGATE OPERATIONS CONSOLE · kb-dashboard-fortigate.html FORTIOS 7.x · 5 CLIENTS · 18 ACTIONS · 30s AUTO-REFRESH · DEMO MODE