From 60c080d71280a0689e2b08589603fe76f2d515e9 Mon Sep 17 00:00:00 2001
From: RehanY147 <rehanyousaf147@gmail.com>
Date: Thu, 12 Sep 2024 16:17:41 +0500
Subject: [PATCH] NAS-130854 / 25.04 / Refactor `trackBy` for `ix-table-body`
 (#10604)

---
 src/app/core/testing/utils/fake-job.utils.ts  |   1 +
 .../ix-cell-actions.component.html            |  26 +--
 .../ix-cell-actions.component.ts              |   2 +-
 .../ix-cell-checkbox.component.html           |   4 +-
 .../ix-cell-checkbox.component.spec.ts        |   9 +-
 .../ix-cell-checkbox.component.ts             |   4 +-
 .../ix-cell-date/ix-cell-date.component.html  |   4 +-
 .../ix-cell-date.component.spec.ts            |   4 +-
 .../ix-cell-date/ix-cell-date.component.ts    |   2 +-
 .../ix-cell-relative-date.component.html      |   2 +-
 .../ix-cell-relative-date.component.spec.ts   |   2 +-
 .../ix-cell-relative-date.component.ts        |   2 +-
 .../ix-cell-schedule.component.html           |   2 +-
 .../ix-cell-schedule.component.spec.ts        |   2 +-
 .../ix-cell-schedule.component.ts             |   2 +-
 .../ix-cell-size/ix-cell-size.component.html  |   2 +-
 .../ix-cell-size.component.spec.ts            |   2 +-
 .../ix-cell-size/ix-cell-size.component.ts    |   2 +-
 .../ix-cell-state-button.component.html       |   8 +-
 .../ix-cell-state-button.component.spec.ts    |  38 +++-
 .../ix-cell-state-button.component.ts         | 163 +++++++++++-------
 .../ix-cell-template.component.ts             |   2 +-
 .../ix-cell-text/ix-cell-text.component.html  |   2 +-
 .../ix-cell-text.component.spec.ts            |   2 +-
 .../ix-cell-text/ix-cell-text.component.ts    |   6 +-
 .../ix-cell-toggle.component.html             |  10 +-
 .../ix-cell-toggle.component.spec.ts          |   9 +-
 .../ix-cell-toggle.component.ts               |   4 +-
 .../ix-cell-yes-no.component.html             |   2 +-
 .../ix-cell-yes-no.component.spec.ts          |   2 +-
 .../ix-cell-yes-no.component.ts               |   2 +-
 .../ix-table-body.component.html              |   9 +-
 .../ix-table-body.component.spec.ts           |   2 +-
 .../ix-table-body/ix-table-body.component.ts  |  15 +-
 ...x-table-columns-selector.component.spec.ts |   4 +-
 .../ix-table-columns-selector.component.ts    |   2 +-
 .../ix-table-details-row.component.ts         |   2 +-
 .../ix-header-cell-checkbox.component.ts      |   2 +-
 .../ix-header-cell-text.component.ts          |   2 +-
 .../ix-table-head.component.spec.ts           |   2 +-
 .../ix-table-head/ix-table-head.component.ts  |   2 +-
 .../directives/ix-body-cell.directive.ts      |  28 ++-
 .../directives/ix-header-cell.directive.ts    |   5 +-
 .../interfaces/column-component.class.ts      |  40 +++++
 .../interfaces/table-column.interface.ts      |  35 ----
 src/app/modules/ix-table/utils.ts             |  18 +-
 .../groups/group-list/group-list.component.ts |   2 +-
 .../privilege-list.component.ts               |   2 +-
 .../users/user-list/user-list.component.ts    |   2 +-
 .../api-key-list/api-key-list.component.ts    |   2 +-
 .../docker-images-list.component.ts           |   2 +-
 .../audit/components/audit/audit.component.ts |   2 +-
 .../cloud-credentials-card.component.ts       |   2 +-
 .../ssh-connection-card.component.ts          |   2 +-
 .../ssh-keypair-card.component.ts             |   2 +-
 .../acme-dns-authenticator-list.component.ts  |   2 +-
 .../certificate-authority-list.component.ts   |   2 +-
 .../certificate-list.component.ts             |   2 +-
 .../csr-list/csr-list.component.ts            |   2 +-
 .../cloud-backup-card.component.spec.ts       |  15 ++
 .../cloud-backup-card.component.ts            |   2 +-
 .../cloud-backup-snapshots.component.ts       |   2 +-
 .../cloud-backup-list.component.spec.ts       |  10 ++
 .../cloud-backup-list.component.ts            |   2 +-
 .../cloudsync-list.component.spec.ts          |  10 ++
 .../cloudsync-list.component.ts               |   4 +-
 .../cloudsync-task-card.component.ts          |   3 +-
 .../replication-list.component.spec.ts        |   6 +-
 .../replication-list.component.ts             |   6 +-
 .../replication-task-card.component.ts        |   2 +-
 .../rsync-task-card.component.ts              |   2 +-
 .../rsync-task-list.component.spec.ts         |  18 +-
 .../rsync-task-list.component.ts              |   2 +-
 .../scrub-list/scrub-list.component.ts        |   2 +-
 .../scrub-task-card.component.ts              |   2 +-
 .../smart-task-card.component.ts              |   2 +-
 .../smart-task-list.component.ts              |   2 +-
 .../snapshot-task-card.component.ts           |   2 +-
 .../snapshot-task-list.component.ts           |   4 +-
 .../vmware-snapshot-list.component.ts         |   2 +-
 .../dataset-quotas-list.component.ts          |   2 +-
 .../snapshot-list/snapshot-list.component.ts  |   2 +-
 .../idmap-list/idmap-list.component.ts        |   2 +-
 .../kerberos-keytabs-list.component.ts        |   2 +-
 .../kerberos-realms-list.component.ts         |   2 +-
 .../jobs/jobs-list/jobs-list.component.ts     |   2 +-
 .../interfaces-card.component.ts              |   9 +-
 .../ip-addresses-cell.component.spec.ts       |   4 +-
 .../ip-addresses-cell.component.ts            |  22 ++-
 .../ipmi-card/ipmi-card.component.ts          |   2 +-
 .../static-routes-card.component.ts           |   2 +-
 src/app/pages/network/tests/checkin.spec.ts   |   4 -
 .../reporting-exporters-list.component.ts     |   2 +-
 src/app/pages/services/services.component.ts  |   2 +-
 .../iscsi-card/iscsi-card.component.ts        |   2 +-
 .../nfs-card/nfs-card.component.ts            |   2 +-
 .../smb-card/smb-card.component.ts            |   2 +-
 .../associated-target-list.component.ts       |   2 +-
 .../authorized-access-list.component.ts       |   2 +-
 .../extent-list/extent-list.component.ts      |   2 +-
 .../initiator-list.component.ts               |   2 +-
 .../portal-list/portal-list.component.ts      |   2 +-
 .../target-list/target-list.component.ts      |   2 +-
 .../nfs/nfs-list/nfs-list.component.ts        |   2 +-
 .../nfs-session-list.component.ts             |   6 +-
 .../smb/smb-list/smb-list.component.ts        |   2 +-
 .../smb-lock-list/smb-lock-list.component.ts  |   2 +-
 .../smb-notification-list.component.ts        |   2 +-
 .../smb-open-files.component.ts               |   2 +-
 .../smb-session-list.component.ts             |   2 +-
 .../smb-share-list.component.ts               |   2 +-
 .../disk-list/disk-list.component.ts          |   4 +-
 .../smart-test-result-list.component.spec.ts  |   4 +-
 .../smart-test-result-list.component.ts       |   4 +-
 .../access-card/access-card.component.ts      |   2 +-
 .../allowed-addresses-card.component.ts       |   2 +-
 .../cron/cron-card/cron-card.component.ts     |   2 +-
 .../cron/cron-list/cron-list.component.ts     |   4 +-
 .../init-shutdown-card.component.ts           |   2 +-
 .../init-shutdown-list.component.ts           |   2 +-
 .../sysctl-card/sysctl-card.component.ts      |   2 +-
 .../tunable-list/tunable-list.component.ts    |   2 +-
 .../alert-service-list.component.ts           |   2 +-
 .../bootenv-list/bootenv-list.component.ts    |   2 +-
 .../jbof-list/jbof-list.component.ts          |   2 +-
 .../elements-page/elements-page.component.ts  |   2 +-
 .../ntp-server-card.component.ts              |   2 +-
 .../device-list/device-list.component.ts      |   2 +-
 src/app/pages/vm/vm-list/vm-list.component.ts |   4 +-
 tsconfig.strictNullChecks.json                |   2 +-
 130 files changed, 462 insertions(+), 301 deletions(-)
 create mode 100644 src/app/modules/ix-table/interfaces/column-component.class.ts
 delete mode 100644 src/app/modules/ix-table/interfaces/table-column.interface.ts

diff --git a/src/app/core/testing/utils/fake-job.utils.ts b/src/app/core/testing/utils/fake-job.utils.ts
index f181250d917..e8e2f5a4d6f 100644
--- a/src/app/core/testing/utils/fake-job.utils.ts
+++ b/src/app/core/testing/utils/fake-job.utils.ts
@@ -13,6 +13,7 @@ export function fakeSuccessfulJob<T = void, A = unknown[]>(
     exception: '',
     id: 0,
     result: jobResult,
+    time_finished: { $date: 12345678900 },
     state: JobState.Success,
   } as Job<T, A>;
 }
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
index fb2fb0d6764..96d4f8abeed 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.html
@@ -1,15 +1,15 @@
 <div class="actions">
   @for (action of actions; track action) {
     @if (action.requiredRoles?.length || action.dynamicRequiredRoles) {
-      <div [matTooltip]="action.dynamicTooltip ? (action.dynamicTooltip(row) | async) : action.tooltip || ''">
-        @if (action.hidden ? !(action.hidden(row) | async) : true) {
+      <div [matTooltip]="action.dynamicTooltip ? (action.dynamicTooltip(row()) | async) : action.tooltip || ''">
+        @if (action.hidden ? !(action.hidden(row()) | async) : true) {
           <button
-            *ixRequiresRoles="action?.dynamicRequiredRoles ? (action.dynamicRequiredRoles(row) | async) : action.requiredRoles"
+            *ixRequiresRoles="action?.dynamicRequiredRoles ? (action.dynamicRequiredRoles(row()) | async) : action.requiredRoles"
             mat-icon-button
-            [ixTest]="[rowTestId(row), action.iconName, 'row-action']"
-            [attr.aria-label]="(action.dynamicTooltip ? (action.dynamicTooltip(row) | async) : action.tooltip || '') + ' ' + getAriaLabel(row)"
-            [disabled]="action.disabled ? (action.disabled(row) | async) : false"
-            (click)="$event.stopPropagation(); action.onClick(row)"
+            [ixTest]="[uniqueRowTag(row()), action.iconName, 'row-action']"
+            [attr.aria-label]="(action.dynamicTooltip ? (action.dynamicTooltip(row()) | async) : action.tooltip || '') + ' ' + getAriaLabel(row())"
+            [disabled]="action.disabled ? (action.disabled(row()) | async) : false"
+            (click)="$event.stopPropagation(); action.onClick(row())"
           >
             <ix-icon [name]="action.iconName"></ix-icon>
           </button>
@@ -18,15 +18,15 @@
     }
     @if (!action.requiredRoles?.length && !action.dynamicRequiredRoles) {
       <div
-        [matTooltip]="action.dynamicTooltip ? (action.dynamicTooltip(row) | async) : action.tooltip || ''"
+        [matTooltip]="action.dynamicTooltip ? (action.dynamicTooltip(row()) | async) : action.tooltip || ''"
       >
-        @if (action.hidden ? !(action.hidden(row) | async) : true) {
+        @if (action.hidden ? !(action.hidden(row()) | async) : true) {
           <button
             mat-icon-button
-            [ixTest]="[rowTestId(row), action.iconName, 'row-action']"
-            [attr.aria-label]="(action.dynamicTooltip ? (action.dynamicTooltip(row) | async) : action.tooltip || '') + ' ' + getAriaLabel(row)"
-            [disabled]="action.disabled ? (action.disabled(row) | async) : false"
-            (click)="$event.stopPropagation(); action.onClick(row)"
+            [ixTest]="[uniqueRowTag(row()), action.iconName, 'row-action']"
+            [attr.aria-label]="(action.dynamicTooltip ? (action.dynamicTooltip(row()) | async) : action.tooltip || '') + ' ' + getAriaLabel(row())"
+            [disabled]="action.disabled ? (action.disabled(row()) | async) : false"
+            (click)="$event.stopPropagation(); action.onClick(row())"
           >
             <ix-icon [name]="action.iconName"></ix-icon>
           </button>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
index 3b100e6199c..8b54f5038a5 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/ix-cell-actions.component.ts
@@ -1,7 +1,7 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { Role } from 'app/enums/role.enum';
 import { IconActionConfig } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-actions/icon-action-config.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-actions',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.html
index 83165205af1..781d6328f51 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.html
@@ -1,8 +1,8 @@
 <mat-checkbox
   color="primary"
-  [ixTest]="[title, rowTestId(row), 'row-checkbox']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-checkbox']"
   [checked]="checked"
-  [aria-label]="(checked ? ('Uncheck' | translate) : ('Check' | translate)) + ' ' + getAriaLabel(row)"
+  [aria-label]="(checked ? ('Uncheck' | translate) : ('Check' | translate)) + ' ' + getAriaLabel(row())"
   (change)="onCheckboxChange($event)"
   (click)="$event.stopPropagation()"
 ></mat-checkbox>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.spec.ts
index 9136029e6a3..d9c8a0b3d46 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.spec.ts
@@ -22,7 +22,7 @@ describe('IxCellCheckboxComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'booleanField';
     spectator.component.setRow({ booleanField: true });
-    spectator.component.rowTestId = (row) => 'checkbox-' + row.booleanField.toString();
+    spectator.component.uniqueRowTag = (row) => 'checkbox-' + row.booleanField.toString();
     spectator.component.ariaLabels = () => ['Label 1', 'Label 2'];
     spectator.detectChanges();
 
@@ -40,8 +40,9 @@ describe('IxCellCheckboxComponent', () => {
     expect(spectator.component.onRowCheck).toHaveBeenCalledWith({ booleanField: true }, false);
   });
 
-  it('gets aria label correctly', () => {
-    const ariaLabel = spectator.component.getAriaLabel(spectator.component.getRow());
-    expect(ariaLabel).toBe('Label 1 Label 2');
+  it('gets aria label correctly', async () => {
+    const checkbox = await loader.getHarness(MatCheckboxHarness);
+    const ariaLabel = await checkbox.getAriaLabel();
+    expect(ariaLabel).toBe('Uncheck Label 1 Label 2');
   });
 });
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.ts
index 3c323aef325..c5fcf39aede 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component.ts
@@ -1,7 +1,7 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { MatCheckboxChange } from '@angular/material/checkbox';
 import { IxHeaderCellCheckboxComponent } from 'app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-checkbox/ix-header-cell-checkbox.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-checkbox',
@@ -16,7 +16,7 @@ export class IxCellCheckboxComponent<T> extends ColumnComponent<T> {
   }
 
   onCheckboxChange(event: MatCheckboxChange): void {
-    this.onRowCheck(this.row, event.checked);
+    this.onRowCheck(this.row(), event.checked);
   }
 }
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.html
index 5ea825404fb..c658679f1d2 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.html
@@ -1,10 +1,10 @@
 @if (date) {
   <ix-date
-    [ixTest]="[title, rowTestId(row), 'row-date']"
+    [ixTest]="[title, uniqueRowTag(row()), 'row-date']"
     [date]="date"
   ></ix-date>
 } @else {
-  <span [ixTest]="[title, rowTestId(row), 'row-date']">
+  <span [ixTest]="[title, uniqueRowTag(row()), 'row-date']">
     {{ 'N/A' | translate }}
   </span>
 }
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.spec.ts
index a556d0a7ab6..07fce5a8b9a 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.spec.ts
@@ -30,7 +30,7 @@ describe('IxCellDateComponent', () => {
       spectator = createComponent();
       spectator.component.propertyName = 'dateField';
       spectator.component.setRow({ dateField: new Date('2023-07-12 09:10:00') });
-      spectator.component.rowTestId = () => '';
+      spectator.component.uniqueRowTag = () => '';
       spectator.detectChanges();
     });
 
@@ -44,7 +44,7 @@ describe('IxCellDateComponent', () => {
       spectator = createComponent();
       spectator.component.propertyName = 'dateField';
       spectator.component.setRow({ dateField: null });
-      spectator.component.rowTestId = () => '';
+      spectator.component.uniqueRowTag = () => '';
       spectator.detectChanges();
     });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.ts
index edf992eceae..d0fecb64752 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-date/ix-cell-date.component.ts
@@ -1,6 +1,6 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { ApiTimestamp } from 'app/interfaces/api-date.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-date',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.html
index 0b1122b6111..644787db15c 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.html
@@ -1,4 +1,4 @@
 <span
   [matTooltip]="dateTooltip"
-  [ixTest]="[title, rowTestId(row), 'row-relative-date']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-relative-date']"
 >{{ date }}</span>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.spec.ts
index 0704fc23746..6e4116690b8 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.spec.ts
@@ -28,7 +28,7 @@ describe('IxCellRelativeDateComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'dateField';
     spectator.component.setRow({ dateField: new Date(new Date().getTime() - (oneDayMillis * 10)) });
-    spectator.component.rowTestId = () => '';
+    spectator.component.uniqueRowTag = () => '';
     spectator.detectChanges();
   });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.ts
index 06c0046810d..7d03a6ca2f5 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-relative-date/ix-cell-relative-date.component.ts
@@ -3,7 +3,7 @@ import { TranslateService } from '@ngx-translate/core';
 import { isValid } from 'date-fns';
 import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
 import { formatDistanceToNowShortened } from 'app/helpers/format-distance-to-now-shortened';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 import { FormatDateTimePipe } from 'app/modules/pipes/format-date-time/format-datetime.pipe';
 import { LocaleService } from 'app/services/locale.service';
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.html
index 77978067dab..9fd84cc937d 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.html
@@ -1,3 +1,3 @@
 <span
-  [ixTest]="[title, rowTestId(row), 'row-schedule']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-schedule']"
 >{{ value | scheduleToCrontab }}</span>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.spec.ts
index 3256893dc56..0ed060df033 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.spec.ts
@@ -26,7 +26,7 @@ describe('IxCellScheduleComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'scheduleField';
     spectator.component.setRow({ scheduleField: schedule });
-    spectator.component.rowTestId = () => '';
+    spectator.component.uniqueRowTag = () => '';
     spectator.detectChanges();
   });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.ts
index 789a48a4c06..914a69c94df 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-schedule/ix-cell-schedule.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-schedule',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.html
index 08a64f578d6..5bc73c19d30 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.html
@@ -1,3 +1,3 @@
 <span
-  [ixTest]="[title, rowTestId(row), 'row-size']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-size']"
 >{{ size | ixFileSize }}</span>
\ No newline at end of file
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.spec.ts
index 89e9695a20f..ea64480abbb 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.spec.ts
@@ -18,7 +18,7 @@ describe('IxCellSizeComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'numberField';
     spectator.component.setRow({ numberField: 5 * 1024 * 1024 * 1024 });
-    spectator.component.rowTestId = () => '';
+    spectator.component.uniqueRowTag = () => '';
     spectator.detectChanges();
   });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.ts
index 1ea31e2baff..9dff82e564e 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-size/ix-cell-size.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-size',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.html
index b91ba2efc03..56a27b3f5a9 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.html
@@ -1,15 +1,15 @@
-@if (state) {
+@if (state()) {
   <button
     mat-stroked-button
     class="state-button"
     matTooltipPosition="above"
-    [ixTest]="[title, rowTestId(row), 'row-state']"
+    [ixTest]="[title, uniqueRowTag(row()), 'row-state']"
     [ngClass]="getButtonClass()"
     [matTooltip]="tooltip"
-    [attr.aria-label]="getAriaLabel(row)"
+    [attr.aria-label]="getAriaLabel(row())"
     (click)="$event.stopPropagation(); onButtonClick()"
   >
-    {{ state }}
+    {{ state() }}
     @if (warnings?.length > 0) {
       <div class="label-warning-icon">
         <ix-icon name="mdi-alert"></ix-icon>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.spec.ts
index ecc6a20c70c..2b6bcd9e465 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.spec.ts
@@ -4,12 +4,16 @@ import { MatButtonHarness } from '@angular/material/button/testing';
 import { MatDialog } from '@angular/material/dialog';
 import { Spectator } from '@ngneat/spectator';
 import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
+import { provideMockStore } from '@ngrx/store/testing';
+import { of } from 'rxjs';
 import { JobState } from 'app/enums/job-state.enum';
 import { Job } from 'app/interfaces/job.interface';
 import { ShowLogsDialogComponent } from 'app/modules/dialog/components/show-logs-dialog/show-logs-dialog.component';
+import { DialogService } from 'app/modules/dialog/dialog.service';
 import { IxIconHarness } from 'app/modules/ix-icon/ix-icon.harness';
 import { IxCellStateButtonComponent } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
+import { selectJobs } from 'app/modules/jobs/store/job.selectors';
 
 interface TestTableData {
   state: JobState;
@@ -26,9 +30,28 @@ describe('IxCellStateButtonComponent', () => {
     imports: [IxTableModule],
     detectChanges: false,
     providers: [
+      provideMockStore({
+        selectors: [
+          {
+            selector: selectJobs,
+            value: [{
+              id: 123456,
+              logs_excerpt: 'completed',
+              state: JobState.Success,
+            } as Job],
+          },
+        ],
+      }),
       mockProvider(MatDialog, {
         open: jest.fn(),
       }),
+      mockProvider(DialogService, {
+        jobDialog: jest.fn(() => {
+          return {
+            afterClosed: jest.fn(() => of()),
+          };
+        }),
+      }),
     ],
   });
 
@@ -37,12 +60,18 @@ describe('IxCellStateButtonComponent', () => {
     spectator.component.propertyName = 'state';
     spectator.component.setRow({
       state: JobState.Success,
-      job: { id: 123456, logs_excerpt: 'completed' },
+      job: { id: 123456, logs_excerpt: 'completed', state: JobState.Success },
       warnings: [{}, {}],
     } as TestTableData);
     spectator.component.getJob = (row) => row.job;
-    spectator.component.rowTestId = () => '';
+    spectator.component.uniqueRowTag = () => '';
     spectator.component.ariaLabels = () => ['Label 1', 'Label 2'];
+    spectator.component.job.set({
+      id: 123456,
+      logs_excerpt: 'completed',
+      state: JobState.Success,
+    } as Job);
+    spectator.component.ngOnInit();
     spectator.detectChanges();
 
     loader = TestbedHarnessEnvironment.loader(spectator.fixture);
@@ -51,6 +80,7 @@ describe('IxCellStateButtonComponent', () => {
   it('shows status text', async () => {
     const button = await loader.getHarness(MatButtonHarness);
     expect(await button.getText()).toBe(JobState.Success);
+    expect(await (await button.host()).hasClass('fn-theme-green')).toBeTruthy();
   });
 
   it('sets class', async () => {
@@ -89,7 +119,7 @@ describe('IxCellStateButtonComponent', () => {
   });
 
   it('gets aria label correctly', () => {
-    const ariaLabel = spectator.component.getAriaLabel(spectator.component.getRow());
-    expect(ariaLabel).toBe('Label 1 Label 2');
+    const button = spectator.query('button');
+    expect(button.getAttribute('aria-label')).toBe('Label 1 Label 2');
   });
 });
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.ts
index 73dcd3d8b9f..80014d780c2 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component.ts
@@ -1,9 +1,13 @@
-import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import {
+  ChangeDetectionStrategy, Component, effect, inject, OnInit, signal,
+} from '@angular/core';
 import { MatDialog } from '@angular/material/dialog';
-import { UntilDestroy } from '@ngneat/until-destroy';
-import { select, Store } from '@ngrx/store';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
+import { Store } from '@ngrx/store';
 import { TranslateService } from '@ngx-translate/core';
-import { catchError, EMPTY, Observable } from 'rxjs';
+import {
+  catchError, EMPTY, Observable, tap,
+} from 'rxjs';
 import { JobState } from 'app/enums/job-state.enum';
 import { observeJob } from 'app/helpers/operators/observe-job.operator';
 import { helptextGlobal } from 'app/helptext/global-helptext';
@@ -11,7 +15,7 @@ import { ApiJobMethod, ApiJobResponse } from 'app/interfaces/api/api-job-directo
 import { Job } from 'app/interfaces/job.interface';
 import { ShowLogsDialogComponent } from 'app/modules/dialog/components/show-logs-dialog/show-logs-dialog.component';
 import { DialogService } from 'app/modules/dialog/dialog.service';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 import { JobSlice, selectJob } from 'app/modules/jobs/store/job.selectors';
 import { ErrorHandlerService } from 'app/services/error-handler.service';
 
@@ -31,31 +35,60 @@ interface RowState {
   styleUrls: ['./ix-cell-state-button.component.scss'],
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class IxCellStateButtonComponent<T> extends ColumnComponent<T> {
+export class IxCellStateButtonComponent<T> extends ColumnComponent<T> implements OnInit {
   matDialog: MatDialog = inject(MatDialog);
   translate: TranslateService = inject(TranslateService);
   dialogService: DialogService = inject(DialogService);
   errorHandler: ErrorHandlerService = inject(ErrorHandlerService);
 
-  private store$: Store<JobSlice> = inject<Store<JobSlice>>(Store<JobSlice>);
+  private readonly rowUpdateEffect = effect(() => {
+    const row = this.row();
+    const job = !row || !this.getJob ? undefined : this.getJob(row);
 
-  getWarnings?: (row: T) => unknown[];
-  getJob?: (row: T) => Job;
+    if (!job) {
+      return;
+    }
 
-  protected get warnings(): unknown[] {
-    return this.getWarnings ? this.getWarnings(this.row) : [];
-  }
+    this.state.set(job.state);
+  }, {
+    allowSignalWrites: true,
+  });
+
+  getJob: (row: T) => Job;
+  private store$: Store<JobSlice> = inject<Store<JobSlice>>(Store<JobSlice>);
+  job = signal<Job>(null);
+  jobUpdates$: Observable<Job<ApiJobResponse<ApiJobMethod>>>;
+  state = signal<JobState>(null);
 
-  protected get job(): Job {
-    return this.getJob ? this.getJob(this.row) : undefined;
+  ngOnInit(): void {
+    if (this.getJob) {
+      const job = this.getJob(this.row());
+      this.job.set(job);
+      if (job?.state) {
+        this.state.set(job.state);
+      }
+    }
+    if (!this.job()) {
+      this.state.set(this.value as JobState);
+      return;
+    }
+    const jobId = this.getJob(this.row()).id;
+    this.jobUpdates$ = this.store$.select(selectJob(jobId)).pipe(
+      tap((job) => {
+        this.job.set(job);
+        this.state.set(job.state);
+      }),
+    ) as Observable<Job<ApiJobResponse<ApiJobMethod>>>;
   }
 
-  protected get state(): JobState {
-    return this.value as JobState;
+  getWarnings?: (row: T) => unknown[];
+
+  protected get warnings(): unknown[] {
+    return this.getWarnings ? this.getWarnings(this.row()) : [];
   }
 
   protected get tooltip(): string {
-    if (this.job?.logs_path && this.job?.logs_excerpt) {
+    if (this.job()?.logs_path && this.job()?.logs_excerpt) {
       return this.translate.instant('Show Logs');
     }
 
@@ -63,52 +96,62 @@ export class IxCellStateButtonComponent<T> extends ColumnComponent<T> {
   }
 
   protected onButtonClick(): void {
-    let state = (this.row as RowState).state;
+    const state: RowState['state'] = {
+      state: this.state(),
+      error: this.job()?.error,
+    } as RowState['state'];
 
-    if (this.job?.state && !state) {
-      state = {
-        state: this.job.state,
-        error: this.job.error,
-      } as RowState['state'];
+    if (!state.state) {
+      this.dialogService.warn(helptextGlobal.noLogDialog.title, helptextGlobal.noLogDialog.message);
+      return;
     }
 
-    if (this.job && state) {
-      if (this.job.state === JobState.Running) {
-        this.dialogService.jobDialog(
-          this.store$.pipe(
-            select(selectJob(this.job.id)),
-            observeJob(),
-          ) as Observable<Job<ApiJobResponse<ApiJobMethod>>>,
-          {
-            title: this.translate.instant('Task is running'),
-            canMinimize: true,
-          },
-        ).afterClosed().pipe(
-          catchError((error) => {
-            this.errorHandler.showErrorModal(error);
-            return EMPTY;
-          }),
-        // TODO: Remove this ignore eslint lint line and add takeUntil (untilDestroyed)
-        // eslint-disable-next-line rxjs-angular/prefer-takeuntil
-        ).subscribe();
-      } else if (state.state === JobState.Hold) {
-        this.dialogService.info(this.translate.instant('Task is on hold'), state.reason);
-      } else if (state.warnings?.length > 0) {
-        let list = '';
-        state.warnings.forEach((warning: string) => {
-          list += warning + '\n';
-        });
-        this.dialogService.error({ title: state.state, message: `<pre>${list}</pre>` });
-      } else if (state.error) {
-        this.dialogService.error({ title: state.state, message: `<pre>${state.error}</pre>` });
-      } else if (!this.job.logs_excerpt) {
-        this.dialogService.warn(helptextGlobal.noLogDialog.title, helptextGlobal.noLogDialog.message);
-      } else {
-        this.matDialog.open(ShowLogsDialogComponent, { data: this.job });
-      }
-    } else {
-      this.dialogService.warn(helptextGlobal.noLogDialog.title, helptextGlobal.noLogDialog.message);
+    if (state.state === JobState.Running) {
+      this.showJobDialog();
+      return;
+    }
+
+    if (state.state === JobState.Hold) {
+      this.dialogService.info(this.translate.instant('Task is on hold'), state.reason);
+      return;
     }
+
+    if (state.warnings?.length > 0) {
+      let list = '';
+      state.warnings.forEach((warning: string) => {
+        list += warning + '\n';
+      });
+      this.dialogService.error({ title: state.state, message: `<pre>${list}</pre>` });
+      return;
+    }
+
+    if (state.error) {
+      this.dialogService.error({ title: state.state, message: `<pre>${state.error}</pre>` });
+      return;
+    }
+
+    if (this.job()?.logs_excerpt) {
+      this.matDialog.open(ShowLogsDialogComponent, { data: this.job() });
+      return;
+    }
+
+    this.dialogService.warn(helptextGlobal.noLogDialog.title, helptextGlobal.noLogDialog.message);
+  }
+
+  showJobDialog(): void {
+    this.dialogService.jobDialog(
+      this.jobUpdates$.pipe(observeJob()),
+      {
+        title: this.translate.instant('Task is running'),
+        canMinimize: true,
+      },
+    ).afterClosed().pipe(
+      catchError((error) => {
+        this.errorHandler.showErrorModal(error);
+        return EMPTY;
+      }),
+      untilDestroyed(this),
+    ).subscribe();
   }
 
   protected getButtonClass(): string {
@@ -117,7 +160,7 @@ export class IxCellStateButtonComponent<T> extends ColumnComponent<T> {
       return 'fn-theme-orange';
     }
 
-    switch (this.state) {
+    switch (this.state()) {
       case JobState.Pending:
       case JobState.Aborted:
       case JobState.Running:
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-template/ix-cell-template.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-template/ix-cell-template.component.ts
index 9a4b75ece9a..1a6fb18205a 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-template/ix-cell-template.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-template/ix-cell-template.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-template',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.html
index a4e3fe7c579..8635ae8b5d4 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.html
@@ -1,4 +1,4 @@
 <span
   [matTooltip]="value?.toString() | translate"
-  [ixTest]="[title, rowTestId(row), 'row-text']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-text']"
 >{{ value }}</span>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.spec.ts
index 40e82270c28..5702104f1e2 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.spec.ts
@@ -18,7 +18,7 @@ describe('IxCellTextComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'stringField';
     spectator.component.setRow({ stringField: 'text in cell' });
-    spectator.component.rowTestId = (row) => 'text-' + row.stringField.toString();
+    spectator.component.uniqueRowTag = (row) => 'text-' + row.stringField.toString();
     spectator.detectChanges();
   });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.ts
index c8c8f7d8898..53bd00410cb 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component.ts
@@ -1,5 +1,7 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import {
+  ChangeDetectionStrategy, Component,
+} from '@angular/core';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-text',
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.html
index 6f89cf46faa..4b466f794ef 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.html
@@ -1,8 +1,8 @@
 @if (!requiredRoles?.length && !dynamicRequiredRoles) {
   <mat-slide-toggle
     color="primary"
-    [aria-label]="(checked ? ('Disable' | translate) : ('Enable' | translate)) + ' ' + getAriaLabel(row)"
-    [ixTest]="[title, rowTestId(row), 'row-toggle']"
+    [aria-label]="(checked ? ('Disable' | translate) : ('Enable' | translate)) + ' ' + getAriaLabel(row())"
+    [ixTest]="[title, uniqueRowTag(row()), 'row-toggle']"
     [checked]="checked"
     (change)="onSlideToggleChanged($event)"
     (click)="$event.stopPropagation()"
@@ -10,10 +10,10 @@
 }
 @if (requiredRoles?.length || dynamicRequiredRoles) {
   <mat-slide-toggle
-    *ixRequiresRoles="dynamicRequiredRoles ? (dynamicRequiredRoles(row) | async) : requiredRoles"
+    *ixRequiresRoles="dynamicRequiredRoles ? (dynamicRequiredRoles(row()) | async) : requiredRoles"
     color="primary"
-    [aria-label]="(checked ? ('Disable' | translate) : ('Enable' | translate)) + ' ' + getAriaLabel(row)"
-    [ixTest]="[title, rowTestId(row), 'row-toggle']"
+    [aria-label]="(checked ? ('Disable' | translate) : ('Enable' | translate)) + ' ' + getAriaLabel(row())"
+    [ixTest]="[title, uniqueRowTag(row()), 'row-toggle']"
     [checked]="checked"
     (change)="onSlideToggleChanged($event)"
     (click)="$event.stopPropagation()"
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.spec.ts
index 948f6993470..d3b9b5b0e6a 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.spec.ts
@@ -23,7 +23,7 @@ describe('IxCellToggleComponent', () => {
     spectator.component.propertyName = 'booleanField';
     spectator.component.setRow({ booleanField: true });
     spectator.component.onRowToggle = jest.fn();
-    spectator.component.rowTestId = (row) => row.booleanField.toString();
+    spectator.component.uniqueRowTag = (row) => row.booleanField.toString();
     spectator.component.ariaLabels = () => ['Label 1', 'Label 2'];
     spectator.detectChanges();
 
@@ -41,8 +41,9 @@ describe('IxCellToggleComponent', () => {
     expect(spectator.component.onRowToggle).toHaveBeenCalledWith({ booleanField: true }, false);
   });
 
-  it('gets aria label correctly', () => {
-    const ariaLabel = spectator.component.getAriaLabel(spectator.component.getRow());
-    expect(ariaLabel).toBe('Label 1 Label 2');
+  it('gets aria label correctly', async () => {
+    const toggle = await loader.getHarness(MatSlideToggleHarness);
+
+    expect(await toggle.getAriaLabel()).toBe('Disable Label 1 Label 2');
   });
 });
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.ts
index 7969b892e84..224016bde40 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { MatSlideToggleChange } from '@angular/material/slide-toggle';
 import { Observable } from 'rxjs';
 import { Role } from 'app/enums/role.enum';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-toggle',
@@ -19,7 +19,7 @@ export class IxCellToggleComponent<T> extends ColumnComponent<T> {
   }
 
   onSlideToggleChanged(event: MatSlideToggleChange): void {
-    this.onRowToggle(this.row, event.checked);
+    this.onRowToggle(this.row(), event.checked);
   }
 }
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.html b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.html
index 48869c69be0..3fba405adbf 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.html
@@ -1,3 +1,3 @@
 <span
-  [ixTest]="[title, rowTestId(row), 'row-yesno']"
+  [ixTest]="[title, uniqueRowTag(row()), 'row-yesno']"
 >{{ value | yesNo | translate }}</span>
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.spec.ts
index 31287db43c0..feeaebf93f3 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.spec.ts
@@ -18,7 +18,7 @@ describe('IxCellYesNoComponent', () => {
     spectator = createComponent();
     spectator.component.propertyName = 'yesNoField';
     spectator.component.setRow({ yesNoField: true });
-    spectator.component.rowTestId = (row) => row.yesNoField.toString();
+    spectator.component.uniqueRowTag = (row) => row.yesNoField.toString();
     spectator.detectChanges();
   });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.ts b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.ts
index f457327fc85..0ade9bdaaf0 100644
--- a/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent, Column } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-cell-yesno',
diff --git a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.html b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.html
index c81c60d81c2..e463ad13820 100644
--- a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.html
+++ b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.html
@@ -1,16 +1,17 @@
-@for (row of dataProvider.currentPage$ | async; track trackByIdentity(row)) {
+@for (row of dataProvider.currentPage$ | async; track trackRowByIdentity(row)) {
   <tr
     class="row"
     tabindex="0"
-    [ixTest]="getTestAttr(row)"
-    [ixUiSearch]="{ anchor: getTestAttr(row) }"
+    [ixTest]="getRowTag(row)"
+    [ixUiSearch]="{ anchor: getRowTag(row) }"
     [ngStyle]="{ cursor: detailsTemplate ? 'pointer' : null }"
     [class.expanded]="isExpanded(row)"
     [class.hidden]="isLoading"
     (click)="onToggle(row)"
     (keydown.enter)="onToggle(row)"
   >
-    @for (column of displayedColumns; track trackByIdentity(column); let idx = $index) {
+
+    @for (column of displayedColumns; track trackColumnByIdentity(column); let idx = $index) {
       <td [ngClass]="column.cssClass || ''">
         @if (getTemplateByColumnIndex(idx); as template) {
           <ng-container
diff --git a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.spec.ts b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.spec.ts
index eafecd95d49..64ef9bf11df 100644
--- a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.spec.ts
@@ -52,7 +52,7 @@ const columns = createTable<TestTableData>([
     onRowToggle: () => jest.fn(),
   }),
 ], {
-  rowTestId: (row) => 'row-' + row.numberField.toString(),
+  uniqueRowTag: (row) => 'row-' + row.numberField.toString(),
   ariaLabels: (row) => ['Column', row.stringField],
 });
 
diff --git a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts
index a7e0e966958..ba2d538ba18 100644
--- a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts
@@ -12,8 +12,8 @@ import {
 import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
 import { IxTableCellDirective } from 'app/modules/ix-table/directives/ix-table-cell.directive';
 import { IxTableDetailsRowDirective } from 'app/modules/ix-table/directives/ix-table-details-row.directive';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { DataProvider } from 'app/modules/ix-table/interfaces/data-provider.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
 
 @UntilDestroy()
 @Component({
@@ -61,8 +61,8 @@ export class IxTableBodyComponent<T> implements AfterViewInit {
     });
   }
 
-  getTestAttr(row: T): string {
-    return this.columns[0]?.rowTestId(row) ?? '';
+  getRowTag(row: T): string {
+    return this.columns[0]?.uniqueRowTag(row) ?? '';
   }
 
   getTemplateByColumnIndex(idx: number): TemplateRef<{ $implicit: T }> | undefined {
@@ -79,8 +79,11 @@ export class IxTableBodyComponent<T> implements AfterViewInit {
       && (this.dataProvider?.expandedRow?.[this.detailsRowIdentifier] === row?.[this.detailsRowIdentifier]);
   }
 
-  // TODO: Come up with a proper identifier
-  protected trackByIdentity<X>(item: X): X {
-    return item;
+  protected trackRowByIdentity(item: T): string {
+    return this.getRowTag(item);
+  }
+
+  protected trackColumnByIdentity(column: Column<T, ColumnComponent<T>>): Column<T, ColumnComponent<T>> {
+    return column;
   }
 }
diff --git a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.spec.ts b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.spec.ts
index 62a81a3f7eb..dc9b505f9d9 100644
--- a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.spec.ts
@@ -7,7 +7,7 @@ import { createComponentFactory } from '@ngneat/spectator/jest';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
 import { yesNoColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component';
 import { IxTableColumnsSelectorComponent } from 'app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { CronjobRow } from 'app/pages/system/advanced/cron/cron-list/cronjob-row.interface';
 
@@ -40,7 +40,7 @@ describe('IxTableColumnsSelectorComponent', () => {
       hidden: true,
     }),
   ], {
-    rowTestId: (row: CronjobRow) => 'cronjob-' + row.id.toString(),
+    uniqueRowTag: (row: CronjobRow) => 'cronjob-' + row.id.toString(),
     ariaLabels: (row) => ['Column', row.id.toString()],
   }) as Column<unknown, ColumnComponent<unknown>>[];
 
diff --git a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.ts b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.ts
index 2c6a406c95b..ec0f6b6e8a0 100644
--- a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.ts
@@ -5,7 +5,7 @@ import {
 import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
 import * as _ from 'lodash-es';
 import { IxSimpleChanges } from 'app/interfaces/simple-changes.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @UntilDestroy()
 @Component({
diff --git a/src/app/modules/ix-table/components/ix-table-details-row/ix-table-details-row.component.ts b/src/app/modules/ix-table/components/ix-table-details-row/ix-table-details-row.component.ts
index a092d42edd5..21a69dcd43f 100644
--- a/src/app/modules/ix-table/components/ix-table-details-row/ix-table-details-row.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-details-row/ix-table-details-row.component.ts
@@ -1,7 +1,7 @@
 import {
   Component, ChangeDetectionStrategy, input,
 } from '@angular/core';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-table-details-row',
diff --git a/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-checkbox/ix-header-cell-checkbox.component.ts b/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-checkbox/ix-header-cell-checkbox.component.ts
index ae80943b874..60b603244ee 100644
--- a/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-checkbox/ix-header-cell-checkbox.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-checkbox/ix-header-cell-checkbox.component.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { MatCheckboxChange } from '@angular/material/checkbox';
 import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
 import { map, Observable } from 'rxjs';
-import { ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @UntilDestroy()
 @Component({
diff --git a/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-text/ix-header-cell-text.component.ts b/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-text/ix-header-cell-text.component.ts
index e5c42641386..4dfe2b3d560 100644
--- a/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-text/ix-header-cell-text.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-text/ix-header-cell-text.component.ts
@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-header-cell-text',
diff --git a/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.spec.ts b/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.spec.ts
index a84e05d6c0f..982d4a21c74 100644
--- a/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.spec.ts
+++ b/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.spec.ts
@@ -28,7 +28,7 @@ const columns = createTable<TestTableData>([
     disableSorting: true,
   }),
 ], {
-  rowTestId: (row) => 'row' + row.numberField.toString(),
+  uniqueRowTag: (row) => 'row' + row.numberField.toString(),
   ariaLabels: (row) => ['Column', row.stringField],
 });
 
diff --git a/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.ts b/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.ts
index 15f4023fee6..2dc1b9477d2 100644
--- a/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.ts
+++ b/src/app/modules/ix-table/components/ix-table-head/ix-table-head.component.ts
@@ -3,8 +3,8 @@ import {
 } from '@angular/core';
 import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
 import { SortDirection } from 'app/modules/ix-table/enums/sort-direction.enum';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { DataProvider } from 'app/modules/ix-table/interfaces/data-provider.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
 
 @UntilDestroy()
 @Component({
diff --git a/src/app/modules/ix-table/directives/ix-body-cell.directive.ts b/src/app/modules/ix-table/directives/ix-body-cell.directive.ts
index 17039456cd6..b4654831a25 100644
--- a/src/app/modules/ix-table/directives/ix-body-cell.directive.ts
+++ b/src/app/modules/ix-table/directives/ix-body-cell.directive.ts
@@ -1,19 +1,23 @@
 import {
   AfterViewInit, ChangeDetectorRef,
+  ComponentRef,
   Directive,
   Input,
+  OnChanges,
   ViewContainerRef,
 } from '@angular/core';
 import { IxCellTextComponent } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent, ColumnKeys } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Directive({
   selector: '[ix-body-cell]',
 })
-export class IxTableBodyCellDirective<T> implements AfterViewInit {
+export class IxTableBodyCellDirective<T> implements AfterViewInit, OnChanges {
   @Input() row: T;
   @Input() column: Column<T, ColumnComponent<T>>;
 
+  private componentRef: ComponentRef<ColumnComponent<T>>;
+
   constructor(
     private viewContainer: ViewContainerRef,
     private cdr: ChangeDetectorRef,
@@ -23,19 +27,29 @@ export class IxTableBodyCellDirective<T> implements AfterViewInit {
     this.createComponent();
   }
 
+  ngOnChanges(): void {
+    if (!this.componentRef) {
+      return;
+    }
+    this.setComponentProps();
+  }
+
   createComponent(): void {
     if (!this.column.type) {
       this.column.type = IxCellTextComponent;
     }
     this.viewContainer.clear();
-    const componentRef = this.viewContainer.createComponent(
+    this.componentRef = this.viewContainer.createComponent(
       this.column.type,
     );
 
-    componentRef.instance.setRow(this.row);
-    Object.keys(this.column).forEach((key: keyof ColumnComponent<T>) => {
-      // TODO: Replace never.
-      componentRef.instance[key] = this.column[key] as never;
+    this.setComponentProps();
+  }
+
+  private setComponentProps(): void {
+    this.componentRef.instance.setRow(this.row);
+    Object.keys(this.column).forEach((key: ColumnKeys<T>) => {
+      this.componentRef.instance[key] = this.column[key] as never;
     });
 
     this.cdr.detectChanges();
diff --git a/src/app/modules/ix-table/directives/ix-header-cell.directive.ts b/src/app/modules/ix-table/directives/ix-header-cell.directive.ts
index a500d356b81..f65f2ffba44 100644
--- a/src/app/modules/ix-table/directives/ix-header-cell.directive.ts
+++ b/src/app/modules/ix-table/directives/ix-header-cell.directive.ts
@@ -5,8 +5,8 @@ import {
   ViewContainerRef,
 } from '@angular/core';
 import { IxHeaderCellTextComponent } from 'app/modules/ix-table/components/ix-table-head/head-cells/ix-header-cell-text/ix-header-cell-text.component';
+import { Column, ColumnComponent, ColumnKeys } from 'app/modules/ix-table/interfaces/column-component.class';
 import { DataProvider } from 'app/modules/ix-table/interfaces/data-provider.interface';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
 
 @Directive({
   selector: '[ix-header-cell]',
@@ -31,7 +31,8 @@ export class IxTableHeaderCellDirective<T> implements AfterViewInit {
     );
 
     componentRef.instance.dataProvider = this.dataProvider;
-    Object.keys(this.column).forEach((key: keyof ColumnComponent<T>) => {
+    Object.keys(this.column).forEach((key: ColumnKeys<T>) => {
+      // TODO: replace never
       componentRef.instance[key] = this.column[key] as never;
     });
   }
diff --git a/src/app/modules/ix-table/interfaces/column-component.class.ts b/src/app/modules/ix-table/interfaces/column-component.class.ts
new file mode 100644
index 00000000000..f8982b69478
--- /dev/null
+++ b/src/app/modules/ix-table/interfaces/column-component.class.ts
@@ -0,0 +1,40 @@
+import { signal, WritableSignal } from '@angular/core';
+import { MutableKeys } from 'utility-types';
+import { DataProvider } from 'app/modules/ix-table/interfaces/data-provider.interface';
+
+export abstract class ColumnComponent<T> {
+  propertyName?: keyof T;
+  title?: string;
+  cssClass?: string;
+  uniqueRowTag: (row: T) => string;
+  ariaLabels: (row: T) => string[];
+  sortBy?: (row: T) => string | number;
+  disableSorting?: boolean;
+  dataProvider?: DataProvider<T>;
+  getValue?: (row: T) => unknown;
+  hidden = false;
+
+  get value(): unknown {
+    if (this.getValue) {
+      return this.getValue(this.row());
+    }
+    return this.propertyName ? this.row()[this.propertyName] : '';
+  }
+
+  protected row: WritableSignal<T> = signal(null as T);
+
+  setRow = (row: T): void => {
+    this.row.set(row);
+  };
+
+  getAriaLabel = (row: T): string => {
+    return this.ariaLabels(row)?.join(' ') || (this.title ? this.title : '');
+  };
+}
+
+export type Column<T, C extends ColumnComponent<T>> = {
+  type?: new () => C;
+  headerType?: new () => C;
+} & Partial<C>;
+
+export type ColumnKeys<T> = MutableKeys<ColumnComponent<T>>;
diff --git a/src/app/modules/ix-table/interfaces/table-column.interface.ts b/src/app/modules/ix-table/interfaces/table-column.interface.ts
deleted file mode 100644
index bffb9bf694f..00000000000
--- a/src/app/modules/ix-table/interfaces/table-column.interface.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { DataProvider } from 'app/modules/ix-table/interfaces/data-provider.interface';
-
-export abstract class ColumnComponent<T> {
-  propertyName: keyof T;
-  title: string;
-  cssClass?: string;
-  rowTestId: (row: T) => string;
-  ariaLabels: (row: T) => string[];
-  sortBy?: (row: T) => string | number;
-  disableSorting?: boolean;
-  getValue?: (row: T) => unknown;
-  hidden = false;
-
-  protected get value(): unknown {
-    return this.getValue ? this.getValue(this.row) : this.row[this.propertyName];
-  }
-
-  protected row: T;
-
-  getRow(): T {
-    return this.row;
-  }
-  setRow(row: T): void {
-    this.row = row;
-  }
-  getAriaLabel(row: T): string {
-    return this.ariaLabels(row)?.join(' ') || this.title;
-  }
-  dataProvider?: DataProvider<T>;
-}
-
-export type Column<T, C extends ColumnComponent<T>> = {
-  type?: new () => C;
-  headerType?: new () => C;
-} & Partial<C>;
diff --git a/src/app/modules/ix-table/utils.ts b/src/app/modules/ix-table/utils.ts
index 7c27a23a316..100d7f27282 100644
--- a/src/app/modules/ix-table/utils.ts
+++ b/src/app/modules/ix-table/utils.ts
@@ -1,5 +1,5 @@
 import { get } from 'lodash-es';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { TableFilter } from 'app/modules/ix-table/interfaces/table-filter.interface';
 
 function convertStringToId(inputString: string): string {
@@ -14,13 +14,17 @@ function convertStringToId(inputString: string): string {
 
 export function createTable<T>(
   columns: Column<T, ColumnComponent<T>>[],
-  config: { rowTestId: (row: T) => string; ariaLabels: (row: T) => string[] },
+  config: { uniqueRowTag: (row: T) => string; ariaLabels: (row: T) => string[] },
 ): Column<T, ColumnComponent<T>>[] {
-  return columns.map((column) => ({
-    ...column,
-    rowTestId: (row) => convertStringToId(config.rowTestId(row)),
-    ariaLabels: (row) => config.ariaLabels(row),
-  }));
+  return columns.map((column) => {
+    const uniqueRowTag = (row: T): string => convertStringToId(config.uniqueRowTag(row));
+    const ariaLabels = (row: T): string[] => config.ariaLabels(row);
+    return {
+      ...column,
+      uniqueRowTag,
+      ariaLabels,
+    };
+  });
 }
 
 export function filterTableRows<T>(filter: TableFilter<T>): T[] {
diff --git a/src/app/pages/account/groups/group-list/group-list.component.ts b/src/app/pages/account/groups/group-list/group-list.component.ts
index a7660314f53..83f31bd9dbb 100644
--- a/src/app/pages/account/groups/group-list/group-list.component.ts
+++ b/src/app/pages/account/groups/group-list/group-list.component.ts
@@ -69,7 +69,7 @@ export class GroupListComponent implements OnInit {
         .join(', ') || this.translate.instant('N/A'),
     }),
   ], {
-    rowTestId: (row) => 'group-' + row.group,
+    uniqueRowTag: (row) => 'group-' + row.group,
     ariaLabels: (row) => [row.group, this.translate.instant('Group')],
   });
 
diff --git a/src/app/pages/account/groups/privilege/privilege-list/privilege-list.component.ts b/src/app/pages/account/groups/privilege/privilege-list/privilege-list.component.ts
index 47ad5d51b36..525d2ce25ec 100644
--- a/src/app/pages/account/groups/privilege/privilege-list/privilege-list.component.ts
+++ b/src/app/pages/account/groups/privilege/privilege-list/privilege-list.component.ts
@@ -86,7 +86,7 @@ export class PrivilegeListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'privilege-' + row.name,
+    uniqueRowTag: (row) => 'privilege-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Privilege')],
   });
 
diff --git a/src/app/pages/account/users/user-list/user-list.component.ts b/src/app/pages/account/users/user-list/user-list.component.ts
index 8a64bc508f2..14790974abd 100644
--- a/src/app/pages/account/users/user-list/user-list.component.ts
+++ b/src/app/pages/account/users/user-list/user-list.component.ts
@@ -63,7 +63,7 @@ export class UserListComponent implements OnInit {
         .join(', ') || this.translate.instant('N/A'),
     }),
   ], {
-    rowTestId: (row) => 'user-' + row.username,
+    uniqueRowTag: (row) => 'user-' + row.username,
     ariaLabels: (row) => [row.username, this.translate.instant('User')],
   });
 
diff --git a/src/app/pages/api-keys/components/api-key-list/api-key-list.component.ts b/src/app/pages/api-keys/components/api-key-list/api-key-list.component.ts
index 7aad3d1551e..5ce180992a7 100644
--- a/src/app/pages/api-keys/components/api-key-list/api-key-list.component.ts
+++ b/src/app/pages/api-keys/components/api-key-list/api-key-list.component.ts
@@ -66,7 +66,7 @@ export class ApiKeyListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'api-key-' + row.name + '-' + row.created_at.$date,
+    uniqueRowTag: (row) => 'api-key-' + row.name + '-' + row.created_at.$date,
     ariaLabels: (row) => [row.name, this.translate.instant('Api Key')],
   });
 
diff --git a/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.ts b/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.ts
index b29f7dbe66b..ead3666b43e 100644
--- a/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.ts
+++ b/src/app/pages/apps/components/docker-images/docker-images-list/docker-images-list.component.ts
@@ -92,7 +92,7 @@ export class DockerImagesListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'container-image-' + row.id,
+    uniqueRowTag: (row) => 'container-image-' + row.id,
     ariaLabels: (row) => [row.id, this.translate.instant('Docker Image')],
   });
 
diff --git a/src/app/pages/audit/components/audit/audit.component.ts b/src/app/pages/audit/components/audit/audit.component.ts
index bb1b61968ed..ea5072adf3a 100644
--- a/src/app/pages/audit/components/audit/audit.component.ts
+++ b/src/app/pages/audit/components/audit/audit.component.ts
@@ -97,7 +97,7 @@ export class AuditComponent implements OnInit, OnDestroy {
       getValue: (row) => this.translate.instant(this.getEventDataForLog(row)),
     }),
   ], {
-    rowTestId: (row) => 'audit-' + row.service + '-' + row.username + '-' + row.event,
+    uniqueRowTag: (row) => 'audit-' + row.service + '-' + row.username + '-' + row.event,
     ariaLabels: (row) => [row.service, row.username, row.event, this.translate.instant('Audit Entry')],
   });
 
diff --git a/src/app/pages/credentials/backup-credentials/cloud-credentials-card/cloud-credentials-card.component.ts b/src/app/pages/credentials/backup-credentials/cloud-credentials-card/cloud-credentials-card.component.ts
index f33a9db7eed..b8836c48a22 100644
--- a/src/app/pages/credentials/backup-credentials/cloud-credentials-card/cloud-credentials-card.component.ts
+++ b/src/app/pages/credentials/backup-credentials/cloud-credentials-card/cloud-credentials-card.component.ts
@@ -60,7 +60,7 @@ export class CloudCredentialsCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'cloud-cred-' + row.name,
+    uniqueRowTag: (row) => 'cloud-cred-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Cloud Credential')],
   });
 
diff --git a/src/app/pages/credentials/backup-credentials/ssh-connection-card/ssh-connection-card.component.ts b/src/app/pages/credentials/backup-credentials/ssh-connection-card/ssh-connection-card.component.ts
index a69a9ae900d..a72bd201fb1 100644
--- a/src/app/pages/credentials/backup-credentials/ssh-connection-card/ssh-connection-card.component.ts
+++ b/src/app/pages/credentials/backup-credentials/ssh-connection-card/ssh-connection-card.component.ts
@@ -53,7 +53,7 @@ export class SshConnectionCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'ssh-con-' + row.name,
+    uniqueRowTag: (row) => 'ssh-con-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('SSH Connection')],
   });
 
diff --git a/src/app/pages/credentials/backup-credentials/ssh-keypair-card/ssh-keypair-card.component.ts b/src/app/pages/credentials/backup-credentials/ssh-keypair-card/ssh-keypair-card.component.ts
index 3f244b2110f..d68a8e9a5b7 100644
--- a/src/app/pages/credentials/backup-credentials/ssh-keypair-card/ssh-keypair-card.component.ts
+++ b/src/app/pages/credentials/backup-credentials/ssh-keypair-card/ssh-keypair-card.component.ts
@@ -61,7 +61,7 @@ export class SshKeypairCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'ssh-keypair-' + row.name,
+    uniqueRowTag: (row) => 'ssh-keypair-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('SSH Key Pair')],
   });
 
diff --git a/src/app/pages/credentials/certificates-dash/acme-dns-authenticator-list/acme-dns-authenticator-list.component.ts b/src/app/pages/credentials/certificates-dash/acme-dns-authenticator-list/acme-dns-authenticator-list.component.ts
index 6e3ed645f6f..6a4cd02cb69 100644
--- a/src/app/pages/credentials/certificates-dash/acme-dns-authenticator-list/acme-dns-authenticator-list.component.ts
+++ b/src/app/pages/credentials/certificates-dash/acme-dns-authenticator-list/acme-dns-authenticator-list.component.ts
@@ -57,7 +57,7 @@ export class AcmeDnsAuthenticatorListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'amce-dns-' + row.name,
+    uniqueRowTag: (row) => 'amce-dns-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('ACME DNS Authenticator')],
   });
 
diff --git a/src/app/pages/credentials/certificates-dash/certificate-authority-list/certificate-authority-list.component.ts b/src/app/pages/credentials/certificates-dash/certificate-authority-list/certificate-authority-list.component.ts
index 726faa732b3..95b92deb927 100644
--- a/src/app/pages/credentials/certificates-dash/certificate-authority-list/certificate-authority-list.component.ts
+++ b/src/app/pages/credentials/certificates-dash/certificate-authority-list/certificate-authority-list.component.ts
@@ -102,7 +102,7 @@ export class CertificateAuthorityListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'ca-' + row.name,
+    uniqueRowTag: (row) => 'ca-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Certificate Authority')],
   });
 
diff --git a/src/app/pages/credentials/certificates-dash/certificate-list/certificate-list.component.ts b/src/app/pages/credentials/certificates-dash/certificate-list/certificate-list.component.ts
index 8124a5db923..5d30fa77ef5 100644
--- a/src/app/pages/credentials/certificates-dash/certificate-list/certificate-list.component.ts
+++ b/src/app/pages/credentials/certificates-dash/certificate-list/certificate-list.component.ts
@@ -98,7 +98,7 @@ export class CertificateListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'cert-' + row.name,
+    uniqueRowTag: (row) => 'cert-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Certificate')],
   });
 
diff --git a/src/app/pages/credentials/certificates-dash/csr-list/csr-list.component.ts b/src/app/pages/credentials/certificates-dash/csr-list/csr-list.component.ts
index 656d0dceb7f..8b8178d6511 100644
--- a/src/app/pages/credentials/certificates-dash/csr-list/csr-list.component.ts
+++ b/src/app/pages/credentials/certificates-dash/csr-list/csr-list.component.ts
@@ -87,7 +87,7 @@ export class CertificateSigningRequestsListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'csr-' + row.name,
+    uniqueRowTag: (row) => 'csr-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('CSR')],
   });
 
diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.spec.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.spec.ts
index d7a48c03e79..5a689d302a2 100644
--- a/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.spec.ts
+++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.spec.ts
@@ -4,6 +4,7 @@ import { MatButtonHarness } from '@angular/material/button/testing';
 import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing';
 import { Router } from '@angular/router';
 import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
+import { provideMockStore } from '@ngrx/store/testing';
 import { of } from 'rxjs';
 import { mockAuth } from 'app/core/testing/utils/mock-auth.utils';
 import { mockCall, mockWebSocket } from 'app/core/testing/utils/mock-websocket.utils';
@@ -14,6 +15,7 @@ import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/
 import { IxIconHarness } from 'app/modules/ix-icon/ix-icon.harness';
 import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-table.harness';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
+import { selectJobs } from 'app/modules/jobs/store/job.selectors';
 import { AppLoaderModule } from 'app/modules/loader/app-loader.module';
 import {
   CloudBackupCardComponent,
@@ -68,6 +70,19 @@ describe('CloudBackupCardComponent', () => {
           response: true,
         })),
       }),
+      provideMockStore({
+        selectors: [
+          {
+            selector: selectJobs,
+            value: [{
+              state: JobState.Finished,
+              time_finished: {
+                $date: new Date().getTime() - 50000,
+              },
+            }],
+          },
+        ],
+      }),
     ],
   });
 
diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.ts
index 40cb5581e19..61a390c4467 100644
--- a/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.ts
+++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-card/cloud-backup-card.component.ts
@@ -106,7 +106,7 @@ export class CloudBackupCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'cloud-backup-' + row.description,
+    uniqueRowTag: (row) => 'cloud-backup-' + row.description,
     ariaLabels: (row) => [row.description, this.translate.instant('Cloud Backup')],
   });
 
diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-snapshots/cloud-backup-snapshots.component.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-snapshots/cloud-backup-snapshots.component.ts
index 5d029730201..cd2a492f8ec 100644
--- a/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-snapshots/cloud-backup-snapshots.component.ts
+++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-snapshots/cloud-backup-snapshots.component.ts
@@ -68,7 +68,7 @@ export class CloudBackupSnapshotsComponent implements OnChanges {
       ],
     }),
   ], {
-    rowTestId: (row) => 'cloud-backup-snapshot-' + row.hostname,
+    uniqueRowTag: (row) => 'cloud-backup-snapshot-' + row.hostname,
     ariaLabels: (row) => [row.hostname, this.translate.instant('Cloud Backup Snapshot')],
   });
 
diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.spec.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.spec.ts
index e8e43315384..f69871781f9 100644
--- a/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.spec.ts
+++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.spec.ts
@@ -18,6 +18,7 @@ import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provi
 import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-table.harness';
 import { SortDirection } from 'app/modules/ix-table/enums/sort-direction.enum';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
+import { selectJobs } from 'app/modules/jobs/store/job.selectors';
 import { AppLoaderModule } from 'app/modules/loader/app-loader.module';
 import { PageHeaderModule } from 'app/modules/page-header/page-header.module';
 import { CloudBackupDetailsComponent } from 'app/pages/data-protection/cloud-backup/cloud-backup-details/cloud-backup-details.component';
@@ -92,6 +93,15 @@ describe('CloudBackupListComponent', () => {
             selector: selectAdvancedConfig,
             value: {},
           },
+          {
+            selector: selectJobs,
+            value: [{
+              state: JobState.Finished,
+              time_finished: {
+                $date: new Date().getTime() - 50000,
+              },
+            }],
+          },
         ],
       }),
     ],
diff --git a/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.ts b/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.ts
index 07e221340ac..c957d398e71 100644
--- a/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.ts
+++ b/src/app/pages/data-protection/cloud-backup/cloud-backup-list/cloud-backup-list.component.ts
@@ -104,7 +104,7 @@ export class CloudBackupListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'cloud-backup-' + row.description,
+    uniqueRowTag: (row) => 'cloud-backup-' + row.description,
     ariaLabels: (row) => [row.description, this.translate.instant('Cloud Backup')],
   });
 
