Index: /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/lib/task_scheduler.py
===================================================================
--- /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/lib/task_scheduler.py	(revision 2973)
+++ /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/lib/task_scheduler.py	(working copy)
@@ -2122,6 +2122,93 @@
 
 def set_device_snmp(device_id, snmp_general):
     db = DB.get_connected_db()
+    
+    import json, os
+    try:
+        new_snmp_general = json.loads(snmp_general)
+        
+        existing_res = db.fetchall("SELECT ip_address, snmp_general FROM device WHERE id='%s'" % device_id)
+        if existing_res and existing_res[0]:
+            device_ip = existing_res[0][0]
+            existing_snmp_raw = existing_res[0][1]
+            
+            real_v3user = None
+            json_config = None
+            try:
+                json_path = "/var/lib/snmp/snmpv3_devices.json"
+                if os.path.exists(json_path):
+                    target_user = None
+                    if "v3user" in new_snmp_general:
+                        parts = new_snmp_general["v3user"].split()
+                        if parts:
+                            target_user = parts[0]
+
+                    with open(json_path, 'r') as f:
+                        snmpv3_list = json.load(f)
+                    for d in snmpv3_list:
+                        if d.get("device_ip") == device_ip and d.get("enabled", True):
+                            usr = d.get('username', '')
+                            # Ensure we only pick the config for the same SNMPv3 user
+                            if target_user and usr != target_user:
+                                continue
+                            json_config = d
+                            # No break - last matching enabled entry wins.
+            except Exception:
+                pass
+
+            if json_config:
+                usr = json_config.get('username', '')
+                auth = json_config.get('auth_pass', '')
+                slvl = json_config.get('sec_level', 'authPriv')
+                priv = json_config.get('priv_pass', '')
+                
+                new_snmp_general["auth_protocol"] = json_config.get("auth_protocol", "MD5")
+                new_snmp_general["priv_protocol"] = json_config.get("priv_protocol", "AES")
+                new_snmp_general["community"] = json_config.get("community", new_snmp_general.get("community", "public"))
+
+                if "********" not in auth:
+                    if slvl == 'authPriv' and "********" not in priv:
+                        new_snmp_general["v3user"] = "%s %s %s %s" % (usr, auth, slvl, priv)
+                    else:
+                        new_snmp_general["v3user"] = "%s %s %s" % (usr, auth, slvl)
+                    snmp_general = json.dumps(new_snmp_general)
+            elif "v3user" in new_snmp_general and "********" in new_snmp_general["v3user"]:
+                if existing_snmp_raw:
+                    try:
+                        existing_snmp = json.loads(existing_snmp_raw)
+                        if "v3user" in existing_snmp and "********" not in existing_snmp["v3user"]:
+                            new_snmp_general["v3user"] = existing_snmp["v3user"]
+                    except Exception:
+                        pass
+                snmp_general = json.dumps(new_snmp_general)
+            else:
+                # No JSON override and no masked v3user.
+                # Preserve priv_protocol / auth_protocol from the existing DB
+                # record so that a periodic REST-API poll never silently clobbers
+                # them with the binary's DES default.
+                if existing_snmp_raw:
+                    try:
+                        existing_snmp = json.loads(existing_snmp_raw)
+                        for key in ("priv_protocol", "auth_protocol"):
+                            if key in existing_snmp and key not in new_snmp_general:
+                                new_snmp_general[key] = existing_snmp[key]
+                    except Exception:
+                        pass
+                snmp_general = json.dumps(new_snmp_general)
+    except Exception, e:
+        logger.error("Failed to preserve unmasked SNMPv3 credentials in task_scheduler: %s" % str(e))
+
+    # Widen snmp_general from varchar(256) to text before writing.
+    # varchar(256) silently fails on overflow (exception swallowed above),
+    # leaving the binary reading a stale DES value from the old row.
+    try:
+        db.execute_sql("ALTER TABLE device ALTER COLUMN snmp_general TYPE text")
+    except Exception:
+        try:
+            db.conn.rollback()
+        except Exception:
+            pass
+
     update_sql = "UPDATE device SET snmp_general='%s' WHERE id='%s'" % (snmp_general, device_id)
     db.execute_sql(update_sql)
     db.close()
Index: /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py
===================================================================
--- /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py	(revision 2973)
+++ /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/cm/models/device_mgmt/device/__init__.py	(working copy)
@@ -675,6 +675,66 @@
             db.close()
             mark_expire_all(get_model('cm', ['device_mgmt', 'device_group', 'DeviceGroup']))
             return
