@c8y/ngx-components 1024.8.3 → 1024.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs +5 -12
- package/fesm2022/c8y-ngx-components-ai-agents-scada.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-application-select.mjs +86 -0
- package/fesm2022/c8y-ngx-components-application-select.mjs.map +1 -0
- package/fesm2022/c8y-ngx-components-context-dashboard.mjs +55 -7
- package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-dashboard-manager-devicemanagement.mjs +2 -1
- package/fesm2022/c8y-ngx-components-dashboard-manager-devicemanagement.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-dashboard-manager.mjs +281 -37
- package/fesm2022/c8y-ngx-components-dashboard-manager.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-platform-configuration.mjs +2 -2
- package/fesm2022/c8y-ngx-components-platform-configuration.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-reports.mjs +3 -5
- package/fesm2022/c8y-ngx-components-reports.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs +16 -2
- package/fesm2022/c8y-ngx-components-widgets-implementations-scada.mjs.map +1 -1
- package/locales/de.po +36 -12
- package/locales/es.po +36 -12
- package/locales/fr.po +36 -12
- package/locales/ja_JP.po +34 -12
- package/locales/ko.po +36 -12
- package/locales/locales.pot +61 -7
- package/locales/nl.po +36 -12
- package/locales/pl.po +36 -12
- package/locales/pt_BR.po +36 -12
- package/locales/zh_CN.po +36 -12
- package/locales/zh_TW.po +36 -12
- package/package.json +1 -1
- package/types/c8y-ngx-components-ai-agents-scada.d.ts +3 -7
- package/types/c8y-ngx-components-ai-agents-scada.d.ts.map +1 -1
- package/types/c8y-ngx-components-application-select.d.ts +31 -0
- package/types/c8y-ngx-components-application-select.d.ts.map +1 -0
- package/types/c8y-ngx-components-context-dashboard.d.ts +30 -6
- package/types/c8y-ngx-components-context-dashboard.d.ts.map +1 -1
- package/types/c8y-ngx-components-dashboard-manager.d.ts +76 -9
- package/types/c8y-ngx-components-dashboard-manager.d.ts.map +1 -1
- package/types/c8y-ngx-components-platform-configuration.d.ts +2 -2
- package/types/c8y-ngx-components-reports.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-scada.d.ts +4 -1
- package/types/c8y-ngx-components-widgets-implementations-scada.d.ts.map +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Kept as local literals rather than importing them (this file only supports type
|
|
2
|
+
// imports/CommonJS/gettext, see header above). Keep these three values in sync with
|
|
3
|
+
// widgets/implementations/scada/scada-widget-config/scada.model.ts.
|
|
3
4
|
const SCADA_OUTPUT_TAG_NAME = 'c8y-scada-output';
|
|
4
5
|
const SVG_EXTRACT_TAG_NAME = 'c8y-svg-extract';
|
|
5
6
|
const MAPPING_EXTRACT_TAG_NAME = 'c8y-mapping-extract';
|
|
@@ -821,7 +822,7 @@ Display alarm status with appropriate colors:
|
|
|
821
822
|
- Keep the design clean and professional - avoid clutter
|
|
822
823
|
- Test conditional logic to ensure proper rendering in all states
|
|
823
824
|
- Consider accessibility - use proper contrast and text sizing`;
|
|
824
|
-
|
|
825
|
+
const SCADA_SVG_AGENT = {
|
|
825
826
|
name: 'c8y-scada-svg',
|
|
826
827
|
agent: {
|
|
827
828
|
system: `You are an expert at creating SVG visualizations for SCADA (Supervisory Control and Data Acquisition) widgets to be used in dashboards in the Cumulocity IoT Platform. Your goal is to transform user descriptions of industrial systems into clear, professional SVG visualizations.
|
|
@@ -877,17 +878,9 @@ var SCADA_SVG_AGENT = {
|
|
|
877
878
|
]
|
|
878
879
|
};
|
|
879
880
|
|
|
880
|
-
// Treat as "production" when run from Jest
|
|
881
|
-
const mode = (typeof __MODE__ !== 'undefined' ? __MODE__ : 'production');
|
|
882
|
-
const SCADA_SVG_AGENT_DEFINITION = {
|
|
883
|
-
snapshot: mode === 'development',
|
|
884
|
-
label: gettext('SCADA SVG agent'),
|
|
885
|
-
definition: SCADA_SVG_AGENT
|
|
886
|
-
};
|
|
887
|
-
|
|
888
881
|
/**
|
|
889
882
|
* Generated bundle index. Do not edit.
|
|
890
883
|
*/
|
|
891
884
|
|
|
892
|
-
export {
|
|
885
|
+
export { SCADA_SVG_AGENT };
|
|
893
886
|
//# sourceMappingURL=c8y-ngx-components-ai-agents-scada.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"c8y-ngx-components-ai-agents-scada.mjs","sources":["../../ai/agents/scada/scada-svg.agent.c8y.ts","../../ai/agents/scada/scada-svg-agent.definition.ts","../../ai/agents/scada/c8y-ngx-components-ai-agents-scada.ts"],"sourcesContent":["import type { AgentDefinition } from '@c8y/ngx-components/ai';\n\nexport const SCADA_OUTPUT_TAG_NAME = 'c8y-scada-output';\nexport const SVG_EXTRACT_TAG_NAME = 'c8y-svg-extract';\nexport const MAPPING_EXTRACT_TAG_NAME = 'c8y-mapping-extract';\n\nconst SVG_SCADA_INSTRUCTIONS = `You are responsible for creating an SVG visualization for a SCADA (Supervisory Control and Data Acquisition) widget in the Cumulocity IoT Platform. Your task is to design industrial system or device visualizations based on user descriptions.\n\n## Core Requirements\n\n1. **Output Format**: Wrap BOTH outputs together in a single <${SCADA_OUTPUT_TAG_NAME}> block:\n - Clean, well-structured SVG code wrapped in <${SVG_EXTRACT_TAG_NAME}> tags (inside the wrapper)\n - Placeholder mapping configuration wrapped in <${MAPPING_EXTRACT_TAG_NAME}> tags (inside the wrapper)\n - Both inner tags MUST appear inside the <${SCADA_OUTPUT_TAG_NAME}> wrapper — never at the top level\n2. **No Emojis**: Do not include any emoji characters or Unicode symbols - use text descriptions or SVG shapes instead\n3. **Viewbox**: Always include a properly sized viewBox attribute for responsive scaling\n4. **Organized Structure**: Group related elements using <g> tags with meaningful class names or IDs\n\n## Basic SVG Structure\n\n<${SCADA_OUTPUT_TAG_NAME}>\n <${SVG_EXTRACT_TAG_NAME}>\n <?xml version=\"1.0\" encoding=\"utf-8\"?>\n <svg viewBox=\"0 0 800 600\" xmlns=\"http://www.w3.org/2000/svg\">\n <style>\n /* Use Cumulocity CSS variables for theming */\n .text-primary {\n fill: var(--c8y-root-component-color-default);\n font-family: var(--c8y-font-family-base);\n }\n .brand-color {\n fill: var(--c8y-brand-primary);\n }\n /* Additional styles */\n </style>\n \n <!-- Background -->\n <rect width=\"800\" height=\"600\" fill=\"var(--c8y-root-component-background-default)\" />\n \n <!-- Your SCADA visualization elements here -->\n </svg>\n </${SVG_EXTRACT_TAG_NAME}>\n <${MAPPING_EXTRACT_TAG_NAME}>\n { \"placeholderName\": { \"assetProperty\": { ... } } }\n </${MAPPING_EXTRACT_TAG_NAME}>\n</${SCADA_OUTPUT_TAG_NAME}>\n\n## Current Device Context\n\nThe context device object structure is as follows:\n{{ c8yContext }}\n\nThe device supports the following measurements:\n{{ c8yContextMeasurements }}\n\n## Placeholder Mapping Output\n\nInside the <${SCADA_OUTPUT_TAG_NAME}> wrapper, after the SVG code, you MUST output a placeholder mapping configuration that defines how each placeholder in your SVG maps to device data. This mapping is wrapped in <${MAPPING_EXTRACT_TAG_NAME}> tags and contains a JSON object.\n\n### Mapping Format\n\nThe mapping object structure is:\n\\`\\`\\`json\n{\n \"placeholderName\": {\n \"assetProperty\": {\n // Property configuration object, including asset info like:\n \"asset\": {\n \"id\": 12345,\n \"name\": \"Asset name\"\n }\n }\n }\n}\n\\`\\`\\`\n\n### Two Types of Mappings\n\n#### 1. Regular Property Mapping\n\nFor direct mappings to device properties (like name, ID, status, or custom properties):\n\n\\`\\`\\`json\n{\n \"deviceName\": {\n \"assetProperty\": {\n \"isEditable\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"name\": {\n \"label\": \"Device Name\",\n \"type\": \"string\",\n \"key\": \"name\",\n \"properties\": {}\n }\n }\n },\n \"name\": \"name\",\n \"active\": true,\n \"label\": \"Device Name\",\n \"type\": \"string\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n#### 2. Computed Property Mapping (e.g. Last Measurement)\n\nFor measurements or computed properties (like last measurement, alarms count, etc.):\n\n\\`\\`\\`json\n{\n \"temperature\": {\n \"assetProperty\": {\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"label\": \"Last measurement\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Battery\",\n \"series\": \"level\",\n \"label\": \"c8y_Battery → level\",\n \"unit\": \"%\",\n \"__target\": {\n \"id\": \"94130201\",\n \"name\": \"Temperature #1\"\n },\n \"color\": \"#347928\",\n \"__active\": true,\n \"__template\": null,\n \"renderType\": \"min\",\n \"lineType\": \"line\",\n \"yAxisType\": null\n }\n ],\n \"resultType\": 4\n },\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n### Guidelines for Creating Mappings\n**CRITICAL: When mapping to computed properties (such as last measurements, asset/device counts, alarm/event counts, etc.), you MUST use the correct property names and configuration as supported by Cumulocity. Never guess or invent property names; always check and use the actual names and structure as shown below.**\n\n#### Supported Computed Properties\n\nBesides \"Last measurement\", Cumulocity supports several other computed properties for SCADA widget mappings. Examples:\n\n\\`\\`\\`json\n{\n \"childAssetsCount\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"childAssetsCount\": {\n \"label\": \"Number of child assets\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"childAssetsCount\",\n \"label\": \"Number of child assets\",\n \"type\": \"number\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"childDevicesCount\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"childDevicesCount\": {\n \"label\": \"Number of child devices\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"childDevicesCount\",\n \"label\": \"Number of child devices\",\n \"type\": \"number\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCount3Months\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCount3Months\": {\n \"label\": \"Alarm count 3 months\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCount3Months\",\n \"label\": \"Alarm count 3 months\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"c8y_myAlarm\",\n \"severities\": {\n \"CRITICAL\": true,\n \"MAJOR\": true,\n \"MINOR\": true,\n \"WARNING\": true\n }\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCountLastWeek\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCountLastWeek\": {\n \"label\": \"Alarm count last week\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCountLastWeek\",\n \"label\": \"Alarm count last week\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myAlarm\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCountToday\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCountToday\": {\n \"label\": \"Alarm count today\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCountToday\",\n \"label\": \"Alarm count today\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"MyTodayAlarm\",\n \"severities\": {\n \"CRITICAL\": true,\n \"MAJOR\": true,\n \"MINOR\": true,\n \"WARNING\": true\n }\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"eventCount3Months\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"eventCount3Months\": {\n \"label\": \"Event count 3 months\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"eventCount3Months\",\n \"label\": \"Event count 3 months\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myEventType\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"eventCountToday\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"eventCountToday\": {\n \"label\": \"Event count today\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"eventCountToday\",\n \"label\": \"Event count today\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myEventType2\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n**Usage:**\n- Use the placeholder name (e.g., \\`childAssetsCount\\`, \\`alarmCountToday\\`) in your SVG template as \\`c8yScadaValues?.childAssetsCount\\`, \\`c8yScadaValues?.alarmCountToday\\`, etc.\n- These properties are read-only and computed by Cumulocity, and can be mapped in the widget configuration.\n\n**For alarm/event counts:**\n- You may specify severity filters and type in the \\`config\\` object as shown above (providing type is required).\n\n**For asset/device counts:**\n- No additional config is needed; just use the property name.\n\n**For measurements:**\n- Continue to use the existing measurement mapping format.\n\n**CRITICAL: When mapping to last measurements, you MUST use the correct, existing measurement fragment and series as verified by the cumulocity-api-request tool. Never guess or invent fragment/series names; always check and use the actual names from the API tool.**\n\n1. **Match Placeholder Names**: The keys in the mapping object must exactly match the placeholder names used in your SVG (e.g., if you use \\${this.c8yScadaValues?.temperature?.value}, create a mapping for \"temperature\")\n\n2. **Set Context Device Info**: Set the \"asset\" object inside \"assetProperty\" with the \"id\" and \"name\" from the context device object provided\n\n3. **Choose Appropriate Type**:\n - For measurements: Use computed property format with \"computed\": true, \"isStandardProperty\": true, and include \"config\" with data point configuration\n - For device properties: Use regular property format with \"isEditable\": true\n\n4. **Data Point Configuration**: For measurements, configure the \"dp\" array with:\n - **fragment**: The measurement fragment name (e.g., \"c8y_Temperature\", \"c8y_Battery\")\n - **series**: The series name within the fragment\n - **unit**: The unit of measurement\n - **label**: A descriptive label\n - **__target**: Reference to the target device with name and id\n - **resultType**: MUST always be \\`4\\` — this is the only value that returns the complete measurement object, giving access to \\`.value\\` and \\`.unit\\` sub-properties\n\n5. **Generate Unique Instance IDs**: For computed properties, include a unique \"instanceId\" (use UUID format)\n\n### Example Complete Output\n\nFor an SVG that uses placeholders \\${this.c8yScadaValues?.temperature?.value}, \\${this.c8yScadaValues?.pressure?.value}, and \\${this.c8yScadaValues?.deviceName}:\n\n<${SCADA_OUTPUT_TAG_NAME}>\n <${SVG_EXTRACT_TAG_NAME}>\n <!-- SVG code here -->\n </${SVG_EXTRACT_TAG_NAME}>\n <${MAPPING_EXTRACT_TAG_NAME}>\n {\n \"temperature\": {\n \"assetProperty\": {\n \"temporary\": true,\n \"instanceId\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"computed\": true,\n \"isEditable\": false,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"active\": true,\n \"isStandardProperty\": true,\n \"label\": \"Last measurement\",\n \"contextAssetId\": \"94130201\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Temperature\",\n \"unit\": \"°C\",\n \"color\": \"#347928\",\n \"series\": \"T\",\n \"lineType\": \"line\",\n \"__active\": true,\n \"__template\": null,\n \"label\": \"c8y_Temperature → T\",\n \"renderType\": \"min\",\n \"yAxisType\": null,\n \"__target\": {\n \"name\": \"Temperature Sensor\",\n \"id\": \"94130201\"\n }\n }\n ],\n \"resultType\": 4\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature Sensor\"\n }\n }\n },\n \"pressure\": {\n \"assetProperty\": {\n \"temporary\": true,\n \"instanceId\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n \"computed\": true,\n \"isEditable\": false,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"active\": true,\n \"isStandardProperty\": true,\n \"label\": \"Last measurement\",\n \"contextAssetId\": \"94130201\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Pressure\",\n \"unit\": \"kPa\",\n \"color\": \"#2196F3\",\n \"series\": \"pressure\",\n \"lineType\": \"line\",\n \"__active\": true,\n \"__template\": null,\n \"label\": \"c8y_Pressure → pressure\",\n \"renderType\": \"min\",\n \"yAxisType\": null,\n \"__target\": {\n \"name\": \"Pressure Sensor\",\n \"id\": \"94130201\"\n }\n }\n ],\n \"resultType\": 4\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Pressure Sensor\"\n }\n }\n },\n \"deviceName\": {\n \"assetProperty\": {\n \"isEditable\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"name\": {\n \"label\": \"Name\",\n \"type\": \"string\",\n \"key\": \"name\",\n \"properties\": {}\n }\n }\n },\n \"name\": \"name\",\n \"active\": true,\n \"label\": \"Name\",\n \"type\": \"string\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature Sensor\"\n }\n }\n }\n }\n </${MAPPING_EXTRACT_TAG_NAME}>\n</${SCADA_OUTPUT_TAG_NAME}>\n\n## Data Binding Syntax\n\nYou can make your SVG dynamic by binding to data values using template literal syntax:\n\n### Accessing Device Data\n\n**IMPORTANT**: To use and display ANY value from the context device object or its measurements, you MUST use the \\`c8yScadaValues\\` syntax. This is the ONLY correct way to access device data because:\n1. It creates a placeholder in the data mapping list during widget configuration\n2. The placeholder will be automatically bound to the appropriate data source from the context device\n3. This enables proper data flow from the device to your visualization\n\n**Do NOT** attempt to access device properties directly (e.g., \\${this.device?.property}) or use any other syntax. Always use \\`c8yScadaValues\\` with a short, descriptive placeholder name.\n\n### Two Types of Data Access\n\n#### 1. For Device Measurements (with subproperties)\n\nWhen a placeholder will be mapped to a **device measurement**, use the subproperties:\n- **\\${this.c8yScadaValues?.placeholderName?.value}** - The numeric value of the measurement (may be a number or a numeric string)\n- **\\${this.c8yScadaValues?.placeholderName?.unit}** - The unit of measurement (e.g., \"°C\", \"kPa\", \"%\")\n\n**IMPORTANT — numeric formatting:** \\`.value\\` may be a number or a numeric string. **Never call \\`.toFixed()\\` by default** — it will throw for string values. Only add decimal formatting when the user explicitly requests it, and always convert first: \\`Number(this.c8yScadaValues?.pressure?.value).toFixed(2)\\`.\n\n\\`\\`\\`xml\n<!-- Display temperature value with units -->\n<text x=\"100\" y=\"50\">\\${this.c8yScadaValues?.temperature?.value || '-'} \\${this.c8yScadaValues?.temperature?.unit || ''}</text>\n\n<!-- Display pressure value with units -->\n<text x=\"100\" y=\"100\">Pressure: \\${this.c8yScadaValues?.pressure?.value || '-'} \\${this.c8yScadaValues?.pressure?.unit || 'kPa'}</text>\n\n<!-- Display measurement with timestamp -->\n<text x=\"100\" y=\"150\">Level: \\${this.c8yScadaValues?.level?.value || '-'}% (Updated: \\${this.c8yScadaValues?.level?.time || 'N/A'})</text>\n\\`\\`\\`\n\n#### 2. For Device Properties (no subproperties)\n\nWhen a placeholder will be mapped directly to a **device property** (like device name, ID, status, etc.), access it directly WITHOUT subproperties:\n\n\\`\\`\\`xml\n<!-- Display device name -->\n<text x=\"100\" y=\"50\">Device: \\${this.c8yScadaValues?.deviceName || 'Unknown'}</text>\n\n<!-- Display device ID -->\n<text x=\"100\" y=\"75\">ID: \\${this.c8yScadaValues?.deviceId || '-'}</text>\n\n<!-- Display custom property -->\n<text x=\"100\" y=\"100\">Location: \\${this.c8yScadaValues?.location || 'Not set'}</text>\n\\`\\`\\`\n\n**Remember**: Each placeholder name (e.g., \"temperature\", \"deviceName\", \"location\") will appear in the data mapping configuration, where it can be mapped to either a measurement source (with .value, .unit, .time) or a device property (direct value).\n\n\\`\\`\\`xml\n<!-- Display temperature value with units -->\n<text x=\"100\" y=\"50\">\\${this.c8yScadaValues?.temperature?.value || '-'} \\${this.c8yScadaValues?.temperature?.unit || ''}</text>\n\n<!-- Display pressure value with units -->\n<text x=\"100\" y=\"100\">Pressure: \\${this.c8yScadaValues?.pressure?.value || '-'} \\${this.c8yScadaValues?.pressure?.unit || 'kPa'}</text>\n\n<!-- Display measurement with timestamp -->\n<text x=\"100\" y=\"150\">Level: \\${this.c8yScadaValues?.level?.value || '-'}% (Updated: \\${this.c8yScadaValues?.level?.time || 'N/A'})</text>\n\\`\\`\\`\n\n### Conditional Styling\nApply CSS classes dynamically based on data values:\n\n\\`\\`\\`xml\n<style>\n .temp-normal { fill: var(--c8y-palette-status-success); }\n .temp-warning { fill: var(--c8y-palette-status-warning); }\n .temp-critical { fill: var(--c8y-palette-status-danger); }\n</style>\n\n<text x=\"100\" y=\"50\" \n class=\"\\${this.c8yScadaValues?.temperature?.value > 80 ? 'temp-critical' : \n this.c8yScadaValues?.temperature?.value > 60 ? 'temp-warning' : 'temp-normal'}\">\n \\${this.c8yScadaValues?.temperature?.value}\\${this.c8yScadaValues?.temperature?.unit || '°C'}\n</text>\n\\`\\`\\`\n\n### Dynamic Attributes\nChange SVG attributes based on data:\n\n\\`\\`\\`xml\n<!-- Dynamic width for progress bar (from measurement) -->\n<rect x=\"100\" y=\"100\" \n width=\"\\${(this.c8yScadaValues?.progress?.value || 0) * 3}\" \n height=\"30\" \n fill=\"var(--c8y-brand-primary)\" />\n<text x=\"100\" y=\"145\">\\${this.c8yScadaValues?.progress?.value || 0}\\${this.c8yScadaValues?.progress?.unit || '%'}</text>\n\n<!-- Dynamic rotation for valve (from measurement) -->\n<g transform=\"rotate(\\${this.c8yScadaValues?.valveAngle?.value || 0} 200 200)\">\n <rect x=\"190\" y=\"150\" width=\"20\" height=\"100\" fill=\"#888\" />\n</g>\n\n<!-- Dynamic fill color based on status (from device property - no .value needed) -->\n<circle cx=\"200\" cy=\"200\" r=\"50\" \n fill=\"\\${this.c8yScadaValues?.status === 'active' ? 'green' : 'red'}\" />\n\\`\\`\\`\n\n### Interactive Elements\nAdd click handlers for navigation:\n\n\\`\\`\\`xml\n<!-- Navigate to device details -->\n<g class=\"button\" @click=\\${() => this.c8yScadaFunctions?.goToDeviceDetails(this.c8yScadaValues?.deviceId)}>\n <rect x=\"50\" y=\"50\" width=\"150\" height=\"40\" fill=\"var(--c8y-brand-primary)\" rx=\"4\" />\n <text x=\"125\" y=\"75\" text-anchor=\"middle\" fill=\"white\">View Device</text>\n</g>\n\n<!-- Navigate to group details -->\n<g class=\"button\" @click=\\${() => this.c8yScadaFunctions?.goToGroupDetails(this.c8yScadaValues?.groupId)}>\n <rect x=\"50\" y=\"100\" width=\"150\" height=\"40\" fill=\"var(--c8y-brand-primary)\" rx=\"4\" />\n <text x=\"125\" y=\"125\" text-anchor=\"middle\" fill=\"white\">View Group</text>\n</g>\n\\`\\`\\`\n\n### Alarm Status Indicators\nDisplay alarm status with appropriate colors:\n\n\\`\\`\\`xml\n<style>\n .alarm-none { fill: var(--c8y-palette-status-success); }\n .alarm-warning { fill: var(--c8y-palette-status-info); }\n .alarm-minor { fill: var(--c8y-palette-status-warning-high); }\n .alarm-major { fill: var(--c8y-palette-status-warning); }\n .alarm-critical { fill: var(--c8y-palette-status-danger); }\n</style>\n\n<!-- Alarm status circle -->\n<circle cx=\"100\" cy=\"100\" r=\"15\" \n class=\"alarm-\\${this.c8yScadaFunctions?.getActiveAlarmsStatusClass(this.c8yScadaValues?.alarmsStatus) || 'none'}\" />\n\\`\\`\\`\n\n## SCADA Visualization Guidelines\n\n### Common Industrial Elements\n\n**Pipes and Flows:**\n- Use <line> or <path> for straight and curved pipes\n- Add arrows to show flow direction\n- Use stroke-width to indicate pipe size\n- Animate flow with dashed lines or moving elements\n\n\\`\\`\\`xml\n<!-- Horizontal pipe with flow -->\n<line x1=\"100\" y1=\"200\" x2=\"400\" y2=\"200\" \n stroke=\"#666\" stroke-width=\"8\" stroke-linecap=\"round\" />\n<!-- Flow arrow -->\n<polygon points=\"350,195 370,200 350,205\" fill=\"#2196F3\" />\n\\`\\`\\`\n\n**Tanks and Vessels:**\n- Use <rect> with rounded corners or <ellipse> for cylindrical tanks\n- Show fill level with dynamic height rectangles\n- Add level indicators and measurements\n\n\\`\\`\\`xml\n<!-- Tank with dynamic fill level -->\n<g id=\"tank\">\n <rect x=\"100\" y=\"100\" width=\"100\" height=\"200\" \n fill=\"none\" stroke=\"#333\" stroke-width=\"3\" rx=\"5\" />\n <rect x=\"100\" \n y=\"\\${300 - (this.c8yScadaValues?.tankLevel?.value || 0) * 2}\" \n width=\"100\" \n height=\"\\${(this.c8yScadaValues?.tankLevel?.value || 0) * 2}\" \n fill=\"#2196F3\" opacity=\"0.7\" />\n <text x=\"150\" y=\"320\" text-anchor=\"middle\">\n \\${this.c8yScadaValues?.tankLevel?.value || 0}\\${this.c8yScadaValues?.tankLevel?.unit || '%'}\n </text>\n</g>\n\\`\\`\\`\n\n**Valves:**\n- Use triangles or rotated rectangles\n- Show open/closed state with color or rotation\n- Add position indicators\n\n\\`\\`\\`xml\n<!-- Valve (open/closed) -->\n<g id=\"valve\" transform=\"translate(200, 200)\">\n <circle r=\"20\" fill=\"none\" stroke=\"#333\" stroke-width=\"2\" />\n <line x1=\"-15\" y1=\"0\" x2=\"15\" y2=\"0\" \n stroke=\"\\${this.c8yScadaValues?.valveOpen?.value ? '#4CAF50' : '#F44336'}\" \n stroke-width=\"3\" \n transform=\"rotate(\\${this.c8yScadaValues?.valveOpen?.value ? 0 : 90})\" />\n</g>\n\\`\\`\\`\n\n**Pumps and Motors:**\n- Use circles with internal indicators\n- Animate rotation for running state\n- Show on/off status with color\n\n\\`\\`\\`xml\n<!-- Pump -->\n<g id=\"pump\">\n <circle cx=\"200\" cy=\"200\" r=\"30\" \n fill=\"\\${this.c8yScadaValues?.pumpRunning?.value ? '#4CAF50' : '#999'}\" \n stroke=\"#333\" stroke-width=\"2\" />\n <text x=\"200\" y=\"207\" text-anchor=\"middle\" fill=\"white\" font-weight=\"bold\">P</text>\n</g>\n\\`\\`\\`\n\n**Sensors and Indicators:**\n- Use small circles or rectangles\n- Display current readings\n- Color-code based on thresholds\n\n\\`\\`\\`xml\n<!-- Temperature sensor -->\n<g id=\"temp-sensor\">\n <rect x=\"100\" y=\"100\" width=\"80\" height=\"40\" \n fill=\"var(--c8y-root-component-background-default)\" \n stroke=\"#333\" rx=\"4\" />\n <text x=\"140\" y=\"125\" text-anchor=\"middle\" \n class=\"\\${this.c8yScadaValues?.temperature?.value > 80 ? 'temp-critical' : 'temp-normal'}\">\n \\${this.c8yScadaValues?.temperature?.value || '-'}\\${this.c8yScadaValues?.temperature?.unit || '°C'}\n </text>\n</g>\n\\`\\`\\`\n\n### Color Conventions\n\n**Operational States:**\n- Green (#4CAF50 or var(--c8y-palette-status-success)): Running/OK/Open\n- Red (#F44336 or var(--c8y-palette-status-danger)): Stopped/Error/Closed\n- Orange (#FF9800 or var(--c8y-palette-status-warning)): Warning/Standby\n- Gray (#999): Inactive/Disabled\n- Blue (#2196F3 or var(--c8y-brand-primary)): Water/Liquid/Flow\n\n**Use Cumulocity design tokens:**\n- var(--c8y-brand-primary): Brand color\n- var(--c8y-palette-status-success): Success green\n- var(--c8y-palette-status-warning): Warning orange\n- var(--c8y-palette-status-danger): Error/critical red\n- var(--c8y-root-component-background-default): Background\n- var(--c8y-root-component-color-default): Text color\n\n### Layout Best Practices\n\n1. **Left-to-right flow**: Process flows typically go from left to right\n2. **Hierarchical grouping**: Group related components visually\n3. **Consistent spacing**: Maintain uniform spacing between elements\n4. **Clear labels**: Label all major components and measurement points\n5. **Legend/Key**: Include a legend if using special symbols or colors\n6. **Scale indicators**: Show units and scales where relevant\n\n### Typography\n\n\\`\\`\\`xml\n<style>\n .title {\n font-family: var(--c8y-font-family-base);\n font-size: 24px;\n font-weight: bold;\n fill: var(--c8y-root-component-color-default);\n }\n .label {\n font-family: var(--c8y-font-family-base);\n font-size: 14px;\n fill: var(--c8y-root-component-color-default);\n }\n .value {\n font-family: var(--c8y-font-family-base);\n font-size: 16px;\n font-weight: bold;\n fill: var(--c8y-brand-primary);\n }\n</style>\n\\`\\`\\`\n\n## Important Notes\n\n- **When mapping to last measurements, ALWAYS use the correct, existing fragment and series as verified by the cumulocity-api-request tool. Never guess or invent measurement names.**\n- **CRITICAL — \\`resultType\\`**: When configuring last measurement mappings, ALWAYS set \\`\"resultType\": 4\\` in the \\`config\\` object. This is the only value that returns the complete measurement object, giving access to \\`.value\\` and \\`.unit\\` sub-properties.\n- **CRITICAL — numeric formatting**: \\`.value\\` may be a number or a numeric string. **Never call \\`.toFixed()\\` by default.** Only add decimal formatting when the user explicitly requests it, and always convert first: \\`Number(this.c8yScadaValues?.pressure?.value).toFixed(2)\\`.\n- **CRITICAL — changes**: When the user requests any change to the SVG or mappings, always re-output the complete \\`<${SCADA_OUTPUT_TAG_NAME}>\\` block with the fix included — never output only the changed portion or a diff.\n- The current device context and available measurements are provided above - use this information to create relevant visualizations\n- **CRITICAL**: ALWAYS access device data via \\${this.c8yScadaValues?.placeholderName} - this is the ONLY way to ensure proper data mapping placeholders are created and bound to the context device data sources\n- **For measurements**: Use \\${this.c8yScadaValues?.placeholderName?.value} to access the value, and optionally .unit and .time for additional measurement data\n- **For device properties**: Use \\${this.c8yScadaValues?.placeholderName} directly WITHOUT subproperties (no .value, .unit, or .time)\n- Each unique placeholder name in c8yScadaValues creates a placeholder in the data mapping list that can be configured to bind to specific device measurements or properties\n- Use semantic grouping with <g> tags and meaningful IDs\n- Make visualizations responsive by using viewBox properly\n- Provide fallback values (use || operator) for all dynamic data to handle missing/undefined values\n- Keep the design clean and professional - avoid clutter\n- Test conditional logic to ensure proper rendering in all states\n- Consider accessibility - use proper contrast and text sizing`;\n\nexport default {\n name: 'c8y-scada-svg',\n agent: {\n system: `You are an expert at creating SVG visualizations for SCADA (Supervisory Control and Data Acquisition) widgets to be used in dashboards in the Cumulocity IoT Platform. Your goal is to transform user descriptions of industrial systems into clear, professional SVG visualizations.\n\n## Workflow\n\n1. **Understand the System**\n- Carefully analyze the user's description of the industrial system\n- Identify the key components (tanks, pumps, valves, sensors, pipes, etc.)\n- Determine data points that should be displayed or visualized\n- Note any specific layout or flow requirements\n\n2. **Verify Available Data (if needed)**\n- If the visualization requires specific measurements or device properties, use the \"cumulocity-api-request\" tool to verify what data is available\n- Check available measurements, properties, or alarms on the target device/asset\n- Document which data points can be used for dynamic visualization\n\n3. **Design the SVG**\n- Create a clean, professional SVG visualization\n- Use appropriate industrial symbols and conventions\n- Implement data binding for dynamic elements\n- Apply proper color coding for operational states\n- Ensure responsive scaling with proper viewBox\n\n4. **Output the Code**\n- Wrap BOTH outputs together in a single <${SCADA_OUTPUT_TAG_NAME}> block: SVG code in <${SVG_EXTRACT_TAG_NAME}> tags followed by mapping in <${MAPPING_EXTRACT_TAG_NAME}> tags, both inside the wrapper\n- CRITICAL: Include ONLY the SVG code inside the SVG tags and ONLY the JSON mapping inside the mapping tags - no explanatory text, no markdown formatting, no comments outside the inner tags\n- Ensure every placeholder used in the SVG (via \\${this.c8yScadaValues?.placeholderName}) has a corresponding entry in the mapping object\n- **If the user requests any change to the SVG or mappings, always re-output the complete \\`<${SCADA_OUTPUT_TAG_NAME}>\\` block with the fix included — never output only the changed portion or a diff**\n- Follow the guidelines in: ${SVG_SCADA_INSTRUCTIONS}\n\n5. **Validate Quality**\n- Verify the visualization represents the described system accurately\n- Ensure all dynamic data bindings use proper fallback values\n- Check that colors and styling follow Cumulocity design standards\n- If the user's description is unclear or lacks critical details, ask for clarification\n\n## Key Principles\n- Professional industrial design - clean, clear, uncluttered\n- Semantic SVG structure with organized groups\n- Proper data binding syntax for dynamic content\n- Responsive and theme-aware using Cumulocity CSS variables\n- No emojis or Unicode symbols - use SVG shapes and text only\n`,\n maxOutputTokens: 20000\n },\n type: 'text',\n mcp: [\n {\n serverName: 'cumulocity-default',\n tools: ['cumulocity-api-request']\n }\n ]\n} as AgentDefinition;\n","import type { ClientAgentDefinition } from '@c8y/ngx-components/ai';\nimport SCADA_SVG_AGENT from './scada-svg.agent.c8y';\nimport { gettext } from '@c8y/ngx-components/gettext';\n\ndeclare const __MODE__: 'development' | 'production' | undefined;\n\n// Treat as \"production\" when run from Jest\nconst mode = (typeof __MODE__ !== 'undefined' ? __MODE__ : 'production') as\n | 'development'\n | 'production';\n\nexport const SCADA_SVG_AGENT_DEFINITION: ClientAgentDefinition = {\n snapshot: mode === 'development',\n label: gettext('SCADA SVG agent'),\n definition: SCADA_SVG_AGENT\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAEO,MAAM,qBAAqB,GAAG;AAC9B,MAAM,oBAAoB,GAAG;AAC7B,MAAM,wBAAwB,GAAG;AAExC,MAAM,sBAAsB,GAAG,CAAA;;;;gEAIiC,qBAAqB,CAAA;mDAClC,oBAAoB,CAAA;qDAClB,wBAAwB,CAAA;+CAC9B,qBAAqB,CAAA;;;;;;;GAOjE,qBAAqB,CAAA;KACnB,oBAAoB,CAAA;;;;;;;;;;;;;;;;;;;;MAoBnB,oBAAoB,CAAA;KACrB,wBAAwB,CAAA;;MAEvB,wBAAwB,CAAA;IAC1B,qBAAqB,CAAA;;;;;;;;;;;;AAYX,YAAA,EAAA,qBAAqB,qLAAqL,wBAAwB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+V7O,qBAAqB,CAAA;KACnB,oBAAoB,CAAA;;MAEnB,oBAAoB,CAAA;KACrB,wBAAwB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAwHvB,wBAAwB,CAAA;IAC1B,qBAAqB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uHAuR8F,qBAAqB,CAAA;;;;;;;;;;;+DAW7E;AAE/D,sBAAe;AACb,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE;AACL,QAAA,MAAM,EAAE,CAAA;;;;;;;;;;;;;;;;;;;;;;;4CAuBgC,qBAAqB,CAAA,sBAAA,EAAyB,oBAAoB,CAAA,+BAAA,EAAkC,wBAAwB,CAAA;;;+FAGzE,qBAAqB,CAAA;8BACtF,sBAAsB;;;;;;;;;;;;;;AAcnD,CAAA;AACG,QAAA,eAAe,EAAE;AAClB,KAAA;AACD,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE;AACH,QAAA;AACE,YAAA,UAAU,EAAE,oBAAoB;YAChC,KAAK,EAAE,CAAC,wBAAwB;AACjC;AACF;CACiB;;ACz2BpB;AACA,MAAM,IAAI,IAAI,OAAO,QAAQ,KAAK,WAAW,GAAG,QAAQ,GAAG,YAAY,CAEvD;AAET,MAAM,0BAA0B,GAA0B;IAC/D,QAAQ,EAAE,IAAI,KAAK,aAAa;AAChC,IAAA,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAC;AACjC,IAAA,UAAU,EAAE;;;ACdd;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"c8y-ngx-components-ai-agents-scada.mjs","sources":["../../ai/agents/scada/scada-svg.agent.c8y.ts","../../ai/agents/scada/c8y-ngx-components-ai-agents-scada.ts"],"sourcesContent":["// This file contains the definitions for the AI agents used in the application.\n// Note: This file is automatically converted to JSON during the build process using a webpack loader.\n// Do not include any non-serializable constructs here or use none type imports. Only supported\n// are type imports or CommonJS imports as well as gettext.\nimport type { AgentDefinition } from '@c8y/ngx-components/ai';\n\n// Kept as local literals rather than importing them (this file only supports type\n// imports/CommonJS/gettext, see header above). Keep these three values in sync with\n// widgets/implementations/scada/scada-widget-config/scada.model.ts.\nconst SCADA_OUTPUT_TAG_NAME = 'c8y-scada-output';\nconst SVG_EXTRACT_TAG_NAME = 'c8y-svg-extract';\nconst MAPPING_EXTRACT_TAG_NAME = 'c8y-mapping-extract';\n\nconst SVG_SCADA_INSTRUCTIONS = `You are responsible for creating an SVG visualization for a SCADA (Supervisory Control and Data Acquisition) widget in the Cumulocity IoT Platform. Your task is to design industrial system or device visualizations based on user descriptions.\n\n## Core Requirements\n\n1. **Output Format**: Wrap BOTH outputs together in a single <${SCADA_OUTPUT_TAG_NAME}> block:\n - Clean, well-structured SVG code wrapped in <${SVG_EXTRACT_TAG_NAME}> tags (inside the wrapper)\n - Placeholder mapping configuration wrapped in <${MAPPING_EXTRACT_TAG_NAME}> tags (inside the wrapper)\n - Both inner tags MUST appear inside the <${SCADA_OUTPUT_TAG_NAME}> wrapper — never at the top level\n2. **No Emojis**: Do not include any emoji characters or Unicode symbols - use text descriptions or SVG shapes instead\n3. **Viewbox**: Always include a properly sized viewBox attribute for responsive scaling\n4. **Organized Structure**: Group related elements using <g> tags with meaningful class names or IDs\n\n## Basic SVG Structure\n\n<${SCADA_OUTPUT_TAG_NAME}>\n <${SVG_EXTRACT_TAG_NAME}>\n <?xml version=\"1.0\" encoding=\"utf-8\"?>\n <svg viewBox=\"0 0 800 600\" xmlns=\"http://www.w3.org/2000/svg\">\n <style>\n /* Use Cumulocity CSS variables for theming */\n .text-primary {\n fill: var(--c8y-root-component-color-default);\n font-family: var(--c8y-font-family-base);\n }\n .brand-color {\n fill: var(--c8y-brand-primary);\n }\n /* Additional styles */\n </style>\n \n <!-- Background -->\n <rect width=\"800\" height=\"600\" fill=\"var(--c8y-root-component-background-default)\" />\n \n <!-- Your SCADA visualization elements here -->\n </svg>\n </${SVG_EXTRACT_TAG_NAME}>\n <${MAPPING_EXTRACT_TAG_NAME}>\n { \"placeholderName\": { \"assetProperty\": { ... } } }\n </${MAPPING_EXTRACT_TAG_NAME}>\n</${SCADA_OUTPUT_TAG_NAME}>\n\n## Current Device Context\n\nThe context device object structure is as follows:\n{{ c8yContext }}\n\nThe device supports the following measurements:\n{{ c8yContextMeasurements }}\n\n## Placeholder Mapping Output\n\nInside the <${SCADA_OUTPUT_TAG_NAME}> wrapper, after the SVG code, you MUST output a placeholder mapping configuration that defines how each placeholder in your SVG maps to device data. This mapping is wrapped in <${MAPPING_EXTRACT_TAG_NAME}> tags and contains a JSON object.\n\n### Mapping Format\n\nThe mapping object structure is:\n\\`\\`\\`json\n{\n \"placeholderName\": {\n \"assetProperty\": {\n // Property configuration object, including asset info like:\n \"asset\": {\n \"id\": 12345,\n \"name\": \"Asset name\"\n }\n }\n }\n}\n\\`\\`\\`\n\n### Two Types of Mappings\n\n#### 1. Regular Property Mapping\n\nFor direct mappings to device properties (like name, ID, status, or custom properties):\n\n\\`\\`\\`json\n{\n \"deviceName\": {\n \"assetProperty\": {\n \"isEditable\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"name\": {\n \"label\": \"Device Name\",\n \"type\": \"string\",\n \"key\": \"name\",\n \"properties\": {}\n }\n }\n },\n \"name\": \"name\",\n \"active\": true,\n \"label\": \"Device Name\",\n \"type\": \"string\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n#### 2. Computed Property Mapping (e.g. Last Measurement)\n\nFor measurements or computed properties (like last measurement, alarms count, etc.):\n\n\\`\\`\\`json\n{\n \"temperature\": {\n \"assetProperty\": {\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"label\": \"Last measurement\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Battery\",\n \"series\": \"level\",\n \"label\": \"c8y_Battery → level\",\n \"unit\": \"%\",\n \"__target\": {\n \"id\": \"94130201\",\n \"name\": \"Temperature #1\"\n },\n \"color\": \"#347928\",\n \"__active\": true,\n \"__template\": null,\n \"renderType\": \"min\",\n \"lineType\": \"line\",\n \"yAxisType\": null\n }\n ],\n \"resultType\": 4\n },\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n### Guidelines for Creating Mappings\n**CRITICAL: When mapping to computed properties (such as last measurements, asset/device counts, alarm/event counts, etc.), you MUST use the correct property names and configuration as supported by Cumulocity. Never guess or invent property names; always check and use the actual names and structure as shown below.**\n\n#### Supported Computed Properties\n\nBesides \"Last measurement\", Cumulocity supports several other computed properties for SCADA widget mappings. Examples:\n\n\\`\\`\\`json\n{\n \"childAssetsCount\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"childAssetsCount\": {\n \"label\": \"Number of child assets\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"childAssetsCount\",\n \"label\": \"Number of child assets\",\n \"type\": \"number\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"childDevicesCount\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"childDevicesCount\": {\n \"label\": \"Number of child devices\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"childDevicesCount\",\n \"label\": \"Number of child devices\",\n \"type\": \"number\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCount3Months\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCount3Months\": {\n \"label\": \"Alarm count 3 months\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCount3Months\",\n \"label\": \"Alarm count 3 months\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"c8y_myAlarm\",\n \"severities\": {\n \"CRITICAL\": true,\n \"MAJOR\": true,\n \"MINOR\": true,\n \"WARNING\": true\n }\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCountLastWeek\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCountLastWeek\": {\n \"label\": \"Alarm count last week\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCountLastWeek\",\n \"label\": \"Alarm count last week\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myAlarm\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"alarmCountToday\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"alarmCountToday\": {\n \"label\": \"Alarm count today\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"alarmCountToday\",\n \"label\": \"Alarm count today\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"MyTodayAlarm\",\n \"severities\": {\n \"CRITICAL\": true,\n \"MAJOR\": true,\n \"MINOR\": true,\n \"WARNING\": true\n }\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"eventCount3Months\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"eventCount3Months\": {\n \"label\": \"Event count 3 months\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"eventCount3Months\",\n \"label\": \"Event count 3 months\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myEventType\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n },\n \"eventCountToday\": {\n \"assetProperty\": {\n \"computed\": true,\n \"isEditable\": false,\n \"isStandardProperty\": true,\n \"active\": true,\n \"temporary\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"eventCountToday\": {\n \"label\": \"Event count today\",\n \"type\": \"number\"\n }\n }\n },\n \"name\": \"eventCountToday\",\n \"label\": \"Event count today\",\n \"type\": \"number\",\n \"config\": {\n \"type\": \"myEventType2\"\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature #1\"\n }\n }\n }\n}\n\\`\\`\\`\n\n**Usage:**\n- Use the placeholder name (e.g., \\`childAssetsCount\\`, \\`alarmCountToday\\`) in your SVG template as \\`c8yScadaValues?.childAssetsCount\\`, \\`c8yScadaValues?.alarmCountToday\\`, etc.\n- These properties are read-only and computed by Cumulocity, and can be mapped in the widget configuration.\n\n**For alarm/event counts:**\n- You may specify severity filters and type in the \\`config\\` object as shown above (providing type is required).\n\n**For asset/device counts:**\n- No additional config is needed; just use the property name.\n\n**For measurements:**\n- Continue to use the existing measurement mapping format.\n\n**CRITICAL: When mapping to last measurements, you MUST use the correct, existing measurement fragment and series as verified by the cumulocity-api-request tool. Never guess or invent fragment/series names; always check and use the actual names from the API tool.**\n\n1. **Match Placeholder Names**: The keys in the mapping object must exactly match the placeholder names used in your SVG (e.g., if you use \\${this.c8yScadaValues?.temperature?.value}, create a mapping for \"temperature\")\n\n2. **Set Context Device Info**: Set the \"asset\" object inside \"assetProperty\" with the \"id\" and \"name\" from the context device object provided\n\n3. **Choose Appropriate Type**:\n - For measurements: Use computed property format with \"computed\": true, \"isStandardProperty\": true, and include \"config\" with data point configuration\n - For device properties: Use regular property format with \"isEditable\": true\n\n4. **Data Point Configuration**: For measurements, configure the \"dp\" array with:\n - **fragment**: The measurement fragment name (e.g., \"c8y_Temperature\", \"c8y_Battery\")\n - **series**: The series name within the fragment\n - **unit**: The unit of measurement\n - **label**: A descriptive label\n - **__target**: Reference to the target device with name and id\n - **resultType**: MUST always be \\`4\\` — this is the only value that returns the complete measurement object, giving access to \\`.value\\` and \\`.unit\\` sub-properties\n\n5. **Generate Unique Instance IDs**: For computed properties, include a unique \"instanceId\" (use UUID format)\n\n### Example Complete Output\n\nFor an SVG that uses placeholders \\${this.c8yScadaValues?.temperature?.value}, \\${this.c8yScadaValues?.pressure?.value}, and \\${this.c8yScadaValues?.deviceName}:\n\n<${SCADA_OUTPUT_TAG_NAME}>\n <${SVG_EXTRACT_TAG_NAME}>\n <!-- SVG code here -->\n </${SVG_EXTRACT_TAG_NAME}>\n <${MAPPING_EXTRACT_TAG_NAME}>\n {\n \"temperature\": {\n \"assetProperty\": {\n \"temporary\": true,\n \"instanceId\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"computed\": true,\n \"isEditable\": false,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"active\": true,\n \"isStandardProperty\": true,\n \"label\": \"Last measurement\",\n \"contextAssetId\": \"94130201\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Temperature\",\n \"unit\": \"°C\",\n \"color\": \"#347928\",\n \"series\": \"T\",\n \"lineType\": \"line\",\n \"__active\": true,\n \"__template\": null,\n \"label\": \"c8y_Temperature → T\",\n \"renderType\": \"min\",\n \"yAxisType\": null,\n \"__target\": {\n \"name\": \"Temperature Sensor\",\n \"id\": \"94130201\"\n }\n }\n ],\n \"resultType\": 4\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature Sensor\"\n }\n }\n },\n \"pressure\": {\n \"assetProperty\": {\n \"temporary\": true,\n \"instanceId\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n \"computed\": true,\n \"isEditable\": false,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"lastMeasurement\": {\n \"label\": \"Last measurement\",\n \"type\": \"string\"\n }\n }\n },\n \"name\": \"lastMeasurement\",\n \"active\": true,\n \"isStandardProperty\": true,\n \"label\": \"Last measurement\",\n \"contextAssetId\": \"94130201\",\n \"type\": \"string\",\n \"config\": {\n \"dp\": [\n {\n \"fragment\": \"c8y_Pressure\",\n \"unit\": \"kPa\",\n \"color\": \"#2196F3\",\n \"series\": \"pressure\",\n \"lineType\": \"line\",\n \"__active\": true,\n \"__template\": null,\n \"label\": \"c8y_Pressure → pressure\",\n \"renderType\": \"min\",\n \"yAxisType\": null,\n \"__target\": {\n \"name\": \"Pressure Sensor\",\n \"id\": \"94130201\"\n }\n }\n ],\n \"resultType\": 4\n },\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Pressure Sensor\"\n }\n }\n },\n \"deviceName\": {\n \"assetProperty\": {\n \"isEditable\": true,\n \"c8y_JsonSchema\": {\n \"properties\": {\n \"name\": {\n \"label\": \"Name\",\n \"type\": \"string\",\n \"key\": \"name\",\n \"properties\": {}\n }\n }\n },\n \"name\": \"name\",\n \"active\": true,\n \"label\": \"Name\",\n \"type\": \"string\",\n \"asset\": {\n \"id\": 94130201,\n \"name\": \"Temperature Sensor\"\n }\n }\n }\n }\n </${MAPPING_EXTRACT_TAG_NAME}>\n</${SCADA_OUTPUT_TAG_NAME}>\n\n## Data Binding Syntax\n\nYou can make your SVG dynamic by binding to data values using template literal syntax:\n\n### Accessing Device Data\n\n**IMPORTANT**: To use and display ANY value from the context device object or its measurements, you MUST use the \\`c8yScadaValues\\` syntax. This is the ONLY correct way to access device data because:\n1. It creates a placeholder in the data mapping list during widget configuration\n2. The placeholder will be automatically bound to the appropriate data source from the context device\n3. This enables proper data flow from the device to your visualization\n\n**Do NOT** attempt to access device properties directly (e.g., \\${this.device?.property}) or use any other syntax. Always use \\`c8yScadaValues\\` with a short, descriptive placeholder name.\n\n### Two Types of Data Access\n\n#### 1. For Device Measurements (with subproperties)\n\nWhen a placeholder will be mapped to a **device measurement**, use the subproperties:\n- **\\${this.c8yScadaValues?.placeholderName?.value}** - The numeric value of the measurement (may be a number or a numeric string)\n- **\\${this.c8yScadaValues?.placeholderName?.unit}** - The unit of measurement (e.g., \"°C\", \"kPa\", \"%\")\n\n**IMPORTANT — numeric formatting:** \\`.value\\` may be a number or a numeric string. **Never call \\`.toFixed()\\` by default** — it will throw for string values. Only add decimal formatting when the user explicitly requests it, and always convert first: \\`Number(this.c8yScadaValues?.pressure?.value).toFixed(2)\\`.\n\n\\`\\`\\`xml\n<!-- Display temperature value with units -->\n<text x=\"100\" y=\"50\">\\${this.c8yScadaValues?.temperature?.value || '-'} \\${this.c8yScadaValues?.temperature?.unit || ''}</text>\n\n<!-- Display pressure value with units -->\n<text x=\"100\" y=\"100\">Pressure: \\${this.c8yScadaValues?.pressure?.value || '-'} \\${this.c8yScadaValues?.pressure?.unit || 'kPa'}</text>\n\n<!-- Display measurement with timestamp -->\n<text x=\"100\" y=\"150\">Level: \\${this.c8yScadaValues?.level?.value || '-'}% (Updated: \\${this.c8yScadaValues?.level?.time || 'N/A'})</text>\n\\`\\`\\`\n\n#### 2. For Device Properties (no subproperties)\n\nWhen a placeholder will be mapped directly to a **device property** (like device name, ID, status, etc.), access it directly WITHOUT subproperties:\n\n\\`\\`\\`xml\n<!-- Display device name -->\n<text x=\"100\" y=\"50\">Device: \\${this.c8yScadaValues?.deviceName || 'Unknown'}</text>\n\n<!-- Display device ID -->\n<text x=\"100\" y=\"75\">ID: \\${this.c8yScadaValues?.deviceId || '-'}</text>\n\n<!-- Display custom property -->\n<text x=\"100\" y=\"100\">Location: \\${this.c8yScadaValues?.location || 'Not set'}</text>\n\\`\\`\\`\n\n**Remember**: Each placeholder name (e.g., \"temperature\", \"deviceName\", \"location\") will appear in the data mapping configuration, where it can be mapped to either a measurement source (with .value, .unit, .time) or a device property (direct value).\n\n\\`\\`\\`xml\n<!-- Display temperature value with units -->\n<text x=\"100\" y=\"50\">\\${this.c8yScadaValues?.temperature?.value || '-'} \\${this.c8yScadaValues?.temperature?.unit || ''}</text>\n\n<!-- Display pressure value with units -->\n<text x=\"100\" y=\"100\">Pressure: \\${this.c8yScadaValues?.pressure?.value || '-'} \\${this.c8yScadaValues?.pressure?.unit || 'kPa'}</text>\n\n<!-- Display measurement with timestamp -->\n<text x=\"100\" y=\"150\">Level: \\${this.c8yScadaValues?.level?.value || '-'}% (Updated: \\${this.c8yScadaValues?.level?.time || 'N/A'})</text>\n\\`\\`\\`\n\n### Conditional Styling\nApply CSS classes dynamically based on data values:\n\n\\`\\`\\`xml\n<style>\n .temp-normal { fill: var(--c8y-palette-status-success); }\n .temp-warning { fill: var(--c8y-palette-status-warning); }\n .temp-critical { fill: var(--c8y-palette-status-danger); }\n</style>\n\n<text x=\"100\" y=\"50\" \n class=\"\\${this.c8yScadaValues?.temperature?.value > 80 ? 'temp-critical' : \n this.c8yScadaValues?.temperature?.value > 60 ? 'temp-warning' : 'temp-normal'}\">\n \\${this.c8yScadaValues?.temperature?.value}\\${this.c8yScadaValues?.temperature?.unit || '°C'}\n</text>\n\\`\\`\\`\n\n### Dynamic Attributes\nChange SVG attributes based on data:\n\n\\`\\`\\`xml\n<!-- Dynamic width for progress bar (from measurement) -->\n<rect x=\"100\" y=\"100\" \n width=\"\\${(this.c8yScadaValues?.progress?.value || 0) * 3}\" \n height=\"30\" \n fill=\"var(--c8y-brand-primary)\" />\n<text x=\"100\" y=\"145\">\\${this.c8yScadaValues?.progress?.value || 0}\\${this.c8yScadaValues?.progress?.unit || '%'}</text>\n\n<!-- Dynamic rotation for valve (from measurement) -->\n<g transform=\"rotate(\\${this.c8yScadaValues?.valveAngle?.value || 0} 200 200)\">\n <rect x=\"190\" y=\"150\" width=\"20\" height=\"100\" fill=\"#888\" />\n</g>\n\n<!-- Dynamic fill color based on status (from device property - no .value needed) -->\n<circle cx=\"200\" cy=\"200\" r=\"50\" \n fill=\"\\${this.c8yScadaValues?.status === 'active' ? 'green' : 'red'}\" />\n\\`\\`\\`\n\n### Interactive Elements\nAdd click handlers for navigation:\n\n\\`\\`\\`xml\n<!-- Navigate to device details -->\n<g class=\"button\" @click=\\${() => this.c8yScadaFunctions?.goToDeviceDetails(this.c8yScadaValues?.deviceId)}>\n <rect x=\"50\" y=\"50\" width=\"150\" height=\"40\" fill=\"var(--c8y-brand-primary)\" rx=\"4\" />\n <text x=\"125\" y=\"75\" text-anchor=\"middle\" fill=\"white\">View Device</text>\n</g>\n\n<!-- Navigate to group details -->\n<g class=\"button\" @click=\\${() => this.c8yScadaFunctions?.goToGroupDetails(this.c8yScadaValues?.groupId)}>\n <rect x=\"50\" y=\"100\" width=\"150\" height=\"40\" fill=\"var(--c8y-brand-primary)\" rx=\"4\" />\n <text x=\"125\" y=\"125\" text-anchor=\"middle\" fill=\"white\">View Group</text>\n</g>\n\\`\\`\\`\n\n### Alarm Status Indicators\nDisplay alarm status with appropriate colors:\n\n\\`\\`\\`xml\n<style>\n .alarm-none { fill: var(--c8y-palette-status-success); }\n .alarm-warning { fill: var(--c8y-palette-status-info); }\n .alarm-minor { fill: var(--c8y-palette-status-warning-high); }\n .alarm-major { fill: var(--c8y-palette-status-warning); }\n .alarm-critical { fill: var(--c8y-palette-status-danger); }\n</style>\n\n<!-- Alarm status circle -->\n<circle cx=\"100\" cy=\"100\" r=\"15\" \n class=\"alarm-\\${this.c8yScadaFunctions?.getActiveAlarmsStatusClass(this.c8yScadaValues?.alarmsStatus) || 'none'}\" />\n\\`\\`\\`\n\n## SCADA Visualization Guidelines\n\n### Common Industrial Elements\n\n**Pipes and Flows:**\n- Use <line> or <path> for straight and curved pipes\n- Add arrows to show flow direction\n- Use stroke-width to indicate pipe size\n- Animate flow with dashed lines or moving elements\n\n\\`\\`\\`xml\n<!-- Horizontal pipe with flow -->\n<line x1=\"100\" y1=\"200\" x2=\"400\" y2=\"200\" \n stroke=\"#666\" stroke-width=\"8\" stroke-linecap=\"round\" />\n<!-- Flow arrow -->\n<polygon points=\"350,195 370,200 350,205\" fill=\"#2196F3\" />\n\\`\\`\\`\n\n**Tanks and Vessels:**\n- Use <rect> with rounded corners or <ellipse> for cylindrical tanks\n- Show fill level with dynamic height rectangles\n- Add level indicators and measurements\n\n\\`\\`\\`xml\n<!-- Tank with dynamic fill level -->\n<g id=\"tank\">\n <rect x=\"100\" y=\"100\" width=\"100\" height=\"200\" \n fill=\"none\" stroke=\"#333\" stroke-width=\"3\" rx=\"5\" />\n <rect x=\"100\" \n y=\"\\${300 - (this.c8yScadaValues?.tankLevel?.value || 0) * 2}\" \n width=\"100\" \n height=\"\\${(this.c8yScadaValues?.tankLevel?.value || 0) * 2}\" \n fill=\"#2196F3\" opacity=\"0.7\" />\n <text x=\"150\" y=\"320\" text-anchor=\"middle\">\n \\${this.c8yScadaValues?.tankLevel?.value || 0}\\${this.c8yScadaValues?.tankLevel?.unit || '%'}\n </text>\n</g>\n\\`\\`\\`\n\n**Valves:**\n- Use triangles or rotated rectangles\n- Show open/closed state with color or rotation\n- Add position indicators\n\n\\`\\`\\`xml\n<!-- Valve (open/closed) -->\n<g id=\"valve\" transform=\"translate(200, 200)\">\n <circle r=\"20\" fill=\"none\" stroke=\"#333\" stroke-width=\"2\" />\n <line x1=\"-15\" y1=\"0\" x2=\"15\" y2=\"0\" \n stroke=\"\\${this.c8yScadaValues?.valveOpen?.value ? '#4CAF50' : '#F44336'}\" \n stroke-width=\"3\" \n transform=\"rotate(\\${this.c8yScadaValues?.valveOpen?.value ? 0 : 90})\" />\n</g>\n\\`\\`\\`\n\n**Pumps and Motors:**\n- Use circles with internal indicators\n- Animate rotation for running state\n- Show on/off status with color\n\n\\`\\`\\`xml\n<!-- Pump -->\n<g id=\"pump\">\n <circle cx=\"200\" cy=\"200\" r=\"30\" \n fill=\"\\${this.c8yScadaValues?.pumpRunning?.value ? '#4CAF50' : '#999'}\" \n stroke=\"#333\" stroke-width=\"2\" />\n <text x=\"200\" y=\"207\" text-anchor=\"middle\" fill=\"white\" font-weight=\"bold\">P</text>\n</g>\n\\`\\`\\`\n\n**Sensors and Indicators:**\n- Use small circles or rectangles\n- Display current readings\n- Color-code based on thresholds\n\n\\`\\`\\`xml\n<!-- Temperature sensor -->\n<g id=\"temp-sensor\">\n <rect x=\"100\" y=\"100\" width=\"80\" height=\"40\" \n fill=\"var(--c8y-root-component-background-default)\" \n stroke=\"#333\" rx=\"4\" />\n <text x=\"140\" y=\"125\" text-anchor=\"middle\" \n class=\"\\${this.c8yScadaValues?.temperature?.value > 80 ? 'temp-critical' : 'temp-normal'}\">\n \\${this.c8yScadaValues?.temperature?.value || '-'}\\${this.c8yScadaValues?.temperature?.unit || '°C'}\n </text>\n</g>\n\\`\\`\\`\n\n### Color Conventions\n\n**Operational States:**\n- Green (#4CAF50 or var(--c8y-palette-status-success)): Running/OK/Open\n- Red (#F44336 or var(--c8y-palette-status-danger)): Stopped/Error/Closed\n- Orange (#FF9800 or var(--c8y-palette-status-warning)): Warning/Standby\n- Gray (#999): Inactive/Disabled\n- Blue (#2196F3 or var(--c8y-brand-primary)): Water/Liquid/Flow\n\n**Use Cumulocity design tokens:**\n- var(--c8y-brand-primary): Brand color\n- var(--c8y-palette-status-success): Success green\n- var(--c8y-palette-status-warning): Warning orange\n- var(--c8y-palette-status-danger): Error/critical red\n- var(--c8y-root-component-background-default): Background\n- var(--c8y-root-component-color-default): Text color\n\n### Layout Best Practices\n\n1. **Left-to-right flow**: Process flows typically go from left to right\n2. **Hierarchical grouping**: Group related components visually\n3. **Consistent spacing**: Maintain uniform spacing between elements\n4. **Clear labels**: Label all major components and measurement points\n5. **Legend/Key**: Include a legend if using special symbols or colors\n6. **Scale indicators**: Show units and scales where relevant\n\n### Typography\n\n\\`\\`\\`xml\n<style>\n .title {\n font-family: var(--c8y-font-family-base);\n font-size: 24px;\n font-weight: bold;\n fill: var(--c8y-root-component-color-default);\n }\n .label {\n font-family: var(--c8y-font-family-base);\n font-size: 14px;\n fill: var(--c8y-root-component-color-default);\n }\n .value {\n font-family: var(--c8y-font-family-base);\n font-size: 16px;\n font-weight: bold;\n fill: var(--c8y-brand-primary);\n }\n</style>\n\\`\\`\\`\n\n## Important Notes\n\n- **When mapping to last measurements, ALWAYS use the correct, existing fragment and series as verified by the cumulocity-api-request tool. Never guess or invent measurement names.**\n- **CRITICAL — \\`resultType\\`**: When configuring last measurement mappings, ALWAYS set \\`\"resultType\": 4\\` in the \\`config\\` object. This is the only value that returns the complete measurement object, giving access to \\`.value\\` and \\`.unit\\` sub-properties.\n- **CRITICAL — numeric formatting**: \\`.value\\` may be a number or a numeric string. **Never call \\`.toFixed()\\` by default.** Only add decimal formatting when the user explicitly requests it, and always convert first: \\`Number(this.c8yScadaValues?.pressure?.value).toFixed(2)\\`.\n- **CRITICAL — changes**: When the user requests any change to the SVG or mappings, always re-output the complete \\`<${SCADA_OUTPUT_TAG_NAME}>\\` block with the fix included — never output only the changed portion or a diff.\n- The current device context and available measurements are provided above - use this information to create relevant visualizations\n- **CRITICAL**: ALWAYS access device data via \\${this.c8yScadaValues?.placeholderName} - this is the ONLY way to ensure proper data mapping placeholders are created and bound to the context device data sources\n- **For measurements**: Use \\${this.c8yScadaValues?.placeholderName?.value} to access the value, and optionally .unit and .time for additional measurement data\n- **For device properties**: Use \\${this.c8yScadaValues?.placeholderName} directly WITHOUT subproperties (no .value, .unit, or .time)\n- Each unique placeholder name in c8yScadaValues creates a placeholder in the data mapping list that can be configured to bind to specific device measurements or properties\n- Use semantic grouping with <g> tags and meaningful IDs\n- Make visualizations responsive by using viewBox properly\n- Provide fallback values (use || operator) for all dynamic data to handle missing/undefined values\n- Keep the design clean and professional - avoid clutter\n- Test conditional logic to ensure proper rendering in all states\n- Consider accessibility - use proper contrast and text sizing`;\n\nexport const SCADA_SVG_AGENT = {\n name: 'c8y-scada-svg',\n agent: {\n system: `You are an expert at creating SVG visualizations for SCADA (Supervisory Control and Data Acquisition) widgets to be used in dashboards in the Cumulocity IoT Platform. Your goal is to transform user descriptions of industrial systems into clear, professional SVG visualizations.\n\n## Workflow\n\n1. **Understand the System**\n- Carefully analyze the user's description of the industrial system\n- Identify the key components (tanks, pumps, valves, sensors, pipes, etc.)\n- Determine data points that should be displayed or visualized\n- Note any specific layout or flow requirements\n\n2. **Verify Available Data (if needed)**\n- If the visualization requires specific measurements or device properties, use the \"cumulocity-api-request\" tool to verify what data is available\n- Check available measurements, properties, or alarms on the target device/asset\n- Document which data points can be used for dynamic visualization\n\n3. **Design the SVG**\n- Create a clean, professional SVG visualization\n- Use appropriate industrial symbols and conventions\n- Implement data binding for dynamic elements\n- Apply proper color coding for operational states\n- Ensure responsive scaling with proper viewBox\n\n4. **Output the Code**\n- Wrap BOTH outputs together in a single <${SCADA_OUTPUT_TAG_NAME}> block: SVG code in <${SVG_EXTRACT_TAG_NAME}> tags followed by mapping in <${MAPPING_EXTRACT_TAG_NAME}> tags, both inside the wrapper\n- CRITICAL: Include ONLY the SVG code inside the SVG tags and ONLY the JSON mapping inside the mapping tags - no explanatory text, no markdown formatting, no comments outside the inner tags\n- Ensure every placeholder used in the SVG (via \\${this.c8yScadaValues?.placeholderName}) has a corresponding entry in the mapping object\n- **If the user requests any change to the SVG or mappings, always re-output the complete \\`<${SCADA_OUTPUT_TAG_NAME}>\\` block with the fix included — never output only the changed portion or a diff**\n- Follow the guidelines in: ${SVG_SCADA_INSTRUCTIONS}\n\n5. **Validate Quality**\n- Verify the visualization represents the described system accurately\n- Ensure all dynamic data bindings use proper fallback values\n- Check that colors and styling follow Cumulocity design standards\n- If the user's description is unclear or lacks critical details, ask for clarification\n\n## Key Principles\n- Professional industrial design - clean, clear, uncluttered\n- Semantic SVG structure with organized groups\n- Proper data binding syntax for dynamic content\n- Responsive and theme-aware using Cumulocity CSS variables\n- No emojis or Unicode symbols - use SVG shapes and text only\n`,\n maxOutputTokens: 20000\n },\n type: 'text',\n mcp: [\n {\n serverName: 'cumulocity-default',\n tools: ['cumulocity-api-request']\n }\n ]\n} as AgentDefinition;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAMA;AACA;AACA;AACA,MAAM,qBAAqB,GAAG,kBAAkB;AAChD,MAAM,oBAAoB,GAAG,iBAAiB;AAC9C,MAAM,wBAAwB,GAAG,qBAAqB;AAEtD,MAAM,sBAAsB,GAAG,CAAA;;;;gEAIiC,qBAAqB,CAAA;mDAClC,oBAAoB,CAAA;qDAClB,wBAAwB,CAAA;+CAC9B,qBAAqB,CAAA;;;;;;;GAOjE,qBAAqB,CAAA;KACnB,oBAAoB,CAAA;;;;;;;;;;;;;;;;;;;;MAoBnB,oBAAoB,CAAA;KACrB,wBAAwB,CAAA;;MAEvB,wBAAwB,CAAA;IAC1B,qBAAqB,CAAA;;;;;;;;;;;;AAYX,YAAA,EAAA,qBAAqB,qLAAqL,wBAAwB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+V7O,qBAAqB,CAAA;KACnB,oBAAoB,CAAA;;MAEnB,oBAAoB,CAAA;KACrB,wBAAwB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAwHvB,wBAAwB,CAAA;IAC1B,qBAAqB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uHAuR8F,qBAAqB,CAAA;;;;;;;;;;;+DAW7E;AAExD,MAAM,eAAe,GAAG;AAC7B,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE;AACL,QAAA,MAAM,EAAE,CAAA;;;;;;;;;;;;;;;;;;;;;;;4CAuBgC,qBAAqB,CAAA,sBAAA,EAAyB,oBAAoB,CAAA,+BAAA,EAAkC,wBAAwB,CAAA;;;+FAGzE,qBAAqB,CAAA;8BACtF,sBAAsB;;;;;;;;;;;;;;AAcnD,CAAA;AACG,QAAA,eAAe,EAAE;AAClB,KAAA;AACD,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE;AACH,QAAA;AACE,YAAA,UAAU,EAAE,oBAAoB;YAChC,KAAK,EAAE,CAAC,wBAAwB;AACjC;AACF;;;ACr3BH;;AAEG;;;;"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, inject, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import * as i3 from '@angular/forms';
|
|
4
|
+
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
|
+
import * as i2 from '@c8y/ngx-components';
|
|
6
|
+
import { HumanizeAppNamePipe, SelectComponent, C8yTranslatePipe } from '@c8y/ngx-components';
|
|
7
|
+
import * as i1 from '@c8y/ngx-components/ecosystem/shared';
|
|
8
|
+
import { gettext } from '@c8y/ngx-components/gettext';
|
|
9
|
+
import { uniqBy } from 'lodash-es';
|
|
10
|
+
import { firstValueFrom } from 'rxjs';
|
|
11
|
+
|
|
12
|
+
class ApplicationSelectComponent {
|
|
13
|
+
constructor(ecosystemService, appStateService) {
|
|
14
|
+
this.ecosystemService = ecosystemService;
|
|
15
|
+
this.appStateService = appStateService;
|
|
16
|
+
this.placeholder = gettext('Select applications…');
|
|
17
|
+
this.availableApps = signal([], ...(ngDevMode ? [{ debugName: "availableApps" }] : /* istanbul ignore next */ []));
|
|
18
|
+
this.isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
19
|
+
this.selectedApps = [];
|
|
20
|
+
this.pendingKeys = null;
|
|
21
|
+
this.humanizeAppName = inject(HumanizeAppNamePipe);
|
|
22
|
+
}
|
|
23
|
+
async ngOnInit() {
|
|
24
|
+
const currentAppId = this.appStateService.currentApplication.value?.id;
|
|
25
|
+
const apps = await this.ecosystemService.getWebApplications();
|
|
26
|
+
const uniqueApps = uniqBy(apps.filter(app => app.key && app.id !== currentAppId), 'key');
|
|
27
|
+
const items = await Promise.all(uniqueApps.map(async (app) => ({
|
|
28
|
+
label: await firstValueFrom(this.humanizeAppName.transform(app)),
|
|
29
|
+
value: app.key
|
|
30
|
+
})));
|
|
31
|
+
this.availableApps.set(items);
|
|
32
|
+
if (this.pendingKeys !== null) {
|
|
33
|
+
this.applyKeys(this.pendingKeys);
|
|
34
|
+
this.pendingKeys = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
writeValue(keys) {
|
|
38
|
+
if (this.availableApps().length) {
|
|
39
|
+
this.applyKeys(keys ?? []);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.pendingKeys = keys ?? [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
registerOnChange(fn) {
|
|
46
|
+
this.onChange = fn;
|
|
47
|
+
}
|
|
48
|
+
registerOnTouched(fn) {
|
|
49
|
+
this.onTouched = fn;
|
|
50
|
+
}
|
|
51
|
+
setDisabledState(isDisabled) {
|
|
52
|
+
this.isDisabled.set(isDisabled);
|
|
53
|
+
}
|
|
54
|
+
onSelectionChange() {
|
|
55
|
+
this.onChange(this.selectedApps.map(item => item.value));
|
|
56
|
+
this.onTouched();
|
|
57
|
+
}
|
|
58
|
+
applyKeys(keys) {
|
|
59
|
+
this.selectedApps = this.availableApps().filter(item => keys.includes(item.value));
|
|
60
|
+
}
|
|
61
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ApplicationSelectComponent, deps: [{ token: i1.EcosystemService }, { token: i2.AppStateService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
62
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: ApplicationSelectComponent, isStandalone: true, selector: "c8y-application-select", providers: [
|
|
63
|
+
{
|
|
64
|
+
provide: NG_VALUE_ACCESSOR,
|
|
65
|
+
useExisting: forwardRef(() => ApplicationSelectComponent),
|
|
66
|
+
multi: true
|
|
67
|
+
}
|
|
68
|
+
], ngImport: i0, template: "<c8y-select\n name=\"applicationSelect\"\n [items]=\"availableApps()\"\n [multi]=\"true\"\n [filterItems]=\"true\"\n [disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder | translate\"\n [(ngModel)]=\"selectedApps\"\n (ngModelChange)=\"onSelectionChange()\"\n></c8y-select>\n", dependencies: [{ kind: "component", type: SelectComponent, selector: "c8y-select", inputs: ["placeholder", "items", "selected", "container", "multi", "canSelectWithSpace", "disabled", "autoClose", "insideClick", "required", "canDeselect", "name", "icon", "filterItems"], outputs: ["onSelect", "onDeselect", "onIconClick"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
69
|
+
}
|
|
70
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ApplicationSelectComponent, decorators: [{
|
|
71
|
+
type: Component,
|
|
72
|
+
args: [{ selector: 'c8y-application-select', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SelectComponent, FormsModule, C8yTranslatePipe], providers: [
|
|
73
|
+
{
|
|
74
|
+
provide: NG_VALUE_ACCESSOR,
|
|
75
|
+
useExisting: forwardRef(() => ApplicationSelectComponent),
|
|
76
|
+
multi: true
|
|
77
|
+
}
|
|
78
|
+
], template: "<c8y-select\n name=\"applicationSelect\"\n [items]=\"availableApps()\"\n [multi]=\"true\"\n [filterItems]=\"true\"\n [disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder | translate\"\n [(ngModel)]=\"selectedApps\"\n (ngModelChange)=\"onSelectionChange()\"\n></c8y-select>\n" }]
|
|
79
|
+
}], ctorParameters: () => [{ type: i1.EcosystemService }, { type: i2.AppStateService }] });
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generated bundle index. Do not edit.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
export { ApplicationSelectComponent };
|
|
86
|
+
//# sourceMappingURL=c8y-ngx-components-application-select.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"c8y-ngx-components-application-select.mjs","sources":["../../application-select/application-select.component.ts","../../application-select/application-select.component.html","../../application-select/c8y-ngx-components-application-select.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n OnInit,\n forwardRef,\n inject,\n signal\n} from '@angular/core';\nimport { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport {\n AppStateService,\n C8yTranslatePipe,\n HumanizeAppNamePipe,\n SelectComponent,\n SelectableItem\n} from '@c8y/ngx-components';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { uniqBy } from 'lodash-es';\nimport { firstValueFrom } from 'rxjs';\n\n@Component({\n selector: 'c8y-application-select',\n templateUrl: './application-select.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [SelectComponent, FormsModule, C8yTranslatePipe],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => ApplicationSelectComponent),\n multi: true\n }\n ]\n})\nexport class ApplicationSelectComponent implements OnInit, ControlValueAccessor {\n readonly placeholder = gettext('Select applications…');\n\n readonly availableApps = signal<SelectableItem[]>([]);\n readonly isDisabled = signal(false);\n\n selectedApps: SelectableItem[] = [];\n\n private pendingKeys: string[] | null = null;\n private onChange: (_: string[]) => void;\n private onTouched: () => void;\n\n private humanizeAppName = inject(HumanizeAppNamePipe);\n\n constructor(\n private ecosystemService: EcosystemService,\n private appStateService: AppStateService\n ) {}\n\n async ngOnInit(): Promise<void> {\n const currentAppId = this.appStateService.currentApplication.value?.id;\n const apps = await this.ecosystemService.getWebApplications();\n\n const uniqueApps = uniqBy(\n apps.filter(app => app.key && app.id !== currentAppId),\n 'key'\n );\n const items: SelectableItem[] = await Promise.all(\n uniqueApps.map(async app => ({\n label: await firstValueFrom(this.humanizeAppName.transform(app)),\n value: app.key\n }))\n );\n\n this.availableApps.set(items);\n\n if (this.pendingKeys !== null) {\n this.applyKeys(this.pendingKeys);\n this.pendingKeys = null;\n }\n }\n\n writeValue(keys: string[]): void {\n if (this.availableApps().length) {\n this.applyKeys(keys ?? []);\n } else {\n this.pendingKeys = keys ?? [];\n }\n }\n\n registerOnChange(fn: (_: string[]) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.isDisabled.set(isDisabled);\n }\n\n onSelectionChange(): void {\n this.onChange(this.selectedApps.map(item => item.value as string));\n this.onTouched();\n }\n\n private applyKeys(keys: string[]): void {\n this.selectedApps = this.availableApps().filter(item => keys.includes(item.value as string));\n }\n}\n","<c8y-select\n name=\"applicationSelect\"\n [items]=\"availableApps()\"\n [multi]=\"true\"\n [filterItems]=\"true\"\n [disabled]=\"isDisabled()\"\n [placeholder]=\"placeholder | translate\"\n [(ngModel)]=\"selectedApps\"\n (ngModelChange)=\"onSelectionChange()\"\n></c8y-select>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;MAkCa,0BAA0B,CAAA;IAcrC,WAAA,CACU,gBAAkC,EAClC,eAAgC,EAAA;QADhC,IAAA,CAAA,gBAAgB,GAAhB,gBAAgB;QAChB,IAAA,CAAA,eAAe,GAAf,eAAe;AAfhB,QAAA,IAAA,CAAA,WAAW,GAAG,OAAO,CAAC,sBAAsB,CAAC;AAE7C,QAAA,IAAA,CAAA,aAAa,GAAG,MAAM,CAAmB,EAAE,oFAAC;AAC5C,QAAA,IAAA,CAAA,UAAU,GAAG,MAAM,CAAC,KAAK,iFAAC;QAEnC,IAAA,CAAA,YAAY,GAAqB,EAAE;QAE3B,IAAA,CAAA,WAAW,GAAoB,IAAI;AAInC,QAAA,IAAA,CAAA,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAAC;IAKlD;AAEH,IAAA,MAAM,QAAQ,GAAA;QACZ,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE;QACtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE;QAE7D,MAAM,UAAU,GAAG,MAAM,CACvB,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,YAAY,CAAC,EACtD,KAAK,CACN;AACD,QAAA,MAAM,KAAK,GAAqB,MAAM,OAAO,CAAC,GAAG,CAC/C,UAAU,CAAC,GAAG,CAAC,OAAM,GAAG,MAAK;AAC3B,YAAA,KAAK,EAAE,MAAM,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAChE,KAAK,EAAE,GAAG,CAAC;SACZ,CAAC,CAAC,CACJ;AAED,QAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;AAE7B,QAAA,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE;AAC7B,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC;AAChC,YAAA,IAAI,CAAC,WAAW,GAAG,IAAI;QACzB;IACF;AAEA,IAAA,UAAU,CAAC,IAAc,EAAA;AACvB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE;AAC/B,YAAA,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC;QAC5B;aAAO;AACL,YAAA,IAAI,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE;QAC/B;IACF;AAEA,IAAA,gBAAgB,CAAC,EAAyB,EAAA;AACxC,QAAA,IAAI,CAAC,QAAQ,GAAG,EAAE;IACpB;AAEA,IAAA,iBAAiB,CAAC,EAAc,EAAA;AAC9B,QAAA,IAAI,CAAC,SAAS,GAAG,EAAE;IACrB;AAEA,IAAA,gBAAgB,CAAC,UAAmB,EAAA;AAClC,QAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC;IACjC;IAEA,iBAAiB,GAAA;AACf,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,KAAe,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS,EAAE;IAClB;AAEQ,IAAA,SAAS,CAAC,IAAc,EAAA;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC9F;+GArEW,0BAA0B,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,gBAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,eAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAA1B,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,0BAA0B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,wBAAA,EAAA,SAAA,EAR1B;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,iBAAiB;AAC1B,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,0BAA0B,CAAC;AACzD,gBAAA,KAAK,EAAE;AACR;AACF,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EChCH,iSAUA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDeY,eAAe,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,OAAA,EAAA,UAAA,EAAA,WAAA,EAAA,OAAA,EAAA,oBAAA,EAAA,UAAA,EAAA,WAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,MAAA,EAAA,MAAA,EAAA,aAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,EAAA,YAAA,EAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,WAAW,0VAAE,gBAAgB,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAS7C,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBAbtC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,eAAA,EAEjB,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,eAAe,EAAE,WAAW,EAAE,gBAAgB,CAAC,EAAA,SAAA,EAC9C;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,iBAAiB;AAC1B,4BAAA,WAAW,EAAE,UAAU,CAAC,gCAAgC,CAAC;AACzD,4BAAA,KAAK,EAAE;AACR;AACF,qBAAA,EAAA,QAAA,EAAA,iSAAA,EAAA;;;AEhCH;;AAEG;;;;"}
|
|
@@ -130,6 +130,44 @@ class AddDashboardFactory {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
const CONTEXT_DASHBOARD_CONFIG = new InjectionToken('ContextDashboardConfig');
|
|
133
|
+
/** Managed object fragment listing the application keys a dashboard is applied to. */
|
|
134
|
+
const APPLIED_TO_APPLICATIONS_FRAGMENT = 'c8y_AppliedToApplications';
|
|
135
|
+
/**
|
|
136
|
+
* Returns the keys of the applications the given data sharing configuration shares
|
|
137
|
+
* the specified entity type with.
|
|
138
|
+
*/
|
|
139
|
+
function getSharedApplicationKeys(dataSharing, entityType) {
|
|
140
|
+
return Object.entries(dataSharing ?? {})
|
|
141
|
+
.filter(([, types]) => types?.includes(entityType))
|
|
142
|
+
.map(([key]) => key);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Produces a new data sharing configuration where the set of applications sharing
|
|
146
|
+
* the given entity type is replaced with `applicationKeys`. Entity types already
|
|
147
|
+
* configured for other applications (or other entity types on the same
|
|
148
|
+
* applications) are preserved.
|
|
149
|
+
*/
|
|
150
|
+
function applyEntitySharing(dataSharing, entityType, applicationKeys) {
|
|
151
|
+
const selected = new Set(applicationKeys);
|
|
152
|
+
const result = {};
|
|
153
|
+
// Keep other entity types and re-add the managed one for still-selected apps.
|
|
154
|
+
for (const [key, types] of Object.entries(dataSharing ?? {})) {
|
|
155
|
+
const kept = (types ?? []).filter(type => type !== entityType);
|
|
156
|
+
if (selected.has(key)) {
|
|
157
|
+
kept.push(entityType);
|
|
158
|
+
}
|
|
159
|
+
if (kept.length) {
|
|
160
|
+
result[key] = kept;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Add newly selected apps that had no previous sharing configuration.
|
|
164
|
+
for (const key of selected) {
|
|
165
|
+
if (!result[key]) {
|
|
166
|
+
result[key] = [entityType];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
133
171
|
const DASHBOARD_SETTINGS_CHANGES = {
|
|
134
172
|
classes: gettext('theme'),
|
|
135
173
|
globalRolesIds: gettext('global roles'),
|
|
@@ -347,7 +385,7 @@ class ContextDashboardService {
|
|
|
347
385
|
this.cache = new Map();
|
|
348
386
|
this.DEFAULT_PAGESIZE = 1000;
|
|
349
387
|
this.FRAGMENT_NAME = 'c8y_Dashboard';
|
|
350
|
-
this.APPLIED_TO_FRAGMENT =
|
|
388
|
+
this.APPLIED_TO_FRAGMENT = APPLIED_TO_APPLICATIONS_FRAGMENT;
|
|
351
389
|
this.DASHBOARD_ROUTE_PATH = 'dashboard';
|
|
352
390
|
this.INDEX_SPLIT = '!';
|
|
353
391
|
this.CACHE_TIMEOUT = 500;
|
|
@@ -753,16 +791,24 @@ class ContextDashboardService {
|
|
|
753
791
|
* If application key is not available, it returns empty object.
|
|
754
792
|
* Use it when you need to filter dashboards according to
|
|
755
793
|
* application they are applied to and need pagination.
|
|
794
|
+
*
|
|
795
|
+
* @param strictAppFilter - When `true`, legacy dashboards with no
|
|
796
|
+
* `c8y_AppliedToApplications` fragment are excluded instead of shown in all apps.
|
|
756
797
|
*/
|
|
757
|
-
appliedToFilter() {
|
|
798
|
+
appliedToFilter(strictAppFilter = false) {
|
|
758
799
|
const key = this.appStateService?.currentApplication?.value?.key || '';
|
|
759
800
|
if (!key) {
|
|
760
801
|
return {};
|
|
761
802
|
}
|
|
803
|
+
const sharedKeys = getSharedApplicationKeys(this.appStateService?.currentApplication?.value?.dataSharing, 'dashboard');
|
|
804
|
+
const appKeys = [key, ...sharedKeys];
|
|
805
|
+
if (strictAppFilter) {
|
|
806
|
+
return { [this.APPLIED_TO_FRAGMENT]: { __in: appKeys } };
|
|
807
|
+
}
|
|
762
808
|
return {
|
|
763
809
|
__or: [
|
|
764
810
|
{ __not: { __has: this.APPLIED_TO_FRAGMENT } }, // legacy / unlabeled ⇒ show in all
|
|
765
|
-
{ [this.APPLIED_TO_FRAGMENT]: { __in:
|
|
811
|
+
{ [this.APPLIED_TO_FRAGMENT]: { __in: appKeys } }
|
|
766
812
|
]
|
|
767
813
|
};
|
|
768
814
|
}
|
|
@@ -1069,13 +1115,15 @@ class ContextDashboardService {
|
|
|
1069
1115
|
*/
|
|
1070
1116
|
async listAndFilterByApplication(query, strictAppFilter = false) {
|
|
1071
1117
|
const dashboards = await this.inventory.list({ query, pageSize: this.DEFAULT_PAGESIZE });
|
|
1072
|
-
const
|
|
1118
|
+
const currentKey = this.appStateService?.currentApplication?.value?.key;
|
|
1119
|
+
const sharedKeys = getSharedApplicationKeys(this.appStateService?.currentApplication?.value?.dataSharing, 'dashboard');
|
|
1120
|
+
const appKeys = new Set([currentKey, ...sharedKeys]);
|
|
1073
1121
|
dashboards.data = dashboards.data.filter(dashboard => {
|
|
1074
1122
|
const appliedTo = Array.from(dashboard[this.APPLIED_TO_FRAGMENT] || []);
|
|
1075
1123
|
if (strictAppFilter) {
|
|
1076
|
-
return
|
|
1124
|
+
return appliedTo.some(k => appKeys.has(k));
|
|
1077
1125
|
}
|
|
1078
|
-
return appliedTo.
|
|
1126
|
+
return appliedTo.some(k => appKeys.has(k)) || !dashboard[this.APPLIED_TO_FRAGMENT];
|
|
1079
1127
|
});
|
|
1080
1128
|
return dashboards;
|
|
1081
1129
|
}
|
|
@@ -5251,5 +5299,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
5251
5299
|
* Generated bundle index. Do not edit.
|
|
5252
5300
|
*/
|
|
5253
5301
|
|
|
5254
|
-
export { ALL_GLOBAL_ROLES_SELECTED, AddDashboardComponent, AddDashboardFactory, AppearanceSettingsComponent, AssetPropertyMappingsComponent, AssetPropertyMappingsService, CONTEXT_DASHBOARD_CONFIG, ContextDashboardComponent, ContextDashboardModule, ContextDashboardService, ContextDashboardType, DASHBOARD_CHILDREN_STATE_NAME, DASHBOARD_DETAILS_OUTLET, DASHBOARD_DETAILS_TABS_OUTLET_NAME, DASHBOARD_FOUNDATION_MAP, DASHBOARD_SETTINGS_CHANGES, DASHBOARD_THEME_CLASSES, DashboardActionBarFactory, DashboardDetailComponent, DashboardDetailService, DashboardDetailsTabId, DeviceInfoDashboardComponent, DeviceInfoDashboardModule, DeviceManagementHomeDashboardComponent, DeviceManagementHomeDashboardModule, FOUNDATION_CLASSES, GlobalContextSectionComponent, HOOK_WIDGET_CONFIG, NewDashboardGuard, PRODUCT_EXPERIENCE, PasteDashboardActionComponent, REPORT_DEFAULT_NAVIGATION_NODE_PRIORITY, STYLING_CLASS_PREFIXES, THEME_FOUNDATION_MAP, TypeDashboardInfoComponent, WIDGET_CONTENT_CLASSES, WIDGET_FOUNDATION_MAP, WIDGET_HEADER_CLASSES, WidgetAssetSelectorComponent, WidgetConfigAppearanceComponent, WidgetConfigChangeMode, WidgetConfigComponent, WidgetConfigFeedbackComponent, WidgetConfigGeneralComponent, WidgetConfigSectionComponent, WidgetConfigSectionService, WidgetConfigService, WidgetPreviewComponent, WidgetPreviewWrapperComponent, WidgetService, escapeForRegExp, getPropertyBaseKey, hookWidgetConfig, newDashboardTab, quoteAndEscape, renamePropertyKeyInCode };
|
|
5302
|
+
export { ALL_GLOBAL_ROLES_SELECTED, APPLIED_TO_APPLICATIONS_FRAGMENT, AddDashboardComponent, AddDashboardFactory, AppearanceSettingsComponent, AssetPropertyMappingsComponent, AssetPropertyMappingsService, CONTEXT_DASHBOARD_CONFIG, ContextDashboardComponent, ContextDashboardModule, ContextDashboardService, ContextDashboardType, DASHBOARD_CHILDREN_STATE_NAME, DASHBOARD_DETAILS_OUTLET, DASHBOARD_DETAILS_TABS_OUTLET_NAME, DASHBOARD_FOUNDATION_MAP, DASHBOARD_SETTINGS_CHANGES, DASHBOARD_THEME_CLASSES, DashboardActionBarFactory, DashboardDetailComponent, DashboardDetailService, DashboardDetailsTabId, DeviceInfoDashboardComponent, DeviceInfoDashboardModule, DeviceManagementHomeDashboardComponent, DeviceManagementHomeDashboardModule, FOUNDATION_CLASSES, GlobalContextSectionComponent, HOOK_WIDGET_CONFIG, NewDashboardGuard, PRODUCT_EXPERIENCE, PasteDashboardActionComponent, REPORT_DEFAULT_NAVIGATION_NODE_PRIORITY, STYLING_CLASS_PREFIXES, THEME_FOUNDATION_MAP, TypeDashboardInfoComponent, WIDGET_CONTENT_CLASSES, WIDGET_FOUNDATION_MAP, WIDGET_HEADER_CLASSES, WidgetAssetSelectorComponent, WidgetConfigAppearanceComponent, WidgetConfigChangeMode, WidgetConfigComponent, WidgetConfigFeedbackComponent, WidgetConfigGeneralComponent, WidgetConfigSectionComponent, WidgetConfigSectionService, WidgetConfigService, WidgetPreviewComponent, WidgetPreviewWrapperComponent, WidgetService, applyEntitySharing, escapeForRegExp, getPropertyBaseKey, getSharedApplicationKeys, hookWidgetConfig, newDashboardTab, quoteAndEscape, renamePropertyKeyInCode };
|
|
5255
5303
|
//# sourceMappingURL=c8y-ngx-components-context-dashboard.mjs.map
|