v1.15.0

Summary

This release introduces a web_panel.access permission and improves vehicle image loading with a configurable fallback. Staff group changes now stay in sync with the web panel, and several inventory and vehicle actions are more tolerant of older frameworks.

What's New

Features

  • Added full one_inventory support (items, carry checks, stashes, and item images)
  • Added jpr-inventory support for add, remove, carry, has, and count
  • New web_panel.access permission; owners/staff managers get a one-time prompt to grant it to all groups or keep it restricted
  • Vehicle images now try vanilla docs, then your custom images_url, then JG Vehicle Studio, and finally a configurable fallback_url
  • Staff group permission maps are now synced to the web panel for player-list redaction

Bug Fixes

  • Giving items no longer fails when canCarryItem throws on older QB-style inventories
  • Giving a vehicle now sets fuel to 100
  • Money transaction reason is optional and no longer errors when omitted
  • Creating, updating, or deleting a staff group now refreshes the web panel on success
  • Floating spectate/stream windows close when staff go off duty
  • op-crime gang lookup now uses a fallback source when PlayerData.source is missing

File Changes

⚙️ Configuration & Bridge Files

These files are not encrypted. Review the changes below — you may need to manually apply updates to your server's config files.

config/config.json

   "vehicles": {
     "use_ox_lib_for_mods": false,
     "images_url": "https://docs.fivem.net/vehicles/%s.webp",
-    "_comment_": "Replace the url with your own website or resource that contains the custom vehicles images, keep the %s placeholder, it will be replaced with the vehicle model name."
+    "fallback_url": "./placeholders/vehicle.svg",
+    "_comment_": "images_url is for custom vehicle images and must keep the %s placeholder. fallback_url is shown when no image is found."
   },
   "jail": {
     "enabled": true,

config/permissions.json

     "staff.view": "perm_staff_view",
     "self.quickmenu": "perm_self_quickmenu",
     "self.panel": "perm_self_panel",
+    "web_panel.access": "perm_web_panel_access",
     "events.create": "perm_events_create",
     "events.delete": "perm_events_delete",
     "events.view": "perm_events_view",

fxmanifest.lua


 author 'luxu-gg'
 description 'Admin menu for FiveM'
-version '1.14.9'
+version '1.15.0'

 dependencies {
     'oxmysql',

bridge/server/inventory.lua

     if not playerObj then return {} end

     local result = {}
-    if Framework.name == 'ox' then
+    if isResourceRunning("one_inventory") then
+        result = exports.one_inventory:GetInventoryItems(source) --[[ @as table ]]
+    elseif Framework.name == 'ox' then
         result = exports.ox_inventory:GetInventoryItems(source) --[[ @as table ]]
     elseif isResourceRunning("tgiann-inventory") then
         result = exports["tgiann-inventory"]:GetPlayerItems(source)
     if not playerObj then return false end

-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:AddItem(source, item, amount, metadata, slot) == true
+    elseif isResourceRunning('ox_inventory') then
         exports.ox_inventory:AddItem(source, item, amount, metadata, slot, nil)
     elseif isResourceRunning("origen_inventory") then
         exports.origen_inventory:AddItem(source, item, amount, slot, metadata)
         exports["codem-inventory"]:AddItem(source, item, amount, slot, metadata)
     elseif isResourceRunning("jaksam_inventory") then
         exports['jaksam_inventory']:addItem(source, item, amount, metadata, slot)
+    elseif isResourceRunning("jpr-inventory") then
+        exports["jpr-inventory"]:AddItem(source, item, amount, slot, metadata)
     else
         if Framework.name == "qb" or Framework.name == "qbx" then
             playerObj.Functions.AddItem(item, amount, slot, metadata)
     if not playerObj then return false end

-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:RemoveItem(source, item, amount, nil, slot) == true
+    elseif isResourceRunning('ox_inventory') then
         exports.ox_inventory:RemoveItem(source, item, amount)
     elseif isResourceRunning('origen_inventory') then
         exports.origen_inventory:RemoveItem(source, item, amount)
         exports["jaksam_inventory"]:removeItem(source, item, amount, nil, slot)
     elseif isResourceRunning("codem-inventory") then
         exports["codem-inventory"]:RemoveItem(source, item, amount, slot)
+    elseif isResourceRunning("jpr-inventory") then
+        exports["jpr-inventory"]:RemoveItem(source, item, amount, slot)
     else
         if Framework.name == "qb" or Framework.name == "qbx" then
             playerObj.Functions.RemoveItem(item, amount)
     if not playerObj then return false end

+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:ClearInventory(source) == true
+    end
+
     for _, item in ipairs(normalizeItems(inventory.getPlayerItems(source))) do
         local ok, result = pcall(inventory.removeItem, source, item.name, item.amount, item.slot)
         if not ok or result == false then
     if not playerObj then return false end

-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:CanCarryItem(source, item, amount) == true
+    elseif isResourceRunning('ox_inventory') then
         return exports.ox_inventory:CanCarryItem(source, item, amount) == true
     elseif isResourceRunning("origen_inventory") then
         return exports.origen_inventory:CanCarryItem(source, item, amount)
         return exports["tgiann-inventory"]:CanCarryItem(source, item, amount)
     elseif isResourceRunning("jaksam_inventory") then
         return exports["jaksam_inventory"]:canCarryItem(source, item, amount)
+    elseif isResourceRunning("jpr-inventory") then
+        return exports["jpr-inventory"]:CanAddItem(source, item, amount)
     elseif isResourceRunning("codem-inventory") then
         local itemList = exports["codem-inventory"]:GetItemList()
         local itemData = itemList[item]
     if not playerObj then return false end

-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:HasItem(source, item, amount) == true
+    elseif isResourceRunning('ox_inventory') then
         local count = exports.ox_inventory:GetItem(source, item, nil, true)
         return count >= amount
     elseif isResourceRunning('origen_inventory') then
         return exports["tgiann-inventory"]:HasItem(source, item, amount)
     elseif isResourceRunning("jaksam_inventory") then
         return exports['jaksam_inventory']:hasItem(source, item, amount)
+    elseif isResourceRunning("jpr-inventory") then
+        return exports["jpr-inventory"]:HasItem(source, item, amount)
     else
         if Framework.name == "qb" or Framework.name == "qbx" then
             local items = playerObj.PlayerData.items
     if not playerObj then return 0 end

-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        return exports.one_inventory:GetItemCount(source, item) or 0
+    elseif isResourceRunning('ox_inventory') then
         return exports.ox_inventory:GetItem(source, item, nil, true)
     elseif isResourceRunning('origen_inventory') then
         return exports.origen_inventory:GetItemCount(source, item)
         return exports["tgiann-inventory"]:GetItemCount(source, item)
     elseif isResourceRunning("jaksam_inventory") then
         return exports['jaksam_inventory']:getTotalItemAmount(source, item)
+    elseif isResourceRunning("jpr-inventory") then
+        return #exports['jpr-inventory']:GetItemsByName(source, item)
     else
         if Framework.name == "qb" or Framework.name == "qbx" then
             local items = playerObj.PlayerData.items
 ---@param typeofStash "stash" | "trunk" | "glovebox"
 ---@param id string could be vehicle plate
 function inventory.getStash(typeofStash, id)
-    if isResourceRunning('ox_inventory') then
+    if isResourceRunning("one_inventory") then
+        local invId
+        if typeofStash == "stash" then
+            invId = ("stash:%s"):format(id)
+        elseif typeofStash == "trunk" then
+            invId = ("trunk:%s"):format(id)
+        elseif typeofStash == "glovebox" then
+            invId = ("glovebox:%s"):format(id)
+        else
+            return {}
+        end
+
+        local result = exports.one_inventory:GetInventoryItems(invId)
+        if type(result) ~= "table" then return {} end
+
+        local items = {}
+        for _, item in pairs(result) do
+            if type(item) == "table" and item.name then
+                items[#items + 1] = {
+                    name = item.name,
+                    amount = item.count or item.amount,
+                    slot = item.slot,
+                    metadata = item.metadata,
+                }
+            end
+        end
+        return items
+    elseif isResourceRunning('ox_inventory') then
         if typeofStash == "stash" then

         else

bridge/server/player.lua


 ---@return boolean
 function player:canCarryItem(source, playerObj, item, count)
-    if GetResourceState("ox_inventory") == "started" then
+    if GetResourceState("one_inventory") == "started" then
+        return exports.one_inventory:CanCarryItem(source, item, count) == true
+    elseif GetResourceState("ox_inventory") == "started" then
         return exports.ox_inventory:CanCarryItem(source, item, count) == true
     elseif GetResourceState("origen_inventory") == "started" then
         return exports.origen_inventory:CanCarryItem(source, item, count)
 ---@return {name:string, label:string, grade: {name:string, level:number, isboss:boolean}} | nil
 function player.getGang(playerObj)
     if GetResourceState("op-crime") == "started" then
-        local gangName = Player(playerObj.PlayerData.source).state.gangName
-        local gangId = Player(playerObj.PlayerData.source).state.gangId
-        local rank = Player(playerObj.PlayerData.source).state.gangRankName
+        local source = playerObj.source or playerObj.PlayerData.source
+        local gangName = Player(source).state.gangName
+        local gangId = Player(source).state.gangId
+        local rank = Player(source).state.gangRankName
         return {
             name = gangId,
             label = gangName,

bridge/shared/inventory.lua

 local images = {
     { name = "tgiann-inventory",   path = "https://cfx-nui-inventory_images/images/%s.png" },
     { name = "jaksam_inventory",   path = "https://cfx-nui-jaksam_inventory/_images/%s.png" },
+    { name = "one_inventory",      path = "https://cfx-nui-one_inventory/web/images/%s.png" },
     { name = "ak47_inventory",     path = "https://cfx-nui-ak47_inventory/web/build/images/%s.png" },
     { name = "ak47_qb_inventory",  path = "https://cfx-nui-ak47_qb_inventory/web/build/images/%s.png" },
     { name = "qs-inventory",       path = "https://cfx-nui-qs-inventory/html/images/%s.png" },
         return list
     end

+    if Config.inventory.name == "one_inventory" then
+        local result = exports.one_inventory:GetAllItemDefinitions()
+        local list = {}
+        for name, v in pairs(result or {}) do
+            if type(v) == "table" then
+                list[#list + 1] = {
+                    name = v.name or name,
+                    label = v.label or v.name or name,
+                    image = v.image
+                }
+            end
+        end
+        return list
+    end
+
     if Config.inventory.name == "ak47_inventory" then
         local result = exports.ak47_inventory:Items()
         local list = {}

Full list of files changed

Change File
Modified config/config.json
Modified config/permissions.json
Modified fxmanifest.lua
Modified bridge/server/inventory.lua
Modified bridge/server/player.lua
Modified bridge/shared/inventory.lua
Modified client/modules/nui.lua
Modified web/
Modified server/index.js