+
+        def _save_snmp_general(self, device_id, new_snmp_general):
+            try:
+                db = DB.get_connected_db()
+                existing_res = db.fetchall("SELECT ip_address, snmp_general FROM device WHERE id='%s'" % device_id)
+                if existing_res and existing_res[0]:
+                    device_ip = existing_res[0][0]
+                    existing_snmp_raw = existing_res[0][1]
+                    
+                    import json, os
+                    json_path = "/var/lib/snmp/snmpv3_devices.json"
+                    json_config = None
+                    try:
+                        if os.path.exists(json_path):
+                            target_user = None
+                            if "v3user" in new_snmp_general:
+                                parts = new_snmp_general["v3user"].split()
+                                if parts:
+                                    target_user = parts[0]
+
+                            with open(json_path, 'r') as f:
+                                snmpv3_list = json.load(f)
+                            for d in snmpv3_list:
+                                if d.get("device_ip") == device_ip and d.get("enabled", True):
+                                    usr = d.get('username', '')
+                                    # Ensure we only pick the config for the same SNMPv3 user
+                                    if target_user and usr != target_user:
+                                        continue
+                                    json_config = d
+                                    # No break - last matching enabled entry wins.
+                    except Exception:
+                        pass
+
+                    if json_config:
+                        usr = json_config.get('username', '')
+                        auth = json_config.get('auth_pass', '')
+                        slvl = json_config.get('sec_level', 'authPriv')
+                        priv = json_config.get('priv_pass', '')
+                        
+                        # Populate protocols and community from JSON to keep DB in sync
+                        new_snmp_general["auth_protocol"] = json_config.get("auth_protocol", "MD5")
+                        new_snmp_general["priv_protocol"] = json_config.get("priv_protocol", "AES")
+                        new_snmp_general["community"] = json_config.get("community", new_snmp_general.get("community", "public"))
+
+                        if "********" not in auth:
+                            if slvl == 'authPriv' and "********" not in priv:
+                                new_snmp_general["v3user"] = "%s %s %s %s" % (usr, auth, slvl, priv)
+                            else:
+                                new_snmp_general["v3user"] = "%s %s %s" % (usr, auth, slvl)
+                    elif "v3user" in new_snmp_general and "********" in new_snmp_general["v3user"]:
+                        if existing_snmp_raw:
+                            existing_snmp = json.loads(existing_snmp_raw)
+                            if "v3user" in existing_snmp and "********" not in existing_snmp["v3user"]:
+                                new_snmp_general["v3user"] = existing_snmp["v3user"]
+                
+                update_sql = "UPDATE device SET snmp_general='%s' WHERE id='%s'" % (json.dumps(new_snmp_general), device_id)
+                db.execute_sql(update_sql)
+                db.close()
+            except Exception, e:
+                raise ModelQueryException(CLICmdError(__('Failed to preserve unmasked SNMPv3 credentials: %s' % str(e))))
 
         def __update_snmp_general(self, device_id, device_info, cmd):
             each = device_info[0]
@@ -708,10 +768,7 @@
                             }
                             if result[6]:
                                 snmp_general["v3user"] = result[6]['v3user'].replace('\"', '')
-                            db = DB.get_connected_db()
-                            update_sql = "UPDATE device SET snmp_general='%s' WHERE id='%s'" %(json.dumps(snmp_general), device_id)
-                            db.execute_sql(update_sql)
-                            db.close()
+                            self._save_snmp_general(device_id, snmp_general)
                         except Exception, e:
                             raise ModelQueryException(CLICmdError(__('Update device<%s> snmp failed. %s' % (each['ip_address'], str(e)))))
                 else:
@@ -744,10 +801,7 @@
                                                     snmp_general['v3user'] = v3user
                                                 break
 
-                                        db = DB.get_connected_db()
-                                        update_sql = "UPDATE device SET snmp_general='%s' WHERE id='%s'" %(json.dumps(snmp_general), device_id)
-                                        db.execute_sql(update_sql)
-                                        db.close()
+                                        self._save_snmp_general(device_id, snmp_general)
                                 except Exception, e:
                                     raise ModelQueryException(CLICmdError(__('Update device<%s> snmp failed. %s' % (each['ip_address'], str(e)))))
                     elif each['type'].lower() in VPN_TYPE_LIST:
@@ -817,10 +871,7 @@
                                 except Exception, e:
                                     raise ModelQueryException(CLICmdError(__('Set device<%s> snmp v3_user failed. %s' % (each['name'], str(e)))))
 
-                        db = DB.get_connected_db()
-                        update_sql = "UPDATE device SET snmp_general='%s' WHERE id='%s'" %(json.dumps(snmp_general), device_id)
-                        db.execute_sql(update_sql)
-                        db.close()
+                        self._save_snmp_general(device_id, snmp_general)
             return
 
         def _perform_EnableMonitoring(self, options):
