Index: /branches/amp_4_0/platform/config/telegraf/ag.toml
===================================================================
--- /branches/amp_4_0/platform/config/telegraf/ag.toml	(revision 2968)
+++ /branches/amp_4_0/platform/config/telegraf/ag.toml	(working copy)
@@ -1,14 +1,20 @@
 [[inputs.snmp]]
 agents = []
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
 oid = ".1.3.6.1.4.1.7564.30.1.0"
 
 [[inputs.snmp.field]]
+name = "mem_usage"
+# AG memory usage % (ArrayOS enterprise MIB - confirmed in SNMP output)
+oid = ".1.3.6.1.4.1.7564.30.5.0"
+
+[[inputs.snmp.field]]
 name = "net_mem_usage"
 oid = ".1.3.6.1.4.1.7564.30.4.0"
 
@@ -32,6 +38,21 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
+[[inputs.snmp.field]]
+# Disk bytes used (ArrayOS flash storage - confirmed in SNMP output as .7564.25.2.0)
+name = "disk_used"
+oid = ".1.3.6.1.4.1.7564.25.2.0"
+
+[[inputs.snmp.field]]
+# Disk bytes total (ArrayOS flash storage - confirmed in SNMP output as .7564.25.4.0)
+name = "disk_total"
+oid = ".1.3.6.1.4.1.7564.25.4.0"
+
+# NOTE: AG/vxAG does NOT implement hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.3)
+# Disk metrics are collected as scalar fields above via the ArrayOS enterprise MIB.
+
+
+
 [[inputs.snmp.table]]
 name = "ag_virtual_site_stats"
 
Index: /branches/amp_4_0/platform/config/telegraf/apv.toml
===================================================================
--- /branches/amp_4_0/platform/config/telegraf/apv.toml	(revision 2968)
+++ /branches/amp_4_0/platform/config/telegraf/apv.toml	(working copy)
@@ -1,8 +1,9 @@
 [[inputs.snmp]]
 agents = []
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
@@ -36,9 +37,19 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
+[[inputs.snmp.field]]
+# APV disk usage % direct scalar (confirmed in SNMP output as .7564.3.8.0 = 28%)
+# .7564.3.5.0 = total, .7564.3.6.0 = used, .7564.3.7.0 = free, .7564.3.8.0 = % used
+name = "disk_pct"
+oid = ".1.3.6.1.4.1.7564.3.8.0"
 
+# NOTE: APV does NOT implement hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.*)
+# Disk % is available directly via the ArrayOS enterprise MIB as a scalar.
+
+
 [[inputs.snmp]]
 agents = []
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_performance"
 timeout = "2s"
@@ -50,23 +61,6 @@
 [[inputs.snmp.field]]
 name = "ssl_se_core_utilization"
 oid = ".1.3.6.1.4.1.7564.30.10.0"
-
-
-[[inputs.snmp.table]]
-name = "an_device_storage"
-
-[[inputs.snmp.table.field]]
-is_tag = true
-name = "prefix"
-oid = ".1.3.6.1.2.1.25.2.3.1.3"
-
-[[inputs.snmp.table.field]]
-name = "size"
-oid = ".1.3.6.1.2.1.25.2.3.1.5"
-
-[[inputs.snmp.table.field]]
-name = "used"
-oid = ".1.3.6.1.2.1.25.2.3.1.6"
 
 
 [[inputs.snmp.table]]
Index: /branches/amp_4_0/platform/config/telegraf/asf.toml
===================================================================
--- /branches/amp_4_0/platform/config/telegraf/asf.toml	(revision 2968)
+++ /branches/amp_4_0/platform/config/telegraf/asf.toml	(working copy)
@@ -1,8 +1,9 @@
 [[inputs.snmp]]
 agents = []
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
@@ -36,25 +37,12 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
-[[inputs.snmp.table]]
-name = "an_device_storage"
-
-[[inputs.snmp.table.field]]
-is_tag = true
-name = "prefix"
-oid = ".1.3.6.1.2.1.25.2.3.1.3"
-
-[[inputs.snmp.table.field]]
-name = "size"
-oid = ".1.3.6.1.2.1.25.2.3.1.5"
-
-[[inputs.snmp.table.field]]
-name = "used"
-oid = ".1.3.6.1.2.1.25.2.3.1.6"
-
-[[inputs.snmp.table.field]]
-name = "alloc_unit"
-oid = ".1.3.6.1.2.1.25.2.3.1.4"
+[[inputs.snmp.field]]
+# ASF disk usage % direct scalar (confirmed in SNMP output as .7564.3.8.0 = 34%)
+# .7564.3.5.0 = total, .7564.3.6.0 = used, .7564.3.7.0 = free, .7564.3.8.0 = % used
+# No hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.*) present in ASF SNMP output.
+name = "disk_pct"
+oid = ".1.3.6.1.4.1.7564.3.8.0"
 
 [[inputs.snmp.table]]
 name = "asf_ssl_statistics"
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql	(revision 2968)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/02_telegraf_snmp.sql	(working copy)
@@ -22,6 +22,9 @@
     requests BIGINT,
     total_in BIGINT,
     total_out BIGINT,
+    disk_used BIGINT,    -- ArrayOS enterprise MIB disk bytes used (AG/vxAG: .7564.25.2.0)
+    disk_total BIGINT,   -- ArrayOS enterprise MIB disk bytes total (AG/vxAG: .7564.25.4.0)
+    disk_pct DOUBLE PRECISION,  -- ArrayOS enterprise MIB disk usage % (APV: .7564.3.8.0)
     PRIMARY KEY (time, agent_host)
 );
 SELECT create_hypertable('an_device_metrics','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
@@ -46,7 +49,6 @@
     prefix TEXT NOT NULL,
     size BIGINT,          -- hrStorageSize
     used BIGINT,          -- hrStorageUsed
-    alloc_unit BIGINT,    -- hrStorageAllocationUnits
     PRIMARY KEY (time, agent_host, prefix)
 );
 SELECT create_hypertable('an_device_storage','time','agent_host', number_partitions => 4, if_not_exists => TRUE);
