@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>
}