@@ -976,8 +1027,18 @@
 
             delete_sql = "DELETE FROM vsite_list WHERE device_name='%s'" % data['name']
             db.execute_sql(delete_sql)
-            db.close()
 
+            # remove all device template key-value by device ip
+            device_ip = data['ip'].values()[0]
+            delete_all_tmpkey(device_ip)
+
+            # cleanup snmpv3 configuration backup
+            try:
+                from hive.services.snmpv3_service import Snmpv3Service
+                Snmpv3Service().delete_snmpv3_device(device_ip)
+            except Exception:
+                pass
+
             #remove the vsite config file
             for item in result:
                 vsite_path = DEFAULT_VSITE_PATH + data['name'] + '-' + item['vs_name']
@@ -988,8 +1049,7 @@
             if os.path.exists(file_path):
                 os.remove(file_path)
 
-            # remove all device template key-value by device ip
-            delete_all_tmpkey(data['ip'].values()[0])
+            db.close()
 
             """
             for item in result:
Index: /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/controller/snmpv3_controller.py
===================================================================
--- /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/controller/snmpv3_controller.py	(revision 2973)
+++ /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/controller/snmpv3_controller.py	(working copy)
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 import json
 import subprocess
 from hive.util.utils import json_response
@@ -15,16 +16,15 @@
     """
     Main SNMPv3 handler for all SNMPv3-related routes.
     Supported endpoints:
-        POST   /snmpv3/config   → Configure or update SNMPv3 device
-        GET    /snmpv3/config   → Get SNMPv3 config(s)
-        DELETE /snmpv3/config   → Delete SNMPv3 device config
-        POST   /snmpv3/toggle   → Enable or disable SNMPv3 polling
+        POST   /snmpv3/config   -> Configure or update SNMPv3 device
+        GET    /snmpv3/config   -> Get SNMPv3 config(s)
+        DELETE /snmpv3/config   -> Delete SNMPv3 device config
+        POST   /snmpv3/toggle   -> Enable or disable SNMPv3 polling
     """
     try:
         if request.method == 'POST':
             if path == 'config':
                 return configure_snmpv3_device(request)
-
             elif path == 'toggle':
                 return toggle_snmpv3_device(request)
 
@@ -49,18 +49,16 @@
 
 def configure_snmpv3_device(request):
     """
-        Configure SNMPv3 for a device (save + reconfigure telegraf).
-        Accepts sec_level = "authPriv" or "authNoPriv".
-        If not provided, defaults to "authPriv".
-        """
-    # Parse JSON input
+    Configure SNMPv3 for a device (save + reconfigure telegraf).
+    Accepts sec_level = "authPriv" or "authNoPriv".
+    If not provided, defaults to "authPriv".
+    """
     try:
         data = json.loads(request.body)
     except Exception:
         return json_response({"error": "Invalid JSON body"}, status=400)
 
     required = ["device_ip", "username", "auth_pass", "auth_protocol", "device_type"]
-    # priv_pass and priv_protocol required only for authPriv
     sec_level = data.get("sec_level", "authPriv")
 
     if sec_level not in ["authPriv", "authNoPriv"]:
@@ -129,11 +127,11 @@
 
         device_ip = data.get("device_ip")
         enabled = data.get("enabled")
-
+        username = data.get("username")
         if not device_ip or enabled is None:
             return json_response({"error": "Missing required fields: device_ip and enabled"}, status=400)
 
-        success, msg = snmpv3_service.update_snmpv3_device_status(device_ip, enabled)
+        success, msg = snmpv3_service.update_snmpv3_device_status(device_ip, enabled, username)
 
         if not success:
             oper_log('error', 'system', "SNMPv3 toggle failed: %s" % msg)
@@ -152,22 +150,15 @@
     """Get SNMPv3 configuration for a specific device or all devices."""
     try:
         device_ip = request.GET.get('device_ip')
-
         success, data = snmpv3_service.get_snmpv3_device_config(device_ip)
 
-        # Return specific device
         if device_ip:
             if success:
                 return json_response(data, status=200)
             return json_response({"error": data}, status=404)
-        # All devices request
         else:
             if success:
-                # If devices list is empty, return 200 with empty list
-                if isinstance(data, list) and len(data) == 0:
-                    return json_response([], status=200)
-                return json_response(data, status=200)
-            # Unexpected error for all devices
+                return json_response(data if data else [], status=200)
             return json_response({"error": data}, status=500)
 
     except Exception as e:
Index: /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/services/snmpv3_service.py
===================================================================
--- /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/services/snmpv3_service.py	(revision 2973)
+++ /branches/amp_3_7_2/src/webui/webui/htdocs/new/src/hive/services/snmpv3_service.py	(working copy)
@@ -2,6 +2,7 @@
 import os
 import json
 import time