Index: /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/04_migrations.sql
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/04_migrations.sql	(nonexistent)
+++ /branches/amp_4_0/platform/tools/container/services/postgres/initdb.d/04_migrations.sql	(working copy)
@@ -0,0 +1,31 @@
+-- =============================================================================
+-- AMP Timescale DB Migrations
+-- Run-safe: all statements use IF NOT EXISTS so they are idempotent.
+-- =============================================================================
+
+-- Migration 001: Ensure an_device_storage exists on live deployments that pre-date
+-- the unified storage table (previously only APV had a storage table).
+-- This is a no-op if the table already exists.
+CREATE TABLE IF NOT EXISTS an_device_storage (
+    time TIMESTAMPTZ NOT NULL,
+    agent_host TEXT NOT NULL,
+    prefix TEXT NOT NULL,
+    size BIGINT,
+    used BIGINT,
+    PRIMARY KEY (time, agent_host, prefix)
+);
+SELECT create_hypertable('an_device_storage', 'time', 'agent_host',
+                         number_partitions => 4, if_not_exists => TRUE);
+CREATE INDEX IF NOT EXISTS idx_storage_prefix ON an_device_storage (prefix);
+CREATE INDEX IF NOT EXISTS idx_storage_agent_time ON an_device_storage (agent_host, time DESC);
+
+-- Migration 002: Add disk_used / disk_total to an_device_metrics.
+-- AG/vxAG devices expose disk via the ArrayOS enterprise MIB (.7564.25.2.0 / .7564.25.4.0)
+-- rather than the standard hrStorage table, so these are stored as scalars alongside
+-- the other per-device metrics.
+ALTER TABLE IF EXISTS an_device_metrics
+    ADD COLUMN IF NOT EXISTS disk_used  BIGINT;
+ALTER TABLE IF EXISTS an_device_metrics
+    ADD COLUMN IF NOT EXISTS disk_total BIGINT;
+ALTER TABLE IF EXISTS an_device_metrics
+    ADD COLUMN IF NOT EXISTS disk_pct   DOUBLE PRECISION;
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml	(revision 2968)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/ag.toml	(working copy)
@@ -1,15 +1,20 @@
 [[inputs.snmp]]
 agents = []
-agent_host_tag = "source"
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
 oid = ".1.3.6.1.4.1.7564.30.1.0"
 
 [[inputs.snmp.field]]
+name = "mem_usage"
+# AG memory usage % (ArrayOS enterprise MIB - confirmed in SNMP output)
+oid = ".1.3.6.1.4.1.7564.30.5.0"
+
+[[inputs.snmp.field]]
 name = "net_mem_usage"
 oid = ".1.3.6.1.4.1.7564.30.4.0"
 
@@ -33,6 +38,20 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
+[[inputs.snmp.field]]
+# Disk bytes used (ArrayOS flash storage - confirmed in SNMP output as .7564.25.2.0)
+name = "disk_used"
+oid = ".1.3.6.1.4.1.7564.25.2.0"
+
+[[inputs.snmp.field]]
+# Disk bytes total (ArrayOS flash storage - confirmed in SNMP output as .7564.25.4.0)
+name = "disk_total"
+oid = ".1.3.6.1.4.1.7564.25.4.0"
+
+# NOTE: AG/vxAG does NOT implement hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.3)
+# Disk metrics are collected as scalar fields above via the ArrayOS enterprise MIB.
+
+
 [[inputs.snmp.table]]
 name = "ag_virtual_site_stats"
 
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml	(revision 2968)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/apv.toml	(working copy)
@@ -1,9 +1,9 @@
 [[inputs.snmp]]
 agents = []
-agent_host_tag = "source"
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
@@ -37,9 +37,19 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
+[[inputs.snmp.field]]
+# APV disk usage % direct scalar (confirmed in SNMP output as .7564.3.8.0 = 28%)
+# .7564.3.5.0 = total, .7564.3.6.0 = used, .7564.3.7.0 = free, .7564.3.8.0 = % used
+name = "disk_pct"
+oid = ".1.3.6.1.4.1.7564.3.8.0"
 
+# NOTE: APV does NOT implement hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.*)
+# Disk % is available directly via the ArrayOS enterprise MIB as a scalar.
+
+
 [[inputs.snmp]]
 agents = []
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_performance"
 timeout = "2s"
@@ -54,23 +64,6 @@
 
 
 [[inputs.snmp.table]]
-name = "an_device_storage"
-
-[[inputs.snmp.table.field]]
-is_tag = true
-name = "prefix"
-oid = ".1.3.6.1.2.1.25.2.3.1.3"
-
-[[inputs.snmp.table.field]]
-name = "size"
-oid = ".1.3.6.1.2.1.25.2.3.1.5"
-
-[[inputs.snmp.table.field]]
-name = "used"
-oid = ".1.3.6.1.2.1.25.2.3.1.6"
-
-
-[[inputs.snmp.table]]
 name = "apv_virtual_stats"
 
 [[inputs.snmp.table.field]]
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml	(revision 2968)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/asf.toml	(working copy)
@@ -1,9 +1,9 @@
 [[inputs.snmp]]
 agents = []
-agent_host_tag = "source"
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
-timeout = "2s"
+timeout = "5s"
 
 [[inputs.snmp.field]]
 name = "cpu_usage"
@@ -37,25 +37,12 @@
 name = "total_out"
 oid = ".1.3.6.1.4.1.7564.23.3.0"
 
-[[inputs.snmp.table]]
-name = "an_device_storage"
-
-[[inputs.snmp.table.field]]
-is_tag = true
-name = "prefix"
-oid = ".1.3.6.1.2.1.25.2.3.1.3"
-
-[[inputs.snmp.table.field]]
-name = "size"
-oid = ".1.3.6.1.2.1.25.2.3.1.5"
-
-[[inputs.snmp.table.field]]
-name = "used"
-oid = ".1.3.6.1.2.1.25.2.3.1.6"
-
-[[inputs.snmp.table.field]]
-name = "alloc_unit"
-oid = ".1.3.6.1.2.1.25.2.3.1.4"
+[[inputs.snmp.field]]
+# ASF disk usage % direct scalar (confirmed in SNMP output as .7564.3.8.0 = 34%)
+# .7564.3.5.0 = total, .7564.3.6.0 = used, .7564.3.7.0 = free, .7564.3.8.0 = % used
+# No hrStorage (HOST-RESOURCES-MIB .1.3.6.1.2.1.25.2.*) present in ASF SNMP output.
+name = "disk_pct"
+oid = ".1.3.6.1.4.1.7564.3.8.0"
 
 [[inputs.snmp.table]]
 name = "asf_ssl_statistics"
Index: /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/avx.toml
===================================================================
--- /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/avx.toml	(revision 2968)
+++ /branches/amp_4_0/platform/tools/container/services/telegraf/telegraf.d/avx.toml	(working copy)
@@ -1,6 +1,6 @@
 [[inputs.snmp]]
 agents = []
-agent_host_tag = "source"
+agent_host_tag = "agent_host"
 community = "public"
 name = "an_device_metrics"
 timeout = "5s"
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/apps.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/apps.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/apps.py	(working copy)
@@ -6,3 +6,40 @@
 class CmConfig(AppConfig):
     name = 'cm'
     verbose_name = 'CM Module'
