Add co-sign checkbox for project policy (#16184)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
孙世军 2022-01-10 18:14:34 +08:00 committed by GitHub
parent 063991078a
commit 634f0139a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 88 additions and 75 deletions

View File

@ -4,27 +4,32 @@
<label>{{ 'PROJECT_CONFIG.REGISTRY' | translate }}</label>
<clr-checkbox-wrapper>
<input type="checkbox" id="clr-checkbox-wrapper-public" clrCheckbox [(ngModel)]="projectPolicy.Public"
name="public" [disabled]="!hasChangeConfigRole" />
name="public" [disabled]="!hasChangeConfigRole" />
<label>{{ 'PROJECT_CONFIG.PUBLIC_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.PUBLIC_POLICY' | translate }}
</clr-control-helper>
</clr-checkbox-container>
<clr-checkbox-container *ngIf="withNotary && !isProxyCacheProject">
<label><span *ngIf="withNotary && !isProxyCacheProject">{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
<clr-checkbox-wrapper *ngIf="withNotary && !isProxyCacheProject">
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrust" name="content-trust"
[disabled]="!hasChangeConfigRole" />
<label>{{ 'PROJECT_CONFIG.CONTENT_TRUST_TOGGLE' | translate }}</label>
<clr-checkbox-container *ngIf="!isProxyCacheProject" clrInline>
<label><span>{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
<clr-checkbox-wrapper>
<input id="content-trust-cosign" type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrustCosign" name="content-trust-cosign"
[disabled]="!hasChangeConfigRole" />
<label for="content-trust-cosign">{{ 'ACCESSORY.CO_SIGN' | translate }}</label>
</clr-checkbox-wrapper>
<clr-checkbox-wrapper *ngIf="withNotary">
<input id="content-trust" type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrust" name="content-trust"
[disabled]="!hasChangeConfigRole" />
<label for="content-trust">{{ 'ACCESSORY.NOTARY' | translate }}</label>
</clr-checkbox-wrapper>
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.CONTENT_TRUST_POLCIY' | translate }}
</clr-control-helper>
</clr-checkbox-container>
<clr-checkbox-container id="prevent-vulenrability-image" class="margin-top-05">
<label><span *ngIf="!(withNotary && !isProxyCacheProject)">{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
<label><span *ngIf="!(!isProxyCacheProject)">{{ 'PROJECT_CONFIG.SECURITY' | translate }}</span></label>
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.PreventVulImg"
name="prevent-vulenrability-image-input" [disabled]="!hasChangeConfigRole" />
name="prevent-vulenrability-image-input" [disabled]="!hasChangeConfigRole" />
<label>{{ 'PROJECT_CONFIG.PREVENT_VULNERABLE_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
<clr-control-helper class="config-subtext">
@ -34,8 +39,8 @@
<div>{{ 'PROJECT_CONFIG.PREVENT_VULNERABLE_1' | translate }}</div>
<div class="clr-select-wrapper">
<select id="severity" name="severity" class="clr-select"
[(ngModel)]="projectPolicy.PreventVulImgSeverity"
[disabled]="!projectPolicy.PreventVulImg">
[(ngModel)]="projectPolicy.PreventVulImgSeverity"
[disabled]="!projectPolicy.PreventVulImg">
<option *ngFor='let s of severityOptions' [ngValue]="s.severity">
{{ s.severityLevel | translate }}</option>
</select>
@ -50,14 +55,14 @@
<label>{{ 'PROJECT_CONFIG.SCAN' | translate }}</label>
<clr-checkbox-wrapper id="scan-image-on-push-wrapper">
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ScanImgOnPush"
[disabled]="!hasChangeConfigRole" name="scan-image-on-push" />
[disabled]="!hasChangeConfigRole" name="scan-image-on-push" />
<label>{{ 'PROJECT_CONFIG.AUTOSCAN_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
<clr-control-helper class="config-subtext"> {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }}
</clr-control-helper>
</clr-checkbox-container>
<div *ngIf="systemInfo" class="clr-form-control" [class.clr-form-control-disabled]="!hasChangeConfigRole">
<label for="systemAllowlist" class="clr-control-label">{{'CVE_ALLOWLIST.CVE_ALLOWLIST'|translate}}</label>
<label class="clr-control-label">{{'CVE_ALLOWLIST.CVE_ALLOWLIST'|translate}}</label>
<div class="w-100 clr-control-container">
<div class="config-subtext">
<div>
@ -71,22 +76,22 @@
</div>
<div *ngIf="hasExpired">
<span *ngIf="isUseSystemAllowlist()"
class="label label-warning">{{'CVE_ALLOWLIST.WARNING_SYS'|translate}}</span>
class="label label-warning">{{'CVE_ALLOWLIST.WARNING_SYS'|translate}}</span>
<span *ngIf="!isUseSystemAllowlist()"
class="label label-warning">{{'CVE_ALLOWLIST.WARNING_PRO'|translate}}</span>
class="label label-warning">{{'CVE_ALLOWLIST.WARNING_PRO'|translate}}</span>
</div>
</div>
<clr-radio-container clrInline>
<clr-radio-wrapper>
<input id="use-system" [attr.disabled]="!hasChangeConfigRole?'disabled':null" type="radio" clrRadio
name="systemAllowlistOrProjectAllowlist" required value="true"
[(ngModel)]="systemAllowlistOrProjectAllowlist" />
name="systemAllowlistOrProjectAllowlist" required value="true"
[(ngModel)]="systemAllowlistOrProjectAllowlist" />
<label>{{'CVE_ALLOWLIST.SYS_ALLOWLIST'|translate}}</label>
</clr-radio-wrapper>
<clr-radio-wrapper>
<input id="use-project" [attr.disabled]="!hasChangeConfigRole?'disabled':null" type="radio" clrRadio
name="systemAllowlistOrProjectAllowlist" required value="false"
[(ngModel)]="systemAllowlistOrProjectAllowlist" />
name="systemAllowlistOrProjectAllowlist" required value="false"
[(ngModel)]="systemAllowlistOrProjectAllowlist" />
<label>{{'CVE_ALLOWLIST.PRO_ALLOWLIST'|translate}}</label>
</clr-radio-wrapper>
</clr-radio-container>
@ -94,27 +99,27 @@
<div class="clr-col position-relative col-flex-grow-0 ">
<div>
<button id="show-add-modal" [disabled]="isUseSystemAllowlist() || !hasChangeConfigRole"
(click)="showAddModal=!showAddModal"
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
(click)="showAddModal=!showAddModal"
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
<button id="add-system" [disabled]="isUseSystemAllowlist() || !hasChangeConfigRole"
(click)="addSystem()"
class="btn btn-link ml-1">{{'CVE_ALLOWLIST.ADD_SYSTEM'|translate}}</button>
(click)="addSystem()"
class="btn btn-link ml-1">{{'CVE_ALLOWLIST.ADD_SYSTEM'|translate}}</button>
</div>
<div class="add-modal add-modal-dark" *ngIf="showAddModal && !isUseSystemAllowlist()">
<clr-icon (click)="showAddModal=false" class="float-lg-right margin-top-4"
shape="window-close"></clr-icon>
shape="window-close"></clr-icon>
<div>
<clr-textarea-container class="flex-direction-column">
<label>{{'CVE_ALLOWLIST.ENTER'|translate}}</label>
<textarea id="allowlist-textarea" class="w-100" clrTextarea [(ngModel)]="cveIds"
name="cveIds"></textarea>
name="cveIds"></textarea>
<clr-control-helper>{{'CVE_ALLOWLIST.HELP'|translate}}</clr-control-helper>
</clr-textarea-container>
</div>
<div>
<button id="add-to-allowlist" [disabled]="isDisabled()"
(click)="addToProjectAllowlist()"
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
(click)="addToProjectAllowlist()"
class="btn btn-link">{{'CVE_ALLOWLIST.ADD'|translate}}</button>
</div>
</div>
<ul class="allowlist-window" *ngIf="isUseSystemAllowlist()">
@ -137,24 +142,23 @@
</div>
<div class="clr-col padding-top-16 pl-2">
<div class="clr-row expire-data">
<label for="expires"
class="bottom-line bottom-line-project-config clr-col-3">{{'CVE_ALLOWLIST.EXPIRES_AT'|translate}}</label>
<label class="bottom-line bottom-line-project-config clr-col-3">{{'CVE_ALLOWLIST.EXPIRES_AT'|translate}}</label>
<div class="underline">
<input #dateSystemInput readonly type="date" [(clrDate)]="systemExpiresDate">
<input [disabled]="!hasChangeConfigRole" *ngIf="!isUseSystemAllowlist()" #dateInput
placeholder="{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}" readonly type="date"
[(clrDate)]="expiresDate" newFormLayout="true">
placeholder="{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}" readonly type="date"
[(clrDate)]="expiresDate" newFormLayout="true">
<input clrInput [disabled]="!hasChangeConfigRole" *ngIf="isUseSystemAllowlist()"
placeholder="{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}" readonly type="text"
value="{{systemExpiresDateString}}">
placeholder="{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}" readonly type="text"
value="{{systemExpiresDateString}}">
</div>
</div>
<div class="clr-row">
<label for="expires" class="clr-col-3"></label>
<label for="neverExpires" class="clr-col-3"></label>
<clr-checkbox-wrapper>
<input [disabled]="isUseSystemAllowlist() || !hasChangeConfigRole"
[checked]="neverExpires" [(ngModel)]="neverExpires" type="checkbox" clrCheckbox
name="neverExpires" id="neverExpires" />
[checked]="neverExpires" [(ngModel)]="neverExpires" type="checkbox" clrCheckbox
name="neverExpires" id="neverExpires" />
<label>
{{'CVE_ALLOWLIST.NEVER_EXPIRES'|translate}}
</label>
@ -165,10 +169,10 @@
</div>
</div>
<button type="button" class="btn btn-primary" (click)="save()"
[disabled]="((!isValid() || !hasChanges()) && !hasAllowlistChanged) || !hasChangeConfigRole">{{'BUTTON.SAVE'
[disabled]="((!isValid() || !hasChanges()) && !hasAllowlistChanged) || !hasChangeConfigRole">{{'BUTTON.SAVE'
| translate}}</button>
<button type="button" class="btn btn-outline" (click)="cancel()"
[disabled]="((!isValid() || !hasChanges()) && !hasAllowlistChanged) || !hasChangeConfigRole">{{'BUTTON.CANCEL'
[disabled]="((!isValid() || !hasChanges()) && !hasAllowlistChanged) || !hasChangeConfigRole">{{'BUTTON.CANCEL'
| translate}}</button>
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
</section>

View File

@ -23,6 +23,7 @@ const TARGET_BLANK = "_blank";
export class ProjectPolicy {
Public: boolean;
ContentTrust: boolean;
ContentTrustCosign: boolean;
PreventVulImg: boolean;
PreventVulImgSeverity: string;
ScanImgOnPush: boolean;
@ -30,6 +31,7 @@ export class ProjectPolicy {
constructor() {
this.Public = false;
this.ContentTrust = false;
this.ContentTrustCosign = false;
this.PreventVulImg = false;
this.PreventVulImgSeverity = LOW;
this.ScanImgOnPush = false;
@ -38,6 +40,7 @@ export class ProjectPolicy {
initByProject(pro: Project) {
this.Public = pro.metadata.public === 'true';
this.ContentTrust = pro.metadata.enable_content_trust === 'true';
this.ContentTrustCosign = pro.metadata.enable_content_trust_cosign === 'true';
this.PreventVulImg = pro.metadata.prevent_vul === 'true';
if (pro.metadata.severity) {
this.PreventVulImgSeverity = pro.metadata.severity;

View File

@ -15,6 +15,7 @@ export class Project {
metadata?: {
public: string | boolean;
enable_content_trust: string | boolean;
enable_content_trust_cosign?: string | boolean;
prevent_vul: string | boolean;
severity: string;
auto_scan: string | boolean;
@ -24,6 +25,7 @@ export class Project {
constructor () {
this.metadata.public = false;
this.metadata.enable_content_trust = false;
this.metadata.enable_content_trust_cosign = false;
this.metadata.prevent_vul = false;
this.metadata.severity = 'low';
this.metadata.auto_scan = false;

View File

@ -157,7 +157,7 @@
</clr-dg-column>
<clr-dg-column class="pull-command-column">{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
<clr-dg-column *ngIf="depth">{{'REPOSITORY.PLATFORM' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPOSITORY.TAGS' | translate}}</clr-dg-column>
<clr-dg-column class="tag-column">{{'REPOSITORY.TAGS' | translate}}</clr-dg-column>
<clr-dg-column class="co-signed-column">{{'ACCESSORY.CO_SIGNED' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>
<clr-dg-column class="vul-column">{{'REPOSITORY.VULNERABILITY' | translate}}</clr-dg-column>
@ -166,7 +166,7 @@
<clr-dg-column [clrDgSortBy]="pushComparator">{{'REPOSITORY.PUSH_TIME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullComparator">{{'REPOSITORY.PULL_TIME' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'ARTIFACT.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let artifact of artifactList" [clrDgItem]="artifact" >
<clr-dg-row *ngFor="let artifact of artifactList;let i = index" [clrDgItem]="artifact" >
<clr-dg-cell class="flex-max-width truncated">
<div class="cell white-normal">
<div class="artifact-icon clr-display-inline-block" *ngIf="artifact.icon">
@ -203,10 +203,8 @@
<clr-tooltip class="width-p-100">
<div clrTooltipTrigger class="center">
<div class="center">
<span #tagName class="truncated">{{artifact?.tags[0]?.name}}</span>
<span class="eslip"
*ngIf="artifact?.tags?.length>1 && isOverflow()">...</span>
<span *ngIf="artifact?.tags?.length>1 || isOverflow()">({{artifact?.tagNumber}})</span>
<span class="truncated">{{tagsString(artifact?.tags)}}</span>
<span *ngIf="artifact?.tags?.length>1">({{artifact?.tagNumber}})</span>
</div>
</div>
<clr-tooltip-content [clrPosition]="'top-right'" class="lg" [clrSize]="'lg'" *clrIfOpen>

View File

@ -422,7 +422,7 @@ clr-datagrid {
width: 6rem !important;
}
.co-signed-column {
width: 6rem !important;
width: 7rem !important;
}
.vul-column {
width: 11rem !important;
@ -430,6 +430,9 @@ clr-datagrid {
.annotations-column {
width: 5rem !important;
}
.tag-column {
width: 7rem !important;
}
.signed {
color: #00d40f;
}

View File

@ -16,7 +16,6 @@ import { forkJoin, Observable, of, Subject, Subscription } from "rxjs";
import { catchError, debounceTime, distinctUntilChanged, finalize, map } from 'rxjs/operators';
import { TranslateService } from "@ngx-translate/core";
import { ClrDatagridComparatorInterface, ClrDatagridStateInterface, ClrLoadingState } from "@clr/angular";
import { ActivatedRoute, Router } from "@angular/router";
import { Comparator, } from "../../../../../../../shared/services";
import {
@ -62,6 +61,7 @@ import { AppConfigService } from "src/app/services/app-config.service";
import { ArtifactListPageService } from "../../artifact-list-page.service";
import { ACCESSORY_PAGE_SIZE } from "./sub-accessories/sub-accessories.component";
import { Accessory } from "ng-swagger-gen/models/accessory";
import { Tag } from '../../../../../../../../../ng-swagger-gen/models/tag';
export interface LabelState {
iconsShow: boolean;
@ -176,8 +176,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
onScanArtifactsLength: number = 0;
stopBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
updateArtifactSub: Subscription;
@ViewChild('tagName') nameSpan: ElementRef;
constructor(
private errorHandlerService: ErrorHandler,
private artifactService: ArtifactService,
@ -1200,11 +1198,15 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
});
}
}
isOverflow(): boolean {
if (!this.nameSpan) {
return false;
tagsString(tags: Tag[]): string {
if (tags?.length) {
const arr: string[] = [];
tags.forEach(item => {
arr.push(item.name);
});
return arr.join(', ');
}
return !(this.nameSpan?.nativeElement?.clientWidth >= this.nameSpan?.nativeElement?.scrollWidth);
return null;
}
deleteAccessory(a: Accessory) {
let titleKey: string, summaryKey: string, content: string, buttons: ConfirmationButtons;

View File

@ -115,6 +115,7 @@ export class ProjectDefaultService extends ProjectService {
metadata: {
public: projectPolicy.Public ? "true" : "false",
enable_content_trust: projectPolicy.ContentTrust ? "true" : "false",
enable_content_trust_cosign: projectPolicy.ContentTrustCosign ? "true" : "false",
prevent_vul: projectPolicy.PreventVulImg ? "true" : "false",
severity: projectPolicy.PreventVulImgSeverity,
auto_scan: projectPolicy.ScanImgOnPush ? "true" : "false",

View File

@ -1713,12 +1713,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1713,12 +1713,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1712,12 +1712,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1681,12 +1681,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1709,12 +1709,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1713,12 +1713,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}

View File

@ -1711,12 +1711,12 @@
"DELETE_ACCESSORY": "删除附件",
"DELETED_SUCCESS": "删除附件成功",
"DELETED_FAILED": "删除附件失败",
"CO_SIGNED": "Co-sign 签名",
"CO_SIGNED": "Cosign 签名",
"NOTARY_SIGNED": "Notary 签名",
"ACCESSORY": "附件",
"ACCESSORIES": "附件",
"SUBJECT_ARTIFACT": "主体 Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "未发现任何附件!"
}

View File

@ -1698,12 +1698,12 @@
"DELETE_ACCESSORY": "Delete Accessory",
"DELETED_SUCCESS": "Accessory deleted successfully",
"DELETED_FAILED": "Deleting accessory failed",
"CO_SIGNED": "Co-signed",
"NOTARY_SIGNED": "Notary signed",
"CO_SIGNED": "Signed by Cosign",
"NOTARY_SIGNED": "Signed by Notary",
"ACCESSORY": "Accessory",
"ACCESSORIES": "Accessories",
"SUBJECT_ARTIFACT": "Subject Artifact",
"CO_SIGN": "Co-sign",
"CO_SIGN": "Cosign",
"NOTARY": "Notary",
"PLACEHOLDER": "We couldn't find any accessories!"
}