diff --git a/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.spec.ts b/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.spec.ts
index c78f8aa6f7d..6b38aa850f3 100644
--- a/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.spec.ts
+++ b/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.spec.ts
@@ -4,6 +4,7 @@ import { MatButtonHarness } from '@angular/material/button/testing';
 import { MatDialog } from '@angular/material/dialog';
 import { Spectator } from '@ngneat/spectator';
 import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
+import { provideMockStore } from '@ngrx/store/testing';
 import { MockModule } from 'ng-mocks';
 import { of } from 'rxjs';
 import { fakeSuccessfulJob } from 'app/core/testing/utils/fake-job.utils';
@@ -15,6 +16,7 @@ import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/
 import { SearchInput1Component } from 'app/modules/forms/search-input1/search-input1.component';
 import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-table.harness';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
+import { selectJob } from 'app/modules/jobs/store/job.selectors';
 import { AppLoaderModule } from 'app/modules/loader/app-loader.module';
 import { PageHeaderModule } from 'app/modules/page-header/page-header.module';
 import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
@@ -118,6 +120,14 @@ describe('CloudSyncListComponent', () => {
         getTaskNextTime: jest.fn(() => new Date(new Date().getTime() + (25 * 60 * 60 * 1000))),
       }),
       mockProvider(SnackbarService),
