updates
This commit is contained in:
@@ -11,39 +11,39 @@
|
|||||||
"id": "temperature",
|
"id": "temperature",
|
||||||
"name": "Temperature",
|
"name": "Temperature",
|
||||||
"models": [
|
"models": [
|
||||||
{ "id": "vega-temp-10", "name": "VegaTemp 10", "units": ["degC", "degF"] },
|
{ "id": "vega-temp-10", "name": "VegaTemp 10", "units": ["degC", "degF"], "product_model_id": 1001, "product_model_uuid": "vega-temp-10" },
|
||||||
{ "id": "vega-temp-20", "name": "VegaTemp 20", "units": ["degC", "degF"] }
|
{ "id": "vega-temp-20", "name": "VegaTemp 20", "units": ["degC", "degF"], "product_model_id": 1002, "product_model_uuid": "vega-temp-20" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pressure",
|
"id": "pressure",
|
||||||
"name": "Pressure",
|
"name": "Pressure",
|
||||||
"models": [
|
"models": [
|
||||||
{ "id": "vega-pressure-10", "name": "VegaPressure 10", "units": ["bar", "mbar", "psi"] },
|
{ "id": "vega-pressure-10", "name": "VegaPressure 10", "units": ["bar", "mbar", "psi"], "product_model_id": 1003, "product_model_uuid": "vega-pressure-10" },
|
||||||
{ "id": "vega-pressure-20", "name": "VegaPressure 20", "units": ["bar", "mbar", "psi"] }
|
{ "id": "vega-pressure-20", "name": "VegaPressure 20", "units": ["bar", "mbar", "psi"], "product_model_id": 1004, "product_model_uuid": "vega-pressure-20" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flow",
|
"id": "flow",
|
||||||
"name": "Flow",
|
"name": "Flow",
|
||||||
"models": [
|
"models": [
|
||||||
{ "id": "vega-flow-10", "name": "VegaFlow 10", "units": ["m3/h", "gpm", "l/min"] },
|
{ "id": "vega-flow-10", "name": "VegaFlow 10", "units": ["m3/h", "gpm", "l/min"], "product_model_id": 1005, "product_model_uuid": "vega-flow-10" },
|
||||||
{ "id": "vega-flow-20", "name": "VegaFlow 20", "units": ["m3/h", "gpm", "l/min"] }
|
{ "id": "vega-flow-20", "name": "VegaFlow 20", "units": ["m3/h", "gpm", "l/min"], "product_model_id": 1006, "product_model_uuid": "vega-flow-20" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "level",
|
"id": "level",
|
||||||
"name": "Level",
|
"name": "Level",
|
||||||
"models": [
|
"models": [
|
||||||
{ "id": "vega-level-10", "name": "VegaLevel 10", "units": ["m", "ft", "mm"] },
|
{ "id": "vega-level-10", "name": "VegaLevel 10", "units": ["m", "ft", "mm"], "product_model_id": 1007, "product_model_uuid": "vega-level-10" },
|
||||||
{ "id": "vega-level-20", "name": "VegaLevel 20", "units": ["m", "ft", "mm"] }
|
{ "id": "vega-level-20", "name": "VegaLevel 20", "units": ["m", "ft", "mm"], "product_model_id": 1008, "product_model_uuid": "vega-level-20" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "oxygen",
|
"id": "oxygen",
|
||||||
"name": "Quantity (oxygen)",
|
"name": "Quantity (oxygen)",
|
||||||
"models": [
|
"models": [
|
||||||
{ "id": "vega-oxy-10", "name": "VegaOxySense 10", "units": ["g/m3", "mol/m3"] }
|
{ "id": "vega-oxy-10", "name": "VegaOxySense 10", "units": ["g/m3", "mol/m3"], "product_model_id": 1009, "product_model_uuid": "vega-oxy-10" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
2
index.js
2
index.js
@@ -15,6 +15,7 @@ const configUtils = require('./src/helper/configUtils.js');
|
|||||||
const assertions = require('./src/helper/assertionUtils.js')
|
const assertions = require('./src/helper/assertionUtils.js')
|
||||||
const coolprop = require('./src/coolprop-node/src/index.js');
|
const coolprop = require('./src/coolprop-node/src/index.js');
|
||||||
const gravity = require('./src/helper/gravity.js')
|
const gravity = require('./src/helper/gravity.js')
|
||||||
|
const assetApiConfig = require('./src/configs/assetApiConfig.js');
|
||||||
|
|
||||||
// Domain-specific modules
|
// Domain-specific modules
|
||||||
const { MeasurementContainer } = require('./src/measurements/index.js');
|
const { MeasurementContainer } = require('./src/measurements/index.js');
|
||||||
@@ -34,6 +35,7 @@ module.exports = {
|
|||||||
predict,
|
predict,
|
||||||
interpolation,
|
interpolation,
|
||||||
configManager,
|
configManager,
|
||||||
|
assetApiConfig,
|
||||||
outputUtils,
|
outputUtils,
|
||||||
configUtils,
|
configUtils,
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
15
src/configs/assetApiConfig.js
Normal file
15
src/configs/assetApiConfig.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const BASE_URL = 'http://localhost:8000';
|
||||||
|
const AUTHORIZATION = '4a49332a-fc3e-11f0-bf0a-9457f8d645d9';
|
||||||
|
const CSRF_TOKEN = 'dcWLY6luSVuQu4mIlKNCGlk3i9VzG9n3p2pxihcm';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
baseUrl: BASE_URL,
|
||||||
|
registerPath: '/assets/store',
|
||||||
|
updatePath: (tag) => `/assets/${encodeURIComponent(tag)}/edit`,
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
Authorization: AUTHORIZATION,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': CSRF_TOKEN
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -117,6 +117,14 @@
|
|||||||
"description": "Asset tag code which is a unique identifier for this asset. May be null if not assigned."
|
"description": "Asset tag code which is a unique identifier for this asset. May be null if not assigned."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tagNumber": {
|
||||||
|
"default": null,
|
||||||
|
"rules": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Asset tag number assigned by the asset registry. May be null if not assigned."
|
||||||
|
}
|
||||||
|
},
|
||||||
"geoLocation": {
|
"geoLocation": {
|
||||||
"default": {
|
"default": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
@@ -166,6 +174,10 @@
|
|||||||
{
|
{
|
||||||
"value": "sensor",
|
"value": "sensor",
|
||||||
"description": "A device that detects or measures a physical property and responds to it (e.g. temperature sensor)."
|
"description": "A device that detects or measures a physical property and responds to it (e.g. temperature sensor)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "measurement",
|
||||||
|
"description": "Measurement software category used by the asset menu for this node."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -208,6 +220,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"assetRegistration": {
|
||||||
|
"profileId": 1,
|
||||||
|
"locationId": 1,
|
||||||
|
"processId": 1,
|
||||||
|
"status": "actief",
|
||||||
|
"childAssets": []
|
||||||
|
},
|
||||||
"scaling": {
|
"scaling": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"default": false,
|
"default": false,
|
||||||
|
|||||||
@@ -1,3 +1,191 @@
|
|||||||
export function getAssetVariables() {
|
const http = require('node:http');
|
||||||
|
const https = require('node:https');
|
||||||
|
const { URL } = require('node:url');
|
||||||
|
const { assetCategoryManager } = require('../../datasets/assetData');
|
||||||
|
|
||||||
}
|
function toNumber(value, fallback = 1) {
|
||||||
|
const result = Number(value);
|
||||||
|
return Number.isFinite(result) && result > 0 ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(value = []) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter((item) => typeof item !== 'undefined' && item !== null);
|
||||||
|
}
|
||||||
|
if (typeof value === 'string' && value.trim()) {
|
||||||
|
return [value.trim()];
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return [value];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function findModelMetadata(selection = {}) {
|
||||||
|
if (!selection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryKey = selection.softwareType || 'measurement';
|
||||||
|
if (!assetCategoryManager.hasCategory(categoryKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suppliers = assetCategoryManager.getCategory(categoryKey).suppliers || [];
|
||||||
|
const supplierMatch = (entry, value) => {
|
||||||
|
if (!entry || !value) return false;
|
||||||
|
const key = value.toString().toLowerCase();
|
||||||
|
return (
|
||||||
|
(entry.id && entry.id.toLowerCase() === key) ||
|
||||||
|
(entry.name && entry.name.toLowerCase() === key)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supplier = suppliers.find((item) => supplierMatch(item, selection.supplier));
|
||||||
|
const types = supplier?.types || [];
|
||||||
|
const type = types.find((item) => supplierMatch(item, selection.assetType));
|
||||||
|
const models = type?.models || [];
|
||||||
|
const model = models.find((item) => supplierMatch(item, selection.model));
|
||||||
|
|
||||||
|
return model || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAssetPayload({ assetSelection = {}, registrationDefaults = {} }) {
|
||||||
|
const defaults = {
|
||||||
|
profileId: 1,
|
||||||
|
locationId: 1,
|
||||||
|
processId: 1,
|
||||||
|
status: 'actief',
|
||||||
|
childAssets: [],
|
||||||
|
...registrationDefaults
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata = assetSelection.modelMetadata || findModelMetadata(assetSelection) || {};
|
||||||
|
const rawName = assetSelection.assetName || assetSelection.name || assetSelection.assetType || assetSelection.model;
|
||||||
|
const assetName = (rawName || 'Measurement asset').toString();
|
||||||
|
const assetDescription = (assetSelection.assetDescription || assetSelection.description || assetName).toString();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
profile_id: toNumber(defaults.profileId, 1),
|
||||||
|
location_id: toNumber(defaults.locationId, 1),
|
||||||
|
process_id: toNumber(defaults.processId, 1),
|
||||||
|
asset_name: assetName,
|
||||||
|
asset_description: assetDescription,
|
||||||
|
asset_status: (assetSelection.assetStatus || defaults.status || 'actief').toString(),
|
||||||
|
product_model_id: metadata.product_model_id || metadata.id || assetSelection.model || null,
|
||||||
|
product_model_uuid: metadata.product_model_uuid || metadata.uuid || metadata.id || assetSelection.model || null,
|
||||||
|
child_assets: toArray(defaults.childAssets)
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagNumber = typeof assetSelection.tagNumber === 'string' && assetSelection.tagNumber.trim()
|
||||||
|
? assetSelection.tagNumber.trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
payload,
|
||||||
|
tagNumber,
|
||||||
|
isUpdate: Boolean(tagNumber)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHeaders(headers = {}, body = '') {
|
||||||
|
const normalized = { ...headers };
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(normalized, 'Content-Length')) {
|
||||||
|
normalized['Content-Length'] = Buffer.byteLength(body);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareUrl(baseUrl = '', path = '') {
|
||||||
|
const trimmedBase = (baseUrl || '').replace(/\/+$/g, '').replace(/\\/g, '/');
|
||||||
|
const trimmedPath = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
if (!trimmedBase) {
|
||||||
|
return trimmedPath;
|
||||||
|
}
|
||||||
|
return `${trimmedBase}${trimmedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendHttpRequest(url, method, headers = {}, body = '') {
|
||||||
|
const parsedUrl = new URL(url, 'http://localhost');
|
||||||
|
const agent = parsedUrl.protocol === 'https:' ? https : http;
|
||||||
|
const requestOptions = {
|
||||||
|
method,
|
||||||
|
hostname: parsedUrl.hostname,
|
||||||
|
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
||||||
|
path: `${parsedUrl.pathname}${parsedUrl.search}`,
|
||||||
|
headers: normalizeHeaders(headers, body)
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = agent.request(requestOptions, (res) => {
|
||||||
|
let raw = '';
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', (chunk) => { raw += chunk; });
|
||||||
|
res.on('end', () => resolve({ status: res.statusCode, body: raw }));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', reject);
|
||||||
|
if (body) {
|
||||||
|
req.write(body);
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseApiResponse(raw, status) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return {
|
||||||
|
success: parsed.success === true,
|
||||||
|
data: parsed.data || null,
|
||||||
|
message: parsed.message || (status >= 400 ? `HTTP ${status}` : 'Result returned')
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
data: raw,
|
||||||
|
message: `Unable to decode asset API response: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncAsset({ assetSelection = {}, registrationDefaults = {}, apiConfig = {}, nodeContext = {} }) {
|
||||||
|
const { payload, tagNumber, isUpdate } = buildAssetPayload({ assetSelection, registrationDefaults });
|
||||||
|
if (!apiConfig || !apiConfig.baseUrl) {
|
||||||
|
const message = 'Asset API configuration is missing';
|
||||||
|
console.warn('[assetUtils] ' + message, { nodeContext });
|
||||||
|
return { success: false, data: null, message };
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = isUpdate && tagNumber && typeof apiConfig.updatePath === 'function'
|
||||||
|
? apiConfig.updatePath(tagNumber)
|
||||||
|
: apiConfig.registerPath;
|
||||||
|
const url = prepareUrl(apiConfig.baseUrl, path);
|
||||||
|
const method = isUpdate ? 'PUT' : 'POST';
|
||||||
|
const headers = apiConfig.headers || {};
|
||||||
|
|
||||||
|
console.info('[assetUtils] Sending asset update', { nodeContext, method, url });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await sendHttpRequest(url, method, headers, JSON.stringify(payload));
|
||||||
|
const parsed = parseApiResponse(response.body, response.status);
|
||||||
|
return {
|
||||||
|
success: parsed.success,
|
||||||
|
data: parsed.data,
|
||||||
|
message: parsed.message
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[assetUtils] Asset API request failed', error, { nodeContext });
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: `Asset API request error: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
syncAsset,
|
||||||
|
buildAssetPayload,
|
||||||
|
findModelMetadata
|
||||||
|
};
|
||||||
@@ -75,6 +75,7 @@ class AssetMenu {
|
|||||||
const htmlCode = this.getHtmlInjectionCode(nodeName);
|
const htmlCode = this.getHtmlInjectionCode(nodeName);
|
||||||
const dataCode = this.getDataInjectionCode(nodeName);
|
const dataCode = this.getDataInjectionCode(nodeName);
|
||||||
const eventsCode = this.getEventInjectionCode(nodeName);
|
const eventsCode = this.getEventInjectionCode(nodeName);
|
||||||
|
const syncCode = this.getSyncInjectionCode(nodeName);
|
||||||
const saveCode = this.getSaveInjectionCode(nodeName);
|
const saveCode = this.getSaveInjectionCode(nodeName);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -85,6 +86,7 @@ class AssetMenu {
|
|||||||
${htmlCode}
|
${htmlCode}
|
||||||
${dataCode}
|
${dataCode}
|
||||||
${eventsCode}
|
${eventsCode}
|
||||||
|
${syncCode}
|
||||||
${saveCode}
|
${saveCode}
|
||||||
|
|
||||||
window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) {
|
window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = function(node) {
|
||||||
@@ -285,6 +287,9 @@ class AssetMenu {
|
|||||||
const activeModel = models.find(
|
const activeModel = models.find(
|
||||||
(model) => (model.id || model.name) === node.model
|
(model) => (model.id || model.name) === node.model
|
||||||
);
|
);
|
||||||
|
if (activeModel) {
|
||||||
|
node.modelMetadata = activeModel;
|
||||||
|
}
|
||||||
populate(
|
populate(
|
||||||
elems.unit,
|
elems.unit,
|
||||||
activeModel ? activeModel.units || [] : [],
|
activeModel ? activeModel.units || [] : [],
|
||||||
@@ -292,6 +297,7 @@ class AssetMenu {
|
|||||||
(unit) => ({ value: unit, label: unit }),
|
(unit) => ({ value: unit, label: unit }),
|
||||||
activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection'
|
activeModel ? 'Select...' : activeType ? 'Awaiting Model Selection' : 'Awaiting Type Selection'
|
||||||
);
|
);
|
||||||
|
this.setAssetTagNumber(node, node.assetTagNumber || '');
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -381,6 +387,7 @@ class AssetMenu {
|
|||||||
(type) => ({ value: type.id || type.name, label: type.name }),
|
(type) => ({ value: type.id || type.name, label: type.name }),
|
||||||
supplier ? 'Select...' : 'Awaiting Supplier Selection'
|
supplier ? 'Select...' : 'Awaiting Supplier Selection'
|
||||||
);
|
);
|
||||||
|
node.modelMetadata = null;
|
||||||
populate(elems.model, [], '', undefined, 'Awaiting Type Selection');
|
populate(elems.model, [], '', undefined, 'Awaiting Type Selection');
|
||||||
populate(elems.unit, [], '', undefined, 'Awaiting Type Selection');
|
populate(elems.unit, [], '', undefined, 'Awaiting Type Selection');
|
||||||
});
|
});
|
||||||
@@ -405,6 +412,7 @@ class AssetMenu {
|
|||||||
(model) => ({ value: model.id || model.name, label: model.name }),
|
(model) => ({ value: model.id || model.name, label: model.name }),
|
||||||
type ? 'Select...' : 'Awaiting Type Selection'
|
type ? 'Select...' : 'Awaiting Type Selection'
|
||||||
);
|
);
|
||||||
|
node.modelMetadata = null;
|
||||||
populate(
|
populate(
|
||||||
elems.unit,
|
elems.unit,
|
||||||
[],
|
[],
|
||||||
@@ -431,6 +439,7 @@ class AssetMenu {
|
|||||||
(item) => (item.id || item.name) === elems.model.value
|
(item) => (item.id || item.name) === elems.model.value
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
node.modelMetadata = model;
|
||||||
populate(
|
populate(
|
||||||
elems.unit,
|
elems.unit,
|
||||||
model ? model.units || [] : [],
|
model ? model.units || [] : [],
|
||||||
@@ -443,6 +452,84 @@ class AssetMenu {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSyncInjectionCode(nodeName) {
|
||||||
|
return `
|
||||||
|
// Asset synchronization helpers for ${nodeName}
|
||||||
|
window.EVOLV.nodes.${nodeName}.assetMenu.setAssetTagNumber = function(node, tag) {
|
||||||
|
const normalized = tag ? tag.toString() : '';
|
||||||
|
const input = document.getElementById('node-input-assetTagNumber');
|
||||||
|
const hint = document.getElementById('node-input-assetTagNumber-hint');
|
||||||
|
if (input) {
|
||||||
|
input.value = normalized;
|
||||||
|
}
|
||||||
|
if (hint) {
|
||||||
|
hint.textContent = normalized ? 'Assigned tag ' + normalized : 'Not registered yet';
|
||||||
|
}
|
||||||
|
if (node) {
|
||||||
|
node.assetTagNumber = normalized;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.EVOLV.nodes.${nodeName}.assetMenu.buildSyncRequest = function(node) {
|
||||||
|
const tagInput = document.getElementById('node-input-assetTagNumber');
|
||||||
|
const candidateTag = tagInput && tagInput.value ? tagInput.value.trim() : '';
|
||||||
|
const fallbackTag = node && node.assetTagNumber ? node.assetTagNumber : '';
|
||||||
|
const registrationDefaults =
|
||||||
|
(window.EVOLV.nodes.${nodeName}.config && window.EVOLV.nodes.${nodeName}.config.assetRegistration) || {};
|
||||||
|
const displayName = node && node.name ? node.name : node && node.id ? node.id : '${nodeName}';
|
||||||
|
return {
|
||||||
|
asset: {
|
||||||
|
tagNumber: candidateTag || fallbackTag,
|
||||||
|
supplier: node && node.supplier ? node.supplier : '',
|
||||||
|
assetType: node && node.assetType ? node.assetType : '',
|
||||||
|
model: node && node.model ? node.model : '',
|
||||||
|
unit: node && node.unit ? node.unit : '',
|
||||||
|
assetName: displayName,
|
||||||
|
assetDescription: displayName,
|
||||||
|
assetStatus: registrationDefaults.status || 'actief',
|
||||||
|
modelMetadata: node && node.modelMetadata ? node.modelMetadata : null
|
||||||
|
},
|
||||||
|
nodeId: node && node.id ? node.id : null,
|
||||||
|
nodeName: node && node.type ? node.type : '${nodeName}'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.EVOLV.nodes.${nodeName}.assetMenu.syncAsset = function(node) {
|
||||||
|
const payload = this.buildSyncRequest(node);
|
||||||
|
const redSettings = window.RED && window.RED.settings;
|
||||||
|
const adminRoot = redSettings ? redSettings.httpAdminRoot : '';
|
||||||
|
const trimmedRoot = adminRoot && adminRoot.endsWith('/') ? adminRoot.slice(0, -1) : adminRoot || '';
|
||||||
|
const prefix = trimmedRoot || '';
|
||||||
|
const endpoint = (prefix || '') + '/${nodeName}/asset-reg';
|
||||||
|
fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
.then((res) =>
|
||||||
|
res.json().catch((err) => {
|
||||||
|
console.warn('[AssetMenu] asset sync response is not JSON', err);
|
||||||
|
return { success: false, message: err.message || 'Invalid API response' };
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((result) => {
|
||||||
|
if (result && result.success) {
|
||||||
|
const newTag = (result.data && result.data.asset_tag_number) || payload.asset.tagNumber || '';
|
||||||
|
this.setAssetTagNumber(node, newTag);
|
||||||
|
if (window.RED && typeof window.RED.notify === 'function') {
|
||||||
|
window.RED.notify('Asset synced: ' + (newTag || 'no tag'), 'info');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[AssetMenu] asset sync failed', result && result.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('[AssetMenu] asset sync error', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
getHtmlTemplate() {
|
getHtmlTemplate() {
|
||||||
return `
|
return `
|
||||||
<!-- Asset Properties -->
|
<!-- Asset Properties -->
|
||||||
@@ -464,6 +551,11 @@ class AssetMenu {
|
|||||||
<label for="node-input-unit"><i class="fa fa-balance-scale"></i> Unit</label>
|
<label for="node-input-unit"><i class="fa fa-balance-scale"></i> Unit</label>
|
||||||
<select id="node-input-unit" style="width:70%;"></select>
|
<select id="node-input-unit" style="width:70%;"></select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-assetTagNumber"><i class="fa fa-hashtag"></i> Asset Tag</label>
|
||||||
|
<input type="text" id="node-input-assetTagNumber" readonly style="width:70%;" />
|
||||||
|
<div class="form-tips" id="node-input-assetTagNumber-hint">Not registered yet</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -505,7 +597,7 @@ class AssetMenu {
|
|||||||
|
|
||||||
node.category = resolveCategoryKey();
|
node.category = resolveCategoryKey();
|
||||||
|
|
||||||
const fields = ['supplier', 'assetType', 'model', 'unit'];
|
const fields = ['supplier', 'assetType', 'model', 'unit', 'assetTagNumber'];
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
@@ -528,6 +620,10 @@ class AssetMenu {
|
|||||||
}, {});
|
}, {});
|
||||||
console.log('[AssetMenu] save result:', saved);
|
console.log('[AssetMenu] save result:', saved);
|
||||||
|
|
||||||
|
if (errors.length === 0 && this.syncAsset) {
|
||||||
|
this.syncAsset(node);
|
||||||
|
}
|
||||||
|
|
||||||
return errors.length === 0;
|
return errors.length === 0;
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user