+import re
 import subprocess
 from cm.lib.libbasic_operation import oper_log
 from hive.utils import andebug
@@ -10,7 +11,17 @@
 SNMPV3_DEVICES_PATH = "/var/lib/snmp/snmpv3_devices.json"
 SNMP_TOML_DIR = "/ca/webui/conf/snmp"
 
+_MASKED_FIELDS = ("auth_pass", "priv_pass", "auth_password", "priv_password")
 
+
+def _is_masked(value):
+    """Return True if value is an all-asterisk masked password placeholder."""
+    if not isinstance(value, str):
+        return False
+    stripped = value.strip()
+    return len(stripped) > 0 and all(c == '*' for c in stripped)
+
+
 def _load_json(path):
     """Helper to safely load JSON."""
     if not os.path.exists(path):
@@ -28,6 +39,30 @@
         json.dump(data, f, indent=4)
 
 
+def _widen_snmp_general_column(db):
+    """Widen snmp_general from varchar(256) to text if needed.
+
+    varchar(256) is the schema default, but once v3user (username + passwords)
+    plus the other protocol fields push the serialised JSON past 256 bytes
+    PostgreSQL raises an error that callers catch and swallow — leaving the DB
+    row unchanged and the binary reading stale/DES values forever.
+
+    Altering varchar → text is a metadata-only, lock-minimal operation in
+    PostgreSQL and is safe to call repeatedly (no-op when already TEXT).
+    """
+    try:
+        db.execute_sql(
+            "ALTER TABLE device ALTER COLUMN snmp_general TYPE text"
+        )
+    except Exception:
+        # If the ALTER fails for any reason, roll back so the connection
+        # stays usable for the UPDATE that follows.
+        try:
+            db.conn.rollback()
+        except Exception:
+            pass
+
+
 class Snmpv3Service:
     def __init__(self):
         pass
@@ -35,42 +70,114 @@
     def save_snmpv3_device(self, data):
         """Save or update SNMPv3 device configuration."""
         try:
-            # Ensure 'enabled' flag exists — default True
             if "enabled" not in data:
                 data["enabled"] = True
 
             devices = _load_json(SNMPV3_DEVICES_PATH)
             updated = False
 
-            # Update existing entry if found
-            for i, d in enumerate(devices):
-                if d.get("device_ip") == data["device_ip"]:
-                    devices[i] = data
+            for i, existing in enumerate(devices):
+                if existing.get("device_ip") == data["device_ip"]:
+                    merged = dict(existing)
+                    for key, value in data.items():
+                        if key in _MASKED_FIELDS and _is_masked(value):
+                            oper_log(
+                                'info', 'system',
+                                "SNMPv3 save: skipping masked %s for %s" % (key, data["device_ip"])
+                            )
+                            continue
+                        merged[key] = value
+                    devices.pop(i)
+                    devices.append(merged)
                     updated = True
                     break
 
-            # Otherwise append new device
             if not updated:
+                for field in _MASKED_FIELDS:
+                    if _is_masked(data.get(field, "")):
+                        msg = (
+                            "Cannot create new SNMPv3 device %s: "
+                            "field '%s' contains a masked placeholder (all '*'). "
+                            "Provide the actual password." % (data["device_ip"], field)
+                        )
+                        oper_log('error', 'system', msg)
+                        return False, msg
                 devices.append(data)
 
-            # Ensure directory exists
             directory = os.path.dirname(SNMPV3_DEVICES_PATH)
             if not os.path.exists(directory):
                 os.makedirs(directory)
 
-            # Save back to JSON
             _save_json(SNMPV3_DEVICES_PATH, devices)