+      provideMockStore({
+        selectors: [
+          {
+            selector: selectJob(1),
+            value: fakeSuccessfulJob(),
+          },
+        ],
+      }),
     ],
   });
 
diff --git a/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.ts b/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.ts
index 8bacde1e09e..484c2a4c5a8 100644
--- a/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.ts
+++ b/src/app/pages/data-protection/cloudsync/cloudsync-list/cloudsync-list.component.ts
@@ -21,7 +21,7 @@ import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provi
 import { stateButtonColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
 import { yesNoColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { selectJob } from 'app/modules/jobs/store/job.selectors';
 import { scheduleToCrontab } from 'app/modules/scheduler/utils/schedule-to-crontab.utils';
@@ -121,7 +121,7 @@ export class CloudSyncListComponent implements OnInit {
       propertyName: 'enabled',
     }),
   ], {
-    rowTestId: (row) => 'cloudsync-task-' + row.description,
+    uniqueRowTag: (row) => 'cloudsync-task-' + row.description,
     ariaLabels: (row) => [row.description, this.translate.instant('Cloud Sync Task')],
   });
 
diff --git a/src/app/pages/data-protection/cloudsync/cloudsync-task-card/cloudsync-task-card.component.ts b/src/app/pages/data-protection/cloudsync/cloudsync-task-card/cloudsync-task-card.component.ts
index 3e26b88abae..8a286b32f55 100644
--- a/src/app/pages/data-protection/cloudsync/cloudsync-task-card/cloudsync-task-card.component.ts
+++ b/src/app/pages/data-protection/cloudsync/cloudsync-task-card/cloudsync-task-card.component.ts
@@ -77,7 +77,6 @@ export class CloudSyncTaskCardComponent implements OnInit {
     stateButtonColumn({
       title: this.translate.instant('State'),
       getValue: (row) => row.state.state,
-      getJob: (row) => row.job,
       cssClass: 'state-button',
     }),
     actionsColumn({
@@ -123,7 +122,7 @@ export class CloudSyncTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-cloudsync-task-' + row.description,
+    uniqueRowTag: (row) => 'card-cloudsync-task-' + row.description,
     ariaLabels: (row) => [row.description, this.translate.instant('Cloud Sync Task')],
   });
 
diff --git a/src/app/pages/data-protection/replication/replication-list/replication-list.component.spec.ts b/src/app/pages/data-protection/replication/replication-list/replication-list.component.spec.ts
index 5c10f368730..f8b645f62de 100644
--- a/src/app/pages/data-protection/replication/replication-list/replication-list.component.spec.ts
+++ b/src/app/pages/data-protection/replication/replication-list/replication-list.component.spec.ts
@@ -25,7 +25,7 @@ import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/
 import { SearchInput1Component } from 'app/modules/forms/search-input1/search-input1.component';
 import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-table.harness';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
-import { selectJob } from 'app/modules/jobs/store/job.selectors';
+import { selectJobs } from 'app/modules/jobs/store/job.selectors';
 import { AppLoaderModule } from 'app/modules/loader/app-loader.module';
 import { PageHeaderModule } from 'app/modules/page-header/page-header.module';
 import { ReplicationFormComponent } from 'app/pages/data-protection/replication/replication-form/replication-form.component';
@@ -122,8 +122,8 @@ describe('ReplicationListComponent', () => {
       provideMockStore({
         selectors: [
           {
-            selector: selectJob(1),
-            value: {} as Job,
+            selector: selectJobs,
+            value: [{ id: 2, state: JobState.Success } as Job],
           },
           {
             selector: selectSystemConfigState,
diff --git a/src/app/pages/data-protection/replication/replication-list/replication-list.component.ts b/src/app/pages/data-protection/replication/replication-list/replication-list.component.ts
index 851ceea6bbe..d6135d8015a 100644
--- a/src/app/pages/data-protection/replication/replication-list/replication-list.component.ts
+++ b/src/app/pages/data-protection/replication/replication-list/replication-list.component.ts
@@ -23,7 +23,7 @@ import {
 } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component';
 import { yesNoColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-yes-no/ix-cell-yes-no.component';
 import { SortDirection } from 'app/modules/ix-table/enums/sort-direction.enum';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { AppLoaderService } from 'app/modules/loader/app-loader.service';
 import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service';
@@ -113,8 +113,8 @@ export class ReplicationListComponent implements OnInit {
     stateButtonColumn({
       title: this.translate.instant('State'),
       getValue: (row) => row.state.state,
-      getJob: (row) => row.job,
       cssClass: 'state-button',
+      getJob: (row) => row.job,
     }),
     toggleColumn({
       title: this.translate.instant('Enabled'),
@@ -131,7 +131,7 @@ export class ReplicationListComponent implements OnInit {
       },
     }),
   ], {
-    rowTestId: (row) => 'replication-task-' + row.name,
+    uniqueRowTag: (row) => 'replication-task-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Replication Task')],
   });
 
diff --git a/src/app/pages/data-protection/replication/replication-task-card/replication-task-card.component.ts b/src/app/pages/data-protection/replication/replication-task-card/replication-task-card.component.ts
index 039f2da4bef..cc50fe12c39 100644
--- a/src/app/pages/data-protection/replication/replication-task-card/replication-task-card.component.ts
+++ b/src/app/pages/data-protection/replication/replication-task-card/replication-task-card.component.ts
@@ -120,7 +120,7 @@ export class ReplicationTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'replication-task-' + row.name,
+    uniqueRowTag: (row) => 'replication-task-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Replication Task')],
   });
 
diff --git a/src/app/pages/data-protection/rsync-task/rsync-task-card/rsync-task-card.component.ts b/src/app/pages/data-protection/rsync-task/rsync-task-card/rsync-task-card.component.ts
index 289bdb3daa4..2effcd46b39 100644
--- a/src/app/pages/data-protection/rsync-task/rsync-task-card/rsync-task-card.component.ts
+++ b/src/app/pages/data-protection/rsync-task/rsync-task-card/rsync-task-card.component.ts
@@ -104,7 +104,7 @@ export class RsyncTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-rsync-task-' + row.path + '-' + row.remotehost,
+    uniqueRowTag: (row) => 'card-rsync-task-' + row.path + '-' + row.remotehost,
     ariaLabels: (row) => [row.path, row.remotehost, this.translate.instant('Rsync Task')],
   });
 