+
+    def ready(self):
+        """
+        Called once when Django starts.  We register a periodic job that
+        re-syncs VA management IPs into the Telegraf TOML files every 5 min.
+        The guard against the reloader's double import is required.
+        """
+        import os
+        # Django's runserver spawns a second process (the reloader); only
+        # run the scheduler in the main process to avoid duplicate jobs.
+        if os.environ.get('RUN_MAIN') == 'true' or not os.environ.get('RUN_MAIN'):
+            try:
+                from apscheduler.schedulers.background import BackgroundScheduler
+                from cm.models.virtualization import sync_va_telegraf_all_types
+
+                _va_sched = BackgroundScheduler(
+                    job_defaults={'misfire_grace_time': 60},
+                    daemon=True
+                )
+                _va_sched.add_job(
+                    sync_va_telegraf_all_types,
+                    'interval',
+                    minutes=5,
+                    id='va_telegraf_sync',
+                    replace_existing=True
+                )
+                _va_sched.start()
+
+                # Also run once immediately at startup so the agents lists are
+                # up-to-date without waiting for the first 5-minute tick.
+                import threading
+                threading.Thread(target=sync_va_telegraf_all_types, daemon=True).start()
+            except Exception as e:
+                import logging
+                logging.getLogger('hive.debug').warning(
+                    '[VA-TELEGRAF] Failed to start periodic sync scheduler: %s' % str(e)
+                )
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py	(working copy)
@@ -79,8 +79,131 @@
     key = ['name', 'ip_address', 'restapi_port', 'restapi_username', 'restapi_password', 'connection']
     result = [dict(zip(key, each)) for each in data]
     return result
+
+
+# ---------------------------------------------------------------------------
+# VA Telegraf helper — collects VA management IPs from all AVX hosts
+# ---------------------------------------------------------------------------
+
+# Maps normalised device-type strings to the product_category values returned
+# by the AVX VA list API (GET /aest/avx/va/instance/VAInstance/_get_list_data)
+_VA_TYPE_TO_PRODUCT_CATEGORY = {
+    'apv':  ['apv', 'vapv'],
+    'vapv': ['apv', 'vapv'],
+    'ag':   ['ag', 'vxag'],
+    'vxag': ['ag', 'vxag'],
+    'vag':  ['ag', 'vxag'],
+    'asf':  ['asf', 'vasf'],
+    'vasf': ['asf', 'vasf'],
+}
+
+
+def _get_va_mgmt_ips_for_type(device_type):
+    """
+    Query all AVX hosts and return a list of Telegraf agent strings
+    (e.g. ['"10.0.0.1:161"', '"10.0.0.2:161"']) for VA instances whose
+    product_category matches the given device_type (apv/ag/asf and their
+    virtual aliases).
+
+    VA IPs are only included when:
+      - The VA instance has a non-empty management IP.
+      - The product_category matches the requested device type.
+
+    Returns an empty list on any error so as not to break the caller.
+    """
+    import json
+    import logging
+    from cm.lib.postgres_db import DB
+    from cm.lib.communication import call_restapi
+
+    logger = logging.getLogger('hive.debug')
+
+    # Determine which product_category values we are looking for
+    targets = _VA_TYPE_TO_PRODUCT_CATEGORY.get(device_type.lower(), [])
+    if not targets:
+        logger.info("[VA-TELEGRAF] No VA mapping found for device type '%s', skipping." % device_type)
+        return []
+
+    va_agents = []
+
+    try:
+        db = DB.get_connected_db()
+        rows = db.fetchall(
+            "SELECT ip, restapi_port, restapi_username, restapi_password FROM host"
+        )
+        db.close()
+    except Exception as e:
+        logger.error("[VA-TELEGRAF] DB error fetching hosts: %s" % str(e))
+        return []
+
+    if not rows:
+        logger.info("[VA-TELEGRAF] No AVX hosts in DB, skipping VA agent collection.")
+        return []
+
+    for row in rows:
+        host_ip, restapi_port, restapi_username, restapi_password = row
+        try:
+            resp = call_restapi(
+                'GET',
+                '/aest/avx/va/instance/VAInstance/_get_list_data'
+                '?key=%5B%22tgt_name%22,%22type%22,%22va_name%22,%22ip%22,%22product_category%22,%22va_size%22,%22status%22,%22va_vncport%22,%22product_name%22,%22vendor%22,%22boottime%22,%22type%22%5D',
+                '',
+                host_ip,
+                restapi_port,
+                restapi_username,
+                restapi_password,
+                'https'
+            )
+            if not resp or resp.get('status') != 200:
+                logger.warning(
+                    "[VA-TELEGRAF] Non-200 response from host %s: %s"
+                    % (host_ip, resp.get('status') if resp else 'no response')
+                )
+                continue
+
+            body = json.loads(resp['body'])
+            logger.info("[VA-TELEGRAF] RAW API RESPONSE FROM HOST %s: %s" % (host_ip, str(body)))
+            
+            if isinstance(body, dict):
+                instances = body.get('VAInstance', [])
+            elif isinstance(body, list):
+                instances = body
+            else:
+                instances = []
+                
+            for inst in instances:
+                if not isinstance(inst, dict):
+                    continue
+                mgmt_ip = inst.get('ip', '').strip()
+                product_cat = inst.get('product_category', '').strip().lower()
+                product_name = inst.get('product_name', '').strip().lower()
+                
+                if not mgmt_ip:
+                    continue
+                
+                # Some ArrayOS VAs use generalized categories like 'adc' instead of 'apv'
+                # Map specific generalized categories to their respective targets
+                normalized_cat = product_cat
+                if product_cat == 'adc':
+                    normalized_cat = 'vapv'
+                elif product_cat in ('waf', 'firewall'):
+                    normalized_cat = 'vasf'
+                elif product_cat in ('sslvpn', 'vpn'):
+                    normalized_cat = 'vxag'
+
+                if product_cat in targets or product_name in targets or normalized_cat in targets:
+                    agent_str = '"%s:161"' % mgmt_ip
+                    va_agents.append(agent_str)
+                    logger.info(
+                        "[VA-TELEGRAF] Added VA agent %s (cat=%s, name=%s) from host %s"
+                        % (mgmt_ip, product_cat, product_name, host_ip)
+                    )
+        except Exception as e:
+            logger.warning("[VA-TELEGRAF] Failed to query host %s: %s" % (host_ip, str(e)))
 
+    return va_agents
 