-            oper_log('info', 'system', "SNMPv3 device saved: %s" % data["device_ip"])
-            return True, "SNMPv3 device saved successfully"
+            oper_log('info', 'system', "SNMPv3 device saved to JSON: %s" % data["device_ip"])
+
+            # Sync to Database (snmp_general) so the binary (composer_tele) picks up
+            # the correct priv_protocol/auth_protocol when it regenerates the conf.
+            # IMPORTANT: merge into the existing record — do NOT overwrite it — so that
+            # community and other binary-managed fields are preserved.
+            try:
+                from cm.lib.postgres_db import DB
+                db = DB.get_connected_db()
+
+                # Ensure the column can hold more than 256 bytes before writing.
+                _widen_snmp_general_column(db)
+
+                # Fetch current snmp_general to merge into
+                existing_res = db.fetchall(
+                    "SELECT snmp_general FROM device WHERE ip_address='%s'" % data["device_ip"]
+                )
+                snmp_general = {}
+                if existing_res and existing_res[0] and existing_res[0][0]:
+                    try:
+                        snmp_general = json.loads(existing_res[0][0])
+                    except (ValueError, TypeError):
+                        pass
+
+                # Build v3user string
+                v3user = "%s %s %s" % (
+                    data.get("username", ""),
+                    data.get("auth_pass", ""),
+                    data["sec_level"]
+                )
+                if data["sec_level"] == "authPriv":
+                    v3user += " %s" % data.get("priv_pass", "")
+
+                # Merge — use auth_protocol/priv_protocol (keys the binary reads)
+                snmp_general.update({
+                    "snmp_version": "v3",
+                    "snmp_enable": data.get("enabled", True),
+                    "v3user": v3user,
+                    "auth_protocol": data.get("auth_protocol", "MD5"),
+                    "priv_protocol": data.get("priv_protocol", "AES"),
+                })
+                # Only update community if caller explicitly provided it
+                if "community" in data:
+                    snmp_general["community"] = data["community"]
+
+                update_sql = "UPDATE device SET snmp_general='%s' WHERE ip_address='%s'" % (
+                    json.dumps(snmp_general), data["device_ip"]
+                )
+                db.execute_sql(update_sql)
+                db.close()
+                oper_log('info', 'system', "SNMPv3 device synced to DB: %s" % data["device_ip"])
+            except Exception as e:
+                oper_log('error', 'system', "Failed to sync SNMPv3 to DB: %s" % str(e))
 
+            return True, "SNMPv3 device saved and synced successfully"
+
         except Exception as e:
             oper_log('error', 'system', "Failed to save SNMPv3 device: %s" % str(e))
             return False, str(e)
 
-    def delete_snmpv3_device(self, device_ip):
-        """Remove a device entry and update Telegraf config."""
+    def delete_snmpv3_device(self, device_ip, username=None):
+        """Remove a device entry and update Telegraf config.
+
+        If username is given, only that user entry is removed.
+        Otherwise all entries for device_ip are removed.
+        """
         try:
             devices = _load_json(SNMPV3_DEVICES_PATH)
+
             new_devices = [d for d in devices if d.get("device_ip") != device_ip]
 
             if len(new_devices) == len(devices):
@@ -79,7 +186,6 @@
             _save_json(SNMPV3_DEVICES_PATH, new_devices)
             oper_log('info', 'system', "Removed SNMPv3 device: %s" % device_ip)
 
-            # Rebuild Telegraf config after removal
             self.configure_telegraf_snmp()
             return True, "SNMPv3 device removed and Telegraf updated."
         except Exception as e:
@@ -87,19 +193,17 @@
             return False, str(e)
 
     def get_snmpv3_device_config(self, device_ip=None):
-        """
-        Return SNMPv3 configuration for a specific device or all devices.
-        """
+        """Return SNMPv3 configuration for a specific device or all devices."""
         try:
             devices = _load_json(SNMPV3_DEVICES_PATH)
             if devices is None:
                 devices = []
             if device_ip:
-                for d in devices:
-                    if d.get("device_ip") == device_ip:
-                        return True, d
-                return False, "No SNMPv3 configuration found for device %s" % device_ip
-            # All devices: return devices list (may be empty)
+                matches = [d for d in devices if d.get("device_ip") == device_ip]
+                if not matches:
+                    return False, "No SNMPv3 configuration found for device %s" % device_ip
+                # Return the last entry — most recently configured (pop+append on save).
+                return True, matches[-1]
             return True, devices
         except Exception as e:
             oper_log('error', 'system', "Failed to fetch SNMPv3 config: %s" % str(e))
@@ -200,91 +304,132 @@
             if not devices:
                 return False, "No SNMPv3 devices defined"
 
-            lines = []
-
+            # Last enabled entry per device_ip wins (most recently configured).
+            active_by_ip = {}
             for d in devices:
                 if not d.get("enabled", True):
                     continue
+                ip = d.get("device_ip")
+                if ip:
+                    active_by_ip[ip] = d
 
-                device_ip = d.get("device_ip")
+            lines = []
+
+            for device_ip, d in active_by_ip.items():
                 device_type = d.get("device_type", "").lower()
                 sec_level = d.get("sec_level", "authPriv")
-                if not device_ip:
-                    continue
 
-                # Load SNMP OIDs dynamically from TOML
-                fields, tables = self._load_toml_fields(device_type)
+                username = d.get("username", "")
+                auth_pass = d.get("auth_pass", "")
+                priv_pass = d.get("priv_pass", "")
 