diff --git a/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.spec.ts b/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.spec.ts
index 7bdd38ca48d..03eb030778b 100644
--- a/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.spec.ts
+++ b/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.spec.ts
@@ -16,6 +16,7 @@ import { SearchInput1Component } from 'app/modules/forms/search-input1/search-in
 import { IxIconHarness } from 'app/modules/ix-icon/ix-icon.harness';
 import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-table.harness';
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
+import { selectJobs } from 'app/modules/jobs/store/job.selectors';
 import { PageHeaderModule } from 'app/modules/page-header/page-header.module';
 import { RsyncTaskFormComponent } from 'app/pages/data-protection/rsync-task/rsync-task-form/rsync-task-form.component';
 import { RsyncTaskListComponent } from 'app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component';
@@ -48,11 +49,12 @@ describe('RsyncTaskListComponent', () => {
       },
       user: 'bob',
       job: {
+        id: 1,
         state: JobState.Running,
       } as Job,
     },
     {
-      id: 1,
+      id: 2,
       enabled: false,
       desc: 'Second task',
       direction: Direction.Push,
@@ -68,6 +70,7 @@ describe('RsyncTaskListComponent', () => {
       },
       user: 'peter',
       job: {
+        id: 2,
         state: JobState.Finished,
       } as Job,
     },
@@ -112,6 +115,19 @@ describe('RsyncTaskListComponent', () => {
             selector: selectPreferences,
             value: {},
           },
+          {
+            selector: selectJobs,
+            value: [
+              {
+                id: 1,
+                state: JobState.Running,
+              },
+              {
+                id: 2,
+                state: JobState.Finished,
+              },
+            ],
+          },
         ],
       }),
     ],
diff --git a/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.ts b/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.ts
index eb0129edf23..8b17d6a33dc 100644
--- a/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.ts
+++ b/src/app/pages/data-protection/rsync-task/rsync-task-list/rsync-task-list.component.ts
@@ -152,7 +152,7 @@ export class RsyncTaskListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'rsync-task-' + row.path + '-' + row.remotehost,
+    uniqueRowTag: (row) => 'rsync-task-' + row.path + '-' + row.remotehost,
     ariaLabels: (row) => [row.path, row.remotehost, this.translate.instant('Rsync Task')],
   });
 
diff --git a/src/app/pages/data-protection/scrub-task/scrub-list/scrub-list.component.ts b/src/app/pages/data-protection/scrub-task/scrub-list/scrub-list.component.ts
index f97bc185dbc..1266872e549 100644
--- a/src/app/pages/data-protection/scrub-task/scrub-list/scrub-list.component.ts
+++ b/src/app/pages/data-protection/scrub-task/scrub-list/scrub-list.component.ts
@@ -92,7 +92,7 @@ export class ScrubListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'scrub-task-' + row.pool + '-' + row.description,
+    uniqueRowTag: (row) => 'scrub-task-' + row.pool + '-' + row.description,
     ariaLabels: (row) => [row.pool_name, row.description, this.translate.instant('Scrub Task')],
   });
 
diff --git a/src/app/pages/data-protection/scrub-task/scrub-task-card/scrub-task-card.component.ts b/src/app/pages/data-protection/scrub-task/scrub-task-card/scrub-task-card.component.ts
index 381b1eb8d14..437f6fa395f 100644
--- a/src/app/pages/data-protection/scrub-task/scrub-task-card/scrub-task-card.component.ts
+++ b/src/app/pages/data-protection/scrub-task/scrub-task-card/scrub-task-card.component.ts
@@ -74,7 +74,7 @@ export class ScrubTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-scrub-task-' + row.pool + '-' + row.description,
+    uniqueRowTag: (row) => 'card-scrub-task-' + row.pool + '-' + row.description,
     ariaLabels: (row) => [row.pool.toString(), row.description, this.translate.instant('Scrub Task')],
   });
 
diff --git a/src/app/pages/data-protection/smart-task/smart-task-card/smart-task-card.component.ts b/src/app/pages/data-protection/smart-task/smart-task-card/smart-task-card.component.ts
index 3f8ab7305c2..874580465ca 100644
--- a/src/app/pages/data-protection/smart-task/smart-task-card/smart-task-card.component.ts
+++ b/src/app/pages/data-protection/smart-task/smart-task-card/smart-task-card.component.ts
@@ -76,7 +76,7 @@ export class SmartTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'smart-task-' + row.type + '-' + row.disks.join(','),
+    uniqueRowTag: (row) => 'smart-task-' + row.type + '-' + row.disks.join(','),
     ariaLabels: (row) => [row.type, row.disks.join(','), this.translate.instant('Smart Task')],
   });
 
