diff --git a/ui-ngx/CLAUDE.md b/ui-ngx/CLAUDE.md new file mode 100644 index 0000000000..0ce231c91c --- /dev/null +++ b/ui-ngx/CLAUDE.md @@ -0,0 +1,742 @@ +# ThingsBoard CE Frontend — Navigation & Testing Guide + +## 1. Quick Reference + +| Key | Value | +|-----|-------| +| Angular | 20 | +| Angular Material | 20 | +| Component prefix | `tb` | +| TypeScript | 5.9 | +| RxJS | 7.8 | + +**Path Aliases** (from `tsconfig.json`): + +| Alias | Maps to | +|-------|---------| +| `@app/*` | `src/app/*` | +| `@core/*` | `src/app/core/*` | +| `@modules/*` | `src/app/modules/*` | +| `@home/*` | `src/app/modules/home/*` | +| `@shared/*` | `src/app/shared/*` | + +**Key Directories:** + +``` +src/app/ +├── core/ +│ ├── http/ # HTTP services (one per entity type) +│ └── services/ # Menu, auth, utils +├── modules/ +│ ├── home/ +│ │ ├── components/ # Reusable home components (entity table, details panel, alarm, dashboard) +│ │ │ └── entity/ # EntitiesTableComponent, EntityDetailsPageComponent, EntityComponent base +│ │ └── pages/ # Page modules (one directory per page/feature) +│ └── login/ # Login, reset password, 2FA +└── shared/ + ├── components/ # Shared components (autocompletes, dialogs, time, image) + └── models/ # TypeScript interfaces/enums +``` + +--- + +## 2. Route-to-Source Mapping + +All page source paths are relative to `src/app/modules/home/pages/`. +Page types: `entity-table` = EntitiesTableComponent + resolver, `entity-details` = EntityDetailsPageComponent, `settings-form` = standalone form with ConfirmOnExitGuard, `dashboard-view` = DashboardViewComponent with resolver, `custom` = unique component. + +### Login (unauthenticated) + +Source paths below are relative to `src/app/modules/` (not `home/pages/`). + +| URL | Source | Type | +|-----|--------|------| +| `/login` | `login/pages/login/` | custom (LoginComponent) | +| `/login/resetPasswordRequest` | `login/pages/login/` | custom (ResetPasswordRequestComponent) | +| `/login/resetPassword` | `login/pages/login/` | custom (ResetPasswordComponent) | +| `/login/createPassword` | `login/pages/login/` | custom (CreatePasswordComponent) | +| `/login/mfa` | `login/pages/login/` | custom (TwoFactorAuthLoginComponent) | + +### Home + +| URL | Source | Type | Auth | +|-----|--------|------|------| +| `/home` | `home-links/` | custom (HomeLinksComponent + dashboard resolver) | ALL | + +### Tenants (SYS_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/tenants` | `tenant/` | entity-table | `TenantsTableConfigResolver` | +| `/tenants/:entityId` | `tenant/` | entity-details | `TenantsTableConfigResolver` | +| `/tenants/:tenantId/users` | `tenant/` + `user/` | entity-table | `UsersTableConfigResolver` | +| `/tenantProfiles` | `tenant-profile/` | entity-table | `TenantProfilesTableConfigResolver` | +| `/tenantProfiles/:entityId` | `tenant-profile/` | entity-details | `TenantProfilesTableConfigResolver` | + +### Alarms Center (TENANT_ADMIN, CUSTOMER_USER) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/alarms` | `alarm/` | custom (RouterTabsComponent) | — | +| `/alarms/alarms` | `alarm/` | custom (AlarmTableComponent) | — | +| `/alarms/alarm-rules` | `alarm/` | custom (AlarmRulesTableComponent) | — | +| `/alarms/alarm-rules/:entityId` | `alarm/` | entity-details | `AlarmRulesTableConfigResolver` | + +### Dashboards (TENANT_ADMIN, CUSTOMER_USER) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/dashboards` | `dashboard/` | entity-table | `DashboardsTableConfigResolver` | +| `/dashboards/:dashboardId` | `dashboard/` | custom (DashboardPageComponent) | `DashboardResolver` | + +### Entities (TENANT_ADMIN, CUSTOMER_USER) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/entities/devices` | `device/` | entity-table | `DevicesTableConfigResolver` | +| `/entities/devices/:entityId` | `device/` | entity-details | `DevicesTableConfigResolver` | +| `/entities/assets` | `asset/` | entity-table | `AssetsTableConfigResolver` | +| `/entities/assets/:entityId` | `asset/` | entity-details | `AssetsTableConfigResolver` | +| `/entities/entityViews` | `entity-view/` | entity-table | `EntityViewsTableConfigResolver` | +| `/entities/entityViews/:entityId` | `entity-view/` | entity-details | `EntityViewsTableConfigResolver` | +| `/entities/gateways` | `gateways/` | dashboard-view | `gatewaysDashboardResolver` | + +### Profiles (TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/profiles/deviceProfiles` | `device-profile/` | entity-table | `DeviceProfilesTableConfigResolver` | +| `/profiles/deviceProfiles/:entityId` | `device-profile/` | entity-details | `DeviceProfilesTableConfigResolver` | +| `/profiles/assetProfiles` | `asset-profile/` | entity-table | `AssetProfilesTableConfigResolver` | +| `/profiles/assetProfiles/:entityId` | `asset-profile/` | entity-details | `AssetProfilesTableConfigResolver` | + +### Customers (TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/customers` | `customer/` | entity-table | `CustomersTableConfigResolver` | +| `/customers/:entityId` | `customer/` | entity-details | `CustomersTableConfigResolver` | +| `/customers/:customerId/users` | `customer/` + `user/` | entity-table | `UsersTableConfigResolver` | +| `/customers/:customerId/devices` | `customer/` + `device/` | entity-table | `DevicesTableConfigResolver` | +| `/customers/:customerId/assets` | `customer/` + `asset/` | entity-table | `AssetsTableConfigResolver` | +| `/customers/:customerId/dashboards` | `customer/` + `dashboard/` | entity-table | `DashboardsTableConfigResolver` | +| `/customers/:customerId/edgeInstances` | `customer/` + `edge/` | entity-table | `EdgesTableConfigResolver` | + +### Calculated Fields (TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/calculatedFields` | `calculated-fields/` | custom (CalculatedFieldsTableComponent) | — | +| `/calculatedFields/:entityId` | `calculated-fields/` | entity-details | `CalculatedFieldsTableConfigResolver` | + +### Rule Chains (TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/ruleChains` | `rulechain/` | entity-table | `RuleChainsTableConfigResolver` | +| `/ruleChains/:ruleChainId` | `rulechain/` | custom (RuleChainPageComponent, lazy) | `RuleChainResolver` + others | + +### Edge Management (TENANT_ADMIN, conditional on edgesSupportEnabled) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/edgeManagement/instances` | `edge/` | entity-table | `EdgesTableConfigResolver` | +| `/edgeManagement/instances/:entityId` | `edge/` | entity-details | `EdgesTableConfigResolver` | +| `/edgeManagement/instances/:edgeId/assets` | `edge/` + `asset/` | entity-table | `AssetsTableConfigResolver` | +| `/edgeManagement/instances/:edgeId/devices` | `edge/` + `device/` | entity-table | `DevicesTableConfigResolver` | +| `/edgeManagement/instances/:edgeId/entityViews` | `edge/` + `entity-view/` | entity-table | `EntityViewsTableConfigResolver` | +| `/edgeManagement/instances/:edgeId/dashboards` | `edge/` + `dashboard/` | entity-table | `DashboardsTableConfigResolver` | +| `/edgeManagement/instances/:edgeId/ruleChains` | `edge/` + `rulechain/` | entity-table | `RuleChainsTableConfigResolver` | +| `/edgeManagement/ruleChains` | `edge/` + `rulechain/` | entity-table | `RuleChainsTableConfigResolver` | + +### Advanced Features (TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/features/otaUpdates` | `ota-update/` | entity-table | `OtaUpdateTableConfigResolve` | +| `/features/otaUpdates/:entityId` | `ota-update/` | entity-details | `OtaUpdateTableConfigResolve` | +| `/features/vc` | `vc/` | custom (VersionControlComponent) | — | + +### Resources (SYS_ADMIN, TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/resources/widgets-library/widget-types` | `widget/` | entity-table | `WidgetTypesTableConfigResolver` | +| `/resources/widgets-library/widgets-bundles` | `widget/` | entity-table | `WidgetsBundlesTableConfigResolver` | +| `/resources/widgets-library/widgets-bundles/:widgetsBundleId` | `widget/` | custom (WidgetsBundleWidgetsComponent) | — | +| `/resources/widgets-library/widget-types/:widgetTypeId` | `widget/` | custom (WidgetEditorComponent) | — | +| `/resources/images` | `admin/` | custom (ImageGalleryComponent) | — | +| `/resources/scada-symbols` | `admin/` | custom (ImageGalleryComponent) | — | +| `/resources/scada-symbols/:type/:key` | `admin/` | custom (ScadaSymbolComponent) | — | +| `/resources/resources-library` | `admin/` | entity-table | `ResourcesLibraryTableConfigResolver` | +| `/resources/resources-library/:entityId` | `admin/` | entity-details | `ResourcesLibraryTableConfigResolver` | +| `/resources/javascript-library` | `admin/` | entity-table | `JsLibraryTableConfigResolver` | +| `/resources/javascript-library/:entityId` | `admin/` | entity-details | `JsLibraryTableConfigResolver` | + +### Notifications (ALL authorities) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/notification/inbox` | `notification/inbox/` | entity-table | `InboxTableConfigResolver` | +| `/notification/sent` | `notification/sent/` | entity-table | `SentTableConfigResolver` | +| `/notification/recipients` | `notification/recipient/` | entity-table | `RecipientTableConfigResolver` | +| `/notification/templates` | `notification/template/` | entity-table | `TemplateTableConfigResolver` | +| `/notification/rules` | `notification/rule/` | entity-table | `RuleTableConfigResolver` | + +### Mobile Center (SYS_ADMIN, TENANT_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/mobile-center/bundles` | `mobile/bundes/` | entity-table | `MobileBundleTableConfigResolver` | +| `/mobile-center/applications` | `mobile/applications/` | entity-table | `MobileAppTableConfigResolver` | +| `/mobile-center/applications/:entityId` | `mobile/applications/` | entity-details | `MobileAppTableConfigResolver` | +| `/mobile-center/qr-code-widget` | `mobile/qr-code-widget/` | settings-form | — | + +### Settings — SYS_ADMIN + +| URL | Source | Type | +|-----|--------|------| +| `/settings/general` | `admin/` | settings-form (GeneralSettingsComponent) | +| `/settings/outgoing-mail` | `admin/` | settings-form (MailServerComponent) | +| `/settings/notifications` | `admin/` | settings-form (SmsProviderComponent) | +| `/settings/queues` | `admin/` | entity-table (`QueuesTableConfigResolver`) | + +### Settings — TENANT_ADMIN + +| URL | Source | Type | +|-----|--------|------| +| `/settings/home` | `admin/` | settings-form (HomeSettingsComponent) | +| `/settings/notifications` | `admin/` | settings-form (SmsProviderComponent) | +| `/settings/repository` | `admin/` | settings-form (RepositoryAdminSettingsComponent) | +| `/settings/auto-commit` | `admin/` | settings-form (AutoCommitAdminSettingsComponent) | +| `/settings/trendz` | `admin/` | settings-form (TrendzSettingsComponent) | +| `/settings/ai-models` | `ai-model/` | entity-table (`AiModelsTableConfigResolver`) | + +### Security Settings (SYS_ADMIN) + +| URL | Source | Type | Resolver | +|-----|--------|------|----------| +| `/security-settings/general` | `admin/` | settings-form (SecuritySettingsComponent) | — | +| `/security-settings/2fa` | `admin/` | settings-form (TwoFactorAuthSettingsComponent) | — | +| `/security-settings/oauth2/domains` | `admin/` | entity-table | `DomainTableConfigResolver` | +| `/security-settings/oauth2/clients` | `admin/` | entity-table | `ClientsTableConfigResolver` | +| `/security-settings/auditLogs` | `admin/` | custom (audit log table) | — | + +### Account (ALL authorities) + +| URL | Source | Type | +|-----|--------|------| +| `/account/profile` | `profile/` | settings-form (ProfileComponent) | +| `/account/security` | `security/` | settings-form (SecurityComponent) | + +### API Usage (TENANT_ADMIN) + +| URL | Source | Type | +|-----|--------|------| +| `/usage` | `api-usage/` | dashboard-view (loads `api_usage.json`) | + +--- + +## 3. Entity Table Pattern + +Most pages follow a 3-file pattern per entity type inside `pages//`: + +``` +-table-config.resolver.ts — Column definitions, actions, component wiring +.component.ts + .html — Entity detail form (extends EntityComponent) +-tabs.component.ts + .html — Additional detail tabs (relations, attributes, events, etc.) +``` + +**Framework files** (in `home/components/entity/`): +- `entities-table.component.ts` — The generic table component. Renders columns, actions, pagination from config. +- `entity-details-page.component.ts` — Full-page entity details with tabs. +- `entity-details-panel.component.ts` — Slide-out drawer panel for entity details. +- `entity.component.ts` — Abstract base directive all entity forms extend. +- `entities-table-config.models.ts` — TypeScript interfaces for table configuration. + +**Resolver structure** — each resolver returns an `EntityTableConfig` with: +- `entityType` — enum value (e.g., `EntityType.DEVICE`) +- `columns` — array of column definitions (header key, cell property/template, width) +- `cellActionDescriptors` — per-row action buttons +- `groupActionDescriptors` — bulk-selection action buttons +- `headerActionDescriptors` — toolbar action buttons (add, import) +- `entityComponent` — reference to the detail form component class +- `entityTabsComponent` — reference to the tabs component class +- `addDialogComponent` — optional dialog for add flow (e.g., `DeviceWizardDialogComponent`) + +### All Table Config Resolvers + +| Resolver | Entity Type | Source Dir | Entity Component | Tabs Component | +|----------|-------------|-----------|------------------|----------------| +| `TenantsTableConfigResolver` | TENANT | `tenant/` | `TenantComponent` | `TenantTabsComponent` | +| `TenantProfilesTableConfigResolver` | TENANT_PROFILE | `tenant-profile/` | `TenantProfileComponent` | `TenantProfileTabsComponent` | +| `DevicesTableConfigResolver` | DEVICE | `device/` | `DeviceComponent` | `DeviceTabsComponent` | +| `DeviceProfilesTableConfigResolver` | DEVICE_PROFILE | `device-profile/` | `DeviceProfileComponent` | `DeviceProfileTabsComponent` | +| `AssetsTableConfigResolver` | ASSET | `asset/` | `AssetComponent` | `AssetTabsComponent` | +| `AssetProfilesTableConfigResolver` | ASSET_PROFILE | `asset-profile/` | `AssetProfileComponent` | `AssetProfileTabsComponent` | +| `EntityViewsTableConfigResolver` | ENTITY_VIEW | `entity-view/` | `EntityViewComponent` | `EntityViewTabsComponent` | +| `CustomersTableConfigResolver` | CUSTOMER | `customer/` | `CustomerComponent` | `CustomerTabsComponent` | +| `UsersTableConfigResolver` | USER | `user/` | `UserComponent` | `UserTabsComponent` | +| `DashboardsTableConfigResolver` | DASHBOARD | `dashboard/` | — | — | +| `EdgesTableConfigResolver` | EDGE | `edge/` | `EdgeComponent` | `EdgeTabsComponent` | +| `RuleChainsTableConfigResolver` | RULE_CHAIN | `rulechain/` | `RuleChainComponent` | `RuleChainTabsComponent` | +| `OtaUpdateTableConfigResolve` | OTA_PACKAGE | `ota-update/` | — | — | +| `CalculatedFieldsTableConfigResolver` | CALCULATED_FIELD | `calculated-fields/` | — | — | +| `WidgetTypesTableConfigResolver` | WIDGETS_BUNDLE | `widget/` | `WidgetTypeComponent` | `WidgetTypeTabsComponent` | +| `WidgetsBundlesTableConfigResolver` | WIDGETS_BUNDLE | `widget/` | `WidgetsBundleComponent` | `WidgetsBundleTabsComponent` | +| `ResourcesLibraryTableConfigResolver` | TB_RESOURCE | `admin/` | — | — | +| `JsLibraryTableConfigResolver` | TB_RESOURCE | `admin/` | — | — | +| `QueuesTableConfigResolver` | QUEUE | `admin/queue/` | `QueueComponent` | — | +| `AiModelsTableConfigResolver` | AI_MODEL | `ai-model/` | — | — | +| `ClientsTableConfigResolver` | OAUTH2_CLIENT | `admin/oauth2/clients/` | `ClientComponent` | — | +| `InboxTableConfigResolver` | NOTIFICATION | `notification/inbox/` | — (dialog) | — | +| `SentTableConfigResolver` | NOTIFICATION_REQUEST | `notification/sent/` | — (dialog) | — | +| `RecipientTableConfigResolver` | NOTIFICATION_TARGET | `notification/recipient/` | — (dialog) | — | +| `TemplateTableConfigResolver` | NOTIFICATION_TEMPLATE | `notification/template/` | — (dialog) | — | +| `RuleTableConfigResolver` | NOTIFICATION_RULE | `notification/rule/` | — (dialog) | — | +| `MobileAppTableConfigResolver` | MOBILE_APP | `mobile/applications/` | `MobileAppComponent` | — | +| `MobileBundleTableConfigResolver` | MOBILE_BUNDLE | `mobile/bundes/` | — | — | +| `AlarmRulesTableConfigResolver` | CALCULATED_FIELD | `alarm/` (config in `home/components/alarm-rules/`) | — | — | +| `DomainTableConfigResolver` | DOMAIN | `admin/oauth2/domains/` | `DomainComponent` | — | + +--- + +## 4. Shared Component Catalog + +### `@shared/components/entity/` + +| Selector | Purpose | +|----------|---------| +| `tb-entity-autocomplete` | Autocomplete input for selecting a single entity | +| `tb-entity-select` | Dropdown select for entity | +| `tb-entity-list` | Chip list for multiple entities | +| `tb-entity-list-select` | Dropdown with entity list selection | +| `tb-entity-key-autocomplete` | Autocomplete for entity attribute/telemetry keys | +| `tb-entity-keys-list` | Chip list for multiple entity keys | +| `tb-entity-type-select` | Dropdown for entity type enum | +| `tb-entity-type-list` | Chip list for multiple entity types | +| `tb-entity-subtype-select` | Dropdown for entity subtype (e.g., device type) | +| `tb-entity-subtype-list` | Chip list for multiple subtypes | +| `tb-entity-subtype-autocomplete` | Autocomplete for entity subtype | +| `tb-entity-gateway-select` | Dropdown for selecting a gateway device | + +### `@shared/components/time/` + +| Selector | Purpose | +|----------|---------| +| `tb-timewindow` | Timewindow selector (realtime/history) | +| `tb-timewindow-panel` | Expanded panel for timewindow config | +| `tb-timewindow-config-dialog` | Dialog for advanced timewindow settings | +| `tb-datetime` | Date-time picker input | +| `tb-datetime-period` | Date range picker | +| `tb-timezone` | Timezone display | +| `tb-timezone-select` | Dropdown for timezone selection | +| `tb-quick-time-interval` | Quick interval preset selector | +| `tb-timeinterval` | Duration input (number + unit) | +| `tb-datapoints-limit` | Numeric input for data point limits | +| `tb-aggregation-type-select` | Dropdown for aggregation type (AVG, SUM, etc.) | +| `tb-history-selector` | History navigation selector | + +### `@shared/components/image/` + +| Selector | Purpose | +|----------|---------| +| `tb-image-gallery` | Grid gallery of uploaded images | +| `tb-image-gallery-dialog` | Dialog wrapper for image gallery | +| `tb-image-input` | Single image upload input | +| `tb-gallery-image-input` | Image input that opens gallery for selection | +| `tb-multiple-gallery-image-input` | Multi-image input with gallery | +| `tb-upload-image-dialog` | Upload dialog | +| `tb-embed-image-dialog` | Embed external image dialog | +| `tb-scada-symbol-input` | SCADA symbol selector input | + +### `@shared/components/dialog/` + +| Selector | Purpose | +|----------|---------| +| `tb-confirm-dialog` | Yes/No confirmation dialog | +| `tb-alert-dialog` | Information alert dialog | +| `tb-error-alert-dialog` | Error message dialog | +| `tb-color-picker-dialog` | Color picker in a dialog | +| `tb-material-icons-dialog` | Material icon browser dialog | +| `tb-node-script-test-dialog` | Script testing dialog for rule nodes | +| `tb-object-edit-dialog` | JSON object editor dialog | +| `tb-todo-dialog` | Todo/checklist dialog | + +### `@shared/components/` (other) + +| Selector | Purpose | +|----------|---------| +| `tb-json-content` | JSON syntax-highlighted viewer | +| `tb-json-object-edit` | Editable JSON object component | +| `tb-json-object-view` | Read-only JSON object display | +| `tb-markdown` | Markdown editor/viewer | +| `tb-breadcrumb` | Breadcrumb navigation bar | +| `tb-icon` | Custom icon component | +| `tb-logo` | ThingsBoard logo | +| `tb-copy-button` | Copy-to-clipboard button | +| `tb-toggle-password` | Password visibility toggle | +| `tb-file-input` | File upload input | +| `tb-phone-input` | International phone input | +| `tb-unit-input` | Measurement unit input | +| `tb-value-input` | Generic value input with type selection | +| `tb-color-input` | Color picker input | +| `tb-color-picker` | Inline color picker | +| `tb-key-val-map` | Key-value pair map editor | +| `tb-nav-tree` | Tree navigation component | +| `tb-user-menu` | User profile dropdown menu | +| `tb-notification-bell` | Notification bell icon with badge | +| `tb-footer` | Page footer | +| `tb-scroll-grid` | Virtual-scroll grid | +| `tb-social-share-panel` | Social sharing buttons | + +### Autocomplete / Select Components + +| Selector | Purpose | +|----------|---------| +| `tb-dashboard-autocomplete` | Autocomplete for dashboard selection | +| `tb-dashboard-select` | Dropdown for dashboard | +| `tb-dashboard-state-autocomplete` | Autocomplete for dashboard states | +| `tb-country-autocomplete` | Country autocomplete | +| `tb-string-autocomplete` | Generic string autocomplete | +| `tb-message-type-autocomplete` | Rule engine message type autocomplete | +| `tb-queue-autocomplete` | Queue name autocomplete | +| `tb-relation-type-autocomplete` | Relation type autocomplete | +| `tb-resource-autocomplete` | Resource autocomplete | +| `tb-ota-package-autocomplete` | OTA package autocomplete | +| `tb-branch-autocomplete` | Git branch autocomplete | +| `tb-rule-chain-select` | Rule chain select dropdown | +| `tb-widgets-bundle-select` | Widgets bundle select dropdown | +| `tb-template-autocomplete` | Notification template autocomplete | +| `tb-material-icon-select` | Material icon picker | +| `tb-slack-conversation-autocomplete` | Slack conversation autocomplete | + +### `@home/components/` (key components) + +| Selector | Purpose | +|----------|---------| +| `tb-entities-table` | Main entity table (columns, sort, pagination, actions) | +| `tb-entity-details-panel` | Slide-out panel for entity details | +| `tb-entity-details-page` | Full-page entity details view | +| `tb-add-entity-dialog` | Generic add-entity dialog | +| `tb-entity-filter` | Entity data filter component | +| `tb-entity-chips` | Chip display for entity references | +| `tb-details-panel` | Base details panel (toolbar + edit/apply FABs) | +| `tb-alarm-table` | Alarm list table | +| `tb-alarm-details-dialog` | Alarm detail dialog with ack/clear | +| `tb-alarm-assignee` | Alarm assignee selector | +| `tb-alarm-comment` | Alarm comment thread | +| `tb-alarm-filter-config` | Alarm filter configuration | +| `tb-audit-log-table` | Audit log table | +| `tb-attribute-table` | Entity attribute key-value table | +| `tb-relation-table` | Entity relation table | +| `tb-event-table` | Entity event table | +| `tb-dashboard` | Dashboard renderer | +| `tb-dashboard-page` | Dashboard page (toolbar + dashboard) | +| `tb-dashboard-toolbar` | Dashboard edit/view toolbar | +| `tb-edit-widget` | Widget editor | +| `tb-router-tabs` | Tab navigation via router (used for section tabs) | +| `tb-device-credentials` | Device credentials form | + +--- + +## 5. Angular Material DOM Patterns + +**No `data-testid` attributes** — selectors must use Angular Material structure, `formControlName`, `matColumnDef`, labels, roles, and classes. + +### Login Form + +```html + + + + + +``` + +Key selectors: `#username-input`, `#password-input`, `button[type="submit"]`. + +### Main Layout + +```html + + + + + + + + + + + + + + +``` + +Key selectors: `mat-sidenav#sidenav`, `button#main`, `tb-breadcrumb`, `tb-user-menu`. + +### Entity Table + +```html + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+``` + +Key selectors: `table[mat-table]`, `mat-row`, `mat-cell`, `mat-paginator`, `[matColumnDef="select"]`, `[matColumnDef="actions"]`. Note: the search input uses Angular template ref `#searchInput` (not an HTML `id`); target it via `.tb-entity-table mat-toolbar input[matInput]` in the search toolbar context. + +### Entity Details Page + +```html + + + +
+
+ + +
+
+ + + + + + +
+``` + +Key selectors: `mat-toolbar.details-toolbar`, `button[mat-fab][color="accent"]`, `mat-tab-group`, `mat-tab`. + +### Details Panel (Slide-Out Drawer) + +```html +
+ + + +
+ + +
+
+
+
+ +
+``` + +### Entity Detail Form (inside a tab) + +```html +
+
+
+ + + + + + + + + + + + + + +
+
+
+``` + +Key selectors: `input[formControlName="name"]`, `mat-slide-toggle[formControlName="..."]`, `textarea[formControlName="description"]`. + +### Dialog + +```html +
+ +