-                # --- SNMPv3 section header ---
-                lines.extend([
-                    '[[inputs.snmp]]',
-                    '  agents = ["%s:161"]' % device_ip,
-                    '  version = 3',
-                    '  name = "snmpv3_metrics_%s"' % device_ip.replace('.', '_'),
-                    '  timeout = "2s"',
-                    '  sec_name = "%s"' % d["username"],
-                    '  auth_protocol = "%s"' % d["auth_protocol"],
-                    '  auth_password = "%s"' % d["auth_pass"]
-                ])
+                if not username:
+                    oper_log(
+                        'error', 'system',
+                        "configure_telegraf_snmp: skipping device %s — "
+                        "missing 'username' field in JSON. "
+                        "Re-save the device via the SNMPv3 configuration API." % device_ip
+                    )
+                    continue
 
-                # Optional encryption fields (only for authPriv)
-                if sec_level == "authPriv":
-                    lines.extend([
-                        '  priv_protocol = "%s"' % d.get("priv_protocol", ""),
-                        '  priv_password = "%s"' % d.get("priv_pass", "")
-                    ])
+                if not auth_pass or _is_masked(auth_pass):
+                    oper_log(
+                        'error', 'system',
+                        "configure_telegraf_snmp: skipping device %s — "
+                        "auth_pass is missing or masked. "
+                        "Re-enter credentials via the SNMPv3 configuration API." % device_ip
+                    )
+                    continue
 
-                # Security level
-                lines.append('  sec_level = "%s"' % sec_level)
-                lines.append('')
+                if sec_level == "authPriv" and (not priv_pass or _is_masked(priv_pass)):
+                    oper_log(
+                        'error', 'system',
+                        "configure_telegraf_snmp: skipping device %s — "
+                        "priv_pass is missing or masked for authPriv. "
+                        "Re-enter credentials via the SNMPv3 configuration API." % device_ip
+                    )
+                    continue
 
-                # --- Add SNMP fields ---
-                for f in fields:
-                    if not f.get("name") or not f.get("oid"):
-                        continue
-                    lines.extend([
-                        '  [[inputs.snmp.field]]',
-                        '    name = "%s"' % f["name"],
-                        '    oid = "%s"' % f["oid"]
-                    ])
-                    if f.get("conversion"):
-                        lines.append('    conversion = "%s"' % f["conversion"])
-                    if f.get("is_tag"):
-                        lines.append('    is_tag = true')
-                    lines.append('')
+                fields, tables = self._load_toml_fields(device_type)
 
-                # --- Add SNMP tables ---
-                for table in tables:
-                    if not isinstance(table, dict):
-                        continue
-                    tname = table.get("name")
-                    if not tname:
-                        continue
-
-                    lines.append('  [[inputs.snmp.table]]')
-                    lines.append('    name = "%s"' % tname)
+                def _snmp_header(measurement_name):
+                    # Normalize protocol names for Telegraf compatibility
+                    auth_proto = d.get("auth_protocol", "MD5").upper()
+                    if auth_proto == "SHA1":
+                        auth_proto = "SHA"
 
-                    if "prefix" in table:
-                        lines.append('    prefix = %s' % table["prefix"])
+                    priv_proto = d.get("priv_protocol", "AES").upper()
+                    # if priv_proto == "AES":
+                    #     priv_proto = "AES128"  # Telegraf standard identifier for AES-128
 
-                    fields_list = table.get("field", [])
-                    for f in fields_list:
+                    header = [
+                        '[[inputs.snmp]]',
+                        '  agents = ["%s:161"]' % device_ip,
+                        '  version = 3',
+                        '  name = "%s"' % measurement_name,
+                        '  community = "%s"' % d.get("community", "public"),
+                        '  timeout = "2s"',
+                        '  sec_name = "%s"' % username,
+                        '  auth_protocol = "%s"' % auth_proto,
+                        '  auth_password = "%s"' % auth_pass,
+                    ]
+                    if sec_level == "authPriv":
+                        header.extend([
+                            '  priv_protocol = "%s"' % priv_proto,
+                            '  priv_password = "%s"' % priv_pass,
+                        ])
+                    header.append('  sec_level = "%s"' % sec_level)
+                    header.append('')
+                    return header
+
+                if fields:
+                    lines.extend(_snmp_header("snmp_system"))
+                    for f in fields:
                         if not f.get("name") or not f.get("oid"):
                             continue
                         lines.extend([
-                            '    [[inputs.snmp.table.field]]',
-                            '      name = "%s"' % f["name"],
-                            '      oid = "%s"' % f["oid"]
+                            '  [[inputs.snmp.field]]',
+                            '    name = "%s"' % f["name"],
+                            '    oid = "%s"' % f["oid"]
                         ])
                         if f.get("conversion"):
-                            lines.append('      conversion = "%s"' % f["conversion"])
+                            lines.append('    conversion = "%s"' % f["conversion"])
                         if f.get("is_tag"):