diff --git a/src/app/pages/data-protection/smart-task/smart-task-list/smart-task-list.component.ts b/src/app/pages/data-protection/smart-task/smart-task-list/smart-task-list.component.ts
index 82f5e4aed70..d445a54410f 100644
--- a/src/app/pages/data-protection/smart-task/smart-task-list/smart-task-list.component.ts
+++ b/src/app/pages/data-protection/smart-task/smart-task-list/smart-task-list.component.ts
@@ -80,7 +80,7 @@ export class SmartTaskListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'smart-task-' + row.type + '-' + row.disks.join(','),
+    uniqueRowTag: (row) => 'smart-task-' + row.type + '-' + row.disks.join(','),
     ariaLabels: (row) => [row.type, row.disks.join(','), this.translate.instant('Smart Task')],
   });
 
diff --git a/src/app/pages/data-protection/snapshot-task/snapshot-task-card/snapshot-task-card.component.ts b/src/app/pages/data-protection/snapshot-task/snapshot-task-card/snapshot-task-card.component.ts
index a70ffa73832..42de73f8285 100644
--- a/src/app/pages/data-protection/snapshot-task/snapshot-task-card/snapshot-task-card.component.ts
+++ b/src/app/pages/data-protection/snapshot-task/snapshot-task-card/snapshot-task-card.component.ts
@@ -88,7 +88,7 @@ export class SnapshotTaskCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'snapshot-task-' + row.dataset + '-' + row.state.state,
+    uniqueRowTag: (row) => 'snapshot-task-' + row.dataset + '-' + row.state.state,
     ariaLabels: (row) => [row.dataset, this.translate.instant('Snapshot Task')],
   });
 
diff --git a/src/app/pages/data-protection/snapshot-task/snapshot-task-list/snapshot-task-list.component.ts b/src/app/pages/data-protection/snapshot-task/snapshot-task-list/snapshot-task-list.component.ts
index 575067474e7..a7c16d62e75 100644
--- a/src/app/pages/data-protection/snapshot-task/snapshot-task-list/snapshot-task-list.component.ts
+++ b/src/app/pages/data-protection/snapshot-task/snapshot-task-list/snapshot-task-list.component.ts
@@ -15,7 +15,7 @@ import { EmptyService } from 'app/modules/empty/empty.service';
 import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provider/async-data-provider';
 import { stateButtonColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { extractActiveHoursFromCron, scheduleToCrontab } from 'app/modules/scheduler/utils/schedule-to-crontab.utils';
 import { SnapshotTaskFormComponent } from 'app/pages/data-protection/snapshot-task/snapshot-task-form/snapshot-task-form.component';
@@ -124,7 +124,7 @@ export class SnapshotTaskListComponent implements OnInit {
       cssClass: 'state-button',
     }),
   ], {
-    rowTestId: (row) => 'snapshot-task-' + row.dataset + '-' + row.naming_schema,
+    uniqueRowTag: (row) => 'snapshot-task-' + row.dataset + '-' + row.naming_schema,
     ariaLabels: (row) => [row.dataset, this.translate.instant('Snapshot Task')],
   });
 
diff --git a/src/app/pages/data-protection/vmware-snapshot/vmware-snapshot-list/vmware-snapshot-list.component.ts b/src/app/pages/data-protection/vmware-snapshot/vmware-snapshot-list/vmware-snapshot-list.component.ts
index 8da59023221..1341b110da4 100644
--- a/src/app/pages/data-protection/vmware-snapshot/vmware-snapshot-list/vmware-snapshot-list.component.ts
+++ b/src/app/pages/data-protection/vmware-snapshot/vmware-snapshot-list/vmware-snapshot-list.component.ts
@@ -54,7 +54,7 @@ export class VmwareSnapshotListComponent implements OnInit {
       propertyName: 'state',
     }),
   ], {
-    rowTestId: (row) => 'vmware-snapshot-' + row.hostname,
+    uniqueRowTag: (row) => 'vmware-snapshot-' + row.hostname,
     ariaLabels: (row) => [row.hostname, this.translate.instant('VMware Snapshot')],
   });
 
diff --git a/src/app/pages/datasets/components/dataset-quotas/dataset-quotas-list/dataset-quotas-list.component.ts b/src/app/pages/datasets/components/dataset-quotas/dataset-quotas-list/dataset-quotas-list.component.ts
index 4ae88e9568c..b12e07bb3cb 100644
--- a/src/app/pages/datasets/components/dataset-quotas/dataset-quotas-list/dataset-quotas-list.component.ts
+++ b/src/app/pages/datasets/components/dataset-quotas/dataset-quotas-list/dataset-quotas-list.component.ts
@@ -140,7 +140,7 @@ export class DatasetQuotasListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => `${this.helpTextKey}-quota-` + row.name + this.emptyValue + row.obj_quota,
+    uniqueRowTag: (row) => `${this.helpTextKey}-quota-` + row.name + this.emptyValue + row.obj_quota,
     ariaLabels: (row) => [row.name, this.translate.instant('Dataset Quota')],
   });
 
diff --git a/src/app/pages/datasets/modules/snapshots/snapshot-list/snapshot-list.component.ts b/src/app/pages/datasets/modules/snapshots/snapshot-list/snapshot-list.component.ts
index 2d17a2bbf08..243a6e5bd72 100644
--- a/src/app/pages/datasets/modules/snapshots/snapshot-list/snapshot-list.component.ts
+++ b/src/app/pages/datasets/modules/snapshots/snapshot-list/snapshot-list.component.ts
@@ -124,7 +124,7 @@ export class SnapshotListComponent implements OnInit {
       getValue: (row) => row?.properties?.referenced?.parsed,
     }),
   ], {
-    rowTestId: (row) => 'snapshot-' + row.id,
+    uniqueRowTag: (row) => 'snapshot-' + row.id,
     ariaLabels: (row) => [row.name, this.translate.instant('Snapshot')],
   });
 
diff --git a/src/app/pages/directory-service/components/idmap-list/idmap-list.component.ts b/src/app/pages/directory-service/components/idmap-list/idmap-list.component.ts
index fdeb6fa021a..9cfd1886431 100644
--- a/src/app/pages/directory-service/components/idmap-list/idmap-list.component.ts
+++ b/src/app/pages/directory-service/components/idmap-list/idmap-list.component.ts
@@ -109,7 +109,7 @@ export class IdmapListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'idmap-' + row.name,
+    uniqueRowTag: (row) => 'idmap-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Idmap')],
   });
 
diff --git a/src/app/pages/directory-service/components/kerberos-keytabs/kerberos-keytabs-list/kerberos-keytabs-list.component.ts b/src/app/pages/directory-service/components/kerberos-keytabs/kerberos-keytabs-list/kerberos-keytabs-list.component.ts
index bcad3e36f15..fc3908d0882 100644
--- a/src/app/pages/directory-service/components/kerberos-keytabs/kerberos-keytabs-list/kerberos-keytabs-list.component.ts
+++ b/src/app/pages/directory-service/components/kerberos-keytabs/kerberos-keytabs-list/kerberos-keytabs-list.component.ts
@@ -78,7 +78,7 @@ export class KerberosKeytabsListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'kerberos-keytab-' + row.name,
+    uniqueRowTag: (row) => 'kerberos-keytab-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Kerberos Keytab')],
   });
 
diff --git a/src/app/pages/directory-service/components/kerberos-realms/kerberos-realms-list.component.ts b/src/app/pages/directory-service/components/kerberos-realms/kerberos-realms-list.component.ts
index bb3ceaed1e6..5bd70b2bbc9 100644
--- a/src/app/pages/directory-service/components/kerberos-realms/kerberos-realms-list.component.ts
+++ b/src/app/pages/directory-service/components/kerberos-realms/kerberos-realms-list.component.ts
@@ -93,7 +93,7 @@ export class KerberosRealmsListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'kerberos-realm-' + row.realm,
+    uniqueRowTag: (row) => 'kerberos-realm-' + row.realm,
     ariaLabels: (row) => [row.realm, this.translate.instant('Kerberos Realm')],
   });
 
diff --git a/src/app/pages/jobs/jobs-list/jobs-list.component.ts b/src/app/pages/jobs/jobs-list/jobs-list.component.ts
index f2b8e346eae..49a4941f78d 100644
--- a/src/app/pages/jobs/jobs-list/jobs-list.component.ts
+++ b/src/app/pages/jobs/jobs-list/jobs-list.component.ts
@@ -75,7 +75,7 @@ export class JobsListComponent implements OnInit {
       sortBy: (job) => +job.time_finished,
     }),
   ], {
-    rowTestId: (row) => 'job-' + row.id,
+    uniqueRowTag: (row) => 'job-' + row.id,
     ariaLabels: (row) => [row.description, this.translate.instant('Job')],
   });
 
diff --git a/src/app/pages/network/components/interfaces-card/interfaces-card.component.ts b/src/app/pages/network/components/interfaces-card/interfaces-card.component.ts
index aa8794a1039..338adbfcbdb 100644
--- a/src/app/pages/network/components/interfaces-card/interfaces-card.component.ts
+++ b/src/app/pages/network/components/interfaces-card/interfaces-card.component.ts
@@ -26,7 +26,7 @@ import { AppLoaderService } from 'app/modules/loader/app-loader.service';
 import { InterfaceFormComponent } from 'app/pages/network/components/interface-form/interface-form.component';
 import { interfacesCardElements } from 'app/pages/network/components/interfaces-card/interfaces-card.elements';
 import {
-  IpAddressesCellComponent,
+  ipAddressesColumn,
 } from 'app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component';
 import { InterfacesStore } from 'app/pages/network/stores/interfaces.store';
 import { ErrorHandlerService } from 'app/services/error-handler.service';
@@ -65,11 +65,10 @@ export class InterfacesCardComponent implements OnInit, OnChanges {
       title: this.translate.instant('Name'),
       propertyName: 'name',
     }),
-    {
-      type: IpAddressesCellComponent,
+    ipAddressesColumn({
       title: this.translate.instant('IP Addresses'),
       sortBy: (row) => row.aliases.map((alias) => alias.address).join(', '),
-    },
+    }),
     actionsColumn({
       actions: [
         {
@@ -98,7 +97,7 @@ export class InterfacesCardComponent implements OnInit, OnChanges {
       ],
     }),
   ], {
-    rowTestId: (row) => 'interface-' + row.name,
+    uniqueRowTag: (row) => 'interface-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Interface')],
   });
 
diff --git a/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.spec.ts b/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.spec.ts
index 5bf63cc411c..35215cdccab 100644
--- a/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.spec.ts
+++ b/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.spec.ts
@@ -6,9 +6,9 @@ import {
 } from 'app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component';
 
 describe('IpAddressesCellComponent', () => {
-  let spectator: Spectator<IpAddressesCellComponent>;
+  let spectator: Spectator<IpAddressesCellComponent<NetworkInterface>>;
   const createComponent = createComponentFactory({
-    component: IpAddressesCellComponent,
+    component: IpAddressesCellComponent<NetworkInterface>,
   });
 
   beforeEach(() => {
diff --git a/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.ts b/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.ts
index a44294ec34f..c47ab1ebfaf 100644
--- a/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.ts
+++ b/src/app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component.ts
@@ -1,10 +1,11 @@
 import {
-  ChangeDetectionStrategy, Component,
+  ChangeDetectionStrategy, ChangeDetectorRef, Component,
+  inject,
 } from '@angular/core';
 import * as _ from 'lodash-es';
 import { NetworkInterfaceAliasType } from 'app/enums/network-interface.enum';
 import { NetworkInterface, NetworkInterfaceAlias } from 'app/interfaces/network-interface.interface';
-import { ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 
 @Component({
   selector: 'ix-ip-addresses-cell',
@@ -12,12 +13,15 @@ import { ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.in
   styleUrls: ['./ip-addresses-cell.component.scss'],
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class IpAddressesCellComponent extends ColumnComponent<NetworkInterface> {
+export class IpAddressesCellComponent<T> extends ColumnComponent<T> {
   protected addresses: string[] = [];
+  private readonly cdr = inject(ChangeDetectorRef);
 
-  override setRow(row: NetworkInterface): void {
-    this.addresses = this.extractAddresses(row);
-  }
+  override setRow = (row: T): void => {
+    this.row.set(row);
+    this.addresses = this.extractAddresses(row as NetworkInterface);
+    this.cdr.markForCheck();
+  };
 
   extractAddresses(row: NetworkInterface): string[] {
     const addresses = this.aliasesToAddress(row.aliases);
@@ -44,3 +48,9 @@ export class IpAddressesCellComponent extends ColumnComponent<NetworkInterface>
       .map((alias) => `${alias.address}/${alias.netmask}`);
   }
 }
+
+export function ipAddressesColumn<T>(
+  options: Partial<IpAddressesCellComponent<T>>,
+): Column<T, IpAddressesCellComponent<T>> {
+  return { type: IpAddressesCellComponent, ...options };
+}
diff --git a/src/app/pages/network/components/ipmi-card/ipmi-card.component.ts b/src/app/pages/network/components/ipmi-card/ipmi-card.component.ts
index d118cf330ca..df35c377565 100644
--- a/src/app/pages/network/components/ipmi-card/ipmi-card.component.ts
+++ b/src/app/pages/network/components/ipmi-card/ipmi-card.component.ts
@@ -50,7 +50,7 @@ export class IpmiCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'ipmi-' + row.channel + '-' + row.ip_address,
+    uniqueRowTag: (row) => 'ipmi-' + row.channel + '-' + row.ip_address,
     ariaLabels: (row) => [row.ip_address, this.translate.instant('IPMI')],
   });
 
diff --git a/src/app/pages/network/components/static-routes-card/static-routes-card.component.ts b/src/app/pages/network/components/static-routes-card/static-routes-card.component.ts
index 57d644d3fd9..88cd38bc702 100644
--- a/src/app/pages/network/components/static-routes-card/static-routes-card.component.ts
+++ b/src/app/pages/network/components/static-routes-card/static-routes-card.component.ts
@@ -59,7 +59,7 @@ export class StaticRoutesCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'static-route-' + row.destination + '-' + row.gateway,
+    uniqueRowTag: (row) => 'static-route-' + row.destination + '-' + row.gateway,
     ariaLabels: (row) => [row.description, this.translate.instant('Static Route')],
   });
 
diff --git a/src/app/pages/network/tests/checkin.spec.ts b/src/app/pages/network/tests/checkin.spec.ts
index 247189bd81f..c92afb7699d 100644
--- a/src/app/pages/network/tests/checkin.spec.ts
+++ b/src/app/pages/network/tests/checkin.spec.ts
@@ -29,9 +29,6 @@ import { IxTableHarness } from 'app/modules/ix-table/components/ix-table/ix-tabl
 import { IxTableModule } from 'app/modules/ix-table/ix-table.module';
 import { InterfaceFormComponent } from 'app/pages/network/components/interface-form/interface-form.component';
 import { InterfacesCardComponent } from 'app/pages/network/components/interfaces-card/interfaces-card.component';
-import {
-  IpAddressesCellComponent,
-} from 'app/pages/network/components/interfaces-card/ip-addresses-cell/ip-addresses-cell.component';
 import { IpmiCardComponent } from 'app/pages/network/components/ipmi-card/ipmi-card.component';
 import {
   NetworkConfigurationCardComponent,
@@ -70,7 +67,6 @@ describe('NetworkComponent', () => {
         StaticRoutesCardComponent,
         IpmiCardComponent,
         InterfaceStatusIconComponent,
-        IpAddressesCellComponent,
       ),
     ],
     providers: [
diff --git a/src/app/pages/reports-dashboard/components/exporters/reporting-exporters-list/reporting-exporters-list.component.ts b/src/app/pages/reports-dashboard/components/exporters/reporting-exporters-list/reporting-exporters-list.component.ts
index bb467e29f2a..579615718f1 100644
--- a/src/app/pages/reports-dashboard/components/exporters/reporting-exporters-list/reporting-exporters-list.component.ts
+++ b/src/app/pages/reports-dashboard/components/exporters/reporting-exporters-list/reporting-exporters-list.component.ts
@@ -86,7 +86,7 @@ export class ReportingExporterListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'reporting-exporter-' + row.name,
+    uniqueRowTag: (row) => 'reporting-exporter-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Reporting Exporter')],
   });
 
diff --git a/src/app/pages/services/services.component.ts b/src/app/pages/services/services.component.ts
index 1cd6b67cc42..e3f34c4e780 100644
--- a/src/app/pages/services/services.component.ts
+++ b/src/app/pages/services/services.component.ts
@@ -94,7 +94,7 @@ export class ServicesComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'service-' + row.name.replace(/\./g, ''),
+    uniqueRowTag: (row) => 'service-' + row.name.replace(/\./g, ''),
     ariaLabels: (row) => [row.name, this.translate.instant('Service')],
   });
 
diff --git a/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts b/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts
index a13003ec2aa..6a6077e5f6a 100644
--- a/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts
+++ b/src/app/pages/sharing/components/shares-dashboard/iscsi-card/iscsi-card.component.ts
@@ -68,7 +68,7 @@ export class IscsiCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-iscsi-target-' + row.name,
+    uniqueRowTag: (row) => 'card-iscsi-target-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('iSCSI Target')],
   });
 
