Can someone post a working curl for update_device_port_notes?

Can someone prove to me that this works, so I don’t keep wasting my time? I’m about to give up. I’m simply trying to add notes to a device port (not the device itself) via the api. I keep getting “ Cannot access offset of type string on string” type errors.

curl -X PATCH -d ‘{“notes”: “This port is in a scheduled maintenance with the provider.”}’ -H ‘X-Auth-Token: YOURAPITOKENHERE’ https://librenms.org/api/v0/devices/localhost/port/5

Thank you

./validate.php

Component Version
LibreNMS 24.1.0-39-gf9fc80966 (2024-01-17T16:58:08-05:00)
DB Schema 2023_12_12_171400_alert_rule_note (277)
PHP 8.1.2-1ubuntu2.14
Python 3.10.12
Database MariaDB 10.6.12-MariaDB-0ubuntu0.22.04.1
RRDTool 1.7.2
SNMP 5.9.1
===========================================

[OK] Composer Version: 2.6.6
[OK] Dependencies up-to-date.
[OK] Database connection successful
[OK] Database Schema is current
[OK] SQL Server meets minimum requirements
[OK] lower_case_table_names is enabled
[OK] MySQL engine is optimal
[OK] Database and column collations are correct
[OK] Database schema correct
[OK] MySQL and PHP time match
[OK] Active pollers found
[OK] Dispatcher Service not detected
[OK] Locks are functional
[OK] Python poller wrapper is polling
[OK] Redis is unavailable
[OK] rrd_dir is writable
[OK] rrdtool version ok

Same here

 {
    "message": "Cannot access offset of type string on string",
    "exception": "TypeError",
    "file": "/opt/librenms/includes/common.php",
    "line": 294,
    "trace": [
        {
            "file": "/opt/librenms/includes/html/api_functions.inc.php",
            "line": 1190,
            "function": "set_dev_attrib"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php",
            "line": 36,
            "function": "update_device_port_notes"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Container/Util.php",
            "line": 41,
            "function": "Illuminate\\Container\\{closure}",
            "class": "Illuminate\\Container\\BoundMethod",
            "type": "::"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php",
            "line": 81,
            "function": "unwrapIfClosure",
            "class": "Illuminate\\Container\\Util",
            "type": "::"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php",
            "line": 35,
            "function": "callBoundMethod",
            "class": "Illuminate\\Container\\BoundMethod",
            "type": "::"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Container/Container.php",
            "line": 662,
            "function": "call",
            "class": "Illuminate\\Container\\BoundMethod",
            "type": "::"
        },
        {
            "file": "/opt/librenms/app/Api/Controllers/LegacyApiController.php",
            "line": 43,
            "function": "call",
            "class": "Illuminate\\Container\\Container",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php",
            "line": 46,
            "function": "__call",
            "class": "App\\Api\\Controllers\\LegacyApiController",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
            "line": 259,
            "function": "dispatch",
            "class": "Illuminate\\Routing\\ControllerDispatcher",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
            "line": 205,
            "function": "runController",
            "class": "Illuminate\\Routing\\Route",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
            "line": 806,
            "function": "run",
            "class": "Illuminate\\Routing\\Route",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 144,
            "function": "Illuminate\\Routing\\{closure}",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authorize.php",
            "line": 57,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Auth\\Middleware\\Authorize",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php",
            "line": 50,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Routing\\Middleware\\SubstituteBindings",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php",
            "line": 57,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Auth\\Middleware\\Authenticate",
            "type": "->"
        },
        {
            "file": "/opt/librenms/app/Http/Middleware/EnforceJson.php",
            "line": 44,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "App\\Http\\Middleware\\EnforceJson",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 119,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
            "line": 805,
            "function": "then",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
            "line": 784,
            "function": "runRouteWithinStack",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
            "line": 748,
            "function": "runRoute",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
            "line": 737,
            "function": "dispatchToRoute",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
            "line": 200,
            "function": "dispatch",
            "class": "Illuminate\\Routing\\Router",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 144,
            "function": "Illuminate\\Foundation\\Http\\{closure}",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/barryvdh/laravel-debugbar/src/Middleware/InjectDebugbar.php",
            "line": 59,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Barryvdh\\Debugbar\\Middleware\\InjectDebugbar",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
            "line": 21,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php",
            "line": 31,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
            "line": 21,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php",
            "line": 40,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
            "line": 27,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
            "line": 99,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php",
            "line": 49,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Http\\Middleware\\HandleCors",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php",
            "line": 39,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 183,
            "function": "handle",
            "class": "Illuminate\\Http\\Middleware\\TrustProxies",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
            "line": 119,
            "function": "Illuminate\\Pipeline\\{closure}",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
            "line": 175,
            "function": "then",
            "class": "Illuminate\\Pipeline\\Pipeline",
            "type": "->"
        },
        {
            "file": "/opt/librenms/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
            "line": 144,
            "function": "sendRequestThroughRouter",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        },
        {
            "file": "/opt/librenms/html/api_v0.php",
            "line": 51,
            "function": "handle",
            "class": "Illuminate\\Foundation\\Http\\Kernel",
            "type": "->"
        }
    ]
}

The issue here is that the variable $device is a string, not an array

function set_dev_attrib($device, $attrib_type, $attrib_value)
{
    var_dump($device);
    return DeviceCache::get((int) $device['device_id'])->setAttrib($attrib_type, $attrib_value);
}

The var_dump returns string(2) “73”

Fixing this:

#    return DeviceCache::get((int) $device['device_id'])->setAttrib($attrib_type, $attrib_value);
    return DeviceCache::get((int) $device)->setAttrib($attrib_type, $attrib_value);
{
    "status": "ok",
    "message": "Port notes field has been updated"
}

Thanks for verifying!

Unfortunately, it looks like the fix was rejected

I modified it to stay within the update notes function (to not disturb anything else) and grab the device array from the given device_id and passing that to set_dev_attrib instead. My tests seem to be working

function update_device_port_notes(Illuminate\Http\Request $request): JsonResponse
{
    $portid = $request->route('portid');

    $hostname = $request->route('hostname');
    // use hostname as device_id if it's all digits
    $device_id = ctype_digit($hostname) ? $hostname : getidbyname($hostname);

    // fetching device array from discovered $device_id 
    $device = device_by_id_cache($device_id);

    $data = json_decode($request->getContent(), true);
    $field = 'notes';
    $content = $data[$field];
    if (empty($data)) {
        return api_error(400, 'Port field to patch has not been supplied.');
    }

    // if (set_dev_attrib($device_id, 'port_id_notes:' . $portid, $content)) {
    // Changing to $device array instead of $device_id
    if (set_dev_attrib($device, 'port_id_notes:' . $portid, $content)) {
        return api_success_noresult(200, 'Port ' . $field . ' field has been updated');
    } else {
        return api_error(500, 'Port ' . $field . ' field failed to be updated');
    }
}