@c8y/tutorial 1023.15.0 → 1023.17.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/cumulocity.config.ts +21 -0
- package/package.json +7 -7
- package/src/app/app.config.ts +4 -0
- package/src/icon-panel/icon-panel-example.component.html +20 -0
- package/src/icon-panel/icon-panel-example.component.ts +108 -0
- package/src/icon-panel/icon-panel-example.module.ts +16 -0
- package/src/icon-panel/index.ts +19 -0
- package/src/resizable-grid-example/resizable-grid-example.component.ts +10 -1
- package/src/split-view/fixed-example/split-view-fixed-example.component.html +97 -0
- package/src/split-view/fixed-example/split-view-fixed-example.component.ts +170 -0
- package/src/split-view/fixed-example/split-view-fixed-example.module.ts +15 -0
- package/src/split-view/full-width-example/split-view-full-width-example.component.html +62 -0
- package/src/split-view/full-width-example/split-view-full-width-example.component.ts +86 -0
- package/src/split-view/full-width-example/split-view-full-width-example.module.ts +17 -0
- package/src/split-view/index.ts +42 -0
- package/src/split-view/resizable-example/split-view-resizable-example.component.html +188 -0
- package/src/split-view/resizable-example/split-view-resizable-example.component.ts +214 -0
- package/src/split-view/resizable-example/split-view-resizable-example.module.ts +17 -0
- package/src/split-view/split-view-lazy.module.ts +37 -0
package/cumulocity.config.ts
CHANGED
|
@@ -276,6 +276,27 @@ export default {
|
|
|
276
276
|
description: 'A sample for breadcrumbs content projection.',
|
|
277
277
|
scope: 'self'
|
|
278
278
|
},
|
|
279
|
+
{
|
|
280
|
+
name: 'Resizable example',
|
|
281
|
+
module: 'SplitViewResizableExampleModule',
|
|
282
|
+
path: './src/split-view/resizable-example/split-view-resizable-example.module.ts',
|
|
283
|
+
description: 'A sample for split view with resizable layout.',
|
|
284
|
+
scope: 'self'
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'Fixed example',
|
|
288
|
+
module: 'SplitViewFixedExampleModule',
|
|
289
|
+
path: './src/split-view/fixed-example/split-view-fixed-example.module.ts',
|
|
290
|
+
description: 'A sample for split view with fixed layout.',
|
|
291
|
+
scope: 'self'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'Split view full width',
|
|
295
|
+
module: 'SplitViewFullWidthExampleModule',
|
|
296
|
+
path: './src/split-view/full-width-example/split-view-full-width-example.module.ts',
|
|
297
|
+
description: 'A sample for split view in full width mode.',
|
|
298
|
+
scope: 'self'
|
|
299
|
+
},
|
|
279
300
|
{
|
|
280
301
|
name: 'Component',
|
|
281
302
|
module: 'ComponentModule',
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1023.
|
|
3
|
+
"version": "1023.17.0",
|
|
4
4
|
"description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1023.
|
|
7
|
-
"@c8y/ngx-components": "1023.
|
|
8
|
-
"@c8y/client": "1023.
|
|
9
|
-
"@c8y/bootstrap": "1023.
|
|
6
|
+
"@c8y/style": "1023.17.0",
|
|
7
|
+
"@c8y/ngx-components": "1023.17.0",
|
|
8
|
+
"@c8y/client": "1023.17.0",
|
|
9
|
+
"@c8y/bootstrap": "1023.17.0",
|
|
10
10
|
"@angular/cdk": "^20.2.14",
|
|
11
11
|
"monaco-editor": "~0.53.0",
|
|
12
12
|
"ngx-bootstrap": "20.0.2",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"rxjs": "7.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@c8y/options": "1023.
|
|
18
|
-
"@c8y/devkit": "1023.
|
|
17
|
+
"@c8y/options": "1023.17.0",
|
|
18
|
+
"@c8y/devkit": "1023.17.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@angular/common": ">=20 <21"
|
package/src/app/app.config.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { provideMapExampleNavigator } from '../maps';
|
|
|
16
16
|
import { provideTranslationsNavigator } from '../translations';
|
|
17
17
|
import { provideLazyWidget } from '../lazy-widget';
|
|
18
18
|
import { provideBreadcrumbsNavigator } from '../breadcrumbs';
|
|
19
|
+
import { provideSplitViewSamples } from '../split-view';
|
|
20
|
+
import { provideIconPanelExample } from '../icon-panel';
|
|
19
21
|
import { provideClientInterceptorSample } from '../client-interceptor';
|
|
20
22
|
import { provideUserMenuSample } from '../user-menu';
|
|
21
23
|
import { AlarmsModule } from '@c8y/ngx-components/alarms';
|
|
@@ -53,6 +55,8 @@ export const appConfig: ApplicationConfig = {
|
|
|
53
55
|
...provideRedirectToLastRoute(),
|
|
54
56
|
...provideAPIMock(),
|
|
55
57
|
...provideBreadcrumbsNavigator(),
|
|
58
|
+
...provideSplitViewSamples(),
|
|
59
|
+
...provideIconPanelExample(),
|
|
56
60
|
importProvidersFrom(AlarmsModule.config({ hybrid: false }))
|
|
57
61
|
]
|
|
58
62
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<c8y-title>Icon panel examples</c8y-title>
|
|
2
|
+
<div class="p-16">
|
|
3
|
+
<p class="m-b-16 text-bold">Basic usage</p>
|
|
4
|
+
<c8y-icon-panel [sections]="alarmSections"></c8y-icon-panel>
|
|
5
|
+
|
|
6
|
+
<p class="m-t-24 m-b-16 text-bold">Display device details with icons and structured layout.</p>
|
|
7
|
+
<c8y-icon-panel [sections]="deviceSections"></c8y-icon-panel>
|
|
8
|
+
|
|
9
|
+
<p class="m-t-24 m-b-16 text-bold">
|
|
10
|
+
Combine structured sections with additional custom HTML content.
|
|
11
|
+
</p>
|
|
12
|
+
<c8y-icon-panel [sections]="deviceSections.slice(0, 2)">
|
|
13
|
+
<div class="col-xs-12 m-t-8">
|
|
14
|
+
<div class="alert alert-info m-b-0">
|
|
15
|
+
<strong>Tip:</strong>
|
|
16
|
+
You can project additional custom content alongside the icon panels.
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</c8y-icon-panel>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { IconPanelComponent, IconPanelSection, TitleComponent } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'c8y-icon-panel-example',
|
|
7
|
+
templateUrl: './icon-panel-example.component.html',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [CommonModule, IconPanelComponent, TitleComponent]
|
|
10
|
+
})
|
|
11
|
+
export class IconPanelExampleComponent {
|
|
12
|
+
deviceSections: IconPanelSection[] = [
|
|
13
|
+
{
|
|
14
|
+
id: 'device-info',
|
|
15
|
+
label: 'Device Information',
|
|
16
|
+
icon: 'c8y-device',
|
|
17
|
+
visible: true,
|
|
18
|
+
content: `
|
|
19
|
+
<div>
|
|
20
|
+
<p class="m-b-4"><strong>Model:</strong> Smart Thermostat Pro</p>
|
|
21
|
+
<p class="m-b-4"><strong>Serial:</strong> THM-001-2024</p>
|
|
22
|
+
<p class="m-b-0"><strong>Firmware:</strong> v2.4.1</p>
|
|
23
|
+
</div>
|
|
24
|
+
`,
|
|
25
|
+
colClass: 'col-xs-12 col-md-6'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'location',
|
|
29
|
+
label: 'Location',
|
|
30
|
+
icon: 'map-marker',
|
|
31
|
+
visible: true,
|
|
32
|
+
content: `
|
|
33
|
+
<div>
|
|
34
|
+
<p class="m-b-4">Building A - Floor 1</p>
|
|
35
|
+
<p class="m-b-0"><small class="text-muted">Room 101</small></p>
|
|
36
|
+
</div>
|
|
37
|
+
`,
|
|
38
|
+
colClass: 'col-xs-12 col-md-6'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'connection',
|
|
42
|
+
label: 'Connection Status',
|
|
43
|
+
icon: 'contactless-payment',
|
|
44
|
+
visible: true,
|
|
45
|
+
content:
|
|
46
|
+
'<p class="m-b-0"><strong class="text-success">Online</strong> - Last seen 2 minutes ago</p>',
|
|
47
|
+
colClass: 'col-xs-12 col-md-4',
|
|
48
|
+
iconClass: 'text-success'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'battery',
|
|
52
|
+
label: 'Battery Level',
|
|
53
|
+
icon: 'battery-full',
|
|
54
|
+
visible: true,
|
|
55
|
+
content: '<p class="m-b-0"><strong class="text-success">98%</strong> - Charging</p>',
|
|
56
|
+
colClass: 'col-xs-12 col-md-4',
|
|
57
|
+
iconClass: 'text-success'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'signal',
|
|
61
|
+
label: 'Signal Strength',
|
|
62
|
+
icon: 'signal',
|
|
63
|
+
visible: true,
|
|
64
|
+
content: '<p class="m-b-0"><strong class="text-warning">Medium</strong> - 3/5 bars</p>',
|
|
65
|
+
colClass: 'col-xs-12 col-md-4',
|
|
66
|
+
iconClass: 'text-warning'
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
alarmSections: IconPanelSection[] = [
|
|
71
|
+
{
|
|
72
|
+
id: 'critical',
|
|
73
|
+
label: 'Critical Alarms',
|
|
74
|
+
icon: 'exclamation-circle',
|
|
75
|
+
visible: true,
|
|
76
|
+
content: '<span class="badge badge-danger">2</span> active',
|
|
77
|
+
colClass: 'col-xs-12 col-sm-6 col-md-3',
|
|
78
|
+
iconClass: 'status critical stroked-icon'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'major',
|
|
82
|
+
label: 'Major Alarms',
|
|
83
|
+
icon: 'warning',
|
|
84
|
+
visible: true,
|
|
85
|
+
content: '<span class="badge badge-warning">5</span> active',
|
|
86
|
+
colClass: 'col-xs-12 col-sm-6 col-md-3',
|
|
87
|
+
iconClass: 'status major stroked-icon'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'minor',
|
|
91
|
+
label: 'Minor Alarms',
|
|
92
|
+
icon: 'high-priority',
|
|
93
|
+
visible: true,
|
|
94
|
+
content: '<span class="badge badge-info">12</span> active',
|
|
95
|
+
colClass: 'col-xs-12 col-sm-6 col-md-3',
|
|
96
|
+
iconClass: 'status minor stroked-icon'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'cleared',
|
|
100
|
+
label: 'Cleared today',
|
|
101
|
+
icon: 'ok',
|
|
102
|
+
visible: true,
|
|
103
|
+
content: '<span class="badge badge-success">28</span> cleared',
|
|
104
|
+
colClass: 'col-xs-12 col-sm-6 col-md-3',
|
|
105
|
+
iconClass: 'text-success'
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { RouterModule, Routes } from '@angular/router';
|
|
3
|
+
import { IconPanelExampleComponent } from './icon-panel-example.component';
|
|
4
|
+
|
|
5
|
+
const routes: Routes = [
|
|
6
|
+
{
|
|
7
|
+
path: '',
|
|
8
|
+
component: IconPanelExampleComponent
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
@NgModule({
|
|
13
|
+
imports: [IconPanelExampleComponent, RouterModule.forChild(routes)],
|
|
14
|
+
exports: [IconPanelExampleComponent]
|
|
15
|
+
})
|
|
16
|
+
export class IconPanelExampleModule {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EnvironmentProviders, Provider } from '@angular/core';
|
|
2
|
+
import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
|
|
3
|
+
|
|
4
|
+
export function provideIconPanelExample(): (Provider | EnvironmentProviders)[] {
|
|
5
|
+
return [
|
|
6
|
+
hookRoute({
|
|
7
|
+
path: 'icon-panel',
|
|
8
|
+
loadChildren: () => import('./icon-panel-example.module').then(m => m.IconPanelExampleModule)
|
|
9
|
+
}),
|
|
10
|
+
hookNavigator(
|
|
11
|
+
new NavigatorNode({
|
|
12
|
+
path: '/icon-panel',
|
|
13
|
+
label: 'Icon panel',
|
|
14
|
+
icon: 'rectangular',
|
|
15
|
+
priority: 250
|
|
16
|
+
})
|
|
17
|
+
)
|
|
18
|
+
];
|
|
19
|
+
}
|
|
@@ -6,7 +6,11 @@ import { ResizableGridComponent, CoreModule } from '@c8y/ngx-components';
|
|
|
6
6
|
template: `<c8y-title>Resizable Grid</c8y-title>
|
|
7
7
|
<div class="p-t-24">
|
|
8
8
|
<div class="card">
|
|
9
|
-
<c8y-resizable-grid
|
|
9
|
+
<c8y-resizable-grid
|
|
10
|
+
style="height: 350px;"
|
|
11
|
+
[collapsible]="collapsible"
|
|
12
|
+
[collapseThreshold]="320"
|
|
13
|
+
>
|
|
10
14
|
<div left-pane>
|
|
11
15
|
<div class="card-header">
|
|
12
16
|
<h4>Left Column</h4>
|
|
@@ -28,6 +32,11 @@ import { ResizableGridComponent, CoreModule } from '@c8y/ngx-components';
|
|
|
28
32
|
</div>
|
|
29
33
|
</c8y-resizable-grid>
|
|
30
34
|
</div>
|
|
35
|
+
<label class="c8y-switch">
|
|
36
|
+
<input type="checkbox" [(ngModel)]="collapsible" />
|
|
37
|
+
<span></span>
|
|
38
|
+
<span class="p-l-8">Enable Collapse Behavior (when width < 320px)</span>
|
|
39
|
+
</label>
|
|
31
40
|
</div>`,
|
|
32
41
|
standalone: true,
|
|
33
42
|
imports: [ResizableGridComponent, CoreModule]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<div class="p-l-16 p-r-16">
|
|
2
|
+
<c8y-title>Static example</c8y-title>
|
|
3
|
+
|
|
4
|
+
<c8y-sv
|
|
5
|
+
[isResizable]="false"
|
|
6
|
+
(selectionChange)="onSelectionChange($event)"
|
|
7
|
+
>
|
|
8
|
+
<c8y-sv-list
|
|
9
|
+
[title]="'Tools & utilities'"
|
|
10
|
+
[showEmptyState]="false"
|
|
11
|
+
>
|
|
12
|
+
<c8y-list-group>
|
|
13
|
+
@for (item of items; track item) {
|
|
14
|
+
<c8y-li
|
|
15
|
+
[selectable]="false"
|
|
16
|
+
[c8ySvListItem]="item"
|
|
17
|
+
>
|
|
18
|
+
<c8y-li-icon>
|
|
19
|
+
<i
|
|
20
|
+
class="icon-24"
|
|
21
|
+
[c8yIcon]="item.icon"
|
|
22
|
+
></i>
|
|
23
|
+
</c8y-li-icon>
|
|
24
|
+
|
|
25
|
+
<c8y-li-body>
|
|
26
|
+
<div class="d-flex a-i-center p-b-4">
|
|
27
|
+
<p class="m-b-0 text-bold text-16 flex-grow">{{ item.name }}</p>
|
|
28
|
+
<span
|
|
29
|
+
class="badge"
|
|
30
|
+
[ngClass]="getStatusClass(item.status)"
|
|
31
|
+
>
|
|
32
|
+
<i
|
|
33
|
+
class="icon-12 m-r-4"
|
|
34
|
+
[c8yIcon]="getStatusIcon(item.status)"
|
|
35
|
+
></i>
|
|
36
|
+
{{ item.status }}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
<p class="text-muted small m-b-4">{{ item.type }}</p>
|
|
40
|
+
</c8y-li-body>
|
|
41
|
+
</c8y-li>
|
|
42
|
+
}
|
|
43
|
+
</c8y-list-group>
|
|
44
|
+
<c8y-sv-footer>
|
|
45
|
+
<div class="d-flex justify-content-between">
|
|
46
|
+
<small class="text-muted">{{ items.length }} tools available</small>
|
|
47
|
+
</div>
|
|
48
|
+
</c8y-sv-footer>
|
|
49
|
+
</c8y-sv-list>
|
|
50
|
+
<c8y-sv-details
|
|
51
|
+
emptyStateIcon="cog"
|
|
52
|
+
emptyStateTitle="No tool selected"
|
|
53
|
+
emptyStateSubtitle="Select a tool from the list to view details"
|
|
54
|
+
[title]="'Tool details' | translate"
|
|
55
|
+
>
|
|
56
|
+
@if (selectedItem) {
|
|
57
|
+
<ng-container>
|
|
58
|
+
<div class="d-flex a-i-center m-b-16">
|
|
59
|
+
<i
|
|
60
|
+
class="icon-24 m-r-8"
|
|
61
|
+
[c8yIcon]="selectedItem.icon"
|
|
62
|
+
></i>
|
|
63
|
+
<h4 class="m-b-0 m-r-8 text-bold">{{ selectedItem.name }}</h4>
|
|
64
|
+
<span
|
|
65
|
+
class="badge"
|
|
66
|
+
[ngClass]="getStatusClass(selectedItem.status)"
|
|
67
|
+
>
|
|
68
|
+
<i
|
|
69
|
+
class="icon-12 m-r-4"
|
|
70
|
+
[c8yIcon]="getStatusIcon(selectedItem.status)"
|
|
71
|
+
></i>
|
|
72
|
+
{{ selectedItem.status }}
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
<p class="m-b-16">{{ selectedItem.description }}</p>
|
|
76
|
+
<c8y-icon-panel [sections]="itemInfoSections"></c8y-icon-panel>
|
|
77
|
+
</ng-container>
|
|
78
|
+
}
|
|
79
|
+
<c8y-sv-footer>
|
|
80
|
+
<button
|
|
81
|
+
class="btn btn-default btn-sm"
|
|
82
|
+
(click)="editItem()"
|
|
83
|
+
>
|
|
84
|
+
<i class="fa fa-edit"></i>
|
|
85
|
+
Edit
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
class="btn btn-danger btn-sm"
|
|
89
|
+
(click)="deleteItem()"
|
|
90
|
+
>
|
|
91
|
+
<i class="fa fa-trash"></i>
|
|
92
|
+
Delete
|
|
93
|
+
</button>
|
|
94
|
+
</c8y-sv-footer>
|
|
95
|
+
</c8y-sv-details>
|
|
96
|
+
</c8y-sv>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import {
|
|
5
|
+
CoreModule,
|
|
6
|
+
IconDirective,
|
|
7
|
+
IconPanelComponent,
|
|
8
|
+
IconPanelSection,
|
|
9
|
+
ListItemBodyComponent,
|
|
10
|
+
ListItemComponent,
|
|
11
|
+
ListItemIconComponent,
|
|
12
|
+
SplitViewComponent,
|
|
13
|
+
SplitViewDetailsComponent,
|
|
14
|
+
SplitViewFooterComponent,
|
|
15
|
+
SplitViewListComponent,
|
|
16
|
+
SplitViewListItemDirective
|
|
17
|
+
} from '@c8y/ngx-components';
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'c8y-split-view-fixed-example',
|
|
21
|
+
templateUrl: './split-view-fixed-example.component.html',
|
|
22
|
+
standalone: true,
|
|
23
|
+
imports: [
|
|
24
|
+
CommonModule,
|
|
25
|
+
FormsModule,
|
|
26
|
+
CoreModule,
|
|
27
|
+
ListItemComponent,
|
|
28
|
+
ListItemIconComponent,
|
|
29
|
+
ListItemBodyComponent,
|
|
30
|
+
IconDirective,
|
|
31
|
+
SplitViewComponent,
|
|
32
|
+
SplitViewListComponent,
|
|
33
|
+
SplitViewDetailsComponent,
|
|
34
|
+
SplitViewFooterComponent,
|
|
35
|
+
IconPanelComponent,
|
|
36
|
+
SplitViewListItemDirective
|
|
37
|
+
]
|
|
38
|
+
})
|
|
39
|
+
export class SplitViewFixedExampleComponent {
|
|
40
|
+
items = [
|
|
41
|
+
{
|
|
42
|
+
id: 1,
|
|
43
|
+
name: 'Configuration Manager',
|
|
44
|
+
type: 'System Tool',
|
|
45
|
+
status: 'Ready',
|
|
46
|
+
description: 'Manage application settings and preferences',
|
|
47
|
+
icon: 'cog'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 2,
|
|
51
|
+
name: 'Data Analytics',
|
|
52
|
+
type: 'Analytics Tool',
|
|
53
|
+
status: 'Processing',
|
|
54
|
+
description: 'Analyze and visualize your data',
|
|
55
|
+
icon: 'c8y-chart'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 3,
|
|
59
|
+
name: 'User Management',
|
|
60
|
+
type: 'Admin Tool',
|
|
61
|
+
status: 'Active',
|
|
62
|
+
description: 'Manage users, roles and permissions',
|
|
63
|
+
icon: 'c8y-user'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 4,
|
|
67
|
+
name: 'Device Monitor',
|
|
68
|
+
type: 'Monitoring Tool',
|
|
69
|
+
status: 'Connected',
|
|
70
|
+
description: 'Monitor device status and health',
|
|
71
|
+
icon: 'c8y-device'
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 5,
|
|
75
|
+
name: 'Report Generator',
|
|
76
|
+
type: 'Reporting Tool',
|
|
77
|
+
status: 'Idle',
|
|
78
|
+
description: 'Generate custom reports and exports',
|
|
79
|
+
icon: 'c8y-report'
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
selectedItem?: (typeof this.items)[0];
|
|
84
|
+
itemInfoSections: IconPanelSection[] = [];
|
|
85
|
+
|
|
86
|
+
onSelectionChange(item: (typeof this.items)[0] | null) {
|
|
87
|
+
this.selectedItem = item || undefined;
|
|
88
|
+
if (this.selectedItem) {
|
|
89
|
+
this.updateItemInfoSections();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getStatusClass(status: string): string {
|
|
94
|
+
switch (status) {
|
|
95
|
+
case 'Ready':
|
|
96
|
+
case 'Active':
|
|
97
|
+
case 'Connected':
|
|
98
|
+
return 'badge-success';
|
|
99
|
+
case 'Processing':
|
|
100
|
+
return 'badge-info';
|
|
101
|
+
case 'Idle':
|
|
102
|
+
return 'badge-warning';
|
|
103
|
+
default:
|
|
104
|
+
return 'badge-system';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getStatusIcon(status: string): string {
|
|
109
|
+
switch (status) {
|
|
110
|
+
case 'Ready':
|
|
111
|
+
case 'Active':
|
|
112
|
+
return 'ok';
|
|
113
|
+
case 'Processing':
|
|
114
|
+
return 'refresh';
|
|
115
|
+
case 'Connected':
|
|
116
|
+
return 'connected';
|
|
117
|
+
case 'Idle':
|
|
118
|
+
return 'pause';
|
|
119
|
+
default:
|
|
120
|
+
return 'info-circle';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
editItem() {
|
|
125
|
+
console.log('Edit item clicked:', this.selectedItem.name);
|
|
126
|
+
console.log('Item details:', this.selectedItem);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
deleteItem() {
|
|
130
|
+
console.log('Delete item clicked:', this.selectedItem.name);
|
|
131
|
+
console.log('Item ID:', this.selectedItem.id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private updateItemInfoSections() {
|
|
135
|
+
const item = this.selectedItem;
|
|
136
|
+
this.itemInfoSections = [
|
|
137
|
+
{
|
|
138
|
+
id: 'basic-info',
|
|
139
|
+
label: 'Basic Information',
|
|
140
|
+
icon: 'info-circle',
|
|
141
|
+
iconClass: 'text-info',
|
|
142
|
+
visible: true,
|
|
143
|
+
content: `
|
|
144
|
+
<div class="content-flex-20">
|
|
145
|
+
<div class="col-6">
|
|
146
|
+
<div class="d-flex a-i-center gap-4">
|
|
147
|
+
<strong>Tool Type:</strong>
|
|
148
|
+
<span>${item.type}</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="col-6">
|
|
152
|
+
<div class="d-flex a-i-center gap-4">
|
|
153
|
+
<strong class="flex-no-shrink">Current Status:</strong>
|
|
154
|
+
<span class="badge ${this.getStatusClass(item.status)}">
|
|
155
|
+
<i class="icon-12 m-r-4 dlt-c8y-icon-${this.getStatusIcon(item.status)}"></i>
|
|
156
|
+
${item.status}
|
|
157
|
+
</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<p class="m-t-8">
|
|
162
|
+
<strong>Description: </strong>
|
|
163
|
+
<span>${item.description}</span>
|
|
164
|
+
</p>
|
|
165
|
+
`,
|
|
166
|
+
colClass: 'col-xs-12'
|
|
167
|
+
}
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { NgModule } from '@angular/core';
|
|
3
|
+
import { hookRoute } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
@NgModule({
|
|
6
|
+
imports: [CommonModule],
|
|
7
|
+
providers: [
|
|
8
|
+
hookRoute({
|
|
9
|
+
path: 'split-view-fixed',
|
|
10
|
+
loadComponent: () =>
|
|
11
|
+
import('./split-view-fixed-example.component').then(m => m.SplitViewFixedExampleComponent)
|
|
12
|
+
})
|
|
13
|
+
]
|
|
14
|
+
})
|
|
15
|
+
export class SplitViewFixedExampleModule {}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<div class="p-l-16 p-r-16">
|
|
2
|
+
<c8y-title>Single column example</c8y-title>
|
|
3
|
+
|
|
4
|
+
<c8y-sv>
|
|
5
|
+
<c8y-sv-list
|
|
6
|
+
[emptyStateIcon]="'c8y-project'"
|
|
7
|
+
[emptyStateTitle]="'No projects to display'"
|
|
8
|
+
[title]="'Projects'"
|
|
9
|
+
[showEmptyState]="items.length === 0"
|
|
10
|
+
>
|
|
11
|
+
<c8y-sv-header-actions>
|
|
12
|
+
<button class="btn btn-primary btn-sm">
|
|
13
|
+
<i c8yIcon="plus"></i>
|
|
14
|
+
Add Project
|
|
15
|
+
</button>
|
|
16
|
+
</c8y-sv-header-actions>
|
|
17
|
+
|
|
18
|
+
@if (items.length > 0) {
|
|
19
|
+
<c8y-list-group>
|
|
20
|
+
@for (item of items; track item) {
|
|
21
|
+
<c8y-li>
|
|
22
|
+
<c8y-li-body>
|
|
23
|
+
<div class="d-flex a-i-start">
|
|
24
|
+
<div class="flex-grow min-width-0">
|
|
25
|
+
<h4 class="m-b-4">{{ item.name }}</h4>
|
|
26
|
+
<p class="text-muted m-b-8">{{ item.description }}</p>
|
|
27
|
+
<div class="progress">
|
|
28
|
+
<div
|
|
29
|
+
class="progress-bar"
|
|
30
|
+
[style.width.%]="item.progress"
|
|
31
|
+
[ngClass]="getProgressClass(item.progress)"
|
|
32
|
+
>
|
|
33
|
+
{{ item.progress }}%
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="text-right m-l-16">
|
|
38
|
+
<span
|
|
39
|
+
class="badge"
|
|
40
|
+
[ngClass]="getStatusClass(item.status)"
|
|
41
|
+
>
|
|
42
|
+
{{ item.status }}
|
|
43
|
+
</span>
|
|
44
|
+
<br />
|
|
45
|
+
<small class="text-muted m-t-4 d-block">Progress: {{ item.progress }}%</small>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</c8y-li-body>
|
|
49
|
+
</c8y-li>
|
|
50
|
+
}
|
|
51
|
+
</c8y-list-group>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
<c8y-sv-footer>
|
|
55
|
+
<div class="d-flex justify-content-between">
|
|
56
|
+
<small class="text-muted">{{ items.length }} projects total</small>
|
|
57
|
+
<small class="text-muted">Mode: List only</small>
|
|
58
|
+
</div>
|
|
59
|
+
</c8y-sv-footer>
|
|
60
|
+
</c8y-sv-list>
|
|
61
|
+
</c8y-sv>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import {
|
|
5
|
+
CoreModule,
|
|
6
|
+
SplitViewComponent,
|
|
7
|
+
SplitViewFooterComponent,
|
|
8
|
+
SplitViewHeaderActionsComponent,
|
|
9
|
+
SplitViewListComponent
|
|
10
|
+
} from '@c8y/ngx-components';
|
|
11
|
+
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'c8y-split-view-full-width-example',
|
|
14
|
+
templateUrl: './split-view-full-width-example.component.html',
|
|
15
|
+
standalone: true,
|
|
16
|
+
imports: [
|
|
17
|
+
CommonModule,
|
|
18
|
+
FormsModule,
|
|
19
|
+
CoreModule,
|
|
20
|
+
SplitViewComponent,
|
|
21
|
+
SplitViewListComponent,
|
|
22
|
+
SplitViewHeaderActionsComponent,
|
|
23
|
+
SplitViewFooterComponent
|
|
24
|
+
]
|
|
25
|
+
})
|
|
26
|
+
export class SplitViewFullWidthExampleComponent {
|
|
27
|
+
items = [
|
|
28
|
+
{
|
|
29
|
+
id: 1,
|
|
30
|
+
name: 'Project Alpha',
|
|
31
|
+
description: 'Web application development project',
|
|
32
|
+
status: 'In Progress',
|
|
33
|
+
progress: 75
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 2,
|
|
37
|
+
name: 'Project Beta',
|
|
38
|
+
description: 'Mobile app development project',
|
|
39
|
+
status: 'Planning',
|
|
40
|
+
progress: 25
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 3,
|
|
44
|
+
name: 'Project Gamma',
|
|
45
|
+
description: 'API integration project',
|
|
46
|
+
status: 'Completed',
|
|
47
|
+
progress: 100
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 4,
|
|
51
|
+
name: 'Project Delta',
|
|
52
|
+
description: 'Database migration project',
|
|
53
|
+
status: 'In Progress',
|
|
54
|
+
progress: 55
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 5,
|
|
58
|
+
name: 'Project Epsilon',
|
|
59
|
+
description: 'Security audit project',
|
|
60
|
+
status: 'On Hold',
|
|
61
|
+
progress: 0
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
getStatusClass(status: string): string {
|
|
66
|
+
switch (status) {
|
|
67
|
+
case 'Completed':
|
|
68
|
+
return 'badge-success';
|
|
69
|
+
case 'In Progress':
|
|
70
|
+
return 'badge-info';
|
|
71
|
+
case 'Planning':
|
|
72
|
+
return 'badge-system';
|
|
73
|
+
case 'On Hold':
|
|
74
|
+
return 'badge-danger';
|
|
75
|
+
default:
|
|
76
|
+
return 'badge-system';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getProgressClass(progress: number): string {
|
|
81
|
+
if (progress === 100) return 'progress-bar-success';
|
|
82
|
+
if (progress >= 75) return 'progress-bar-info';
|
|
83
|
+
if (progress >= 50) return 'progress-bar-warning';
|
|
84
|
+
return 'progress-bar-danger';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { NgModule } from '@angular/core';
|
|
3
|
+
import { hookRoute } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
@NgModule({
|
|
6
|
+
imports: [CommonModule],
|
|
7
|
+
providers: [
|
|
8
|
+
hookRoute({
|
|
9
|
+
path: 'split-view-full-width',
|
|
10
|
+
loadComponent: () =>
|
|
11
|
+
import('./split-view-full-width-example.component').then(
|
|
12
|
+
m => m.SplitViewFullWidthExampleComponent
|
|
13
|
+
)
|
|
14
|
+
})
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
export class SplitViewFullWidthExampleModule {}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EnvironmentProviders, Provider } from '@angular/core';
|
|
2
|
+
import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
|
|
3
|
+
|
|
4
|
+
const root = new NavigatorNode({
|
|
5
|
+
label: 'Split view',
|
|
6
|
+
icon: 'th-list',
|
|
7
|
+
priority: 200
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
root.add(
|
|
11
|
+
new NavigatorNode({
|
|
12
|
+
path: '/split-view/resizable',
|
|
13
|
+
label: 'Basic example',
|
|
14
|
+
icon: 'list'
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
root.add(
|
|
19
|
+
new NavigatorNode({
|
|
20
|
+
path: '/split-view/fixed',
|
|
21
|
+
label: 'Static example',
|
|
22
|
+
icon: 'stop'
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
root.add(
|
|
27
|
+
new NavigatorNode({
|
|
28
|
+
path: '/split-view/full-width',
|
|
29
|
+
label: 'Single column example',
|
|
30
|
+
icon: 'expand'
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export function provideSplitViewSamples(): (Provider | EnvironmentProviders)[] {
|
|
35
|
+
return [
|
|
36
|
+
hookRoute({
|
|
37
|
+
path: 'split-view',
|
|
38
|
+
loadChildren: () => import('./split-view-lazy.module').then(m => m.SplitViewLazyModule)
|
|
39
|
+
}),
|
|
40
|
+
hookNavigator(root)
|
|
41
|
+
];
|
|
42
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<div class="p-l-16 p-r-16">
|
|
2
|
+
<c8y-title>Basic example</c8y-title>
|
|
3
|
+
<c8y-sv
|
|
4
|
+
[resizableConfig]="{ trackId: 'basic-example-split-view', collapsible: false }"
|
|
5
|
+
(selectionChange)="onSelectionChange($event)"
|
|
6
|
+
>
|
|
7
|
+
<c8y-sv-list
|
|
8
|
+
[emptyStateIcon]="'c8y-devices'"
|
|
9
|
+
[title]="'Device list' | translate"
|
|
10
|
+
[emptyStateTitle]="'No devices to display'"
|
|
11
|
+
[loading]="loading"
|
|
12
|
+
[listOpacity]="loading ? 0.6 : 1"
|
|
13
|
+
[showEmptyState]="items.length === 0 && !loading"
|
|
14
|
+
[docsUrl]="'/docs/device-management-application/managing-devices/#device-list'"
|
|
15
|
+
>
|
|
16
|
+
<c8y-sv-header-actions>
|
|
17
|
+
<button
|
|
18
|
+
class="btn btn-default btn-sm"
|
|
19
|
+
(click)="refresh()"
|
|
20
|
+
[disabled]="loading"
|
|
21
|
+
>
|
|
22
|
+
<i
|
|
23
|
+
c8yIcon="refresh"
|
|
24
|
+
[ngClass]="{ 'icon-spin': loading }"
|
|
25
|
+
></i>
|
|
26
|
+
Refresh
|
|
27
|
+
</button>
|
|
28
|
+
</c8y-sv-header-actions>
|
|
29
|
+
|
|
30
|
+
<!-- Actual content items -->
|
|
31
|
+
@if (!loading && items.length > 0) {
|
|
32
|
+
<c8y-list-group>
|
|
33
|
+
@for (item of items; track item) {
|
|
34
|
+
<c8y-li
|
|
35
|
+
[c8ySvListItem]="item"
|
|
36
|
+
[selectable]="false"
|
|
37
|
+
>
|
|
38
|
+
<c8y-li-icon class="a-s-start">
|
|
39
|
+
<div class="device-icons">
|
|
40
|
+
<i
|
|
41
|
+
class="icon-24 text-gray-dark stroked-icon"
|
|
42
|
+
c8yIcon="c8y-device"
|
|
43
|
+
></i>
|
|
44
|
+
</div>
|
|
45
|
+
<c8y-device-status
|
|
46
|
+
[mo]="item.managedObject"
|
|
47
|
+
[size]="20"
|
|
48
|
+
></c8y-device-status>
|
|
49
|
+
</c8y-li-icon>
|
|
50
|
+
<c8y-li-body class="a-s-stretch">
|
|
51
|
+
<div class="d-flex a-i-start fit-h">
|
|
52
|
+
<div class="min-width-0 flex-grow">
|
|
53
|
+
<p class="text-truncate-wrap p-b-4">
|
|
54
|
+
{{ item.name }}
|
|
55
|
+
</p>
|
|
56
|
+
<div class="d-flex">
|
|
57
|
+
<p
|
|
58
|
+
class="small text-muted text-truncate flex-grow"
|
|
59
|
+
[title]="item.location"
|
|
60
|
+
>
|
|
61
|
+
<i c8yIcon="map-marker"></i>
|
|
62
|
+
{{ item.location }}
|
|
63
|
+
</p>
|
|
64
|
+
<span
|
|
65
|
+
class="badge m-l-8"
|
|
66
|
+
[class.badge-success]="item.status === 'Active'"
|
|
67
|
+
[class.badge-info]="item.status === 'Inactive'"
|
|
68
|
+
[class.badge-danger]="item.status === 'Maintenance'"
|
|
69
|
+
>
|
|
70
|
+
{{ item.status }}
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<p class="small text-muted m-t-4">
|
|
74
|
+
<i c8yIcon="tag"></i>
|
|
75
|
+
{{ item.type }} • {{ item.deviceId }}
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</c8y-li-body>
|
|
80
|
+
</c8y-li>
|
|
81
|
+
}
|
|
82
|
+
</c8y-list-group>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
<c8y-sv-footer>
|
|
86
|
+
<small class="text-muted">Total devices: {{ items.length }}</small>
|
|
87
|
+
</c8y-sv-footer>
|
|
88
|
+
</c8y-sv-list>
|
|
89
|
+
|
|
90
|
+
<c8y-sv-details
|
|
91
|
+
emptyStateIcon="c8y-device"
|
|
92
|
+
emptyStateTitle="No device selected"
|
|
93
|
+
[title]="'Device details' | translate"
|
|
94
|
+
emptyStateSubtitle="Select a device from the list to view details"
|
|
95
|
+
>
|
|
96
|
+
@if (selectedItem) {
|
|
97
|
+
<c8y-sv-extra-header>
|
|
98
|
+
<div class="d-flex a-i-center gap-4">
|
|
99
|
+
<label class="m-b-0">Device ID</label>
|
|
100
|
+
<p>{{ selectedItem.deviceId }}</p>
|
|
101
|
+
</div>
|
|
102
|
+
</c8y-sv-extra-header>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@if (selectedItem) {
|
|
106
|
+
<ng-container>
|
|
107
|
+
<div class="row">
|
|
108
|
+
<div class="col-md-4 m-b-16">
|
|
109
|
+
<label class="text-muted small">Type</label>
|
|
110
|
+
<p>{{ selectedItem.type }}</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="col-md-4 m-b-16">
|
|
113
|
+
<label class="text-muted small">Location</label>
|
|
114
|
+
<p>{{ selectedItem.location }}</p>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="col-md-4 m-b-16">
|
|
117
|
+
<label class="text-muted small">Last Seen</label>
|
|
118
|
+
<p>{{ selectedItem.lastSeen }}</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<p class="m-t-16 text-bold">Connection Status</p>
|
|
123
|
+
<div class="row d-flex-md m-b-24">
|
|
124
|
+
<div class="col-md-4 col-xs-12">
|
|
125
|
+
<label class="text-muted small">Send Status</label>
|
|
126
|
+
<p
|
|
127
|
+
[ngClass]="getStatusColorClass(selectedItem.managedObject.c8y_Availability?.status)"
|
|
128
|
+
>
|
|
129
|
+
{{ selectedItem.managedObject.c8y_Availability?.status || 'UNKNOWN' }}
|
|
130
|
+
</p>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="col-sm-4 col-xs-12">
|
|
133
|
+
<label class="text-muted small">Push Status</label>
|
|
134
|
+
<p [ngClass]="getStatusColorClass(selectedItem.managedObject.c8y_Connection?.status)">
|
|
135
|
+
{{ selectedItem.managedObject.c8y_Connection?.status || 'UNKNOWN' }}
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="col-md-4 col-xs-12">
|
|
139
|
+
<label class="text-muted small">Response Interval</label>
|
|
140
|
+
<p>
|
|
141
|
+
{{ selectedItem.managedObject.c8y_RequiredAvailability?.responseInterval || 0 }}s
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<c8y-sv-details-actions class="m-t-16">
|
|
147
|
+
<button
|
|
148
|
+
class="btn btn-default btn-sm"
|
|
149
|
+
(click)="editDevice()"
|
|
150
|
+
>
|
|
151
|
+
<i c8yIcon="pencil"></i>
|
|
152
|
+
Edit
|
|
153
|
+
</button>
|
|
154
|
+
<button
|
|
155
|
+
class="btn btn-primary btn-sm"
|
|
156
|
+
(click)="restartDevice()"
|
|
157
|
+
>
|
|
158
|
+
<i c8yIcon="refresh"></i>
|
|
159
|
+
Restart
|
|
160
|
+
</button>
|
|
161
|
+
</c8y-sv-details-actions>
|
|
162
|
+
</ng-container>
|
|
163
|
+
}
|
|
164
|
+
<c8y-sv-footer>
|
|
165
|
+
<button
|
|
166
|
+
class="btn btn-default btn-sm"
|
|
167
|
+
(click)="clearSelection()"
|
|
168
|
+
>
|
|
169
|
+
Cancel
|
|
170
|
+
</button>
|
|
171
|
+
<button
|
|
172
|
+
class="btn btn-danger btn-sm"
|
|
173
|
+
(click)="deleteDevice()"
|
|
174
|
+
>
|
|
175
|
+
<i class="fa fa-trash"></i>
|
|
176
|
+
Delete
|
|
177
|
+
</button>
|
|
178
|
+
<button
|
|
179
|
+
class="btn btn-default btn-sm"
|
|
180
|
+
(click)="editDevice()"
|
|
181
|
+
>
|
|
182
|
+
<i class="fa fa-edit"></i>
|
|
183
|
+
Edit
|
|
184
|
+
</button>
|
|
185
|
+
</c8y-sv-footer>
|
|
186
|
+
</c8y-sv-details>
|
|
187
|
+
</c8y-sv>
|
|
188
|
+
</div>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component, ViewChild } from '@angular/core';
|
|
3
|
+
import {
|
|
4
|
+
CoreModule,
|
|
5
|
+
DeviceStatusComponent,
|
|
6
|
+
EmptyStateComponent,
|
|
7
|
+
IconDirective,
|
|
8
|
+
ListItemBodyComponent,
|
|
9
|
+
ListItemComponent,
|
|
10
|
+
ListItemIconComponent,
|
|
11
|
+
SplitViewComponent,
|
|
12
|
+
SplitViewDetailsActionsComponent,
|
|
13
|
+
SplitViewDetailsComponent,
|
|
14
|
+
SplitViewExtraHeaderComponent,
|
|
15
|
+
SplitViewFooterComponent,
|
|
16
|
+
SplitViewHeaderActionsComponent,
|
|
17
|
+
SplitViewListComponent,
|
|
18
|
+
SplitViewListItemDirective
|
|
19
|
+
} from '@c8y/ngx-components';
|
|
20
|
+
|
|
21
|
+
type DeviceItem = {
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
status: string;
|
|
25
|
+
location: string;
|
|
26
|
+
deviceId: string;
|
|
27
|
+
lastSeen: string;
|
|
28
|
+
type: string;
|
|
29
|
+
time: string;
|
|
30
|
+
firstOccurrenceTime: string;
|
|
31
|
+
managedObject: {
|
|
32
|
+
id: string;
|
|
33
|
+
c8y_Availability: { status: string };
|
|
34
|
+
c8y_Connection: { status: string };
|
|
35
|
+
c8y_RequiredAvailability: { responseInterval: number };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
@Component({
|
|
40
|
+
selector: 'c8y-split-view-resizable-example',
|
|
41
|
+
templateUrl: './split-view-resizable-example.component.html',
|
|
42
|
+
standalone: true,
|
|
43
|
+
imports: [
|
|
44
|
+
CommonModule,
|
|
45
|
+
CoreModule,
|
|
46
|
+
DeviceStatusComponent,
|
|
47
|
+
ListItemComponent,
|
|
48
|
+
ListItemIconComponent,
|
|
49
|
+
ListItemBodyComponent,
|
|
50
|
+
IconDirective,
|
|
51
|
+
EmptyStateComponent,
|
|
52
|
+
SplitViewComponent,
|
|
53
|
+
SplitViewListComponent,
|
|
54
|
+
SplitViewDetailsComponent,
|
|
55
|
+
SplitViewHeaderActionsComponent,
|
|
56
|
+
SplitViewFooterComponent,
|
|
57
|
+
SplitViewExtraHeaderComponent,
|
|
58
|
+
SplitViewDetailsActionsComponent,
|
|
59
|
+
SplitViewListItemDirective
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
export class SplitViewResizableExampleComponent {
|
|
63
|
+
@ViewChild(SplitViewDetailsComponent) detailsComponent?: SplitViewDetailsComponent;
|
|
64
|
+
|
|
65
|
+
loading = false;
|
|
66
|
+
selectedItem?: DeviceItem;
|
|
67
|
+
|
|
68
|
+
items: DeviceItem[] = [
|
|
69
|
+
{
|
|
70
|
+
id: 1,
|
|
71
|
+
name: 'Smart Thermostat Alpha',
|
|
72
|
+
status: 'Active',
|
|
73
|
+
location: 'Building A - Floor 1',
|
|
74
|
+
deviceId: 'THM-001',
|
|
75
|
+
lastSeen: '2 minutes ago',
|
|
76
|
+
type: 'Temperature Sensor',
|
|
77
|
+
time: new Date(Date.now() - 2 * 60 * 1000).toISOString(),
|
|
78
|
+
firstOccurrenceTime: new Date(Date.now() - 2 * 60 * 1000).toISOString(),
|
|
79
|
+
managedObject: {
|
|
80
|
+
id: 'THM-001',
|
|
81
|
+
c8y_Availability: { status: 'AVAILABLE' },
|
|
82
|
+
c8y_Connection: { status: 'CONNECTED' },
|
|
83
|
+
c8y_RequiredAvailability: { responseInterval: 60 }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 2,
|
|
88
|
+
name: 'Security Camera Beta',
|
|
89
|
+
status: 'Inactive',
|
|
90
|
+
location: 'Building B - Lobby',
|
|
91
|
+
deviceId: 'CAM-002',
|
|
92
|
+
lastSeen: '1 hour ago',
|
|
93
|
+
type: 'Security Camera',
|
|
94
|
+
time: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
|
95
|
+
firstOccurrenceTime: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
|
|
96
|
+
managedObject: {
|
|
97
|
+
id: 'CAM-002',
|
|
98
|
+
c8y_Availability: { status: 'UNAVAILABLE' },
|
|
99
|
+
c8y_Connection: { status: 'DISCONNECTED' },
|
|
100
|
+
c8y_RequiredAvailability: { responseInterval: 60 }
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 3,
|
|
105
|
+
name: 'Air Quality Monitor Gamma',
|
|
106
|
+
status: 'Active',
|
|
107
|
+
location: 'Building C - Office',
|
|
108
|
+
deviceId: 'AQM-003',
|
|
109
|
+
lastSeen: '30 seconds ago',
|
|
110
|
+
type: 'Environmental Sensor',
|
|
111
|
+
time: new Date(Date.now() - 30 * 1000).toISOString(),
|
|
112
|
+
firstOccurrenceTime: new Date(Date.now() - 30 * 1000).toISOString(),
|
|
113
|
+
managedObject: {
|
|
114
|
+
id: 'AQM-003',
|
|
115
|
+
c8y_Availability: { status: 'AVAILABLE' },
|
|
116
|
+
c8y_Connection: { status: 'CONNECTED' },
|
|
117
|
+
c8y_RequiredAvailability: { responseInterval: 30 }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 4,
|
|
122
|
+
name: 'Smart Lock Delta',
|
|
123
|
+
status: 'Maintenance',
|
|
124
|
+
location: 'Building A - Entrance',
|
|
125
|
+
deviceId: 'LOCK-004',
|
|
126
|
+
lastSeen: '5 minutes ago',
|
|
127
|
+
type: 'Access Control',
|
|
128
|
+
time: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
|
129
|
+
firstOccurrenceTime: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
|
130
|
+
managedObject: {
|
|
131
|
+
id: 'LOCK-004',
|
|
132
|
+
c8y_Availability: { status: 'MAINTENANCE' },
|
|
133
|
+
c8y_Connection: { status: 'MAINTENANCE' },
|
|
134
|
+
c8y_RequiredAvailability: { responseInterval: 120 }
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 5,
|
|
139
|
+
name: 'Energy Meter Epsilon',
|
|
140
|
+
status: 'Active',
|
|
141
|
+
location: 'Building D - Utility Room',
|
|
142
|
+
deviceId: 'EMT-005',
|
|
143
|
+
lastSeen: '1 minute ago',
|
|
144
|
+
type: 'Energy Monitor',
|
|
145
|
+
time: new Date(Date.now() - 60 * 1000).toISOString(),
|
|
146
|
+
firstOccurrenceTime: new Date(Date.now() - 60 * 1000).toISOString(),
|
|
147
|
+
managedObject: {
|
|
148
|
+
id: 'EMT-005',
|
|
149
|
+
c8y_Availability: { status: 'AVAILABLE' },
|
|
150
|
+
c8y_Connection: { status: 'CONNECTED' },
|
|
151
|
+
c8y_RequiredAvailability: { responseInterval: 300 }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
onSelectionChange(item: DeviceItem | null): void {
|
|
157
|
+
this.selectedItem = item || undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getCurrentDate(): string {
|
|
161
|
+
return new Date().toLocaleDateString();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
refresh(): void {
|
|
165
|
+
this.loading = true;
|
|
166
|
+
// Simulate API call
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
this.loading = false;
|
|
169
|
+
}, 1500);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
editDevice(): void {
|
|
173
|
+
if (!this.selectedItem) return;
|
|
174
|
+
// eslint-disable-next-line no-console
|
|
175
|
+
console.log('Edit device clicked:', this.selectedItem.name);
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log('Device details:', this.selectedItem);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
restartDevice(): void {
|
|
181
|
+
if (!this.selectedItem) return;
|
|
182
|
+
// eslint-disable-next-line no-console
|
|
183
|
+
console.log('Restart device clicked:', this.selectedItem.name);
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.log('Restarting device:', this.selectedItem.deviceId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
deleteDevice(): void {
|
|
189
|
+
if (!this.selectedItem) return;
|
|
190
|
+
// eslint-disable-next-line no-console
|
|
191
|
+
console.log('Delete device clicked:', this.selectedItem.name);
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.log('Device ID:', this.selectedItem.deviceId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
clearSelection(): void {
|
|
197
|
+
this.detailsComponent?.clearSelection();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
getStatusColorClass(status: string): string {
|
|
201
|
+
switch (status) {
|
|
202
|
+
case 'AVAILABLE':
|
|
203
|
+
case 'CONNECTED':
|
|
204
|
+
return 'text-success';
|
|
205
|
+
case 'UNAVAILABLE':
|
|
206
|
+
case 'DISCONNECTED':
|
|
207
|
+
return 'text-danger';
|
|
208
|
+
case 'MAINTENANCE':
|
|
209
|
+
return 'text-muted';
|
|
210
|
+
default:
|
|
211
|
+
return 'text-muted';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { NgModule } from '@angular/core';
|
|
3
|
+
import { hookRoute } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
@NgModule({
|
|
6
|
+
imports: [CommonModule],
|
|
7
|
+
providers: [
|
|
8
|
+
hookRoute({
|
|
9
|
+
path: 'split-view-resizable',
|
|
10
|
+
loadComponent: () =>
|
|
11
|
+
import('./split-view-resizable-example.component').then(
|
|
12
|
+
m => m.SplitViewResizableExampleComponent
|
|
13
|
+
)
|
|
14
|
+
})
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
export class SplitViewResizableExampleModule {}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { NgModule } from '@angular/core';
|
|
3
|
+
import { RouterModule, Routes } from '@angular/router';
|
|
4
|
+
|
|
5
|
+
const routes: Routes = [
|
|
6
|
+
{
|
|
7
|
+
path: 'resizable',
|
|
8
|
+
loadComponent: () =>
|
|
9
|
+
import('./resizable-example/split-view-resizable-example.component').then(
|
|
10
|
+
m => m.SplitViewResizableExampleComponent
|
|
11
|
+
)
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: 'fixed',
|
|
15
|
+
loadComponent: () =>
|
|
16
|
+
import('./fixed-example/split-view-fixed-example.component').then(
|
|
17
|
+
m => m.SplitViewFixedExampleComponent
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: 'full-width',
|
|
22
|
+
loadComponent: () =>
|
|
23
|
+
import('./full-width-example/split-view-full-width-example.component').then(
|
|
24
|
+
m => m.SplitViewFullWidthExampleComponent
|
|
25
|
+
)
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '',
|
|
29
|
+
redirectTo: 'resizable',
|
|
30
|
+
pathMatch: 'full'
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
@NgModule({
|
|
35
|
+
imports: [CommonModule, RouterModule.forChild(routes)]
|
|
36
|
+
})
|
|
37
|
+
export class SplitViewLazyModule {}
|