diff --git a/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.ts b/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.ts
index 6db05087fef..3d636b88f70 100644
--- a/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.ts
+++ b/src/app/pages/sharing/components/shares-dashboard/nfs-card/nfs-card.component.ts
@@ -67,7 +67,7 @@ export class NfsCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-nfs-share-' + row.path + '-' + row.comment,
+    uniqueRowTag: (row) => 'card-nfs-share-' + row.path + '-' + row.comment,
     ariaLabels: (row) => [row.path, this.translate.instant('NFS Share')],
   });
 
diff --git a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts
index 4c3d243e2ba..817752dd389 100644
--- a/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts
+++ b/src/app/pages/sharing/components/shares-dashboard/smb-card/smb-card.component.ts
@@ -101,7 +101,7 @@ export class SmbCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-smb-share-' + row.name,
+    uniqueRowTag: (row) => 'card-smb-share-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('SMB Share')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/associated-target/associated-target-list/associated-target-list.component.ts b/src/app/pages/sharing/iscsi/associated-target/associated-target-list/associated-target-list.component.ts
index 22f3bf1c385..ed72d344835 100644
--- a/src/app/pages/sharing/iscsi/associated-target/associated-target-list/associated-target-list.component.ts
+++ b/src/app/pages/sharing/iscsi/associated-target/associated-target-list/associated-target-list.component.ts
@@ -113,7 +113,7 @@ export class AssociatedTargetListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-associated-target-' + row.target + '-' + row.extent,
+    uniqueRowTag: (row) => 'iscsi-associated-target-' + row.target + '-' + row.extent,
     ariaLabels: (row) => [row.target.toString(), this.translate.instant('ISCSI Associated Target')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/authorized-access/authorized-access-list/authorized-access-list.component.ts b/src/app/pages/sharing/iscsi/authorized-access/authorized-access-list/authorized-access-list.component.ts
index cad511ff6c9..58210686cc1 100644
--- a/src/app/pages/sharing/iscsi/authorized-access/authorized-access-list/authorized-access-list.component.ts
+++ b/src/app/pages/sharing/iscsi/authorized-access/authorized-access-list/authorized-access-list.component.ts
@@ -87,7 +87,7 @@ export class AuthorizedAccessListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-authorized-access-' + row.user + '-' + row.peeruser,
+    uniqueRowTag: (row) => 'iscsi-authorized-access-' + row.user + '-' + row.peeruser,
     ariaLabels: (row) => [row.user, this.translate.instant('Authorized Access')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/extent/extent-list/extent-list.component.ts b/src/app/pages/sharing/iscsi/extent/extent-list/extent-list.component.ts
index f0f113fc752..c7909c152dc 100644
--- a/src/app/pages/sharing/iscsi/extent/extent-list/extent-list.component.ts
+++ b/src/app/pages/sharing/iscsi/extent/extent-list/extent-list.component.ts
@@ -89,7 +89,7 @@ export class ExtentListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-extent-' + row.name,
+    uniqueRowTag: (row) => 'iscsi-extent-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('iSCSI Extent')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/initiator/initiator-list/initiator-list.component.ts b/src/app/pages/sharing/iscsi/initiator/initiator-list/initiator-list.component.ts
index bd917604e1c..3b7209d740b 100644
--- a/src/app/pages/sharing/iscsi/initiator/initiator-list/initiator-list.component.ts
+++ b/src/app/pages/sharing/iscsi/initiator/initiator-list/initiator-list.component.ts
@@ -86,7 +86,7 @@ export class InitiatorListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-initiator-' + row.id,
+    uniqueRowTag: (row) => 'iscsi-initiator-' + row.id,
     ariaLabels: (row) => [row.id.toString(), this.translate.instant('iSCSI Initiator')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/portal/portal-list/portal-list.component.ts b/src/app/pages/sharing/iscsi/portal/portal-list/portal-list.component.ts
index 409eb66e5af..c944c357bb9 100644
--- a/src/app/pages/sharing/iscsi/portal/portal-list/portal-list.component.ts
+++ b/src/app/pages/sharing/iscsi/portal/portal-list/portal-list.component.ts
@@ -102,7 +102,7 @@ export class PortalListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-portal-' + row.comment,
+    uniqueRowTag: (row) => 'iscsi-portal-' + row.comment,
     ariaLabels: (row) => [row.comment, this.translate.instant('Portal')],
   });
 
diff --git a/src/app/pages/sharing/iscsi/target/target-list/target-list.component.ts b/src/app/pages/sharing/iscsi/target/target-list/target-list.component.ts
index 64ece3c904d..49b4b11b02f 100644
--- a/src/app/pages/sharing/iscsi/target/target-list/target-list.component.ts
+++ b/src/app/pages/sharing/iscsi/target/target-list/target-list.component.ts
@@ -94,7 +94,7 @@ export class TargetListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'iscsi-target-' + row.name,
+    uniqueRowTag: (row) => 'iscsi-target-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Target')],
   });
 
diff --git a/src/app/pages/sharing/nfs/nfs-list/nfs-list.component.ts b/src/app/pages/sharing/nfs/nfs-list/nfs-list.component.ts
index 8356a0884cf..d4657b110b7 100644
--- a/src/app/pages/sharing/nfs/nfs-list/nfs-list.component.ts
+++ b/src/app/pages/sharing/nfs/nfs-list/nfs-list.component.ts
@@ -122,7 +122,7 @@ export class NfsListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'nfs-share-' + row.path + '-' + row.comment,
+    uniqueRowTag: (row) => 'nfs-share-' + row.path + '-' + row.comment,
     ariaLabels: (row) => [row.path, this.translate.instant('NFS Share')],
   });
 
diff --git a/src/app/pages/sharing/nfs/nfs-session-list/nfs-session-list.component.ts b/src/app/pages/sharing/nfs/nfs-session-list/nfs-session-list.component.ts
index 3318158a73a..f4b964ddb3d 100644
--- a/src/app/pages/sharing/nfs/nfs-session-list/nfs-session-list.component.ts
+++ b/src/app/pages/sharing/nfs/nfs-session-list/nfs-session-list.component.ts
@@ -10,7 +10,7 @@ import { Nfs3Session, Nfs4Session, NfsType } from 'app/interfaces/nfs-share.inte
 import { EmptyService } from 'app/modules/empty/empty.service';
 import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provider/async-data-provider';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { nfsSessionListElements } from 'app/pages/sharing/nfs/nfs-session-list/nfs-session-list.elements';
 import { WebSocketService } from 'app/services/ws.service';
@@ -39,7 +39,7 @@ export class NfsSessionListComponent implements OnInit {
       propertyName: 'export',
     }),
   ], {
-    rowTestId: (row) => 'nfs3-session-' + row.export + '-' + row.ip,
+    uniqueRowTag: (row) => 'nfs3-session-' + row.export + '-' + row.ip,
     ariaLabels: (row) => [row.ip, this.translate.instant('NFS3 Session')],
   });
 
@@ -91,7 +91,7 @@ export class NfsSessionListComponent implements OnInit {
       hidden: true,
     }),
   ], {
-    rowTestId: (row) => 'nfs4-session-' + row.address + '-' + row.clientid,
+    uniqueRowTag: (row) => 'nfs4-session-' + row.address + '-' + row.clientid,
     ariaLabels: (row) => [row.name, this.translate.instant('NFS4 Session')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-list/smb-list.component.ts b/src/app/pages/sharing/smb/smb-list/smb-list.component.ts
index cfdb2c43a4d..81038f7ee06 100644
--- a/src/app/pages/sharing/smb/smb-list/smb-list.component.ts
+++ b/src/app/pages/sharing/smb/smb-list/smb-list.component.ts
@@ -165,7 +165,7 @@ export class SmbListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'smb-' + row.name,
+    uniqueRowTag: (row) => 'smb-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('SMB Share')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-status/components/smb-lock-list/smb-lock-list.component.ts b/src/app/pages/sharing/smb/smb-status/components/smb-lock-list/smb-lock-list.component.ts
index cc5d637bf97..c1e3de11842 100644
--- a/src/app/pages/sharing/smb/smb-status/components/smb-lock-list/smb-lock-list.component.ts
+++ b/src/app/pages/sharing/smb/smb-status/components/smb-lock-list/smb-lock-list.component.ts
@@ -46,7 +46,7 @@ export class SmbLockListComponent implements OnInit {
       propertyName: 'num_pending_deletes',
     }),
   ], {
-    rowTestId: (row) => 'smb-lock-' + row.filename + '-' + row.fileid.devid + '-' + row.fileid.extid,
+    uniqueRowTag: (row) => 'smb-lock-' + row.filename + '-' + row.fileid.devid + '-' + row.fileid.extid,
     ariaLabels: (row) => [row.filename, this.translate.instant('SMB Lock')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-status/components/smb-notification-list/smb-notification-list.component.ts b/src/app/pages/sharing/smb/smb-status/components/smb-notification-list/smb-notification-list.component.ts
index 679589c5cd6..0ae5a601871 100644
--- a/src/app/pages/sharing/smb/smb-status/components/smb-notification-list/smb-notification-list.component.ts
+++ b/src/app/pages/sharing/smb/smb-status/components/smb-notification-list/smb-notification-list.component.ts
@@ -29,7 +29,7 @@ export class SmbNotificationListComponent implements OnInit {
     textColumn({ title: this.translate.instant('Subdir Filter'), propertyName: 'subdir_filter' }),
     textColumn({ title: this.translate.instant('Creation Time'), propertyName: 'creation_time' }),
   ], {
-    rowTestId: (row) => 'smb-notification-' + row.creation_time + '-' + row.server_id.unique_id,
+    uniqueRowTag: (row) => 'smb-notification-' + row.creation_time + '-' + row.server_id.unique_id,
     ariaLabels: (row) => [row.creation_time, this.translate.instant('SMB Notification')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-status/components/smb-open-files/smb-open-files.component.ts b/src/app/pages/sharing/smb/smb-status/components/smb-open-files/smb-open-files.component.ts
index 08affa20e08..362f15449a8 100644
--- a/src/app/pages/sharing/smb/smb-status/components/smb-open-files/smb-open-files.component.ts
+++ b/src/app/pages/sharing/smb/smb-status/components/smb-open-files/smb-open-files.component.ts
@@ -41,7 +41,7 @@ export class SmbOpenFilesComponent implements OnChanges {
     }),
     textColumn({ title: this.translate.instant('Opened at'), propertyName: 'opened_at' }),
   ], {
-    rowTestId: (row) => 'smb-open-file-' + row.username + '-' + row.uid,
+    uniqueRowTag: (row) => 'smb-open-file-' + row.username + '-' + row.uid,
     ariaLabels: (row) => [row.username, this.translate.instant('SMB Open File')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-status/components/smb-session-list/smb-session-list.component.ts b/src/app/pages/sharing/smb/smb-status/components/smb-session-list/smb-session-list.component.ts
index dec78b3b9c7..74df17c20ba 100644
--- a/src/app/pages/sharing/smb/smb-status/components/smb-session-list/smb-session-list.component.ts
+++ b/src/app/pages/sharing/smb/smb-status/components/smb-session-list/smb-session-list.component.ts
@@ -43,7 +43,7 @@ export class SmbSessionListComponent implements OnInit {
       getValue: (row) => row.signing.cipher,
     }),
   ], {
-    rowTestId: (row) => 'smb-session-' + row.session_id,
+    uniqueRowTag: (row) => 'smb-session-' + row.session_id,
     ariaLabels: (row) => [row.hostname, this.translate.instant('SMB Session')],
   });
 
diff --git a/src/app/pages/sharing/smb/smb-status/components/smb-share-list/smb-share-list.component.ts b/src/app/pages/sharing/smb/smb-status/components/smb-share-list/smb-share-list.component.ts
index 99f9fedeffd..7bc81edf707 100644
--- a/src/app/pages/sharing/smb/smb-status/components/smb-share-list/smb-share-list.component.ts
+++ b/src/app/pages/sharing/smb/smb-status/components/smb-share-list/smb-share-list.component.ts
@@ -39,7 +39,7 @@ export class SmbShareListComponent implements OnInit {
       getValue: (row) => row.signing.cipher,
     }),
   ], {
-    rowTestId: (row) => 'smb-share-' + row.server_id.unique_id + '-' + row.machine,
+    uniqueRowTag: (row) => 'smb-share-' + row.server_id.unique_id + '-' + row.machine,
     ariaLabels: (row) => [row.machine, this.translate.instant('SMB Share')],
   });
 
diff --git a/src/app/pages/storage/modules/disks/components/disk-list/disk-list.component.ts b/src/app/pages/storage/modules/disks/components/disk-list/disk-list.component.ts
index 82bee551760..aa3a521b2fd 100644
--- a/src/app/pages/storage/modules/disks/components/disk-list/disk-list.component.ts
+++ b/src/app/pages/storage/modules/disks/components/disk-list/disk-list.component.ts
@@ -19,7 +19,7 @@ import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/
 import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provider/async-data-provider';
 import { checkboxColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-checkbox/ix-cell-checkbox.component';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { DiskBulkEditComponent } from 'app/pages/storage/modules/disks/components/disk-bulk-edit/disk-bulk-edit.component';
 import { DiskFormComponent } from 'app/pages/storage/modules/disks/components/disk-form/disk-form.component';
@@ -133,7 +133,7 @@ export class DiskListComponent implements OnInit {
       hidden: true,
     }),
   ], {
-    rowTestId: (row) => `disk-${row.name}`,
+    uniqueRowTag: (row) => `disk-${row.name}`,
     ariaLabels: (row) => [row.name, this.translate.instant('Disk')],
   });
 