-                            lines.append('      is_tag = true')
+                            lines.append('    is_tag = true')
                         lines.append('')
+                    lines.append('')
 
-                lines.append('')  # spacing between devices
+                if tables:
+                    lines.extend(_snmp_header("performance"))
+                    for table in tables:
+                        if not isinstance(table, dict):
+                            continue
+                        tname = table.get("name")
+                        if not tname:
+                            continue
 
-            # --- Write new config ---
+                        lines.append('  [[inputs.snmp.table]]')
+                        lines.append('    name = "%s"' % tname)
+
+                        if "prefix" in table:
+                            lines.append('    prefix = %s' % table["prefix"])
+
+                        fields_list = table.get("field", [])
+                        for f in fields_list:
+                            if not f.get("name") or not f.get("oid"):
+                                continue
+                            lines.extend([
+                                '    [[inputs.snmp.table.field]]',
+                                '      name = "%s"' % f["name"],
+                                '      oid = "%s"' % f["oid"]
+                            ])
+                            if f.get("conversion"):
+                                lines.append('      conversion = "%s"' % f["conversion"])
+                            if f.get("is_tag"):
+                                lines.append('      is_tag = true')
+                            lines.append('')
+                    lines.append('')
+
             start_marker = "# BEGIN SNMPv3 AUTOGEN"
             end_marker = "# END SNMPv3 AUTOGEN"
 
@@ -294,40 +439,154 @@
             else:
                 existing = "[agent]\ninterval = \"10s\"\n\n"
 
+            # Scrub any existing SNMP blocks for these IPs from the 'content' text.
+            # Parses the file into logical blocks (split on top-level [[...]] headers)
+            # so the [[inputs.snmp]] header is never emitted before we know whether to
+            # keep or discard the block.  Sub-directives like [[inputs.snmp.field]] are
+            # NOT treated as new top-level blocks.
+            def _sanitize_conf(content, managed_ips):
+                if not content:
+                    return content
+
+                ip_list = list(managed_ips)
+                all_lines = content.splitlines()
+                blocks = []          # list of (is_snmp_block, [lines])
+                cur_lines = []
+                cur_is_snmp = False
+
+                for line in all_lines:
+                    stripped = line.strip()
+                    # Top-level section header: [[...]] but NOT sub-directives like
+                    # [[inputs.snmp.field]] / [[inputs.snmp.table]] / [[inputs.snmp.table.field]]
+                    is_top_header = (
+                        stripped.startswith('[[') and
+                        not stripped.startswith('[[inputs.snmp.')
+                    )
+                    if is_top_header:
+                        if cur_lines:
+                            blocks.append((cur_is_snmp, cur_lines))
+                        cur_lines = [line]
+                        cur_is_snmp = stripped.startswith('[[inputs.snmp]]')
+                    else:
+                        cur_lines.append(line)
+
+                if cur_lines:
+                    blocks.append((cur_is_snmp, cur_lines))
+
+                final_lines = []
+                for is_snmp, block_lines in blocks:
+                    if is_snmp:
+                        # Drop this block if any line references a managed IP
+                        should_drop = any(
+                            ('"%s:161"' % ip) in ln or ('"%s"' % ip) in ln
+                            for ln in block_lines
+                            for ip in ip_list
+                        )
+                        if should_drop:
+                            continue
+                    final_lines.extend(block_lines)
+
+                return "\n".join(final_lines)
+
+            managed_ips = active_by_ip.keys()
+            
             if start_marker in existing and end_marker in existing:
                 pre = existing.split(start_marker)[0]
                 post = existing.split(end_marker)[-1]
-                new_conf = pre + start_marker + "\n" + "\n".join(lines) + "\n" + end_marker + post
+                
+                # Sanitize pre and post parts independently
+                pre = _sanitize_conf(pre, managed_ips)
+                post = _sanitize_conf(post, managed_ips)
+                
+                new_conf = pre.strip() + "\n\n" + start_marker + "\n" + "\n".join(lines) + "\n" + end_marker + "\n" + post.strip()
             else:
-                new_conf = existing.strip() + "\n\n" + start_marker + "\n" + "\n".join(lines) + "\n" + end_marker + "\n"
+                # Sanitize the whole file if markers aren't there yet
+                sanitized = _sanitize_conf(existing, managed_ips)
+                new_conf = sanitized.strip() + "\n\n" + start_marker + "\n" + "\n".join(lines) + "\n" + end_marker + "\n"
 
+            # Scrub legacy community-string placeholder before any write
+            new_conf = new_conf.replace('$YTINUMMOC', 'public')
+
             with open(COMPOSER_TELEGRAF_CONF, "w") as f:
                 f.write(new_conf)
 
