trusted-certificates/list/trusted-certificate-list.component.ts
@if ((hasReadRole$ | async) || (hasAdminRole$ | async)) {
  <div>
    <c8y-title>{{ 'Trusted certificates' | translate }}</c8y-title>

    <c8y-breadcrumb>
      <c8y-breadcrumb-item
        icon="c8y-management"
        label="{{ 'Management' | translate }}"
      ></c8y-breadcrumb-item>
      <c8y-breadcrumb-item
        icon="certificate"
        label="{{ 'Trusted certificates' | translate }}"
      ></c8y-breadcrumb-item>
    </c8y-breadcrumb>

    @if (certificateAuthorityFeatureEnabled) {
      <c8y-action-bar-item
        [placement]="'right'"
        [priority]="1000"
      >
        <button
          class="btn btn-link"
          title="{{ 'Generate CA certificate' | translate }}"
          type="button"
          data-cy="trusted-certificate-list--generate-ca-certificate"
          [disabled]="!(hasAdminRole$ | async) || certificateAuthorityExists"
          (click)="addCACertificate()"
        >
          <i c8yIcon="contract"></i>
          {{ 'Generate CA certificate' | translate }}
        </button>
        <button
          class="btn-help btn-help--sm"
          [attr.aria-label]="'Help' | translate"
          popover="{{
            'The Certificate Authority (CA) certificate is used to sign device certificates for secure authentication. Only one CA certificate can be generated for the tenant.'
              | translate
          }}"
          placement="bottom"
          triggers="focus"
          type="button"
        ></button>
      </c8y-action-bar-item>
    }

    <c8y-action-bar-item
      [placement]="'right'"
      [priority]="900"
    >
      <button
        class="btn btn-link"
        title="{{ 'Add trusted certificate' | translate }}"
        type="button"
        data-cy="trusted-certificate-list--add-trusted-certificate"
        (click)="addTrustedCertificate()"
        [disabled]="!(hasAdminRole$ | async)"
      >
        <i c8yIcon="plus-circle"></i>
        {{ 'Add trusted certificate' | translate }}
      </button>
    </c8y-action-bar-item>

    <c8y-action-bar-item
      [placement]="'right'"
      [priority]="800"
    >
      <button
        class="btn btn-link"
        title="{{ 'Reload' | translate }}"
        type="button"
        (click)="loadTrustedCertificates()"
      >
        <i
          c8yIcon="refresh"
          [ngClass]="{ 'icon-spin': reloading | async }"
        ></i>
        {{ 'Reload' | translate }}
      </button>
    </c8y-action-bar-item>

    <c8y-help
      src="/docs/device-certificate-authentication/managing-trusted-certificates/#managing-trusted-certificates"
    ></c8y-help>

    @if ((trustedCertificates | async)?.data.length === 0) {
      <c8y-ui-empty-state
        [icon]="'certificate'"
        [title]="'No trusted certificates to display.' | translate"
        [subtitle]="'Add your first certificate by clicking below.' | translate"
      >
        <button
          class="btn btn-primary"
          title="{{ 'Add trusted certificate' | translate }}"
          type="button"
          (click)="addTrustedCertificate()"
        >
          {{ 'Add trusted certificate' | translate }}
        </button>
      </c8y-ui-empty-state>
    }

    <c8y-list-group class="m-b-24">
      @if ((trustedCertificates | async)?.data.length > 0) {
        <div class="page-sticky-header hidden-xs c8y-list__item c8y-list__item--empty-actions">
          <div class="c8y-list__item__block">
            <div class="c8y-list__item__icon">
              <i
                class="invisible"
                c8yIcon="certificate"
              ></i>
            </div>
            <div class="c8y-list__item__body">
              <div class="content-flex-60">
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title=" {{ 'Certificate' | translate }} "
                  >
                    {{ 'Certificate' | translate }}
                  </span>
                </div>
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title="{{ 'Status' | translate }}"
                  >
                    {{ 'Status' | translate }}
                  </span>
                </div>
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title="{{ 'Algorithm' | translate }}"
                  >
                    {{ 'Algorithm' | translate }}
                  </span>
                </div>
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title="{{ 'Expiration date' | translate }}"
                  >
                    {{ 'Expiration date' | translate }}
                  </span>
                </div>
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title=" {{ 'Auto registration' | translate }}"
                  >
                    {{ 'Auto registration' | translate }}
                  </span>
                </div>
                <div class="col-2">
                  <span
                    class="text-truncate"
                    title=" {{ 'Proof of possession' | translate }}"
                  >
                    {{ 'Proof of possession' | translate }}
                  </span>
                </div>
              </div>
            </div>
            <div class="c8y-list__item__actions"></div>
          </div>
        </div>
      }

      <c8y-li
        *c8yFor="
          let trustedCertificate of trustedCertificates | async;
          let i = index;
          pipe: sortByExpirationDateAsc;
          loadMore: 'none'
        "
        #listItem
        data-cy="c8y-trusted-certificates-list--item-block"
      >
        <c8y-li-icon>
          @if (!trustedCertificate.tenantCertificateAuthority) {
            <button
              class="btn-clean"
              attr.aria-label="{{ 'Certificate' | translate }}"
              tooltip="{{ 'Certificate' | translate }}"
              type="button"
              [delay]="500"
            >
              <i c8yIcon="certificate"></i>
            </button>
          }
          @if (trustedCertificate.tenantCertificateAuthority) {
            <button
              class="btn-clean"
              attr.aria-label="{{ 'CA certificate' | translate }}"
              tooltip="{{ 'CA certificate' | translate }}"
              type="button"
              [delay]="500"
            >
              <i c8yIcon="contract"></i>
            </button>
          }
        </c8y-li-icon>

        <c8y-li-body class="content-flex-60">
          <div class="col-2">
            <button
              class="btn-clean text-truncate"
              title="{{ trustedCertificate.name }}"
              type="button"
              (click)="listItem.toggleCollapsed()"
            >
              {{ trustedCertificate.name }}
            </button>
          </div>
          <div class="col-2">
            <div class="visible-xs p-8"></div>
            <button
              class="btn c8y-btn-checkbox--inline"
              name="certificateStatus"
              type="button"
              [(ngModel)]="trustedCertificate.status"
              btnCheckbox
              btnCheckboxTrue="ENABLED"
              btnCheckboxFalse="DISABLED"
              (ngModelChange)="updateCertificate(trustedCertificate, { status: $event })"
              [disabled]="!(hasAdminRole$ | async)"
              [ngClass]="{
                disabled: !(hasAdminRole$ | async)
              }"
            >
              <small
                title="{{ 'Disabled`trusted certificate status`' | translate }}"
                [hidden]="trustedCertificate.status !== 'DISABLED'"
              >
                {{ 'Disabled`trusted certificate status`' | translate }}
              </small>
              <small
                title="{{ 'Enabled`trusted certificate status`' | translate }}"
                [hidden]="trustedCertificate.status !== 'ENABLED'"
              >
                {{ 'Enabled`trusted certificate status`' | translate }}
              </small>
            </button>
            <div class="visible-xs p-8"></div>
          </div>
          <div class="col-2">
            <div
              class="text-truncate"
              title="{{ 'Algorithm' | translate }}: {{ trustedCertificate.algorithmName }}"
            >
              <span
                class="text-label-small m-t-8 m-r-8 visible-xs-inline"
                translate
              >
                Algorithm
              </span>
              {{ trustedCertificate.algorithmName }}
            </div>
          </div>

          <div class="col-2">
            <div
              class="text-truncate"
              title="{{ 'Expiration date' | translate }}: {{
                trustedCertificate.notAfter | c8yDate
              }}"
            >
              <span
                class="text-label-small m-t-8 m-r-8 visible-xs-inline"
                translate
              >
                Expiration date
              </span>
              @let expirationHighlight = getExpirationHighlight(trustedCertificate);
              <span>
                <small
                  [attr.title]="expirationHighlight?.tooltipText"
                  [ngClass]="expirationHighlight?.textClass"
                >
                  @if (expirationHighlight?.expired) {
                    <del>
                      <ng-container *ngTemplateOutlet="expirationDateTpl"></ng-container>
                    </del>
                  } @else {
                    <ng-container *ngTemplateOutlet="expirationDateTpl"></ng-container>
                  }
                  <ng-template #expirationDateTpl>
                    @if (expirationHighlight?.icon) {
                      <i
                        class="m-r-4"
                        c8yIcon="{{ expirationHighlight?.icon }}"
                      ></i>
                    }
                    <span>{{ trustedCertificate.notAfter | c8yDate }}</span>
                  </ng-template>
                </small>
                @if (expirationHighlight?.renewable) {
                  <button
                    class="btn btn-link btn-icon"
                    title="{{ 'Renew certificate' | translate }}"
                    type="button"
                    (click)="renewCertificate()"
                  >
                    <i c8yIcon="refresh"></i>
                  </button>
                }
              </span>
            </div>
          </div>
          <div class="col-2">
            <div class="text-truncate">
              <span class="text-label-small m-t-8 m-r-4 visible-xs-inline">
                {{ 'Auto registration' | translate }}
              </span>
              @if (trustedCertificate.autoRegistrationEnabled) {
                <span
                  title="{{ 'Auto registration' | translate }}:  {{
                    'Enabled`auto registration`' | translate
                  }}"
                >
                  {{ 'Enabled`auto registration`' | translate }}
                </span>
              }
              @if (!trustedCertificate.autoRegistrationEnabled) {
                <span
                  title="{{ 'Auto registration' | translate }}:  {{
                    'Disabled`auto registration`' | translate
                  }}"
                >
                  {{ 'Disabled`auto registration`' | translate }}
                </span>
              }
              <button
                class="btn-help btn-help--sm"
                [attr.aria-label]="'Help' | translate"
                popover="{{ AUTO_REGISTRATION_POPOVER | translate }}"
                placement="right"
                triggers="focus"
                container="body"
                type="button"
              ></button>
            </div>
          </div>
          <div class="col-2 d-flex">
            <div class="text-truncate">
              <span class="text-label-small m-t-8 m-r-4 visible-xs-inline">
                {{ 'Proof of possession' | translate }}
              </span>
              @if (trustedCertificate.tenantCertificateAuthority) {
                <span title="{{ 'Proof of possession' | translate }}: {{ 'N/A' | translate }}">
                  {{ 'N/A' | translate }}
                </span>
              }
              @if (!trustedCertificate.tenantCertificateAuthority) {
                <span>
                  @if (trustedCertificate.proofOfPossessionValid) {
                    <span
                      title="{{ 'Proof of possession' | translate }}:  {{
                        'Complete`proof of possession`' | translate
                      }}"
                    >
                      <div class="icon-flex">
                        <i
                          class="text-success"
                          c8yIcon="success"
                        ></i>
                        {{ 'Complete`proof of possession`' | translate }}
                      </div>
                    </span>
                  }
                  @if (!trustedCertificate.proofOfPossessionValid) {
                    <span
                      title="{{ 'Proof of possession' | translate }}:  {{
                        'Incomplete`proof of possession`' | translate
                      }}"
                    >
                      <div class="icon-flex">
                        <i
                          class="text-warning"
                          c8yIcon="warning"
                        ></i>
                        {{ 'Incomplete`proof of possession`' | translate }}
                      </div>
                    </span>
                  }
                </span>
              }
            </div>
            <button
              class="m-l-auto btn-dot btn-dot--danger btn showOnHover m-r-8"
              [attr.aria-label]="'Delete' | translate"
              tooltip="{{ 'Delete' | translate }}"
              placement="right"
              type="button"
              data-cy="c8y-trusted-certificate--delete"
              [delay]="500"
              (click)="deleteTrustedCertificate(trustedCertificate)"
              [disabled]="!(hasAdminRole$ | async)"
            >
              <i c8yIcon="delete"></i>
            </button>
          </div>
        </c8y-li-body>

        <c8y-li-collapse>
          <div class="p-t-16 p-b-16">
            <div class="row">
              <div class="col-md-4">
                <c8y-form-group>
                  <label class="control-label">
                    {{ 'Certificate name' | translate }}
                  </label>
                  <c8y-input-group-editable
                    required
                    formGroupClass="m-b-0"
                    [ariaLabel]="'Certificate name' | translate"
                    data-cy="c8y-trusted-certificates--edit-certificate-name"
                    [(ngModel)]="trustedCertificate.name"
                    [disabled]="!(hasAdminRole$ | async)"
                    (save)="
                      updateCertificate(trustedCertificate, { name: trustedCertificate.name })
                    "
                  ></c8y-input-group-editable>
                </c8y-form-group>
              </div>
              <div class="col-md-1"></div>
              <div class="col-md-7">
                <c8y-form-group>
                  <label
                    class="control-label"
                    for="certInPemFormat"
                    translate
                  >
                    Certificate
                  </label>
                  <textarea
                    class="form-control no-resize"
                    id="certInPemFormat"
                    name="certInPemFormat"
                    type="text"
                    rows="7"
                    readonly
                    [(ngModel)]="trustedCertificate.certInPemFormat"
                  ></textarea>
                </c8y-form-group>
                <c8y-form-group>
                  <label
                    class="c8y-checkbox"
                    title="{{ 'Auto registration' | translate }}"
                    [ngClass]="{
                      disabled: !(hasAdminRole$ | async)
                    }"
                  >
                    <input
                      type="checkbox"
                      [(ngModel)]="trustedCertificate.autoRegistrationEnabled"
                      (ngModelChange)="
                        updateCertificate(trustedCertificate, { autoRegistrationEnabled: $event })
                      "
                      [disabled]="!(hasAdminRole$ | async)"
                    />
                    <span></span>
                    <span>{{ 'Auto registration' | translate }}</span>
                    <button
                      class="btn-help btn-help--sm"
                      [attr.aria-label]="'Help' | translate"
                      popover="{{ AUTO_REGISTRATION_POPOVER | translate }}"
                      placement="right"
                      triggers="focus"
                      container="body"
                      type="button"
                      type="button"
                    ></button>
                  </label>
                </c8y-form-group>
              </div>
            </div>
            <div class="row">
              <div class="col-md-4">
                <div
                  class="legend form-block"
                  translate
                >
                  Additional properties
                </div>
                <ul class="list-unstyled">
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Algorithm' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.algorithmName }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Version' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.version }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Valid from' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.notBefore | c8yDate }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Issuer' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.issuer }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Expiration date' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.notAfter | c8yDate }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Serial number' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.serialNumber }}
                    </span>
                  </li>
                  <li class="p-t-4 p-b-4 d-flex separator-bottom flex-wrap">
                    <label class="small m-b-0 m-r-8 a-s-start flex-grow">
                      {{ 'Subject`of a certificate`' | translate }}
                    </label>
                    <span class="m-l-auto text-break-word">
                      {{ trustedCertificate.subject }}
                    </span>
                  </li>
                </ul>
              </div>
              <div class="col-md-1"></div>
              @if (!trustedCertificate.tenantCertificateAuthority) {
                <div class="col-md-7">
                  <div class="legend form-block">
                    {{ 'Proof of possession' | translate }}
                    <button
                      class="btn-help btn-help--sm"
                      [attr.aria-label]="'Help' | translate"
                      popover="{{ PROOF_OF_POSSESSION_POPOVER | translate }}"
                      placement="right"
                      triggers="focus"
                      container="body"
                      type="button"
                      type="button"
                    ></button>
                  </div>
                  @if (trustedCertificate.proofOfPossessionValid) {
                    <div>
                      <span
                        class="icon-flex"
                        title="{{ 'Proof of possession' | translate }}:  {{
                          'Complete`proof of possession`' | translate
                        }}"
                      >
                        <i
                          class="text-success"
                          c8yIcon="success"
                        ></i>
                        {{ 'Complete`proof of possession`' | translate }}
                      </span>
                    </div>
                  }
                  @if (!trustedCertificate.proofOfPossessionValid) {
                    <div>
                      <div class="row m-b-16">
                        <div class="col-md-6">
                          <span
                            class="icon-flex"
                            title="{{ 'Proof of possession' | translate }}:  {{
                              'Incomplete`proof of possession`' | translate
                            }}"
                          >
                            <i
                              class="text-warning"
                              c8yIcon="warning"
                            ></i>
                            {{ 'Incomplete`proof of possession`' | translate }}
                          </span>
                        </div>
                        <div class="col-md-6 col-lg-6 text-right-md">
                          <span class="text-label-small m-r-4">
                            {{ 'Verification code expires/expired on' | translate }}
                          </span>
                          {{
                            (trustedCertificate.proofOfPossessionVerificationCodeUsableUntil
                              | c8yDate) || '---'
                          }}
                        </div>
                      </div>
                      <div class="row">
                        <div class="col-md-6 col-lg-6">
                          <c8y-form-group>
                            <label
                              class="control-label"
                              for="unsignedVerificationCode"
                              translate
                            >
                              Verification code
                            </label>
                            <textarea
                              class="form-control no-resize"
                              id="unsignedVerificationCode"
                              name="unsignedVerificationCode"
                              type="text"
                              rows="5"
                              readonly
                              [(ngModel)]="
                                trustedCertificate.proofOfPossessionUnsignedVerificationCode
                              "
                            ></textarea>
                          </c8y-form-group>
                          <div class="d-flex">
                            <button
                              class="btn btn-primary btn-sm"
                              title="{{ 'Regenerate verification code' | translate }}"
                              type="button"
                              (click)="regenerateUnsignedVerificationCode(trustedCertificate)"
                              [disabled]="!(hasAdminRole$ | async)"
                            >
                              {{ 'Regenerate`verification code`' | translate }}
                            </button>

                            <button
                              class="btn btn-sm btn-default m-l-auto m-r-0"
                              [attr.aria-label]="'Copy to clipboard' | translate"
                              tooltip="{{ 'Copy to clipboard' | translate }}"
                              placement="right"
                              type="button"
                              data-cy="c8y-trusted-certificates--copy-to-clipboard"
                              [delay]="500"
                              [disabled]="
                                !trustedCertificate.proofOfPossessionUnsignedVerificationCode
                              "
                              (click)="copyUnsignedVerificationCodeToClipboard(trustedCertificate)"
                            >
                              <i c8yIcon="clipboard"></i>
                            </button>
                            <button
                              class="btn btn-default btn-sm"
                              [attr.aria-label]="'Download as file' | translate"
                              tooltip="{{ 'Download as file' | translate }}"
                              type="button"
                              data-cy="c8y-trusted-certificates--download-as-file"
                              [delay]="500"
                              [disabled]="
                                !trustedCertificate.proofOfPossessionUnsignedVerificationCode
                              "
                              (click)="downloadUnsignedVerificationCode(trustedCertificate)"
                              c8yProductExperience
                              [actionName]="PRODUCT_EXPERIENCE.EVENT"
                              [actionData]="{
                                component:
                                  PRODUCT_EXPERIENCE.VERIFICATION_CODE.COMPONENTS.DOWNLOAD_CODE,
                                action: PRODUCT_EXPERIENCE.VERIFICATION_CODE.ACTIONS.DOWNLOAD
                              }"
                            >
                              <i c8yIcon="download"></i>
                            </button>
                          </div>
                        </div>

                        <div class="col-md-6 col-lg-6">
                          <c8y-form-group>
                            <label
                              class="control-label"
                              for="signedVerificationCode"
                            >
                              {{ 'Signed verification code' | translate }}
                              <button
                                class="btn-help btn-help--sm"
                                [attr.aria-label]="'Help' | translate"
                                popover="{{ SIGNED_VERIFICATION_CODE_POPOVER | translate }}"
                                placement="top"
                                container="body"
                                type="button"
                                data-cy="c8y-trusted-certificates--signed-verification-code-popup"
                                [outsideClick]="true"
                              ></button>
                            </label>
                            <textarea
                              class="form-control no-resize"
                              id="signedVerificationCode"
                              name="signedVerificationCode"
                              type="text"
                              rows="5"
                              [(ngModel)]="trustedCertificate.signedVerificationCode"
                            ></textarea>
                          </c8y-form-group>
                          <div class="d-flex">
                            <button
                              class="btn btn-primary btn-sm"
                              title="{{ 'Verify signed verification code' | translate }}"
                              type="button"
                              (click)="verifySignedVerificationCode(trustedCertificate)"
                              [disabled]="!trustedCertificate.signedVerificationCode"
                            >
                              {{ 'Verify`signed verification code`' | translate }}
                            </button>
                            <button
                              class="btn btn-sm btn-default m-l-auto"
                              [attr.aria-label]="'Upload file' | translate"
                              tooltip="{{ 'Upload file' | translate }}"
                              type="button"
                              [delay]="500"
                              (click)="fileInput.click()"
                              [disabled]="!(hasAdminRole$ | async)"
                            >
                              <i c8yIcon="upload"></i>
                            </button>
                            <input
                              class="hidden"
                              type="file"
                              #fileInput
                              (change)="onFileInput($event, trustedCertificate)"
                            />
                          </div>
                        </div>
                      </div>
                    </div>
                  }
                </div>
              }
            </div>
          </div>
        </c8y-li-collapse>
      </c8y-li>
    </c8y-list-group>
  </div>
}

results matching ""

    No results matching ""