diff --git a/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.spec.ts b/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.spec.ts
index 5841035b72e..a1a6de95200 100644
--- a/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.spec.ts
+++ b/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.spec.ts
@@ -43,7 +43,7 @@ describe('SmartTestResultListComponent', () => {
       remaining: null,
     },
     {
-      num: 1,
+      num: 3,
       description: 'Background short',
       status_verbose: 'Completed',
       segment_number: null,
@@ -75,7 +75,7 @@ describe('SmartTestResultListComponent', () => {
       remaining: null,
     },
     {
-      num: 1,
+      num: 3,
       description: 'Background short',
       status_verbose: 'Completed',
       segment_number: null,
diff --git a/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.ts b/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.ts
index dd811393b92..c66ce98f64a 100644
--- a/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.ts
+++ b/src/app/pages/storage/modules/disks/components/smart-test-result-list/smart-test-result-list.component.ts
@@ -16,7 +16,7 @@ import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provi
 import { stateButtonColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-state-button/ix-cell-state-button.component';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
 import { SortDirection } from 'app/modules/ix-table/enums/sort-direction.enum';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { WebSocketService } from 'app/services/ws.service';
 
@@ -70,7 +70,7 @@ export class SmartTestResultListComponent implements OnInit {
       },
     }),
   ], {
-    rowTestId: (row) => `smart-test-result-${row.disk}-${row.num}`,
+    uniqueRowTag: (row) => `smart-test-result-${row.disk}-${row.num}`,
     ariaLabels: (row) => [row.disk, this.translate.instant('Smart Test Result')],
   });
 
diff --git a/src/app/pages/system/advanced/access/access-card/access-card.component.ts b/src/app/pages/system/advanced/access/access-card/access-card.component.ts
index 8df4b0aebdc..396504f7ccd 100644
--- a/src/app/pages/system/advanced/access/access-card/access-card.component.ts
+++ b/src/app/pages/system/advanced/access/access-card/access-card.component.ts
@@ -88,7 +88,7 @@ export class AccessCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'session-' + this.getUsername(row) + '-' + row.origin,
+    uniqueRowTag: (row) => 'session-' + this.getUsername(row) + '-' + row.origin,
     ariaLabels: (row) => [this.getUsername(row), this.translate.instant('Session')],
   });
 
diff --git a/src/app/pages/system/advanced/allowed-addresses/allowed-addresses-card/allowed-addresses-card.component.ts b/src/app/pages/system/advanced/allowed-addresses/allowed-addresses-card/allowed-addresses-card.component.ts
index 87006ec1ab5..3947d7998d7 100644
--- a/src/app/pages/system/advanced/allowed-addresses/allowed-addresses-card/allowed-addresses-card.component.ts
+++ b/src/app/pages/system/advanced/allowed-addresses/allowed-addresses-card/allowed-addresses-card.component.ts
@@ -56,7 +56,7 @@ export class AllowedAddressesCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'allowed-address-' + row.address,
+    uniqueRowTag: (row) => 'allowed-address-' + row.address,
     ariaLabels: (row) => [row.address, this.translate.instant('Allowed Address')],
   });
 
diff --git a/src/app/pages/system/advanced/cron/cron-card/cron-card.component.ts b/src/app/pages/system/advanced/cron/cron-card/cron-card.component.ts
index 1e49e7404fc..e04f1a4eef2 100644
--- a/src/app/pages/system/advanced/cron/cron-card/cron-card.component.ts
+++ b/src/app/pages/system/advanced/cron/cron-card/cron-card.component.ts
@@ -94,7 +94,7 @@ export class CronCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-cron-' + row.command + '-' + row.user,
+    uniqueRowTag: (row) => 'card-cron-' + row.command + '-' + row.user,
     ariaLabels: (row) => [row.command, this.translate.instant('Cron Job')],
   });
 
diff --git a/src/app/pages/system/advanced/cron/cron-list/cron-list.component.ts b/src/app/pages/system/advanced/cron/cron-list/cron-list.component.ts
index dd9fc335530..b485b4c1526 100644
--- a/src/app/pages/system/advanced/cron/cron-list/cron-list.component.ts
+++ b/src/app/pages/system/advanced/cron/cron-list/cron-list.component.ts
@@ -13,7 +13,7 @@ import { DialogService } from 'app/modules/dialog/dialog.service';
 import { EmptyService } from 'app/modules/empty/empty.service';
 import { AsyncDataProvider } from 'app/modules/ix-table/classes/async-data-provider/async-data-provider';
 import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-text/ix-cell-text.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { scheduleToCrontab } from 'app/modules/scheduler/utils/schedule-to-crontab.utils';
 import { CronDeleteDialogComponent } from 'app/pages/system/advanced/cron/cron-delete-dialog/cron-delete-dialog.component';
@@ -87,7 +87,7 @@ export class CronListComponent implements OnInit {
       hidden: true,
     }),
   ], {
-    rowTestId: (row) => 'cron-' + row.command + '-' + row.description,
+    uniqueRowTag: (row) => 'cron-' + row.command + '-' + row.description,
     ariaLabels: (row) => [row.command, this.translate.instant('Cron Job')],
   });
 
diff --git a/src/app/pages/system/advanced/init-shutdown/init-shutdown-card/init-shutdown-card.component.ts b/src/app/pages/system/advanced/init-shutdown/init-shutdown-card/init-shutdown-card.component.ts
index 19a2f397a87..1600fe52b3e 100644
--- a/src/app/pages/system/advanced/init-shutdown/init-shutdown-card/init-shutdown-card.component.ts
+++ b/src/app/pages/system/advanced/init-shutdown/init-shutdown-card/init-shutdown-card.component.ts
@@ -78,7 +78,7 @@ export class InitShutdownCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'card-init-shutdown-' + row.command + '-' + row.when,
+    uniqueRowTag: (row) => 'card-init-shutdown-' + row.command + '-' + row.when,
     ariaLabels: (row) => [row.command, this.translate.instant('Init/Shutdown Script')],
   });
 
diff --git a/src/app/pages/system/advanced/init-shutdown/init-shutdown-list/init-shutdown-list.component.ts b/src/app/pages/system/advanced/init-shutdown/init-shutdown-list/init-shutdown-list.component.ts
index 2fc74537715..1d2254ff86e 100644
--- a/src/app/pages/system/advanced/init-shutdown/init-shutdown-list/init-shutdown-list.component.ts
+++ b/src/app/pages/system/advanced/init-shutdown/init-shutdown-list/init-shutdown-list.component.ts
@@ -85,7 +85,7 @@ export class InitShutdownListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'init-shutdown-' + row.command + '-' + row.type,
+    uniqueRowTag: (row) => 'init-shutdown-' + row.command + '-' + row.type,
     ariaLabels: (row) => [row.command, this.translate.instant('Init/Shutdown Script')],
   });
 
diff --git a/src/app/pages/system/advanced/sysctl/sysctl-card/sysctl-card.component.ts b/src/app/pages/system/advanced/sysctl/sysctl-card/sysctl-card.component.ts
index b9958023506..8b5d32b3c62 100644
--- a/src/app/pages/system/advanced/sysctl/sysctl-card/sysctl-card.component.ts
+++ b/src/app/pages/system/advanced/sysctl/sysctl-card/sysctl-card.component.ts
@@ -67,7 +67,7 @@ export class SysctlCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'sysctl-' + row.var + '-' + row.value,
+    uniqueRowTag: (row) => 'sysctl-' + row.var + '-' + row.value,
     ariaLabels: (row) => [row.var, this.translate.instant('Sysctl')],
   });
 
diff --git a/src/app/pages/system/advanced/sysctl/tunable-list/tunable-list.component.ts b/src/app/pages/system/advanced/sysctl/tunable-list/tunable-list.component.ts
index 00de6e345b0..f859441ed2a 100644
--- a/src/app/pages/system/advanced/sysctl/tunable-list/tunable-list.component.ts
+++ b/src/app/pages/system/advanced/sysctl/tunable-list/tunable-list.component.ts
@@ -75,7 +75,7 @@ export class TunableListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'tunable-' + row.var + '-' + row.value,
+    uniqueRowTag: (row) => 'tunable-' + row.var + '-' + row.value,
     ariaLabels: (row) => [row.var, this.translate.instant('Tunable')],
   });
 
diff --git a/src/app/pages/system/alert-service/alert-service-list/alert-service-list.component.ts b/src/app/pages/system/alert-service/alert-service-list/alert-service-list.component.ts
index 4aad635f339..0f356266a20 100644
--- a/src/app/pages/system/alert-service/alert-service-list/alert-service-list.component.ts
+++ b/src/app/pages/system/alert-service/alert-service-list/alert-service-list.component.ts
@@ -75,7 +75,7 @@ export class AlertServiceListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => `disk-${row.name}`,
+    uniqueRowTag: (row) => `disk-${row.name}`,
     ariaLabels: (row) => [row.name, this.translate.instant('Disk')],
   });
 
diff --git a/src/app/pages/system/bootenv/bootenv-list/bootenv-list.component.ts b/src/app/pages/system/bootenv/bootenv-list/bootenv-list.component.ts
index 7df85938ea6..1b19cf43cd9 100644
--- a/src/app/pages/system/bootenv/bootenv-list/bootenv-list.component.ts
+++ b/src/app/pages/system/bootenv/bootenv-list/bootenv-list.component.ts
@@ -156,7 +156,7 @@ export class BootEnvironmentListComponent implements OnInit {
       cssClass: 'actions-column',
     }),
   ], {
-    rowTestId: (row) => `bootenv-${row.name}`,
+    uniqueRowTag: (row) => `bootenv-${row.name}`,
     ariaLabels: (row) => [row.name, this.translate.instant('Boot Environment')],
   });
 
diff --git a/src/app/pages/system/enclosure/components/jbof-list/jbof-list.component.ts b/src/app/pages/system/enclosure/components/jbof-list/jbof-list.component.ts
index 9b5321e1749..05d4eea33e8 100644
--- a/src/app/pages/system/enclosure/components/jbof-list/jbof-list.component.ts
+++ b/src/app/pages/system/enclosure/components/jbof-list/jbof-list.component.ts
@@ -66,7 +66,7 @@ export class JbofListComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'jbof-' + row.mgmt_username,
+    uniqueRowTag: (row) => 'jbof-' + row.mgmt_username,
     ariaLabels: (row) => [row.mgmt_username, this.translate.instant('JBOF')],
   });
 
diff --git a/src/app/pages/system/enclosure/components/pages/elements-page/elements-page.component.ts b/src/app/pages/system/enclosure/components/pages/elements-page/elements-page.component.ts
index 6da96ae8df6..402226abd3b 100644
--- a/src/app/pages/system/enclosure/components/pages/elements-page/elements-page.component.ts
+++ b/src/app/pages/system/enclosure/components/pages/elements-page/elements-page.component.ts
@@ -61,7 +61,7 @@ export class ElementsPageComponent {
       }),
     ],
     {
-      rowTestId: (element) => element.descriptor,
+      uniqueRowTag: (element) => element.descriptor,
       ariaLabels: (row) => [row.descriptor, this.translate.instant('Element')],
     },
   );
diff --git a/src/app/pages/system/general-settings/ntp-server/ntp-server-card/ntp-server-card.component.ts b/src/app/pages/system/general-settings/ntp-server/ntp-server-card/ntp-server-card.component.ts
index 2c1f0ec11d8..5d1d68bbc97 100644
--- a/src/app/pages/system/general-settings/ntp-server/ntp-server-card/ntp-server-card.component.ts
+++ b/src/app/pages/system/general-settings/ntp-server/ntp-server-card/ntp-server-card.component.ts
@@ -73,7 +73,7 @@ export class NtpServerCardComponent implements OnInit {
       ],
     }),
   ], {
-    rowTestId: (row) => 'ntp-server-' + row.address + '-' + row.minpoll + '-' + row.maxpoll,
+    uniqueRowTag: (row) => 'ntp-server-' + row.address + '-' + row.minpoll + '-' + row.maxpoll,
     ariaLabels: (row) => [row.address, this.translate.instant('NTP Server')],
   });
 
diff --git a/src/app/pages/vm/devices/device-list/device-list/device-list.component.ts b/src/app/pages/vm/devices/device-list/device-list/device-list.component.ts
index 0b5c24003ef..e4076421f10 100644
--- a/src/app/pages/vm/devices/device-list/device-list/device-list.component.ts
+++ b/src/app/pages/vm/devices/device-list/device-list/device-list.component.ts
@@ -55,7 +55,7 @@ export class DeviceListComponent implements OnInit {
     }),
     actionsColumn({}),
   ], {
-    rowTestId: (row) => 'vm-device-' + row.dtype + '-' + row.order,
+    uniqueRowTag: (row) => 'vm-device-' + row.dtype + '-' + row.order,
     ariaLabels: (row) => [row.dtype, this.translate.instant('Device')],
   });
 
diff --git a/src/app/pages/vm/vm-list/vm-list.component.ts b/src/app/pages/vm/vm-list/vm-list.component.ts
index dbdaae0f917..ae48e2efb2f 100644
--- a/src/app/pages/vm/vm-list/vm-list.component.ts
+++ b/src/app/pages/vm/vm-list/vm-list.component.ts
@@ -19,7 +19,7 @@ import { textColumn } from 'app/modules/ix-table/components/ix-table-body/cells/
 import {
   toggleColumn,
 } from 'app/modules/ix-table/components/ix-table-body/cells/ix-cell-toggle/ix-cell-toggle.component';
-import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/table-column.interface';
+import { Column, ColumnComponent } from 'app/modules/ix-table/interfaces/column-component.class';
 import { createTable } from 'app/modules/ix-table/utils';
 import { FileSizePipe } from 'app/modules/pipes/file-size/file-size.pipe';
 import { vmListElements } from 'app/pages/vm/vm-list/vm-list.elements';
@@ -121,7 +121,7 @@ export class VmListComponent implements OnInit {
       getValue: (row) => `${row.shutdown_timeout} seconds`,
     }),
   ], {
-    rowTestId: (row) => 'virtual-machine-' + row.name,
+    uniqueRowTag: (row) => 'virtual-machine-' + row.name,
     ariaLabels: (row) => [row.name, this.translate.instant('Virtual Machine')],
   });
 
diff --git a/tsconfig.strictNullChecks.json b/tsconfig.strictNullChecks.json
index 8ce81127317..07732aac95a 100644
--- a/tsconfig.strictNullChecks.json
+++ b/tsconfig.strictNullChecks.json
@@ -500,7 +500,7 @@
     "./src/app/modules/ix-table/directives/ix-table-details-row.directive.ts",
     "./src/app/modules/ix-table/enums/sort-direction.enum.ts",
     "./src/app/modules/ix-table/interfaces/data-provider.interface.ts",
-    "./src/app/modules/ix-table/interfaces/table-column.interface.ts",
+    "./src/app/modules/ix-table/interfaces/column-component.class.ts",
     "./src/app/modules/ix-table/interfaces/table-pagination.interface.ts",
     "./src/app/modules/ix-table/interfaces/table-sort.interface.ts",
     "./src/app/modules/ix-table/utils.ts",