+
 class IPPools(ANModel):
     # index = IntegerField(hidden=True, primary_key=True)
     asso = AssoField2(verbose_name=_('Device'), tgt='device_mgmt.device.Device.asso_ip_pool',
@@ -729,12 +852,29 @@
             import os
             import re
             import json
+            import fcntl
+            from cm.models.device_mgmt.device import _get_va_mgmt_ips_for_type
+            from cm.lib.postgres_db import DB
             
             logger = logging.getLogger('hive.debug')
             logger.info("[TELEGRAF] Update trigger for %s" % device_type)
             try:
+                normalized_type = device_type.lower()
+                if normalized_type in ['vapv', 'apv']:
+                    base_filename = 'apv.toml'
+                    db_types = "('apv', 'vapv')"
+                elif normalized_type in ['ag', 'vxag', 'vag']:
+                    base_filename = 'ag.toml'
+                    db_types = "('ag', 'vxag', 'vag')"
+                elif normalized_type in ['asf', 'vasf']:
+                    base_filename = 'asf.toml'
+                    db_types = "('asf', 'vasf')"
+                else:
+                    base_filename = '%s.toml' % normalized_type
+                    db_types = "('%s')" % normalized_type
+
                 db = DB.get_connected_db()
-                select_sql = "SELECT ip_address, snmp_general FROM device WHERE type='%s'" % device_type
+                select_sql = "SELECT ip_address, snmp_general FROM device WHERE lower(type) IN %s" % db_types
                 rows = db.fetchall(select_sql)
                 db.close()
 
@@ -745,55 +885,69 @@
                         try:
                             snmp_info = json.loads(row[1]) if row[1] else {}
                             if snmp_info.get('snmp_enable'):
-                                agents.append('"%s:161"' % ip)
+                                agents.append('"' + ip + ':161"')
                         except Exception as e:
                             logger.error("[TELEGRAF] JSON error for %s: %s" % (ip, str(e)))
 
-                # Form the new agents array string
-                agents_str = 'agents = [%s]' % ', '.join(agents)
-                logger.info("[TELEGRAF] %d agents found. New string: %s" % (len(agents), agents_str))
+                # Also include matching VA instance management IPs from AVX hosts
+                va_ips = _get_va_mgmt_ips_for_type(device_type)
+                if va_ips:
+                    logger.info("[TELEGRAF] Adding %d VA agent(s) for type %s" % (len(va_ips), device_type))
+                    agents.extend(va_ips)
 
                 telegraf_dir = '/opt/amp/telegraf.d'
-                
-                # Normalize device type to match available .toml config files
-                # (vAPV/APV -> apv.toml, vxAG/AG -> ag.toml, vASF/ASF -> asf.toml)
-                normalized_type = device_type.lower()
-                if normalized_type in ['vapv', 'apv']:
-                    base_filename = 'apv.toml'
-                elif normalized_type in ['ag', 'vxag', 'vag']:
-                    base_filename = 'ag.toml'
-                elif normalized_type in ['asf', 'vasf']:
-                    base_filename = 'asf.toml'
-                else:
-                    base_filename = '%s.toml' % normalized_type
-
                 toml_file_path = os.path.join(telegraf_dir, base_filename)
 
                 if os.path.exists(toml_file_path):
-                    with open(toml_file_path, 'r') as f:
-                        file_data = f.read()
-
-                    # Ultra-robust regex: matches 'agents = [...]' even across multiples
-                    # Handles leading whitespace, multi-line brackets, and hidden chars
-                    pattern = r'(?s)agents\s*=\s*\[[^\]]*\]'
-                    
-                    found_matches = re.findall(pattern, file_data)
-                    logger.info("[TELEGRAF] Found %d matching agents blocks in %s" % (len(found_matches), toml_file_path))
-
-                    new_file_data = re.sub(pattern, agents_str, file_data)
+                    # Use exclusive lock to prevent race with sync_va_telegraf_all_types
+                    with open(toml_file_path, 'r+') as f:
+                        fcntl.flock(f, fcntl.LOCK_EX)
+                        try:
+                            file_data = f.read()
 
-                    if new_file_data != file_data:
-                        logger.info("[TELEGRAF] Writing changes to %s" % toml_file_path)
-                        with open(toml_file_path, 'w') as f:
-                            f.write(new_file_data)
-                            f.flush()
-                            os.fsync(f.fileno())
-                        
-                        from hive.services.utils import perform_observability_services_restart
-                        perform_observability_services_restart('telegraf')
-                        logger.info("[TELEGRAF] Restart triggered.")
-                    else:
-                        logger.info("[TELEGRAF] File content is already up-to-date.")
+                            existing_agents = []
+                            for match in re.findall(r'(?s)agents\s*=\s*\[([^\]]*)\]', file_data):
+                                for item in match.split(','):
+                                    item = item.strip().strip('"').strip("'")
+                                    if item:
+                                        existing_agents.append('"' + item + '"')
+
+                            for a in existing_agents:
+                                if a not in agents:
+                                    agents.append(a)
+
+                            # Deduplicate while preserving order
+                            seen = set()
+                            deduped = []
+                            for a in agents:
+                                if a not in seen:
+                                    seen.add(a)
+                                    deduped.append(a)
+
+                            agents_str = 'agents = [%s]' % ', '.join(deduped)
+                            logger.info("[TELEGRAF] %d agents found. New string: %s" % (len(deduped), agents_str))
+
+                            pattern = r'(?s)agents\s*=\s*\[[^\]]*\]'
+                            found_matches = re.findall(pattern, file_data)
+                            logger.info("[TELEGRAF] Found %d matching agents blocks in %s" % (len(found_matches), toml_file_path))
+
+                            new_file_data = re.sub(pattern, agents_str, file_data)
+
+                            if new_file_data != file_data:
+                                logger.info("[TELEGRAF] Writing changes to %s" % toml_file_path)
+                                f.seek(0)
+                                f.write(new_file_data)
+                                f.truncate()
+                                f.flush()
+                                os.fsync(f.fileno())
+
+                                from hive.services.utils import perform_observability_services_restart
+                                perform_observability_services_restart('telegraf')
+                                logger.info("[TELEGRAF] Restart triggered.")
+                            else:
+                                logger.info("[TELEGRAF] File content is already up-to-date.")
+                        finally:
+                            fcntl.flock(f, fcntl.LOCK_UN)
                 else:
                     logger.error("[TELEGRAF] Config file %s does not exist!" % toml_file_path)
             except Exception as e:
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/virtualization/__init__.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/virtualization/__init__.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/cm/models/virtualization/__init__.py	(working copy)
@@ -16,8 +16,124 @@
 from hive.exceptions import ModelQueryException
 
 __ = _
+
+
+# ---------------------------------------------------------------------------
+# VA Telegraf sync helper — called on host lifecycle events and periodically
+# ---------------------------------------------------------------------------
+
+def sync_va_telegraf_all_types():
+    """
+    Refresh Telegraf TOML agent lists for all VA-relevant device types.
+    Should be invoked whenever the AVX host inventory changes (add/remove host)
+    or on a periodic schedule to pick up VA power-state changes.
+    """
+    import logging
+    from cm.models.device_mgmt.device import _get_va_mgmt_ips_for_type
+
+    logger = logging.getLogger('hive.debug')
+    logger.info("[VA-TELEGRAF] Running full VA Telegraf sync for all types.")
+
+    import os
+    import re
+    import fcntl
+
+    telegraf_dir = '/opt/amp/telegraf.d'
+
+    # Each entry maps a canonical device_type key -> (toml_file, db_types_tuple)
+    # db_types_tuple must cover ALL type variants stored in the 'device' table
+    # that belong to the same TOML file.
+    type_to_toml = {
+        'apv': ('apv.toml', ("'apv'", "'vapv'")),
+        'ag':  ('ag.toml',  ("'ag'", "'vxag'", "'vag'")),
+        'asf': ('asf.toml', ("'asf'", "'vasf'")),
+    }
+
+    reload_needed = False
+
+    for device_type, (toml_file, db_types) in type_to_toml.items():
+        toml_path = os.path.join(telegraf_dir, toml_file)
+        if not os.path.exists(toml_path):
+            logger.warning("[VA-TELEGRAF] %s not found, skipping." % toml_path)
+            continue
+        try:
+            # Collect physical device IPs with SNMP enabled for ALL variants of this type
+            from cm.lib.postgres_db import DB
+            import json as _json
+            agents = []
+            db = DB.get_connected_db()
+            rows = db.fetchall(
+                "SELECT ip_address, snmp_general FROM device WHERE lower(type) IN (%s)" % ', '.join(db_types)
+            )
+            db.close()
+            for row in rows:
+                ip, snmp_raw = row
+                try:
+                    snmp_info = _json.loads(snmp_raw) if snmp_raw else {}
+                    if snmp_info.get('snmp_enable'):
+                        agents.append('"' + ip + ':161"')
+                except Exception:
+                    pass
+
+            # Also collect VA management IPs for this type
+            va_ips = _get_va_mgmt_ips_for_type(device_type)
+            agents.extend(va_ips)
+
+            # Use an exclusive lock on the TOML file to prevent race conditions
+            # with _update_telegraf_toml which may run concurrently on device events.
+            with open(toml_path, 'r+') as f:
+                fcntl.flock(f, fcntl.LOCK_EX)
+                try:
+                    file_data = f.read()
 
+                    existing_agents = []
+                    for match in re.findall(r'(?s)agents\s*=\s*\[([^\]]*)\]', file_data):
+                        for item in match.split(','):
+                            item = item.strip().strip('"').strip("'")
+                            if item:
+                                existing_agents.append('"' + item + '"')
 
+                    for a in existing_agents:
+                        if a not in agents:
+                            agents.append(a)
+
+                    # Deduplicate
+                    seen = set()
+                    deduped_agents = []
+                    for a in agents:
+                        if a not in seen:
+                            seen.add(a)
+                            deduped_agents.append(a)
+
+                    agents_str = 'agents = [%s]' % ', '.join(deduped_agents)
+                    logger.info("[VA-TELEGRAF] %s -> %d agent(s)" % (toml_file, len(deduped_agents)))
+
+                    pattern = r'(?s)agents\s*=\s*\[[^\]]*\]'
+                    new_file_data = re.sub(pattern, agents_str, file_data)
+
+                    if new_file_data != file_data:
+                        f.seek(0)
+                        f.write(new_file_data)
+                        f.truncate()
+                        f.flush()
+                        os.fsync(f.fileno())
+                        reload_needed = True
+                        logger.info("[VA-TELEGRAF] Updated %s" % toml_path)
+                finally:
+                    fcntl.flock(f, fcntl.LOCK_UN)
+
+        except Exception as e:
+            logger.error("[VA-TELEGRAF] Failed to update %s: %s" % (toml_file, str(e)))
+
+    if reload_needed:
+        try:
+            from hive.services.utils import perform_observability_services_restart
+            perform_observability_services_restart('telegraf')
+            logger.info("[VA-TELEGRAF] Telegraf reload triggered.")
+        except Exception as e:
+            logger.error("[VA-TELEGRAF] Telegraf reload failed: %s" % str(e))
+
+
 class Host(ANModel):
     default = FieldGroup(verbose_name=_('Base Settings'), level=BASIC, fields={
         'id': CharField(verbose_name='ID', primary_key=True, configurable=False, optional=True, editable=False,
@@ -206,6 +322,12 @@
             save_sql = "INSERT INTO host(id, name, ip, restapi_port, restapi_username, restapi_password, connection, version, vm_number, cpu_usage, mem_usage, disk_usage) Values " + "('%(id)s', '%(name)s', '%(ip)s', '%(restapi_port)s', '%(restapi_username)s', '%(restapi_password)s', 'Connected', '%(version)s', %(vm_number)s, %(cpu_usage)s, %(mem_usage)s, %(disk_usage)s)" % data
             db.execute_sql(save_sql)
             db.close()
+
+            # Refresh VA Telegraf agent lists asynchronously
+            import threading
+            t = threading.Thread(target=sync_va_telegraf_all_types, daemon=True)
+            t.start()
+
             return
 
         def _update_restapi_account(self, instance):
@@ -223,6 +345,12 @@
                 delete_sql = "DELETE FROM host WHERE id = " + "'%s'" % each_pk['id']
                 db.execute_sql(delete_sql)
             db.close()
+
+            # Refresh VA Telegraf agent lists asynchronously
+            import threading
+            t = threading.Thread(target=sync_va_telegraf_all_types, daemon=True)
+            t.start()
+
             return
 
         def _perform_RefreshVersion(self, options):
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-details/avx-va-instance-details.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-details/avx-va-instance-details.ts	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-details/avx-va-instance-details.ts	(working copy)
@@ -34,7 +34,7 @@
     if (this.instanceStatus) {
       this.tabDefinitions.push(
         { label: 'Configuration', component: AvxVaInstanceConfiguration, paramName: 'configuration' },
-        { label: 'Logs', component: AvxVaInstanceLogs, paramName: 'logs' },
+        // { label: 'Logs', component: AvxVaInstanceLogs, paramName: 'logs' },
       )
     }
   }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.html	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.html	(working copy)
@@ -1,6 +1,7 @@
-@if (loader2 && !isSNMPEnabled) {
+@if (loader2 && (!managementIpStr || !isSNMPEnabled)) {
 <br>
-<p class="blue-icon center"><b>Note:</b> To view graph data, please enable the SNMP service first.</p>
+<p class="blue-icon center"><b>Note:</b> If graph data is not displaying, please ensure a Management IP is assigned and
+  SNMP is enabled directly on the VA instance.</p>
 }
 @if (loader1 && !isInstanceActive) {
 <br>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.ts	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instance-overview/avx-va-instance-overview.ts	(working copy)
@@ -1,9 +1,10 @@
-import { Component, inject, OnInit, ViewChild } from '@angular/core';
+import { Component, inject, OnInit, ViewChild, OnDestroy } from '@angular/core';
 import { SharedModule } from '../../../shared/shared-module';
 import { NgxEchartsModule } from 'ngx-echarts';
 import { ActivatedRoute } from '@angular/router';
 import { DeviceFacade } from '../../../services/facades/device.facade';
 import { CoreUiFacade } from '../../../services/facades/core-ui.facade';
+import { forkJoin } from 'rxjs';
 import { take } from 'rxjs/operators';
 import { MatTableDataSource } from '@angular/material/table';
 import { MatPaginator } from '@angular/material/paginator';
@@ -22,12 +23,15 @@
   templateUrl: './avx-va-instance-overview.html',
   styleUrl: './avx-va-instance-overview.scss'
 })
-export class AvxVaInstanceOverview implements OnInit {
+export class AvxVaInstanceOverview implements OnInit, OnDestroy {
 
   deviceName: any = '';
   instanceName: any = '';
   deviceId: any = '';
   instanceStatus: any = false;
+  managementIpStr: string = '';
+  productCategoryStr: string = 'vapv';
+  private statsRefreshInterval: any = null;
 
   private _route = inject(ActivatedRoute);
   private deviceFacade = inject(DeviceFacade);
@@ -43,13 +47,21 @@
     setTimeout(() => {
       this.getAVXVAInstancePortMapping();
       this.getAVXVAInstancePlatformResource();
-      if (this.instanceStatus) {
+    });
+    this.statsRefreshInterval = setInterval(() => {
+      if (this.instanceStatus && this.managementIpStr) {
         this.getDeviceStatistics();
       }
-    })
+    }, 10000);
   }
 
+  ngOnDestroy() {
+    if (this.statsRefreshInterval) {
+      clearInterval(this.statsRefreshInterval);
+    }
+  }
 
+
   vaPortMapping: any = [];
   portDataSource: MatTableDataSource<any> = new MatTableDataSource();
   portDataColumns: string[] = ['serial', 'resource', 'mac', 'portId', 'vlanTag'];
@@ -81,7 +93,15 @@
   getAVXVAInstancePlatformResource() {
     this.deviceFacade.getAVXVAInstancePlatformResource(this.deviceId, this.instanceName).pipe(take(1)).subscribe({
       next: (result: any) => {
-        this.managementIp = result?.ip;
+        this.managementIpStr = result?.ip;
+        const pNameLower = (result?.product_name || '').toLowerCase();
+        if (pNameLower.includes('vxag') || (pNameLower.includes('ag') && !pNameLower.includes('asf'))) {
+          this.productCategoryStr = 'ag';
+        } else if (pNameLower.includes('vasf') || pNameLower.includes('asf')) {
+          this.productCategoryStr = 'asf';
+        } else {
+          this.productCategoryStr = 'vapv'; // default: vAPV and unrecognised types
+        }
         this.managementIp = {
           'IP Address': result?.ip
         }
@@ -117,9 +137,10 @@
     let payload: any = `post_data=%7B%22va_name%22%3A%22${this.instanceName}%22%2C%22va_product_name%22%3A%22${productName}%22%7D`;
     this.deviceFacade.getAVXVAInstanceBasicConfig(this.deviceId, payload).pipe(take(1)).subscribe({
       next: (result: any) => {
-        if (result && result.length > 1) {
-          if (result[1]?.show_version) {
-            let show_version = result[1]?.show_version;
+        if (result && Array.isArray(result)) {
+          const versionObj = result.find((item: any) => item && item.show_version);
+          if (versionObj) {
+            let show_version = versionObj.show_version;
             let selectedValues: any = {
               'Model': show_version?.model,
               'Software Build Info': show_version?.build,
@@ -138,11 +159,23 @@
             }
             this.configDataSource = this._utils.processConfigData(selectedValues);
           }
-          if (result[1]?.show_snmp) {
-            this.isSNMPEnabled = result[1]?.show_snmp?.enable_snmp;
+
+          const snmpObj = result.find((item: any) => item && item.show_snmp);
+          console.log(result);
+          if (snmpObj) {
+            const snmpVal = snmpObj.show_snmp?.enable_snmp;
+            // Handle both boolean and string truthy values like 'enable', 'on', '1', 'true'
+            const isTruthy = snmpVal === true || ['enable', 'on', '1', 'true', 'yes'].includes(String(snmpVal).toLowerCase());
+            this.isSNMPEnabled = isTruthy;
           }
         }
         this.loader2 = true;
+        // Trigger initial chart load. We no longer strictly require this.isSNMPEnabled 
+        // to be true here, because the AVX API often returns empty/default values 
+        // for VA config, leading to false negatives. If data exists, it will render.
+        if (this.instanceStatus && this.managementIpStr) {
+          this.getDeviceStatistics();
+        }
       },
       error: error => {
         this.ui.notifyError(`Error: ${error?.message}`);
@@ -171,17 +204,95 @@
   hasData4: boolean = false;
 
   getDeviceStatistics() {
-    // ToDo: Integrate with APIs once ready.
-    this.updateStatisticsCharts({});
+    if (!this.managementIpStr) {
+      this.updateStatisticsCharts({});
+      return;
+    }
+
+    const from = 'now-15m';
+    const to = 'now';
+
+    const throughputPayload = {
+      metric_name: 'network_throughput',
+      agent_host: this.managementIpStr,
+      device_type: this.productCategoryStr,
+      interval: '10s',
+      from: from,
+      to: to
+    };
+
+    const cpuPayload = {
+      metric_name: 'cpu_usage',
+      agent_host: this.managementIpStr,
+      device_type: this.productCategoryStr,
+      interval: '10s',
+      from: from,
+      to: to
+    };
+
+    const memoryPayload = {
+      metric_name: 'memory_usage',
+      agent_host: this.managementIpStr,
+      device_type: this.productCategoryStr,
+      interval: '10s',
+      from: from,
+      to: to
+    };
+
+    const diskPayload = {
+      metric_name: 'disk_usage',
+      agent_host: this.managementIpStr,
+      device_type: this.productCategoryStr,
+      interval: '10s',
+      from: from,
+      to: to
+    };
+
+    forkJoin({
+      throughput: this.deviceFacade.getDeviceMonitoringMetrics(throughputPayload).pipe(take(1)),
+      cpu: this.deviceFacade.getDeviceMonitoringMetrics(cpuPayload).pipe(take(1)),
+      memory: this.deviceFacade.getDeviceMonitoringMetrics(memoryPayload).pipe(take(1)),
+      disk: this.deviceFacade.getDeviceMonitoringMetrics(diskPayload).pipe(take(1))
+    }).subscribe({
+      next: (results: any) => {
+        let _data: any = {};
+        try {
+          const tpRes = results?.throughput?.data || [];
+          const factor: any = 1024 * 1024;
+          const sentData = tpRes.map((d: any) => [new Date(d.ts).getTime(), d.sent / factor]);
+          const receivedData = tpRes.map((d: any) => [new Date(d.ts).getTime(), d.received / factor]);
+          _data.throughput = { sent: sentData, received: receivedData };
+
+          const cpuRes = results?.cpu?.data || [];
+          const cpuData = cpuRes.map((d: any) => [new Date(d.ts).getTime(), d.cpu]);
+          _data.cpu = cpuData;
+
+          const memRes = results?.memory?.data || [];
+          const memData = memRes.map((d: any) => [new Date(d.ts).getTime(), d.memory_usage]);
+          _data.memory = memData;
+
+          const diskRes = results?.disk?.data || [];
+          const diskData = diskRes.map((d: any) => [new Date(d.ts).getTime(), d.disk_usage]);
+          _data.disk = diskData;
+        } catch (e) {
+          console.error('Error parsing device statistics', e);
+        }
+        this.updateStatisticsCharts(_data);
+      },
+      error: (error: any) => {
+        console.error(error);
+        this.updateStatisticsCharts({});
+      }
+    });
   }
 
   updateStatisticsCharts(_data: any) {
 
-    let inbound_traffic: any = [];
-    let outbound_traffic: any = [];
-    let cpu: any = [];
-    let memory: any = [];
-    let disk: any = [];
+    let inbound_traffic: any = _data?.throughput?.received || [];
+    let outbound_traffic: any = _data?.throughput?.sent || [];
+    let cpu: any = _data?.cpu || [];
+    let memory: any = _data?.memory || [];
+    let disk: any = _data?.disk || [];
     this.hasData1 = inbound_traffic.length > 0 || outbound_traffic.length > 0;
     this.chartOption1 = this._chartOptions.historicalThroughputChartOptions(inbound_traffic, outbound_traffic);
 
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instances/avx-va-instances.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instances/avx-va-instances.html	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/avx-va-instances/avx-va-instances.html	(working copy)
@@ -1,11 +1,4 @@
 <div class="avx-page-container">
-  <!--  Action Bar (Placeholder for future use or if Create button is re-enabled) -->
-  <!-- <div class="action-bar">
-       <button mat-raised-button color="primary" (click)="createVAInstance()" matTooltip="Create VA Instance">
-         <fa-icon [icon]="['fas', 'plus']"></fa-icon> Create
-       </button>
-  </div> -->
-
   <mat-card class="page-card-1" appearance="outlined">
     <mat-card-header>
       <mat-card-title>VA Instances</mat-card-title>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/resource-monitoring-device-details/resource-monitoring-device-details.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/resource-monitoring-device-details/resource-monitoring-device-details.ts	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/resource-monitoring-device-details/resource-monitoring-device-details.ts	(working copy)
@@ -202,9 +202,7 @@
         this.deviceFacade.getDeviceMonitoringMetrics(payload).pipe(take(1)).subscribe({
             next: (result: any) => {
                 if (payload?.metric_name === 'cpu_usage') {
-                    console.log(result?.data);
                     const cpu_data_formatted: any = result?.data.map((d: any) => [d.ts, d.cpu]);
-                    console.log(cpu_data_formatted);
                     this.cpuChartOptions = cpu_data_formatted.length ? this.getAreaChartOptions('CPU Usage', cpu_data_formatted, '#2196f3', '#e3f2fd', '%') : this.getNoDataChartOptions();
                 } else if (payload?.metric_name === 'memory_usage') {
                     const memory_data_formatted: any = result?.data.map((d: any) => [d.ts, d.memory_usage]);
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/controller/device_metrics.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/controller/device_metrics.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/controller/device_metrics.py	(working copy)
@@ -3,9 +3,13 @@
 from cm.lib.libbasic_operation import oper_log
 from hive.custom_exceptions import generic_exception as ge
 import json
+import logging
+import traceback
 from hive.utils import andebug
 
+logger = logging.getLogger('hive.debug')
 
+
 def handle_device_metrics_req(request, path=None):
     try:
         if request.method in ['GET', 'POST']:
@@ -29,7 +33,7 @@
     try:
         req_body = json.loads(request.body)
         agent_host = req_body.get("agent_host", None)
-        device_type = req_body.get("device_type", "vapv")  # <-- NEW (default to an)
+        device_type = req_body.get("device_type", "an")
         interval = req_body.get("interval", "1m")
         time_from = req_body.get("from", None)
         time_to = req_body.get("to", None)
@@ -42,6 +46,14 @@
         return JsonResponse(json_response)
 
     except Exception as e:
+        tb = traceback.format_exc()
+        logger.error("[device_metrics] 500 error for metric='%s' agent='%s' type='%s': %s\n%s",
+                     req_body.get("metric_name") if 'req_body' in dir() else "?",
+                     req_body.get("agent_host") if 'req_body' in dir() else "?",
+                     req_body.get("device_type") if 'req_body' in dir() else "?",
+                     str(e), tb)
+        oper_log('error', 'system', '[device_metrics] Internal error: {}'.format(str(e)))
         return JsonResponse({
-            "error": str(e)
+            "error": str(e),
+            "traceback": tb
         }, status=500)
\ No newline at end of file
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/device_metric_queries.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/device_metric_queries.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/device_metric_queries.py	(working copy)
@@ -3,32 +3,34 @@
 from hive.db.utils import cast_fields
 
 
+def _forward_fill(rows, fields):
+    """
+    Last-Observation-Carried-Forward for gapfill result rows.
+    For each field in `fields`, replace None/0 with the last non-zero value seen.
+    rows: list of dicts (ordered by time ascending)
+    fields: list of column names to forward-fill
+    """
+    last = {f: None for f in fields}
+    for row in rows:
+        for f in fields:
+            val = row.get(f)
+            if val is not None and val != 0:
+                last[f] = val
+            elif last[f] is not None:
+                row[f] = last[f]
+    return rows
+
+
 class DeviceMetrics:
 
     def __init__(self, device_type):
         # Normalize and map device_type to the corresponding table
         device_type = (device_type or "an").lower()
-        table_map = {
-            "apv": "an_device_metrics",
-            "vapv": "an_device_metrics",
-            "ag": "ag_device_metrics",
-            "vxag": "ag_device_metrics",
-            "asf": "asf_device_metrics",
-            "vasf": "asf_device_metrics",
-            "avx": "an_device_metrics",
-            "vavx": "an_device_metrics"
-        }
-        storage_map = {
-            "apv": "an_device_storage",
-            "vapv": "an_device_storage",
-            "asf": "asf_device_storage",
-            "vasf": "asf_device_storage",
-            "avx": "an_device_storage",
-            "vavx": "an_device_storage"
-        }
-        self.metrics_table = table_map.get(device_type, "an_device_metrics")
-        self.storage_table = storage_map.get(device_type, None)  # shared storage table
-        self.has_memory_metrics = self.metrics_table != "ag_device_metrics"
+        self.metrics_table = "an_device_metrics"
+        self.storage_table = "an_device_storage"
+        # AG/vxAG DOES have mem_usage via .7564.30.5.0 (confirmed in SNMP output)
+        # Only exclude if the device genuinely has no memory metric
+        self.has_memory_metrics = True
 
     def get_device_stats(self, time_from, time_to):
         """
@@ -130,17 +132,18 @@
         req = {"query": query}
         response = DBClient.execute_query(req)
 
-        field_casts = {
-            "cpu": int
-        }
+        field_casts = {"cpu": int}
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["cpu"])
         return response
 
     def get_mem_usage_metrics(self, agent_host, interval, time_from, time_to):
         """
-        Memory metrics over time buckets per agent_host
+        Memory metrics over time buckets per agent_host.
+        AG/vxAG/vAG devices do not expose mem_usage via SNMP — return empty gracefully.
         """
+        if not self.has_memory_metrics:
+            return {"data": []}
 
         query = f"""
             SELECT
@@ -163,21 +166,33 @@
             "network_mem_usage": int
         }
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["memory_usage", "network_mem_usage"])
         return response
 
     def get_disk_usage_metrics(self, agent_host, interval, time_from, time_to):
         """
-        Disk usage percentage over time buckets per agent_host
+        Disk usage % per agent_host over time buckets.
+        Priority:
+          1. an_device_storage (hrStorage SNMP — ASF/vASF Linux appliances)
+          2. disk_used / disk_total ratio (AG/vxAG ArrayOS MIB: .7564.25.2.0 / .7564.25.4.0)
+          3. disk_pct direct % scalar (APV/vAPV ArrayOS MIB: .7564.3.8.0)
         """
-        if not self.storage_table:
-            return {"data": []}
+        def _has_real_data(data):
+            return isinstance(data, list) and any(
+                isinstance(r, dict) and r.get("disk_usage", 0) not in (0, None)
+                for r in data
+            )
 
+        # --- Primary: hrStorage table ---
         query = f"""
             SELECT
                 time_bucket_gapfill('{interval}', time, {time_from}, {time_to}) AS ts,
                 agent_host,
-                COALESCE((AVG(used) * 100.0) / NULLIF(AVG(size), 0), 0) AS disk_usage
+                COALESCE(
+                    (SUM(CASE WHEN size > 0 THEN used  ELSE 0 END) * 100.0)
+                    / NULLIF(SUM(CASE WHEN size > 0 THEN size ELSE 0 END), 0),
+                    0
+                )::int AS disk_usage
             FROM {self.storage_table}
             WHERE time >= {time_from}
               AND time <= {time_to}
@@ -185,19 +200,52 @@
             GROUP BY ts, agent_host
             ORDER BY ts;
         """
+        response = DBClient.execute_query({"query": query, "params": [agent_host]})
 
-        # Pass parameters safely to avoid SQL injection
-        params = [agent_host]
+        if not _has_real_data(response.get("data", [])):
+            # --- Fallback 1: disk_used / disk_total ratio (AG/vxAG) ---
+            fb1_query = f"""
+                SELECT
+                    time_bucket_gapfill('{interval}', time, {time_from}, {time_to}) AS ts,
+                    agent_host,
+                    COALESCE(
+                        (AVG(disk_used) * 100.0) / NULLIF(AVG(disk_total), 0),
+                        0
+                    )::int AS disk_usage
+                FROM {self.metrics_table}
+                WHERE time >= {time_from}
+                  AND time <= {time_to}
+                  AND agent_host = '{agent_host}'
+                  AND disk_total IS NOT NULL AND disk_total > 0
+                GROUP BY ts, agent_host
+                ORDER BY ts;
+            """
+            fb1 = DBClient.execute_query({"query": fb1_query})
+            if _has_real_data(fb1.get("data", [])):
+                response = fb1
 
-        req = {"query": query, "params": params}
-        response = DBClient.execute_query(req)
-
-        # Cast disk_usage to int
-        field_casts = {
-            "disk_usage": int
-        }
-        response["data"] = cast_fields(response["data"], field_casts)
+        if not _has_real_data(response.get("data", [])):
+            # --- Fallback 2: disk_pct direct % scalar (APV/vAPV/ASF: .7564.3.8.0) ---
+            fb2_query = f"""
+                SELECT
+                    time_bucket_gapfill('{interval}', time, {time_from}, {time_to}) AS ts,
+                    agent_host,
+                    COALESCE(AVG(disk_pct)::int, 0) AS disk_usage
+                FROM {self.metrics_table}
+                WHERE time >= {time_from}
+                  AND time <= {time_to}
+                  AND agent_host = '{agent_host}'
+                  AND disk_pct IS NOT NULL
+                GROUP BY ts, agent_host
+                ORDER BY ts;
+            """
+            fb2 = DBClient.execute_query({"query": fb2_query})
+            if isinstance(fb2.get("data"), list) and fb2["data"]:
+                response = fb2
 
+        field_casts = {"disk_usage": int}
+        response["data"] = cast_fields(response.get("data", []), field_casts)
+        response["data"] = _forward_fill(response["data"] or [], ["disk_usage"])
         return response
 
     def get_network_throughput(self, agent_host, interval, time_from, time_to):
@@ -267,12 +315,9 @@
         """
         req = {"query": query}
         response = DBClient.execute_query(req)
-
-        field_casts = {
-            "ssl_connection": int
-        }
+        field_casts = {"ssl_connection": int}
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["ssl_connection"])
         return response
 
     def get_connections(self, agent_host, interval, time_from, time_to):
@@ -299,7 +344,7 @@
             "connection": float
         }
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["connection"])
         return response
 
     def get_requests(self, agent_host, interval, time_from, time_to):
@@ -326,13 +371,14 @@
             "requests": float
         }
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["requests"])
         return response
 
     @staticmethod
     def get_ssl_core_utilization(agent_host, interval, time_from, time_to):
         """
-        SSL AE and SE core utilization (mean) over time buckets per agent_host
+        SSL AE and SE core utilization (mean) over time buckets per agent_host.
+        Only APV/vAPV devices have SSL acceleration hardware — others return empty.
         """
 
         query = f"""
@@ -356,7 +402,7 @@
             "ssl_se_core": float
         }
         response["data"] = cast_fields(response["data"], field_casts)
-
+        response["data"] = _forward_fill(response["data"] or [], ["ssl_ae_core", "ssl_se_core"])
         return response
 
     @staticmethod
@@ -408,6 +454,7 @@
                     FROM {self.storage_table}
                     WHERE agent_host = '{agent_host}'
                       AND time BETWEEN {time_from} AND {time_to}
+                      AND size > 0
                     GROUP BY agent_host
                 )
             """
@@ -476,6 +523,7 @@
                 FROM {self.storage_table}
                 WHERE agent_host = '{agent_host}'
                   AND time BETWEEN {time_from} AND {time_to}
+                  AND size > 0
                 GROUP BY ts, agent_host
             )
             """
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/utils.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/utils.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/db/utils.py	(working copy)
@@ -4,6 +4,11 @@
 def cast_fields(rows, field_casts):
     """ Cast fields to appropriate cast specified in the fields_casts"""
 
+    # Guard: if DB returned an error, data will be a string, not a list.
+    # Return it unchanged so the caller can handle it properly.
+    if not isinstance(rows, list):
+        return rows
+
     for row in rows:
         for field, cast_type in field_casts.items():
             try:
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/services/utils.py
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/services/utils.py	(revision 2968)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/hive/services/utils.py	(working copy)
@@ -271,6 +271,10 @@
 
 
 def construct_json_response(metrics):
+    # If the DB layer returned an error, surface it clearly instead of
+    # passing a string as "data" which crashes cast_fields downstream.
+    if metrics.get("status", 200) != 200:
+        raise Exception("DB query failed: %s" % metrics.get("data", "Unknown error"))
     json_response = {
         "data": metrics["data"]
     }