+ + +
+ +
+ +
+
+ + +
+ +``` + +Key selectors: `[mat-dialog-content]`, `[mat-dialog-actions]`, `button[type="submit"]`, `mat-toolbar[color="primary"] h2`. + +### Common Material Selector Patterns + +``` +# Find a form field by its control name +input[formControlName="name"] +mat-select[formControlName="type"] +mat-slide-toggle[formControlName="gateway"] +textarea[formControlName="description"] + +# Find table column content +[matColumnDef="name"] mat-cell + +# Find buttons by tooltip +button[matTooltip="Search"] + +# Find a specific tab +mat-tab-group mat-tab (by index or label text) + +# Find menu items (after opening menu) +button[mat-menu-item] + +# Find dialog actions +[mat-dialog-actions] button[mat-raised-button] +``` + +--- + +## 6. Form Structure Guide + +All entity detail forms extend the abstract `EntityComponent` directive (`@home/components/entity/entity.component.ts`). + +**Key properties:** +- `entityForm: UntypedFormGroup` — the reactive form instance +- `entity: T` — current entity data (input) +- `isEdit: boolean` — whether form is in edit mode (input) + +**Lifecycle:** + +1. **`buildForm(entity: T): UntypedFormGroup`** — Called in constructor. Creates the FormGroup with controls, validators, and initial values. Subscribe to valueChanges here for cascading updates. + +2. **`updateForm(entity: T): void`** — Called when entity input changes. Uses `entityForm.patchValue({...}, {emitEvent: false})` to populate form without triggering change events. + +3. **`updateFormState(): void`** — Called when `isEdit` changes. Calls `entityForm.enable()` or `entityForm.disable()` with `{emitEvent: false}`. + +4. **`entityFormValue(): any`** — Returns `entityForm.getRawValue()` (reads disabled fields too), passed through `prepareFormValue()` (default: `deepTrim`). + +**Read-only pattern:** Templates use `
` to visually disable all inputs. The form group is also programmatically disabled via `entityForm.disable()`. + +**Saving:** Merges `{...this.entity, ...this.entityFormValue()}` and calls the HTTP service. + +**Common validators:** `Validators.required`, `Validators.maxLength(255)`, custom `validateEmail`. + +**Nested FormGroups** — `additionalInfo` is almost always a nested group: +```typescript +additionalInfo: this.fb.group({ + description: [''], + gateway: [false], + // ... +}) +``` + +--- + +## 7. HTTP Services Reference + +All services are in `src/app/core/http/`. + +| Entity / Area | Service File | Class | +|---------------|-------------|-------| +| Device | `device.service.ts` | `DeviceService` | +| Asset | `asset.service.ts` | `AssetService` | +| Device Profile | `device-profile.service.ts` | `DeviceProfileService` | +| Asset Profile | `asset-profile.service.ts` | `AssetProfileService` | +| Entity View | `entity-view.service.ts` | `EntityViewService` | +| Customer | `customer.service.ts` | `CustomerService` | +| User | `user.service.ts` | `UserService` | +| Tenant | `tenant.service.ts` | `TenantService` | +| Tenant Profile | `tenant-profile.service.ts` | `TenantProfileService` | +| Dashboard | `dashboard.service.ts` | `DashboardService` | +| Rule Chain | `rule-chain.service.ts` | `RuleChainService` | +| Edge | `edge.service.ts` | `EdgeService` | +| Alarm | `alarm.service.ts` | `AlarmService` | +| Alarm Comment | `alarm-comment.service.ts` | `AlarmCommentService` | +| OTA Package | `ota-package.service.ts` | `OtaPackageService` | +| Calculated Fields | `calculated-fields.service.ts` | `CalculatedFieldsService` | +| Widget | `widget.service.ts` | `WidgetService` | +| Resource | `resource.service.ts` | `ResourceService` | +| Queue | `queue.service.ts` | `QueueService` | +| AI Model | `ai-model.service.ts` | `AiModelService` | +| Notification | `notification.service.ts` | `NotificationService` | +| OAuth2 | `oauth2.service.ts` | `OAuth2Service` | +| Domain | `domain.service.ts` | `DomainService` | +| Mobile App | `mobile-app.service.ts` | `MobileAppService` | +| Attribute | `attribute.service.ts` | `AttributeService` | +| Entity Relation | `entity-relation.service.ts` | `EntityRelationService` | +| Entity (generic) | `entity.service.ts` | `EntityService` | +| Event | `event.service.ts` | `EventService` | +| Audit Log | `audit-log.service.ts` | `AuditLogService` | +| Admin Settings | `admin.service.ts` | `AdminService` | +| Version Control | `entities-version-control.service.ts` | `EntitiesVersionControlService` | +| 2FA | `two-factor-authentication.service.ts` | `TwoFactorAuthenticationService` | +| User Settings | `user-settings.service.ts` | `UserSettingsService` | +| API Key | `api-key.service.ts` | `ApiKeyService` | +| Image | `image.service.ts` | `ImageService` | +| Trendz Settings | `trendz-settings.service.ts` | `TrendzSettingsService` | +| Usage Info | `usage-info.service.ts` | `UsageInfoService` | +| Component Descriptor | `component-descriptor.service.ts` | `ComponentDescriptorService` |