-            # --- Restart composer_tele safely ---
-            subprocess.call(["pkill", "-f", "composer_tele"])
-            time.sleep(2)
-            subprocess.Popen([
-                "/ca/extensions/auditing/syslogd/composer_tele",
-                "--config", COMPOSER_TELEGRAF_CONF
-            ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            # Sync priv_protocol/auth_protocol into the DB BEFORE restarting the binary.
+            # composer_tele regenerates the conf from device.snmp_general on a periodic
+            # timer — not just at startup.  If the DB still holds stale values (e.g. DES)
+            # every timer-tick overwrites the conf, defeating the file write above.
+            # Updating the DB here ensures every regeneration cycle reads the correct
+            # protocol and produces a consistent conf.
+            try:
+                from cm.lib.postgres_db import DB as _DB
+                _db = _DB.get_connected_db()
 
-            time.sleep(5)
+                # Ensure the column can hold more than 256 bytes before writing.
+                _widen_snmp_general_column(_db)
+
+                for _ip, _d in active_by_ip.items():
+                    _res = _db.fetchall(
+                        "SELECT snmp_general FROM device WHERE ip_address='%s'" % _ip
+                    )
+                    _sg = {}
+                    if _res and _res[0] and _res[0][0]:
+                        try:
+                            _sg = json.loads(_res[0][0])
+                        except (ValueError, TypeError):
+                            pass
+                    _sg["auth_protocol"] = _d.get("auth_protocol", "MD5")
+                    _sg["priv_protocol"] = _d.get("priv_protocol", "AES")
+                    _db.execute_sql(
+                        "UPDATE device SET snmp_general='%s' WHERE ip_address='%s'" % (
+                            json.dumps(_sg), _ip
+                        )
+                    )
+                    oper_log('info', 'system',
+                             "configure_telegraf_snmp: DB synced for %s "
+                             "(auth=%s priv=%s)" % (
+                                 _ip, _sg["auth_protocol"], _sg["priv_protocol"]))
+                _db.close()
+            except Exception as _e:
+                oper_log('error', 'system',
+                         "configure_telegraf_snmp: DB protocol sync failed: %s" % str(_e))
+
+            # Restart composer_tele via its process manager (composer_node).
+            # Direct pkill bypasses composer_node's state tracking and leads
+            # to duplicate processes or the managed instance not being restarted.
+            # The correct mechanism (mirroring stop.sh) is:
+            #   composer_node start composer_tele
+            # which kills any running instance and starts a fresh one using
+            # the conf regenerated from the DB we just updated.
+            COMPOSER_NODE_CLI = "/usr/local/bin/composer/composer_node"
+            subprocess.call([COMPOSER_NODE_CLI, "start", "composer_tele"])
+            time.sleep(3)
+
             ps_check = subprocess.call(["pgrep", "-f", "composer_tele"])
             if ps_check != 0:
-                oper_log('error', 'system', "composer_tele did not start properly")
+                oper_log('error', 'system',
+                         "composer_tele did not start via composer_node — "
+                         "falling back to direct start")
+                subprocess.Popen(
+                    ["/ca/extensions/auditing/syslogd/composer_tele",
+                     "-c", COMPOSER_TELEGRAF_CONF],
+                    stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE
+                )
             else:
-                oper_log('info', 'system', "composer_tele restarted successfully")
+                oper_log('info', 'system',
+                         "composer_tele restarted via composer_node")
 
-            oper_log('info', 'system', "Telegraf SNMPv3 configuration updated successfully")
             return True, "Telegraf SNMPv3 configuration updated successfully"
 
         except Exception as e:
             oper_log('error', 'system', "Failed to configure Telegraf SNMPv3: %s" % str(e))
             return False, str(e)
 
-    def update_snmpv3_device_status(self, device_ip, enabled):
-        """Enable or disable SNMPv3 polling for a device."""
+    def update_snmpv3_device_status(self, device_ip, enabled, username=None):
+        """Enable or disable SNMPv3 polling for a device.
+
+        If username is given, only that user entry is toggled.
+        Otherwise all entries for device_ip are toggled.
+        """
         try:
             devices = _load_json(SNMPV3_DEVICES_PATH)
             found = False
@@ -336,7 +595,6 @@
                 if d.get("device_ip") == device_ip:
                     d["enabled"] = bool(enabled)
                     found = True
-                    break
 
             if not found:
                 return False, "Device not found in SNMPv3 configuration."
@@ -344,7 +602,6 @@
             _save_json(SNMPV3_DEVICES_PATH, devices)
             oper_log('info', 'system', "Updated SNMPv3 device %s: enabled=%s" % (device_ip, enabled))
 
-            # Rebuild telegraf config after toggle
             self.configure_telegraf_snmp()
 
             return True, "Device polling status updated successfully."
