@c8y/ngx-components 1023.43.3 → 1023.47.0
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/ai/agent-chat/index.d.ts +6 -1
- package/ai/agent-chat/index.d.ts.map +1 -1
- package/ai/ai-chat/index.d.ts.map +1 -1
- package/ai/index.d.ts +39 -30
- package/ai/index.d.ts.map +1 -1
- package/datapoints-export-selector/index.d.ts +2 -1
- package/datapoints-export-selector/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs +24 -9
- package/fesm2022/c8y-ngx-components-ai-agent-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs +5 -4
- package/fesm2022/c8y-ngx-components-ai-ai-chat.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-ai.mjs +63 -33
- package/fesm2022/c8y-ngx-components-ai.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +1 -1
- package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs +6 -3
- package/fesm2022/c8y-ngx-components-datapoints-export-selector.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart.mjs +3 -2
- package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-global-context.mjs +29 -20
- package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-trusted-certificates.mjs +1 -5
- package/fesm2022/c8y-ngx-components-trusted-certificates.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-upgrade.mjs +3 -33
- package/fesm2022/c8y-ngx-components-upgrade.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs +1203 -34
- package/fesm2022/c8y-ngx-components-widgets-definitions-html-widget-ai-config.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-definitions-markdown.mjs +3 -2
- package/fesm2022/c8y-ngx-components-widgets-definitions-markdown.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +32 -4
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-table.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-markdown.mjs +122 -80
- package/fesm2022/c8y-ngx-components-widgets-implementations-markdown.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components.mjs +55 -6
- package/fesm2022/c8y-ngx-components.mjs.map +1 -1
- package/global-context/index.d.ts.map +1 -1
- package/index.d.ts +28 -3
- package/index.d.ts.map +1 -1
- package/locales/de.po +9 -18
- package/locales/es.po +8 -17
- package/locales/fr.po +13 -22
- package/locales/ja_JP.po +8 -17
- package/locales/ko.po +8 -17
- package/locales/locales.pot +11 -17
- package/locales/nl.po +8 -17
- package/locales/pl.po +8 -17
- package/locales/pt_BR.po +8 -17
- package/locales/zh_CN.po +8 -17
- package/locales/zh_TW.po +8 -17
- package/package.json +1 -1
- package/trusted-certificates/index.d.ts +0 -2
- package/trusted-certificates/index.d.ts.map +1 -1
- package/upgrade/index.d.ts.map +1 -1
- package/widgets/definitions/markdown/index.d.ts +1 -1
- package/widgets/implementations/datapoints-graph/index.d.ts +3 -0
- package/widgets/implementations/datapoints-graph/index.d.ts.map +1 -1
- package/widgets/implementations/markdown/index.d.ts +23 -13
- package/widgets/implementations/markdown/index.d.ts.map +1 -1
- package/fesm2022/c8y-ngx-components-upgrade-not-found.component-CuCuYAkK.mjs +0 -19
- package/fesm2022/c8y-ngx-components-upgrade-not-found.component-CuCuYAkK.mjs.map +0 -1
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { hookWidgetConfig } from '@c8y/ngx-components/context-dashboard';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, Injectable } from '@angular/core';
|
|
3
|
+
import { inject, Injector, Injectable } from '@angular/core';
|
|
4
4
|
import { PreviewService } from '@c8y/ngx-components';
|
|
5
5
|
import { gettext } from '@c8y/ngx-components/gettext';
|
|
6
6
|
import { AIService } from '@c8y/ngx-components/ai';
|
|
7
7
|
import { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';
|
|
8
8
|
import { combineLatest, from, first, map } from 'rxjs';
|
|
9
9
|
|
|
10
|
-
// This file contains the definitions for the various AI agents used in the application.
|
|
11
10
|
const EXTRACT_TAG_NAME = 'c8y-code-extract';
|
|
12
|
-
const WIDGET_CODE_INSTRUCTIONS = `You are responsible for creating a Web component that is rendered on the Dashboard of the Cumulocity IoT Platform. It is written as a lit-element. The following is a very basic example:
|
|
11
|
+
const WIDGET_CODE_INSTRUCTIONS = `You are responsible for creating a Web component that is rendered on the Dashboard of the Cumulocity IoT Platform. It is written as a lit-element. The following is a very basic example:
|
|
13
12
|
|
|
14
13
|
<${EXTRACT_TAG_NAME}>
|
|
15
14
|
import { LitElement, html, css} from 'lit';
|
|
@@ -17,13 +16,13 @@ import { styleImports } from 'styles';
|
|
|
17
16
|
|
|
18
17
|
export default class DefaultWebComponent extends LitElement {
|
|
19
18
|
static styles = css\`
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
:host > div {
|
|
22
21
|
padding-left: var(--c8y-root-component-padding);
|
|
23
22
|
padding-right: var(--c8y-root-component-padding);
|
|
24
23
|
}
|
|
25
|
-
span.branded {
|
|
26
|
-
color: var(--c8y-brand-primary);
|
|
24
|
+
span.branded {
|
|
25
|
+
color: var(--c8y-brand-primary);
|
|
27
26
|
}
|
|
28
27
|
\`;
|
|
29
28
|
|
|
@@ -61,14 +60,569 @@ span.branded {
|
|
|
61
60
|
|
|
62
61
|
You are allowed to use the following ESM imports and libs:
|
|
63
62
|
|
|
64
|
-
javascript
|
|
65
|
-
import { L } from 'leaflet';
|
|
66
63
|
import * as echarts from 'echarts'
|
|
67
64
|
import { fetch } from 'fetch'
|
|
68
65
|
|
|
66
|
+
## Leaflet Map Integration Guidelines
|
|
67
|
+
|
|
68
|
+
When building a widget that displays a map, you must use the Leaflet library. Follow the implementation pattern below as your foundation,
|
|
69
|
+
with flexibility to make improvements as needed.
|
|
70
|
+
|
|
71
|
+
**IMPORTANT: Remember to not use any Leaflet plugin. You are only allowed to use pure Leaflet.**
|
|
72
|
+
|
|
73
|
+
### Reference Implementation
|
|
74
|
+
|
|
75
|
+
Use this complete example as your guide for implementing map functionality:
|
|
76
|
+
|
|
77
|
+
\`\`\`javascript
|
|
78
|
+
import { LitElement, html, css } from 'lit';
|
|
79
|
+
import { styleImports } from 'styles';
|
|
80
|
+
|
|
81
|
+
export default class DeviceLocationMapWidget extends LitElement {
|
|
82
|
+
static styles = css\`
|
|
83
|
+
:host > div {
|
|
84
|
+
padding: var(--c8y-root-component-padding);
|
|
85
|
+
height: 100%;
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.map-container {
|
|
91
|
+
flex: 1;
|
|
92
|
+
min-height: 400px;
|
|
93
|
+
border: 1px solid var(--c8y-root-component-border-color);
|
|
94
|
+
border-radius: var(--c8y-root-component-border-radius-base);
|
|
95
|
+
position: relative;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.no-position {
|
|
99
|
+
text-align: center;
|
|
100
|
+
padding: 40px;
|
|
101
|
+
color: var(--text-muted);
|
|
102
|
+
}
|
|
103
|
+
\`;
|
|
104
|
+
|
|
105
|
+
static properties = {
|
|
106
|
+
error: { type: String },
|
|
107
|
+
map: { type: Object },
|
|
108
|
+
c8yContext: { type: Object },
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
constructor() {
|
|
112
|
+
super();
|
|
113
|
+
this.error = null;
|
|
114
|
+
this.map = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async connectedCallback() {
|
|
118
|
+
super.connectedCallback();
|
|
119
|
+
await this.updateComplete;
|
|
120
|
+
this.initializeMap();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
disconnectedCallback() {
|
|
124
|
+
super.disconnectedCallback();
|
|
125
|
+
if (this.map) {
|
|
126
|
+
this.map.remove();
|
|
127
|
+
this.map = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getStatus(device) {
|
|
132
|
+
if (!device.c8y_ActiveAlarmsStatus) {
|
|
133
|
+
return 'text-muted';
|
|
134
|
+
}
|
|
135
|
+
if (device.c8y_ActiveAlarmsStatus.critical) {
|
|
136
|
+
return 'status critical';
|
|
137
|
+
}
|
|
138
|
+
if (device.c8y_ActiveAlarmsStatus.major) {
|
|
139
|
+
return 'status major';
|
|
140
|
+
}
|
|
141
|
+
if (device.c8y_ActiveAlarmsStatus.minor) {
|
|
142
|
+
return 'status minor';
|
|
143
|
+
}
|
|
144
|
+
if (device.c8y_ActiveAlarmsStatus.warning) {
|
|
145
|
+
return 'status warning';
|
|
146
|
+
}
|
|
147
|
+
return 'text-muted';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
initializeMap() {
|
|
151
|
+
if (!window.L) {
|
|
152
|
+
console.error('Leaflet (L) is not available');
|
|
153
|
+
this.error = 'Map library not loaded';
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const mapContainer = this.shadowRoot.querySelector('#map');
|
|
158
|
+
if (!mapContainer || !this.c8yContext?.c8y_Position) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { lat, lng } = this.c8yContext.c8y_Position;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
this.map = window.L.map(mapContainer).setView([lat, lng], 15);
|
|
166
|
+
|
|
167
|
+
window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
168
|
+
attribution: '© OpenStreetMap contributors'
|
|
169
|
+
}).addTo(this.map);
|
|
170
|
+
|
|
171
|
+
const status = this.getStatus(this.c8yContext);
|
|
172
|
+
|
|
173
|
+
const deviceIcon = window.L.divIcon({
|
|
174
|
+
html: '<div class="dlt-c8y-icon-marker icon-3x ' + status + '"></div>',
|
|
175
|
+
className: 'c8y-map-marker-icon',
|
|
176
|
+
iconAnchor: [8, 8]
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const marker = window.L.marker([lat, lng], { icon: deviceIcon }).addTo(this.map);
|
|
180
|
+
|
|
181
|
+
const popupContent = '<div style="min-width: 200px;">' +
|
|
182
|
+
'<h4 style="margin: 0 0 8px 0;">' + (this.c8yContext?.name || '') + '</h4>' +
|
|
183
|
+
'<p style="margin: 4px 0; font-size: 12px;">' +
|
|
184
|
+
'<strong>Latitude:</strong> ' + lat.toFixed(6) + '<br>' +
|
|
185
|
+
'<strong>Longitude:</strong> ' + lng.toFixed(6) +
|
|
186
|
+
(this.c8yContext.c8y_Position.alt ? '<br><strong>Altitude:</strong> ' + this.c8yContext.c8y_Position.alt + 'm' : '') +
|
|
187
|
+
'</p>' +
|
|
188
|
+
'<p style="margin: 4px 0 0 0; font-size: 11px;">' +
|
|
189
|
+
'Last updated: ' + new Date(this.c8yContext?.lastUpdated || Date.now()).toLocaleString() +
|
|
190
|
+
'</p>' +
|
|
191
|
+
'</div>';
|
|
192
|
+
|
|
193
|
+
marker.bindPopup(popupContent);
|
|
194
|
+
|
|
195
|
+
this.map.setView([lat, lng], 15);
|
|
196
|
+
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
if (this.map) {
|
|
199
|
+
this.map.invalidateSize();
|
|
200
|
+
}
|
|
201
|
+
}, 100);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('Error initializing map:', error);
|
|
204
|
+
this.error = 'Failed to initialize map: ' + error.message;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
render() {
|
|
209
|
+
if (this.error) {
|
|
210
|
+
return html\`
|
|
211
|
+
<style>\${styleImports}</style>
|
|
212
|
+
<div>
|
|
213
|
+
<div class="alert alert-danger" role="alert">
|
|
214
|
+
<strong>Error:</strong> \${this.error}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
\`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!this.c8yContext?.c8y_Position) {
|
|
221
|
+
return html\`
|
|
222
|
+
<style>\${styleImports}</style>
|
|
223
|
+
<div>
|
|
224
|
+
<div class="no-position">
|
|
225
|
+
<h4>No Position Data Available</h4>
|
|
226
|
+
<p>The device "\${this.c8yContext?.name || 'Unknown'}" does not have position information.</p>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
\`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return html\`
|
|
233
|
+
<style>\${styleImports}</style>
|
|
234
|
+
<div>
|
|
235
|
+
<div class="map-container">
|
|
236
|
+
<div id="map" style="width: 100%; height: 100%;"></div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
\`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
## ECharts Integration Example
|
|
245
|
+
This example provides a generic implementation pattern for creating ECharts visualizations using Lit elements.
|
|
246
|
+
Use this as a reference for implementing any ECharts chart type (line, bar, pie, scatter, gauge, etc.).
|
|
247
|
+
|
|
248
|
+
\`\`\`typescript
|
|
249
|
+
import { LitElement, html, css } from 'lit';
|
|
250
|
+
import { styleImports } from 'styles';
|
|
251
|
+
import * as echarts from 'echarts';
|
|
252
|
+
import { fetch } from 'fetch';
|
|
253
|
+
|
|
254
|
+
export default class EnergyConsumptionPieChart extends LitElement {
|
|
255
|
+
static styles = css\`
|
|
256
|
+
:host > div {
|
|
257
|
+
padding: var(--c8y-root-component-padding);
|
|
258
|
+
height: 100%;
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: column;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.chart-container {
|
|
264
|
+
flex: 1;
|
|
265
|
+
min-height: 400px;
|
|
266
|
+
position: relative;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.chart-header {
|
|
270
|
+
margin-bottom: 16px;
|
|
271
|
+
padding-bottom: 12px;
|
|
272
|
+
border-bottom: 1px solid var(--c8y-root-component-separator-color);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.chart-title {
|
|
276
|
+
font-size: 18px;
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
color: var(--text-color);
|
|
279
|
+
margin: 0 0 4px 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.chart-subtitle {
|
|
283
|
+
font-size: 14px;
|
|
284
|
+
color: var(--text-muted);
|
|
285
|
+
margin: 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.loading-container {
|
|
289
|
+
display: flex;
|
|
290
|
+
justify-content: center;
|
|
291
|
+
align-items: center;
|
|
292
|
+
height: 300px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.error-container {
|
|
296
|
+
text-align: center;
|
|
297
|
+
padding: 40px 20px;
|
|
298
|
+
color: var(--text-muted);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.error-icon {
|
|
302
|
+
font-size: 48px;
|
|
303
|
+
margin-bottom: 16px;
|
|
304
|
+
color: var(--c8y-palette-status-warning);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.energy-summary {
|
|
308
|
+
display: flex;
|
|
309
|
+
justify-content: space-around;
|
|
310
|
+
margin-top: 16px;
|
|
311
|
+
padding: 16px;
|
|
312
|
+
background-color: var(--body-background-color);
|
|
313
|
+
border-radius: var(--c8y-root-component-border-radius-base);
|
|
314
|
+
border: 1px solid var(--c8y-root-component-border-color);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.energy-item {
|
|
318
|
+
text-align: center;
|
|
319
|
+
flex: 1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.energy-value {
|
|
323
|
+
font-size: 20px;
|
|
324
|
+
font-weight: 600;
|
|
325
|
+
margin-bottom: 4px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.energy-label {
|
|
329
|
+
font-size: 12px;
|
|
330
|
+
color: var(--text-muted);
|
|
331
|
+
text-transform: uppercase;
|
|
332
|
+
letter-spacing: 0.5px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.solar { color: #ffa726; }
|
|
336
|
+
.wind { color: #42a5f5; }
|
|
337
|
+
.grid { color: #66bb6a; }
|
|
338
|
+
.battery { color: #ab47bc; }
|
|
339
|
+
\`;
|
|
340
|
+
|
|
341
|
+
static properties = {
|
|
342
|
+
c8yContext: { type: Object },
|
|
343
|
+
chartInstance: { type: Object },
|
|
344
|
+
energyData: { type: Object },
|
|
345
|
+
loading: { type: Boolean },
|
|
346
|
+
error: { type: String }
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
constructor() {
|
|
350
|
+
super();
|
|
351
|
+
this.chartInstance = null;
|
|
352
|
+
this.energyData = null;
|
|
353
|
+
this.loading = false;
|
|
354
|
+
this.error = null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async connectedCallback() {
|
|
358
|
+
super.connectedCallback();
|
|
359
|
+
await this.updateComplete;
|
|
360
|
+
await this.loadEnergyData();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
disconnectedCallback() {
|
|
364
|
+
super.disconnectedCallback();
|
|
365
|
+
if (this.chartInstance) {
|
|
366
|
+
this.chartInstance.dispose();
|
|
367
|
+
this.chartInstance = null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async loadEnergyData() {
|
|
372
|
+
if (!this.c8yContext?.id) {
|
|
373
|
+
this.error = 'No device selected';
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
this.loading = true;
|
|
378
|
+
this.error = null;
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
// Try to fetch energy measurements from the device
|
|
382
|
+
const response = await fetch(\`/measurement/measurements?source=\${this.c8yContext.id}&type=c8y_EnergyMeasurement&pageSize=100&revert=true\`);
|
|
383
|
+
const data = await response.json();
|
|
384
|
+
|
|
385
|
+
if (data.measurements && data.measurements.length > 0) {
|
|
386
|
+
// Process real energy measurements
|
|
387
|
+
this.processRealEnergyData(data.measurements);
|
|
388
|
+
} else {
|
|
389
|
+
// If no energy measurements found, try other energy-related types
|
|
390
|
+
const alternativeResponse = await fetch(\`/measurement/measurements?source=\${this.c8yContext.id}&pageSize=100&revert=true\`);
|
|
391
|
+
const alternativeData = await alternativeResponse.json();
|
|
392
|
+
|
|
393
|
+
const energyMeasurements = alternativeData.measurements?.filter(m =>
|
|
394
|
+
m.type.toLowerCase().includes('energy') ||
|
|
395
|
+
m.type.toLowerCase().includes('power') ||
|
|
396
|
+
Object.keys(m).some(key => key.toLowerCase().includes('energy') || key.toLowerCase().includes('power'))
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (energyMeasurements && energyMeasurements.length > 0) {
|
|
400
|
+
this.processRealEnergyData(energyMeasurements);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('Error loading energy data:', error);
|
|
405
|
+
this.error = 'Failed to load energy data';
|
|
406
|
+
} finally {
|
|
407
|
+
this.loading = false;
|
|
408
|
+
await this.updateComplete;
|
|
409
|
+
this.initializeChart();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
processRealEnergyData(measurements) {
|
|
414
|
+
// Process real energy measurements and categorize by source
|
|
415
|
+
const energyBySource = {
|
|
416
|
+
solar: 0,
|
|
417
|
+
wind: 0,
|
|
418
|
+
grid: 0,
|
|
419
|
+
battery: 0
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
measurements.forEach(measurement => {
|
|
423
|
+
// Look for energy values in the measurement
|
|
424
|
+
Object.keys(measurement).forEach(key => {
|
|
425
|
+
if (key.startsWith('c8y_') && typeof measurement[key] === 'object') {
|
|
426
|
+
const fragment = measurement[key];
|
|
427
|
+
Object.keys(fragment).forEach(subKey => {
|
|
428
|
+
if (fragment[subKey] && typeof fragment[subKey] === 'object' && fragment[subKey].value !== undefined) {
|
|
429
|
+
const value = parseFloat(fragment[subKey].value) || 0;
|
|
430
|
+
|
|
431
|
+
// Categorize based on measurement type or fragment name
|
|
432
|
+
if (key.toLowerCase().includes('solar') || subKey.toLowerCase().includes('solar')) {
|
|
433
|
+
energyBySource.solar += value;
|
|
434
|
+
} else if (key.toLowerCase().includes('wind') || subKey.toLowerCase().includes('wind')) {
|
|
435
|
+
energyBySource.wind += value;
|
|
436
|
+
} else if (key.toLowerCase().includes('battery') || subKey.toLowerCase().includes('battery')) {
|
|
437
|
+
energyBySource.battery += value;
|
|
438
|
+
} else {
|
|
439
|
+
energyBySource.grid += value;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// If no categorized data found, distribute evenly for demo
|
|
448
|
+
const totalEnergy = Object.values(energyBySource).reduce((sum, val) => sum + val, 0);
|
|
449
|
+
this.energyData = energyBySource;
|
|
450
|
+
}
|
|
69
451
|
|
|
70
|
-
|
|
452
|
+
initializeChart() {
|
|
453
|
+
const chartContainer = this.shadowRoot.querySelector('#energyChart');
|
|
454
|
+
if (!chartContainer || !this.energyData) return;
|
|
71
455
|
|
|
456
|
+
if (this.chartInstance) {
|
|
457
|
+
this.chartInstance.dispose();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.chartInstance = echarts.init(chartContainer);
|
|
461
|
+
|
|
462
|
+
const chartData = [
|
|
463
|
+
{ value: this.energyData.solar, name: 'Solar', itemStyle: { color: '#ffa726' } },
|
|
464
|
+
{ value: this.energyData.wind, name: 'Wind', itemStyle: { color: '#42a5f5' } },
|
|
465
|
+
{ value: this.energyData.grid, name: 'Grid', itemStyle: { color: '#66bb6a' } },
|
|
466
|
+
{ value: this.energyData.battery, name: 'Battery', itemStyle: { color: '#ab47bc' } }
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
const totalConsumption = Object.values(this.energyData).reduce((sum, val) => sum + val, 0);
|
|
470
|
+
|
|
471
|
+
const option = {
|
|
472
|
+
tooltip: {
|
|
473
|
+
trigger: 'item',
|
|
474
|
+
formatter: function(params) {
|
|
475
|
+
const percentage = ((params.value / totalConsumption) * 100).toFixed(1);
|
|
476
|
+
return \`<strong>\${params.name}</strong><br/>
|
|
477
|
+
\${params.value.toFixed(1)} kWh (\${percentage}%)\`;
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
legend: {
|
|
481
|
+
orient: 'horizontal',
|
|
482
|
+
bottom: '10%',
|
|
483
|
+
left: 'center',
|
|
484
|
+
textStyle: {
|
|
485
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-color') || '#333'
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
series: [
|
|
489
|
+
{
|
|
490
|
+
name: 'Energy Consumption',
|
|
491
|
+
type: 'pie',
|
|
492
|
+
radius: ['40%', '70%'],
|
|
493
|
+
center: ['50%', '40%'],
|
|
494
|
+
avoidLabelOverlap: false,
|
|
495
|
+
itemStyle: {
|
|
496
|
+
borderRadius: 8,
|
|
497
|
+
borderColor: getComputedStyle(document.documentElement).getPropertyValue('--body-background-color') || '#fff',
|
|
498
|
+
borderWidth: 2
|
|
499
|
+
},
|
|
500
|
+
label: {
|
|
501
|
+
show: true,
|
|
502
|
+
position: 'outside',
|
|
503
|
+
formatter: function(params) {
|
|
504
|
+
const percentage = ((params.value / totalConsumption) * 100).toFixed(1);
|
|
505
|
+
return \`\${params.name}\\n\${percentage}%\`;
|
|
506
|
+
},
|
|
507
|
+
color: getComputedStyle(document.documentElement).getPropertyValue('--text-color') || '#333'
|
|
508
|
+
},
|
|
509
|
+
emphasis: {
|
|
510
|
+
label: {
|
|
511
|
+
show: true,
|
|
512
|
+
fontSize: 14,
|
|
513
|
+
fontWeight: 'bold'
|
|
514
|
+
},
|
|
515
|
+
itemStyle: {
|
|
516
|
+
shadowBlur: 10,
|
|
517
|
+
shadowOffsetX: 0,
|
|
518
|
+
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
data: chartData
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
this.chartInstance.setOption(option);
|
|
527
|
+
|
|
528
|
+
// Handle resize
|
|
529
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
530
|
+
if (this.chartInstance) {
|
|
531
|
+
this.chartInstance.resize();
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
resizeObserver.observe(chartContainer);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
render() {
|
|
538
|
+
if (this.loading) {
|
|
539
|
+
return html\`
|
|
540
|
+
<style>\${styleImports}</style>
|
|
541
|
+
<div>
|
|
542
|
+
<div class="chart-header">
|
|
543
|
+
<h3 class="chart-title">Energy Consumption Breakdown</h3>
|
|
544
|
+
<p class="chart-subtitle">Loading energy data...</p>
|
|
545
|
+
</div>
|
|
546
|
+
<div class="loading-container">
|
|
547
|
+
<div class="spinner">
|
|
548
|
+
<div class="rect1"></div>
|
|
549
|
+
<div class="rect2"></div>
|
|
550
|
+
<div class="rect3"></div>
|
|
551
|
+
<div class="rect4"></div>
|
|
552
|
+
<div class="rect5"></div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
\`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (this.error && !this.energyData) {
|
|
560
|
+
return html\`
|
|
561
|
+
<style>\${styleImports}</style>
|
|
562
|
+
<div>
|
|
563
|
+
<div class="chart-header">
|
|
564
|
+
<h3 class="chart-title">Energy Consumption Breakdown</h3>
|
|
565
|
+
<p class="chart-subtitle">Unable to load data</p>
|
|
566
|
+
</div>
|
|
567
|
+
<div class="error-container">
|
|
568
|
+
<div class="error-icon">⚠</div>
|
|
569
|
+
<h4>No Energy Data Available</h4>
|
|
570
|
+
<p>The device "\${this.c8yContext?.name || 'Unknown'}" does not have energy consumption measurements.</p>
|
|
571
|
+
<p class="text-muted">This widget requires energy measurements with types like c8y_EnergyMeasurement.</p>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
\`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const totalConsumption = this.energyData ? Object.values(this.energyData).reduce((sum, val) => sum + val, 0) : 0;
|
|
578
|
+
const deviceName = this.c8yContext?.name || 'Unknown Device';
|
|
579
|
+
|
|
580
|
+
return html\`
|
|
581
|
+
<style>\${styleImports}</style>
|
|
582
|
+
<div>
|
|
583
|
+
<div class="chart-header">
|
|
584
|
+
<h3 class="chart-title">Energy Consumption Breakdown</h3>
|
|
585
|
+
<p class="chart-subtitle">\${deviceName} - Total: \${totalConsumption.toFixed(1)} kWh</p>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<div class="chart-container">
|
|
589
|
+
<div id="energyChart" style="width: 100%; height: 100%;"></div>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
\${
|
|
593
|
+
this.energyData
|
|
594
|
+
? html\`
|
|
595
|
+
<div class="energy-summary">
|
|
596
|
+
<div class="energy-item">
|
|
597
|
+
<div class="energy-value solar">\${this.energyData.solar.toFixed(1)}</div>
|
|
598
|
+
<div class="energy-label">Solar kWh</div>
|
|
599
|
+
</div>
|
|
600
|
+
<div class="energy-item">
|
|
601
|
+
<div class="energy-value wind">\${this.energyData.wind.toFixed(1)}</div>
|
|
602
|
+
<div class="energy-label">Wind kWh</div>
|
|
603
|
+
</div>
|
|
604
|
+
<div class="energy-item">
|
|
605
|
+
<div class="energy-value grid">\${this.energyData.grid.toFixed(1)}</div>
|
|
606
|
+
<div class="energy-label">Grid kWh</div>
|
|
607
|
+
</div>
|
|
608
|
+
<div class="energy-item">
|
|
609
|
+
<div class="energy-value battery">\${this.energyData.battery.toFixed(1)}</div>
|
|
610
|
+
<div class="energy-label">Battery kWh</div>
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
\`
|
|
614
|
+
: ''
|
|
615
|
+
}
|
|
616
|
+
\</div>
|
|
617
|
+
\`;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
Always use the imported fetch function to make API calls authenticated to the Cumulocity instance.
|
|
624
|
+
|
|
625
|
+
Do not use mocking or fake data. Always use real, actual data whenever possible.
|
|
72
626
|
Do not include any emoji characters or Unicode symbols in the output - replace any decorative icons with plain text descriptions.
|
|
73
627
|
|
|
74
628
|
Among the UI elements that you are allowed to build on your own using HTML and CSS, here are components that you are encouraged to use in your answer:
|
|
@@ -122,7 +676,7 @@ Group a series of buttons together with .btn-group. Can be used for toolbars or
|
|
|
122
676
|
Wrap your message in a <div> with .alert class and a modifier class. Always add appropriate ARIA roles:
|
|
123
677
|
|
|
124
678
|
- Use role="status" for informational messages
|
|
125
|
-
- Use role="alert" for errors or messages
|
|
679
|
+
- Use role="alert" for errors or messages
|
|
126
680
|
requiring immediate attention
|
|
127
681
|
|
|
128
682
|
\`\`\`html
|
|
@@ -179,6 +733,8 @@ Combine badges with icons using .c8y-icon-badge wrapper:
|
|
|
179
733
|
## Loading Spinner
|
|
180
734
|
Shows content is being loaded or processed:
|
|
181
735
|
|
|
736
|
+
When content is loading, show only a visual loading indicator (spinner/progress bar) and suppress any 'Loading...' text messages and show it in the middle of the widget.
|
|
737
|
+
|
|
182
738
|
\`\`\`html
|
|
183
739
|
<div class="spinner">
|
|
184
740
|
<div class="rect1"></div>
|
|
@@ -471,15 +1027,623 @@ Alarms progress through three possible statuses during their lifecycle:
|
|
|
471
1027
|
<!-- Cleared Status -->
|
|
472
1028
|
<i class="c8y-icon c8y-icon-alert-idle"></i>
|
|
473
1029
|
<span>Alarm cleared</span>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1030
|
+
\`\`\`
|
|
1031
|
+
|
|
1032
|
+
## Forms
|
|
1033
|
+
|
|
1034
|
+
### Input fields
|
|
1035
|
+
Input fields play a crucial role in forms, enabling users to input diverse data types like text, numbers, dates, and more.
|
|
1036
|
+
|
|
1037
|
+
\`\`\`html
|
|
1038
|
+
<input class="form-control input-sm" type="text" placeholder="Text input" autocomplete="off" aria-label="Text input">
|
|
1039
|
+
<input class="form-control" type="number" placeholder="Number input" step="5" aria-label="Number input">
|
|
1040
|
+
<input class="form-control input-lg" type="email" placeholder="Email input" aria-label="Email input">
|
|
1041
|
+
\`\`\`
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
### Textarea
|
|
1045
|
+
The textarea is used for multiline text input, often for extensive descriptions.
|
|
1046
|
+
|
|
1047
|
+
Consistent styling with other input fields is achieved by adding the form-control class. This ensures a 100% width and prevents horizontal resizing. You can still customize the rows attribute as necessary.
|
|
1048
|
+
|
|
1049
|
+
\`\`\`html
|
|
1050
|
+
<textarea class="form-control" rows="6" placeholder="Add multiline text here…" aria-label="Multiline text input"></textarea>
|
|
1051
|
+
\`\`\`\`
|
|
1052
|
+
|
|
1053
|
+
### Select
|
|
1054
|
+
The Select displays a list of options with a filter field. It enhances user interaction by providing a searchable list of options and supporting multiple selections, making the selection process efficient and user-friendly.
|
|
1055
|
+
|
|
1056
|
+
\`\`\`html
|
|
1057
|
+
<div class="c8y-select-wrapper">
|
|
1058
|
+
<select id="selectExample" class="form-control" aria-label="Native select example">
|
|
1059
|
+
<option>Pick one option…</option>
|
|
1060
|
+
<option>Option 1</option>
|
|
1061
|
+
<option>Option 2</option>
|
|
1062
|
+
<option>Option 3</option>
|
|
1063
|
+
<option>Option 4</option>
|
|
1064
|
+
<option>Option 5</option>
|
|
1065
|
+
</select>
|
|
1066
|
+
</div>
|
|
1067
|
+
\`\`\`
|
|
1068
|
+
|
|
1069
|
+
### Checkboxes and radio buttons
|
|
1070
|
+
Checkboxes allow users to select one or multiple options from a list, while radio buttons enable the selection of a single option from several choices.
|
|
1071
|
+
|
|
1072
|
+
For consistent styling across different browsers, it is essential to override the default appearance. To achieve this, enclose the input element within a label element and apply the appropriate class: c8y-checkbox for checkboxes or c8y-radio for radio buttons. Then, add an empty span, followed by another span containing the label text. Ensure proper styling by declaring the type attribute of the input element as either checkbox or radio
|
|
1073
|
+
|
|
1074
|
+
\`\`\`html
|
|
1075
|
+
<label class="c8y-checkbox" title="Checkbox One" aria-label="Checkbox One">
|
|
1076
|
+
<input type="checkbox" checked="checked" aria-checked="true">
|
|
1077
|
+
<span></span>
|
|
1078
|
+
<span>Checkbox One</span>
|
|
1079
|
+
</label>
|
|
1080
|
+
|
|
1081
|
+
<label class="c8y-checkbox" title="Checkbox Two" aria-label="Checkbox Two">
|
|
1082
|
+
<input type="checkbox" aria-checked="false">
|
|
1083
|
+
<span></span>
|
|
1084
|
+
<span>Checkbox Two</span>
|
|
1085
|
+
</label>
|
|
1086
|
+
|
|
1087
|
+
<label title="Checkbox Three" class="c8y-checkbox" aria-label="Checkbox Three">
|
|
1088
|
+
<input type="checkbox" [indeterminate]="true" aria-checked="mixed">
|
|
1089
|
+
<span></span>
|
|
1090
|
+
<span>Checkbox Three</span>
|
|
1091
|
+
</label>
|
|
1092
|
+
|
|
1093
|
+
<label class="c8y-radio" title="Radio One" aria-label="Radio One">
|
|
1094
|
+
<input type="radio" name="rgroup1" checked="checked" aria-checked="true">
|
|
1095
|
+
<span></span>
|
|
1096
|
+
<span>Radio One</span>
|
|
1097
|
+
</label>
|
|
1098
|
+
|
|
1099
|
+
<label class="c8y-radio" title="Radio Two" aria-label="Radio Two">
|
|
1100
|
+
<input type="radio" name="rgroup1" aria-checked="false">
|
|
1101
|
+
<span></span>
|
|
1102
|
+
<span>Radio Two</span>
|
|
1103
|
+
</label>
|
|
1104
|
+
|
|
1105
|
+
<label title="Radio Three" class="c8y-radio" aria-label="Radio Three">
|
|
1106
|
+
<input type="radio" name="rgroup1" aria-checked="false">
|
|
1107
|
+
<span></span>
|
|
1108
|
+
<span>Radio Three</span>
|
|
1109
|
+
</label>
|
|
1110
|
+
\`\`\`
|
|
1111
|
+
|
|
1112
|
+
### Toggle switch
|
|
1113
|
+
The toggle switch is a UI element that provides a simple and intuitive way to toggle between two states, typically representing "On" and "Off" options
|
|
1114
|
+
|
|
1115
|
+
\`\`\`html
|
|
1116
|
+
<label class="c8y-switch" aria-label="Toggle switch label">
|
|
1117
|
+
<input type="checkbox" checked="checked" aria-checked="true">
|
|
1118
|
+
<span></span> Toggle switch label
|
|
1119
|
+
</label>
|
|
1120
|
+
|
|
1121
|
+
<label class="c8y-switch" title="Switch on/off" aria-label="Switch on/off">
|
|
1122
|
+
<input type="checkbox" aria-checked="false">
|
|
1123
|
+
<span></span>
|
|
1124
|
+
</label>
|
|
1125
|
+
<p>
|
|
1126
|
+
Some text with the toggle switch aligned
|
|
1127
|
+
<label class="c8y-switch c8y-switch--inline" aria-label="Toggle switch label inline">
|
|
1128
|
+
<input type="checkbox" aria-checked="false">
|
|
1129
|
+
<span></span> Toggle switch label
|
|
1130
|
+
</label>
|
|
1131
|
+
</p>
|
|
1132
|
+
\`\`\`
|
|
1133
|
+
|
|
1134
|
+
### Input groups
|
|
1135
|
+
Elevate text-based inputs with input groups. Easily add text, icons, or buttons before and/or after input fields for enhanced functionality and design.
|
|
1136
|
+
|
|
1137
|
+
Use .input-group with an .input-group-addon or .input-group-btn to prepend or append elements to a single input field. Place one add-on or button on either side of an input. You may also place them on both sides of an input.
|
|
1138
|
+
\`\`\`html
|
|
1139
|
+
<div class="input-group">
|
|
1140
|
+
<span class="input-group-addon" id="basic-addon1"> @ </span>
|
|
1141
|
+
<input class="form-control" type="text" placeholder="e.g. johndoe" [attr.aria-describedby]="'basic-addon1'" aria-label="Username input with @ prefix">
|
|
1142
|
+
</div>
|
|
1143
|
+
|
|
1144
|
+
<div class="input-group">
|
|
1145
|
+
<input class="form-control" type="text" placeholder="e.g. www.example" [attr.aria-describedby]="'basic-addon2'" aria-label="Website input">
|
|
1146
|
+
<span class="input-group-addon" id="basic-addon2">.com</span>
|
|
1147
|
+
</div>
|
|
1148
|
+
|
|
1149
|
+
<div class="input-group">
|
|
1150
|
+
<span class="input-group-addon">
|
|
1151
|
+
<i c8yIcon="euro"></i>
|
|
1152
|
+
</span>
|
|
1153
|
+
<input class="form-control" type="text" placeholder="e.g. 1000" aria-label="Amount input">
|
|
1154
|
+
<span class="input-group-addon">.00</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
|
|
1157
|
+
<div class="input-group">
|
|
1158
|
+
<span class="input-group-addon" id="basic-addon3">https://example.com/users/</span>
|
|
1159
|
+
<input class="form-control" type="text" id="basic-url" [attr.aria-describedby]="'basic-addon3'" placeholder="e.g. johndoe" aria-label="User URL input">
|
|
1160
|
+
<div class="input-group-btn">
|
|
1161
|
+
<button class="btn btn-primary" type="button" aria-label="Submit username">Submit</button>
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
\`\`\`
|
|
1165
|
+
|
|
1166
|
+
Search input-group
|
|
1167
|
+
Search input groups have specific styling, append .input-group-search to the .input-group to place the button inside the input field and add round corners.
|
|
1168
|
+
|
|
1169
|
+
\`\`\`html
|
|
1170
|
+
<div class="input-group input-group-search" id="example">
|
|
1171
|
+
<input class="form-control" type="search" placeholder="Search…" aria-label="Search input">
|
|
1172
|
+
<span class="input-group-btn">
|
|
1173
|
+
<button class="btn btn-clean" type="button" title="Search" aria-label="Search">
|
|
1174
|
+
<i class="c8y-icon dlt-c8y-icon-search"></i>
|
|
1175
|
+
</button>
|
|
1176
|
+
<button class="btn btn-clean" type="button" title="Clear search" aria-label="Clear search">
|
|
1177
|
+
<i class="c8y-icon dlt-c8y-icon-times"></i>
|
|
1178
|
+
</button>
|
|
1179
|
+
</span>
|
|
1180
|
+
</div>
|
|
1181
|
+
\`\`\`
|
|
1182
|
+
|
|
1183
|
+
Input group sizing
|
|
1184
|
+
Add the relative form sizing classes to the .input-group itself and contents within will automatically resize—no need for repeating the form control size classes on each element.
|
|
1185
|
+
|
|
1186
|
+
\`\`\`html
|
|
1187
|
+
<!-- Small size -->
|
|
1188
|
+
<div class="input-group input-group-sm">
|
|
1189
|
+
<span class="input-group-addon" id="sizing-addon3"> @ </span>
|
|
1190
|
+
<input class="form-control" type="text" placeholder="Username" aria-label="Small username input">
|
|
1191
|
+
</div>
|
|
1192
|
+
|
|
1193
|
+
<!-- Regular size -->
|
|
1194
|
+
<div class="input-group">
|
|
1195
|
+
<span class="input-group-addon" id="sizing-addon2"> @ </span>
|
|
1196
|
+
<input type="text" class="form-control" placeholder="Username" aria-label="Regular username input">
|
|
1197
|
+
</div>
|
|
1198
|
+
|
|
1199
|
+
<!-- Large size -->
|
|
1200
|
+
<div class="input-group input-group-lg">
|
|
1201
|
+
<span class="input-group-addon" id="sizing-addon1"> @ </span>
|
|
1202
|
+
<input class="form-control" type="text" placeholder="Username" aria-label="Large username input">
|
|
1203
|
+
</div>
|
|
1204
|
+
\`\`\`
|
|
1205
|
+
|
|
1206
|
+
Button addons
|
|
1207
|
+
Buttons in input groups are a bit different and require one extra level of nesting. Instead of .input-group-addon, you have to use .input-group-btn to wrap the buttons. This is required due to default browser styles that cannot be overridden.
|
|
1208
|
+
|
|
1209
|
+
\`\`\`html
|
|
1210
|
+
|
|
1211
|
+
<div class="input-group">
|
|
1212
|
+
<span class="input-group-btn">
|
|
1213
|
+
<button class="btn btn-default" type="button" aria-label="Verify username">Verify</button>
|
|
1214
|
+
</span>
|
|
1215
|
+
<input class="form-control" type="text" placeholder="e.g. John" aria-label="Name input">
|
|
1216
|
+
</div>
|
|
1217
|
+
|
|
1218
|
+
<div class="input-group">
|
|
1219
|
+
<input class="form-control" type="email" placeholder="e.g. email@example.com" aria-label="Email input">
|
|
1220
|
+
<span class="input-group-btn">
|
|
1221
|
+
<button class="btn btn-primary" type="button" aria-label="Submit email">Submit</button>
|
|
1222
|
+
</span>
|
|
1223
|
+
</div>
|
|
1224
|
+
|
|
1225
|
+
<div class="input-group">
|
|
1226
|
+
<input class="form-control" type="text" placeholder="e.g. 10" aria-label="Number input">
|
|
1227
|
+
<div class="input-group-btn">
|
|
1228
|
+
<button class="btn-dot btn-dot--danger">
|
|
1229
|
+
<i class="c8y-icon dlt-c8y-icon-minus-circle"></i>
|
|
1230
|
+
</button>
|
|
1231
|
+
</div>
|
|
1232
|
+
</div>
|
|
1233
|
+
|
|
1234
|
+
<div class="input-group">
|
|
1235
|
+
<input type="number" class="form-control" placeholder="e.g. 100" aria-label="Number input">
|
|
1236
|
+
<div class="input-group-btn">
|
|
1237
|
+
<button class="btn-dot btn-dot--danger">
|
|
1238
|
+
<i class="c8y-icon dlt-c8y-icon-minus-circle"></i>
|
|
1239
|
+
</button>
|
|
1240
|
+
<button class="btn-dot text-primary">
|
|
1241
|
+
<i class="c8y-icon dlt-c8y-icon-plus-circle"></i>
|
|
1242
|
+
</button>
|
|
1243
|
+
</div>
|
|
1244
|
+
</div>
|
|
1245
|
+
\`\`\`
|
|
1246
|
+
|
|
1247
|
+
### Card
|
|
1248
|
+
The card component is a highly versatile and flexible content container designed to accommodate various types of content.
|
|
1249
|
+
|
|
1250
|
+
A card consists of four primary elements: a .card wrapper containing a .card-header, a .card-block, and a .card-footer.
|
|
1251
|
+
|
|
1252
|
+
\`\`\`html
|
|
1253
|
+
<div class="card" role="region" aria-label="Default card">
|
|
1254
|
+
<div class="card-header separator">
|
|
1255
|
+
<p class="card-title">Card title</p>
|
|
1256
|
+
</div>
|
|
1257
|
+
<div class="card-block">
|
|
1258
|
+
<p>Add <code>.separator</code> to the <code>.card-header</code> to display a border between <code>.card-header</code> and <code>.card-block</code>.</p>
|
|
1259
|
+
</div>
|
|
1260
|
+
<div class="card-footer">
|
|
1261
|
+
<button class="btn btn-primary" aria-label="Button in footer">Button in footer</button>
|
|
1262
|
+
</div>
|
|
1263
|
+
</div>
|
|
1264
|
+
|
|
1265
|
+
<div class="card" role="region" aria-label="Card with subtitle">
|
|
1266
|
+
<div class="card-header">
|
|
1267
|
+
<span>
|
|
1268
|
+
<p class="card-title">Card title</p>
|
|
1269
|
+
<p class="card-subtitle">Card optional subtitle</p>
|
|
1270
|
+
</span>
|
|
1271
|
+
</div>
|
|
1272
|
+
<div class="card-block">
|
|
1273
|
+
<p>When adding a subtitle don't forget to wrap both <code>.card-title</code> and <code>.card-subtitle</code> in a <code>span</code>.</p><br>
|
|
1274
|
+
<p>Add <code>.separator</code> to the <code>.card-footer</code> to display a border between <code>.card-block</code> and <code>.card-footer</code>.</p>
|
|
1275
|
+
</div>
|
|
1276
|
+
<div class="card-footer separator">
|
|
1277
|
+
<button class="btn btn-primary" aria-label="Button in footer">Button in footer</button>
|
|
1278
|
+
</div>
|
|
1279
|
+
</div>
|
|
1280
|
+
\`\`\`
|
|
1281
|
+
|
|
1282
|
+
Card holding a form
|
|
1283
|
+
To integrate forms or form elements within a card, place it inside the .card-block container. If you want the submit button in the footer, either add the .card class to the form or nest the form inside the .card with the .d-contents class containing both .card-block and .card-footer.
|
|
1284
|
+
|
|
1285
|
+
\`\`\`html
|
|
1286
|
+
|
|
1287
|
+
<form name="wanForm2" class="card" role="region" aria-label="Card form">
|
|
1288
|
+
<div class="card-header separator">
|
|
1289
|
+
<p class="card-title">Card form</p>
|
|
1290
|
+
</div>
|
|
1291
|
+
<div class="card-block">
|
|
1292
|
+
<div class="form-group">
|
|
1293
|
+
<label for="simStatus">SIM status</label>
|
|
1294
|
+
<input id="simStatus" type="text" class="form-control" disabled aria-label="SIM status">
|
|
1295
|
+
</div>
|
|
1296
|
+
<div class="form-group">
|
|
1297
|
+
<label for="apn">APN</label>
|
|
1298
|
+
<input id="apn" type="text" class="form-control" aria-label="APN">
|
|
1299
|
+
</div>
|
|
1300
|
+
<div class="form-group">
|
|
1301
|
+
<label for="user">User</label>
|
|
1302
|
+
<input id="user" type="text" class="form-control" aria-label="User">
|
|
1303
|
+
</div>
|
|
1304
|
+
<div class="form-group">
|
|
1305
|
+
<label for="password">Password</label>
|
|
1306
|
+
<input id="password" type="text" class="form-control" aria-label="Password">
|
|
1307
|
+
</div>
|
|
1308
|
+
<div class="form-group">
|
|
1309
|
+
<label for="authType">Auth type</label>
|
|
1310
|
+
<div class="c8y-select-wrapper">
|
|
1311
|
+
<select id="authType" class="form-control" aria-label="Auth type">
|
|
1312
|
+
<option label="PAP" value="string:pap">PAP</option>
|
|
1313
|
+
<option label="CHAP" value="string:chap" selected="selected">CHAP</option>
|
|
1314
|
+
</select>
|
|
1315
|
+
<span></span>
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
</div>
|
|
1319
|
+
<div class="card-footer separator">
|
|
1320
|
+
<button class="btn btn-primary" aria-label="Save changes">
|
|
1321
|
+
Save changes
|
|
1322
|
+
</button>
|
|
1323
|
+
<button class="btn btn-primary" aria-label="Save changes (SMS)">
|
|
1324
|
+
Save changes (SMS)
|
|
1325
|
+
</button>
|
|
1326
|
+
</div>
|
|
1327
|
+
</form>
|
|
1328
|
+
|
|
1329
|
+
<div class="card" role="region" aria-label="Card containing form">
|
|
1330
|
+
<div class="card-header separator">
|
|
1331
|
+
<p class="card-title">Containing the form</p>
|
|
1332
|
+
</div>
|
|
1333
|
+
<form name="wanForm" class="d-contents">
|
|
1334
|
+
<div class="card-block">
|
|
1335
|
+
<div class="form-group">
|
|
1336
|
+
<label for="simStatus">SIM status</label>
|
|
1337
|
+
<input id="simStatus" type="text" class="form-control" disabled aria-label="SIM status">
|
|
1338
|
+
</div>
|
|
1339
|
+
<div class="form-group">
|
|
1340
|
+
<label for="apn">APN</label>
|
|
1341
|
+
<input id="apn" type="text" class="form-control" aria-label="APN">
|
|
1342
|
+
</div>
|
|
1343
|
+
<div class="form-group">
|
|
1344
|
+
<label for="user">User</label>
|
|
1345
|
+
<input id="user" type="text" class="form-control" aria-label="User">
|
|
1346
|
+
</div>
|
|
1347
|
+
<div class="form-group">
|
|
1348
|
+
<label for="password">Password</label>
|
|
1349
|
+
<input id="password" type="text" class="form-control" aria-label="Password">
|
|
1350
|
+
</div>
|
|
1351
|
+
<div class="form-group">
|
|
1352
|
+
<label for="authType">Auth type</label>
|
|
1353
|
+
<div class="c8y-select-wrapper">
|
|
1354
|
+
<select id="authType" class="form-control" aria-label="Auth type">
|
|
1355
|
+
<option label="PAP" value="string:pap">PAP</option>
|
|
1356
|
+
<option label="CHAP" value="string:chap" selected="selected">CHAP</option>
|
|
1357
|
+
</select>
|
|
1358
|
+
<span></span>
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
</div>
|
|
1362
|
+
<div class="card-footer separator">
|
|
1363
|
+
<button class="btn btn-primary" aria-label="Save changes">
|
|
1364
|
+
Save changes
|
|
1365
|
+
</button>
|
|
1366
|
+
<button class="btn btn-primary" aria-label="Save changes (SMS)">
|
|
1367
|
+
Save changes (SMS)
|
|
1368
|
+
</button>
|
|
1369
|
+
</div>
|
|
1370
|
+
</form>
|
|
1371
|
+
</div>
|
|
1372
|
+
\`\`\`
|
|
1373
|
+
|
|
1374
|
+
Modifier classes
|
|
1375
|
+
Use .success, .warning, .danger, .highlight, and .info modifier classes to change the appearance of a card.
|
|
1376
|
+
|
|
1377
|
+
\`\`\`html
|
|
1378
|
+
<div class="card warning" role="region" aria-label="Warning card">
|
|
1379
|
+
<div class="card-header">
|
|
1380
|
+
<span>
|
|
1381
|
+
<p class="card-title">Warning Card</p>
|
|
1382
|
+
<p class="card-subtitle">Identify pain points</p>
|
|
1383
|
+
</span>
|
|
1384
|
+
</div>
|
|
1385
|
+
<div class="card-block">
|
|
1386
|
+
<p>Far, far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts.</p>
|
|
1387
|
+
<p>Separated, they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.</p>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div class="card-footer separator">
|
|
1390
|
+
<button class="btn btn-default" aria-label="Card Button">Card Button</button>
|
|
1391
|
+
</div>
|
|
1392
|
+
</div>
|
|
1393
|
+
|
|
1394
|
+
<div class="card info" role="region" aria-label="Info card">
|
|
1395
|
+
<div class="card-header">
|
|
1396
|
+
<span>
|
|
1397
|
+
<p class="card-title">Info Card</p>
|
|
1398
|
+
<p class="card-subtitle">Let's take this offline</p>
|
|
1399
|
+
</span>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="card-block">
|
|
1402
|
+
<p>Far, far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated, they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.</p>
|
|
1403
|
+
</div>
|
|
1404
|
+
</div>
|
|
1405
|
+
|
|
1406
|
+
<div class="card card-highlight" role="region" aria-label="Highlight card">
|
|
1407
|
+
<div class="card-header">
|
|
1408
|
+
<p class="card-title">Card highlight</p>
|
|
1409
|
+
</div>
|
|
1410
|
+
<div class="card-block">
|
|
1411
|
+
Adds a thick border around the card.
|
|
1412
|
+
</div>
|
|
1413
|
+
</div>
|
|
1414
|
+
|
|
1415
|
+
<div class="card danger" role="region" aria-label="Danger card">
|
|
1416
|
+
<div class="card-header separator">
|
|
1417
|
+
<span>
|
|
1418
|
+
<p class="card-title"><i c8yIcon="exclamation-circle"></i> Watch out</p>
|
|
1419
|
+
<p class="card-subtitle">Pay close attention</p>
|
|
1420
|
+
</span>
|
|
1421
|
+
</div>
|
|
1422
|
+
<div class="card-block">
|
|
1423
|
+
<p>Check your self, you aren't looking too good.</p>
|
|
1424
|
+
</div>
|
|
1425
|
+
<div class="card-footer">
|
|
1426
|
+
<button class="btn btn-default" aria-label="Take some time off">Take some time off</button>
|
|
1427
|
+
</div>
|
|
1428
|
+
</div>
|
|
1429
|
+
<div class="card success" role="region" aria-label="Success card">
|
|
1430
|
+
<div class="card-header separator">
|
|
1431
|
+
<p class="card-title"> <i c8yIcon="check-circle"></i> Well done!</p>
|
|
1432
|
+
</div>
|
|
1433
|
+
<div class="card-block">
|
|
1434
|
+
<p>That's how we do things around here.</p>
|
|
1435
|
+
</div>
|
|
1436
|
+
</div>
|
|
1437
|
+
\`\`\`
|
|
1438
|
+
|
|
1439
|
+
### Card group
|
|
1440
|
+
Card groups provide a flexible and accessible way to display multiple records or pieces of information in a grid layout. By wrapping cards in a .card-group, you ensure a consistent appearance and balanced height for all cards in the same row.
|
|
1441
|
+
|
|
1442
|
+
|
|
1443
|
+
Card-group without gutter
|
|
1444
|
+
To remove the default gutter, use the .card-group-block modifier class instead of .card-group and add the grid classes (for example, .col-sm-4) directly on the .card. This option is typically used in widgets or dashboards. Adding .interact-grid to .card-group-block displays a thick border on hover for every .card with an href or the pointer class.
|
|
1445
|
+
|
|
1446
|
+
\`\`\`html
|
|
1447
|
+
<div class="card">
|
|
1448
|
+
<div class="card-group-block interact-grid m-b-0" role="list" aria-label="Quick links">
|
|
1449
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Users" target="_blank" rel="noopener noreferrer">
|
|
1450
|
+
<div class="card-block">
|
|
1451
|
+
<div class="h1">
|
|
1452
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-user"></i>
|
|
1453
|
+
</div>
|
|
1454
|
+
<p class="text-muted">Users</p>
|
|
1455
|
+
</div>
|
|
1456
|
+
</a>
|
|
1457
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Roles" target="_blank" rel="noopener noreferrer">
|
|
1458
|
+
<div class="card-block">
|
|
1459
|
+
<div class="h1">
|
|
1460
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-users"></i>
|
|
1461
|
+
</div>
|
|
1462
|
+
<p class="text-muted">Roles</p>
|
|
1463
|
+
</div>
|
|
1464
|
+
</a>
|
|
1465
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Applications" target="_blank" rel="noopener noreferrer">
|
|
1466
|
+
<div class="card-block">
|
|
1467
|
+
<div class="h1">
|
|
1468
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-modules"></i>
|
|
1469
|
+
</div>
|
|
1470
|
+
<p class="text-muted">Applications</p>
|
|
1471
|
+
</div>
|
|
1472
|
+
</a>
|
|
1473
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Event processing" target="_blank" rel="noopener noreferrer">
|
|
1474
|
+
<div class="card-block">
|
|
1475
|
+
<div class="h1">
|
|
1476
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-event-processing"></i>
|
|
1477
|
+
</div>
|
|
1478
|
+
<p class="text-muted">Event processing</p>
|
|
1479
|
+
</div>
|
|
1480
|
+
</a>
|
|
1481
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Application settings" target="_blank" rel="noopener noreferrer">
|
|
1482
|
+
<div class="card-block">
|
|
1483
|
+
<div class="h1">
|
|
1484
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-tools"></i>
|
|
1485
|
+
</div>
|
|
1486
|
+
<p class="text-muted">Application settings</p>
|
|
1487
|
+
</div>
|
|
1488
|
+
</a>
|
|
1489
|
+
<a class="col-sm-4 col-xs-6 card text-center" href="javascript: void(0)" role="listitem" aria-label="Usage statistics" target="_blank" rel="noopener noreferrer">
|
|
1490
|
+
<div class="card-block">
|
|
1491
|
+
<div class="h1">
|
|
1492
|
+
<i aria-hidden="true" class="c8y-icon c8y-icon-duocolor c8y-icon-usage-statistics"></i>
|
|
1493
|
+
</div>
|
|
1494
|
+
<p class="text-muted">Usage statistics</p>
|
|
1495
|
+
</div>
|
|
1496
|
+
</a>
|
|
1497
|
+
</div>
|
|
1498
|
+
</div>
|
|
1499
|
+
\`\`\`
|
|
1500
|
+
|
|
1501
|
+
CUMULOCITY STYLE UTILITIES
|
|
1502
|
+
|
|
1503
|
+
The following utility classes are available and encouraged for use in your implementation. These classes provide consistent styling patterns across the platform.
|
|
1504
|
+
|
|
1505
|
+
BACKGROUND COLORS
|
|
1506
|
+
|
|
1507
|
+
Brand Colors:
|
|
1508
|
+
- bg-primary
|
|
1509
|
+
- bg-primary-light
|
|
1510
|
+
- bg-complementary
|
|
1511
|
+
|
|
1512
|
+
Accent Colors:
|
|
1513
|
+
- bg-accent
|
|
1514
|
+
- bg-accent-light
|
|
1515
|
+
- bg-accent-dark
|
|
1516
|
+
|
|
1517
|
+
Status Colors:
|
|
1518
|
+
- bg-info, bg-info-light, bg-info-dark
|
|
1519
|
+
- bg-success, bg-success-light, bg-success-dark
|
|
1520
|
+
- bg-warning, bg-warning-light, bg-warning-dark
|
|
1521
|
+
- bg-danger, bg-danger-light, bg-danger-dark
|
|
1522
|
+
|
|
1523
|
+
Gray Scale:
|
|
1524
|
+
- bg-gray-10 through bg-gray-100 (increments of 10)
|
|
1525
|
+
- Available values: 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
|
|
1526
|
+
|
|
1527
|
+
TYPOGRAPHY
|
|
1528
|
+
|
|
1529
|
+
Font Weight:
|
|
1530
|
+
- text-normal
|
|
1531
|
+
- text-medium
|
|
1532
|
+
- text-bold
|
|
1533
|
+
|
|
1534
|
+
Font Size:
|
|
1535
|
+
- text-10, text-12, text-14, text-16
|
|
1536
|
+
|
|
1537
|
+
Line Height:
|
|
1538
|
+
- l-h-1
|
|
1539
|
+
- l-h-base
|
|
1540
|
+
- l-h-inherit
|
|
1541
|
+
|
|
1542
|
+
Letter Case:
|
|
1543
|
+
- text-lowercase
|
|
1544
|
+
- text-uppercase
|
|
1545
|
+
- text-capitalize
|
|
1546
|
+
|
|
1547
|
+
Text Behavior:
|
|
1548
|
+
- text-nowrap (prevents text wrapping)
|
|
1549
|
+
- text-pre-wrap (preserves line breaks)
|
|
1550
|
+
- text-break-all (breaks words without considering spelling)
|
|
1551
|
+
- text-break-word (breaks words considering spelling)
|
|
1552
|
+
- text-truncate (single line truncation with ellipsis)
|
|
1553
|
+
- text-truncate-wrap (multiline truncation for long words/URLs)
|
|
1554
|
+
- text-rtl (right-to-left text direction)
|
|
1555
|
+
|
|
1556
|
+
USAGE GUIDELINES:
|
|
1557
|
+
- These utility classes are encouraged but not mandatory
|
|
1558
|
+
- They provide consistent styling patterns across the Cumulocity platform
|
|
1559
|
+
- When using truncation classes, always include a title attribute for accessibility
|
|
1560
|
+
- Prefer these utilities over inline styles for maintainability
|
|
1561
|
+
- Avoid using brand colors in the HTML widget for the sections or big elements, the brand is used only as an accent.
|
|
1562
|
+
- Prefer simple or neutral colors
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
## Cumulocity Design Token Usage Guidelines
|
|
1566
|
+
|
|
1567
|
+
## Token Philosophy
|
|
1568
|
+
When generating HTML widget code for Cumulocity IoT platform, **strongly prefer** using CSS custom properties (design tokens) over hardcoded color values. This ensures consistency with the platform's theming system and allows widgets to adapt to both light and dark themes automatically.
|
|
1569
|
+
|
|
1570
|
+
## Token Usage Priority
|
|
1571
|
+
1. **PREFERRED**: Use Cumulocity design tokens (e.g., \`var(--c8y-brand-primary)\`)
|
|
1572
|
+
2. **ACCEPTABLE**: Use semantic color names with fallback (e.g., \`var(--brand-primary, #119d11)\`)
|
|
1573
|
+
3. **AVOID**: Hardcoded hex/rgb colors unless specifically required
|
|
1574
|
+
|
|
1575
|
+
## Available Token Categories
|
|
1576
|
+
|
|
1577
|
+
### Brand Colors
|
|
1578
|
+
Use for primary UI elements, CTAs, and brand identity:
|
|
1579
|
+
- \`var(--c8y-brand-primary)\` - Main brand color (green in light theme, yellow in dark theme)
|
|
1580
|
+
- \`var(--c8y-brand-dark)\` - Darker brand variant
|
|
1581
|
+
- \`var(--c8y-brand-light)\` - Lighter brand variant
|
|
1582
|
+
- Brand shades: \`var(--c8y-brand-10)\` through \`var(--c8y-brand-80)\` for subtle variations
|
|
1583
|
+
|
|
1584
|
+
### Status Colors
|
|
1585
|
+
Use for alerts, notifications, and state indicators:
|
|
1586
|
+
- **Success**: \`var(--c8y-palette-status-success)\`, \`var(--c8y-palette-status-success-light)\`, \`var(--c8y-palette-status-success-dark)\`
|
|
1587
|
+
- **Danger**: \`var(--c8y-palette-status-danger)\`, \`var(--c8y-palette-status-danger-light)\`, \`var(--c8y-palette-status-danger-dark)\`
|
|
1588
|
+
- **Warning**: \`var(--c8y-palette-status-warning)\`, \`var(--c8y-palette-status-warning-light)\`, \`var(--c8y-palette-status-warning-dark)\`
|
|
1589
|
+
- **Info**: \`var(--c8y-palette-status-info)\`, \`var(--c8y-palette-status-info-light)\`, \`var(--c8y-palette-status-info-dark)\`
|
|
1590
|
+
|
|
1591
|
+
### Text & Typography
|
|
1592
|
+
Use for text content and hierarchy:
|
|
1593
|
+
- \`var(--text-color)\` - Primary text color
|
|
1594
|
+
- \`var(--text-muted)\` - Secondary/muted text
|
|
1595
|
+
- \`var(--link-color)\` - Hyperlink color
|
|
1596
|
+
- \`var(--link-hover-color)\` - Hyperlink hover state
|
|
1597
|
+
|
|
1598
|
+
### Background Colors
|
|
1599
|
+
- \`var(--body-background-color)\` - Main background
|
|
1600
|
+
- \`var(--navigator-bg-color)\` - Navigation background
|
|
1601
|
+
- \`var(--action-bar-background-default)\` - Action bar background
|
|
1602
|
+
|
|
1603
|
+
### Borders & Spacing
|
|
1604
|
+
- \`var(--c8y-root-component-border-color)\` - Default border color
|
|
1605
|
+
- \`var(--c8y-root-component-separator-color)\` - Separator lines
|
|
1606
|
+
- \`var(--c8y-root-component-border-width)\` - Border thickness
|
|
1607
|
+
- \`var(--c8y-root-component-border-radius-base)\` - Default border radius
|
|
1608
|
+
- \`var(--btn-border-radius-base)\` - Button border radius
|
|
1609
|
+
|
|
1610
|
+
### Component-Specific Tokens
|
|
1611
|
+
**Navigation**:
|
|
1612
|
+
- \`var(--navigator-text-color)\`, \`var(--navigator-active-bg)\`, \`var(--navigator-border-active)\`
|
|
1613
|
+
|
|
1614
|
+
**Header**:
|
|
1615
|
+
- \`var(--header-color)\`, \`var(--header-text-color)\`, \`var(--header-hover-color)\`
|
|
1616
|
+
|
|
1617
|
+
**Action Bar**:
|
|
1618
|
+
- \`var(--action-bar-color-actions)\`, \`var(--action-bar-color-actions-hover)\`
|
|
1619
|
+
|
|
1620
|
+
## Implementation Examples
|
|
1621
|
+
|
|
1622
|
+
### Good Practice ✓
|
|
1623
|
+
\`\`\`css
|
|
1624
|
+
.my-widget {
|
|
1625
|
+
background-color: var(--body-background-color);
|
|
1626
|
+
color: var(--text-color);
|
|
1627
|
+
border: 1px solid var(--c8y-root-component-border-color);
|
|
1628
|
+
border-radius: var(--c8y-root-component-border-radius-base);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.success-badge {
|
|
1632
|
+
background-color: var(--c8y-palette-status-success-light);
|
|
1633
|
+
color: var(--c8y-palette-status-success-dark);
|
|
1634
|
+
border-left: 3px solid var(--c8y-palette-status-success);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
.primary-button {
|
|
1638
|
+
background-color: var(--c8y-brand-primary);
|
|
1639
|
+
color: var(--c8y-palette-fixed-light);
|
|
1640
|
+
border-radius: var(--btn-border-radius-base);
|
|
1641
|
+
}
|
|
1642
|
+
`;
|
|
1643
|
+
var HTML_WIDGET_AGENT = {
|
|
1644
|
+
name: 'c8y-html-widget',
|
|
1645
|
+
agent: {
|
|
1646
|
+
system: `1. **Analyze the user request**
|
|
483
1647
|
- Extract specific data requirements
|
|
484
1648
|
- Identify visualization needs
|
|
485
1649
|
- Note any context dependencies
|
|
@@ -498,29 +1662,34 @@ const HTML_WIDGET_AGENT = {
|
|
|
498
1662
|
- Check for mock data usage vs real API integration
|
|
499
1663
|
- If inadequate, ask user for specific clarifications needed
|
|
500
1664
|
`,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
]
|
|
1665
|
+
maxTokens: 20000
|
|
1666
|
+
},
|
|
1667
|
+
type: 'text',
|
|
1668
|
+
mcp: [
|
|
1669
|
+
{
|
|
1670
|
+
serverName: 'cumulocity-default',
|
|
1671
|
+
tools: ['cumulocity-api-request']
|
|
510
1672
|
}
|
|
511
1673
|
]
|
|
512
1674
|
};
|
|
513
1675
|
|
|
1676
|
+
const HTML_WIDGET_AGENT_DEFINITIONS = {
|
|
1677
|
+
snapshot: __MODE__ === 'development',
|
|
1678
|
+
label: gettext('HTML Widget Code assistant'),
|
|
1679
|
+
definition: HTML_WIDGET_AGENT
|
|
1680
|
+
};
|
|
1681
|
+
|
|
514
1682
|
class AIHtmlWidgetConfigFactory {
|
|
515
1683
|
constructor() {
|
|
516
1684
|
this.betaPreviewService = inject(PreviewService);
|
|
517
1685
|
this.aiService = inject(AIService);
|
|
1686
|
+
this.injector = inject(Injector);
|
|
518
1687
|
this.aiWidgetConfigDefinition = {
|
|
519
1688
|
widgetId: defaultWidgetIds.HTML,
|
|
520
1689
|
label: gettext('AI Code Assistant'),
|
|
521
1690
|
loadComponent: () => import('@c8y/ngx-components/ai/agent-chat').then(m => m.WidgetAiChatSectionComponent),
|
|
522
1691
|
initialState: {
|
|
523
|
-
agent:
|
|
1692
|
+
agent: HTML_WIDGET_AGENT_DEFINITIONS,
|
|
524
1693
|
title: gettext('I’m your AI Code Assistant, here to help you build powerful widgets for your dashboard.'),
|
|
525
1694
|
welcomeText: gettext('Describe the widget you want or select one of the options below to get started.'),
|
|
526
1695
|
loadRenderStepComponent: () => import('@c8y/ngx-components/widgets/implementations/html-widget').then(m => m.HtmlAiChatFeedbackComponent),
|
|
@@ -539,16 +1708,16 @@ class AIHtmlWidgetConfigFactory {
|
|
|
539
1708
|
}
|
|
540
1709
|
]
|
|
541
1710
|
},
|
|
542
|
-
priority: 100
|
|
1711
|
+
priority: 100,
|
|
1712
|
+
injector: this.injector
|
|
543
1713
|
};
|
|
544
1714
|
}
|
|
545
1715
|
get() {
|
|
546
1716
|
return combineLatest([
|
|
547
|
-
from(this.aiService.getAgentHealth(
|
|
1717
|
+
from(this.aiService.getAgentHealth()),
|
|
548
1718
|
this.betaPreviewService.getState$('ui.html-widget.v2').pipe(first())
|
|
549
|
-
]).pipe(map(([
|
|
550
|
-
|
|
551
|
-
if (state && shouldIncludeAi) {
|
|
1719
|
+
]).pipe(map(([aiHealthCheck, state]) => {
|
|
1720
|
+
if (state && aiHealthCheck.isProviderConfigured) {
|
|
552
1721
|
return [this.aiWidgetConfigDefinition];
|
|
553
1722
|
}
|
|
554
1723
|
return [];
|