Fix legacy issues that html and css file are written on ts file.

Currently, our html and css files are written as string on .ts file. This pr is to solve the legacy issue.
This commit is contained in:
Deng, Qian 2018-05-08 15:34:37 +08:00
parent b4b148e923
commit 282a63f57f
191 changed files with 1041 additions and 2155 deletions

View File

@ -81,7 +81,7 @@ script:
- sudo mkdir -p /harbor
- sudo mv ./VERSION /harbor/UIVERSION
- sudo service postgresql stop
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1
- cat ./src/ui_ng/npm-ut-test-results
- sudo ./tests/testprepare.sh
- sudo make -f make/photon/Makefile _build_postgresql _build_db _build_registry -e VERSIONTAG=dev -e CLAIRDBVERSION=dev -e REGISTRYVERSION=v2.6.2
@ -107,7 +107,7 @@ script:
- sudo rm -rf /data/config/*
- sudo rm -rf /data/database/*
- ls /data/cert
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 NOTARYFLAG=true CLAIRFLAG=true
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1 NOTARYFLAG=true CLAIRFLAG=true
- sleep 10
- docker ps
- ./tests/validatecontainers.sh

View File

@ -34,7 +34,7 @@ sed -i 's/* as//g' src/app/shared/gauge/gauge.component.js
cp ./dist/build.min.js ../ui/static/
cp -r ./src/i18n/ ../ui/static/
cp ./src/styles.css ../ui/static/
cp ./src/styles.scss ../ui/static/
cp -r ./src/images/ ../ui/static/
cp ./src/setting.json ../ui/static/

View File

@ -20,7 +20,7 @@
"styles": [
"../node_modules/clarity-icons/clarity-icons.min.css",
"../node_modules/clarity-ui/clarity-ui.min.css",
"styles.css"
"styles.scss"
],
"scripts": [
"../node_modules/core-js/client/shim.min.js",

View File

@ -1,6 +1,6 @@
{
"name": "harbor-ui",
"version": "0.7.18-dev.1",
"version": "0.7.18-dev.6",
"description": "Harbor shared UI components based on Clarity and Angular4",
"author": "VMware",
"module": "index.js",

View File

@ -1,4 +1,3 @@
export const REGISTRY_CONFIG_HTML: string = `
<div>
<system-settings #systemSettings [(systemSettings)]="config" [showSubTitle]="true" [hasAdminRole]="hasAdminRole" [hasCAFile]="hasCAFile" [withAdmiral]="withAdmiral"></system-settings>
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [(vulnerabilityConfig)]="config" [showSubTitle]="true"></vulnerability-config>
@ -7,5 +6,4 @@ export const REGISTRY_CONFIG_HTML: string = `
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="shouldDisable">{{'BUTTON.CANCEL' | translate}}</button>
</div>
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
</div>
`;
</div>

View File

@ -1,9 +1,7 @@
export const REGISTRY_CONFIG_STYLES: string = `
.info-tips-icon {
color: grey;
}
.info-tips-icon:hover {
color: #007CBB;
}
`;
}

View File

@ -1,7 +1,6 @@
import { Component, OnInit, EventEmitter, Output, ViewChild, Input } from '@angular/core';
import { Configuration, ComplexValueItem } from './config';
import { REGISTRY_CONFIG_HTML } from './registry-config.component.html';
import { ConfigurationService, SystemInfoService, SystemInfo, ClairDBStatus } from '../service/index';
import {
toPromise,
@ -24,7 +23,7 @@ import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'hbr-registry-config',
template: REGISTRY_CONFIG_HTML
templateUrl: './registry-config.component.html'
})
export class RegistryConfigComponent implements OnInit {
config: Configuration = new Configuration();

View File

@ -1,4 +1,3 @@
export const REPLICATION_CONFIG_HTML: string = `
<form #replicationConfigFrom="ngForm" class="compact">
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
<label style="font-size:14px;font-weight:600;" *ngIf="showSubTitle">{{'CONFIG.REPLICATION' | translate}}</label>
@ -13,4 +12,3 @@ export const REPLICATION_CONFIG_HTML: string = `
</div>
</section>
</form>
`;

View File

@ -1,14 +1,12 @@
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { REPLICATION_CONFIG_HTML } from './replication-config.component.html';
import { Configuration } from '../config';
import { REGISTRY_CONFIG_STYLES } from '../registry-config.component.css';
@Component({
selector: 'replication-config',
template: REPLICATION_CONFIG_HTML,
styles: [REGISTRY_CONFIG_STYLES]
templateUrl: './replication-config.component.html',
styles: ['./replication-config.component.scss', '../registry-config.component.scss']
})
export class ReplicationConfigComponent {
config: Configuration;

View File

@ -1,4 +1,3 @@
export const SYSTEM_SETTINGS_HTML: string = `
<form #systemConfigFrom="ngForm" class="compact">
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
<label style="font-size:14px;font-weight:600;" *ngIf="showSubTitle">{{'CONFIG.SYSTEM' | translate}}</label>
@ -37,5 +36,4 @@ export const SYSTEM_SETTINGS_HTML: string = `
</clr-checkbox>
</div>
</section>
</form>
`;
</form>

View File

@ -1,15 +1,13 @@
import { Component, Input, Output, EventEmitter, ViewChild, Inject } from '@angular/core';
import { NgForm } from '@angular/forms';
import { SYSTEM_SETTINGS_HTML } from './system-settings.component.html';
import { Configuration } from '../config';
import { REGISTRY_CONFIG_STYLES } from '../registry-config.component.css';
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';
@Component({
selector: 'system-settings',
template: SYSTEM_SETTINGS_HTML,
styles: [REGISTRY_CONFIG_STYLES]
templateUrl: './system-settings.component.html',
styleUrls: ['./system-settings.component.scss', '../registry-config.component.scss']
})
export class SystemSettingsComponent {
config: Configuration;

View File

@ -1,4 +1,3 @@
export const VULNERABILITY_CONFIG_HTML: string = `
<form #systemConfigFrom="ngForm" class="compact">
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
<label class="section-title" *ngIf="showSubTitle">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label>
@ -43,39 +42,4 @@ export const VULNERABILITY_CONFIG_HTML: string = `
</div>
</div>
</section>
</form>
`;
export const VULNERABILITY_CONFIG_STYLES: string = `
.form-group-override {
padding-left: 0px !important;
}
.section-title {
font-size: 14px !important;
font-weight: 600 !important;
}
.btn-font {
font-size: 12px !important;
}
.namespace {
margin-left: 24px;
}
.clr-dropdown-override {
margin-top: -8px;
}
.btn-scan-right{
margin-left: 10px;
}
.btn-scan-right button{
width: 160px;
margin-bottom: 0px;
margin-top: 5px;
}
.btn-scan-right span{
margin-top: 4px;
}
`;
</form>

View File

@ -0,0 +1,31 @@
.form-group-override {
padding-left: 0px !important;
}
.section-title {
font-size: 14px !important;
font-weight: 600 !important;
}
.btn-font {
font-size: 12px !important;
}
.namespace {
margin-left: 24px;
}
.clr-dropdown-override {
margin-top: -8px;
}
.btn-scan-right{
margin-left: 10px;
}
.btn-scan-right button{
width: 160px;
margin-bottom: 0px;
margin-top: 5px;
}
.btn-scan-right span{
margin-top: 4px;
}

View File

@ -2,7 +2,6 @@ import { Component, Input, Output, EventEmitter, ViewChild, OnInit } from '@angu
import { NgForm } from '@angular/forms';
import { Configuration } from '../config';
import { VULNERABILITY_CONFIG_HTML, VULNERABILITY_CONFIG_STYLES } from './vulnerability-config.component.template';
import {
ScanningResultService,
SystemInfo,
@ -13,15 +12,13 @@ import { toPromise } from '../../utils';
import { TranslateService } from '@ngx-translate/core';
import { ClairDBStatus, ClairDetail } from '../../service/interface';
import { REGISTRY_CONFIG_STYLES } from '../registry-config.component.css';
const ONE_HOUR_SECONDS: number = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@Component({
selector: 'vulnerability-config',
template: VULNERABILITY_CONFIG_HTML,
styles: [VULNERABILITY_CONFIG_STYLES, REGISTRY_CONFIG_STYLES]
templateUrl: './vulnerability-config.component.html',
styles: ['./vulnerability-config.component.scss', '../registry-config.component.scss']
})
export class VulnerabilityConfigComponent implements OnInit {
_localTime: Date = new Date();

View File

@ -1,30 +0,0 @@
export const CONFIRMATION_DIALOG_STYLE: string = `
.confirmation-icon-inline {
display: inline-block;
}
.confirmation-title {
line-height: 24px;
color: #000000;
font-size: 22px;
}
.confirmation-content {
font-size: 14px;
color: #565656;
line-height: 24px;
display: inline-block;
vertical-align: middle;
width: 80%;
white-space: pre-wrap;
}
.batchInfoUl{
padding: 20px; list-style-type: none;
}
.batchInfoUl li {line-height: 24px;border-bottom: 1px solid #e8e8e8;}
.batchInfoUl li span:first-child {padding-right: 20px; width: 240px; display: inline-block; color:#666;
text-overflow: ellipsis; overflow: hidden; vertical-align: middle;}
.batchInfoUl li span:last-child {width: 220px; display: inline-block; color:#666;}
.batchInfoUl li span i {display: inline-block; line-height: 1.2em; font-size: 0.8em; color: #999;}
.batchInfoUl li span a{cursor: pointer; text-decoration: underline;}
`;

View File

@ -0,0 +1,45 @@
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="true">
<h3 class="modal-title" class="confirmation-title" style="margin-top: 0px;">{{dialogTitle}}</h3>
<div class="modal-body">
<div class="confirmation-icon-inline">
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
</div>
<div class="confirmation-content">{{dialogContent}}</div>
<div>
<ul class="batchInfoUl">
<li *ngFor="let info of batchInfors">
<span><i class="spinner spinner-inline spinner-pos" [hidden]='!info.loading'></i>&nbsp;&nbsp;{{info.name}}</span>
<span *ngIf="!info.errorInfo.length" [style.color]="colorChange(info)">{{info.status}}</span>
<span *ngIf="info.errorInfo.length" [style.color]="colorChange(info)">
<a (click)="toggleErrorTitle(errorInfo)">{{info.status}}</a>
<br>
<i #errorInfo style="display: none;">{{info.errorInfo}}</i>
</span>
</li>
</ul>
</div>
</div>
<div class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.CONFIRM' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="1">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NO' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.YES' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="operate()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
</div>
</clr-modal>

View File

@ -1,46 +0,0 @@
export const CONFIRMATION_DIALOG_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="true">
<h3 class="modal-title" class="confirmation-title" style="margin-top: 0px;">{{dialogTitle}}</h3>
<div class="modal-body">
<div class="confirmation-icon-inline">
<clr-icon shape="warning" class="is-warning" size="64"></clr-icon>
</div>
<div class="confirmation-content">{{dialogContent}}</div>
<div>
<ul class="batchInfoUl">
<li *ngFor="let info of batchInfors">
<span> <i class="spinner spinner-inline spinner-pos" [hidden]='!info.loading'></i>&nbsp;&nbsp;{{info.name}}</span>
<span *ngIf="!info.errorInfo.length" [style.color]="colorChange(info)">{{info.status}}</span>
<span *ngIf="info.errorInfo.length" [style.color]="colorChange(info)">
<a (click)="toggleErrorTitle(errorInfo)" >{{info.status}}</a><br>
<i #errorInfo style="display: none;">{{info.errorInfo}}</i>
</span>
</li>
</ul>
</div>
</div>
<div class="modal-footer" [ngSwitch]="buttons">
<ng-template [ngSwitchCase]="0">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.CONFIRM' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="1">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.NO' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{ 'BUTTON.YES' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="2">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="operate()" [hidden]="isDelete">{{'BUTTON.DELETE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="operate()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="!batchOverStatus" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
</div>
</clr-modal>
`;

View File

@ -0,0 +1,57 @@
.confirmation-icon-inline {
display: inline-block;
}
.confirmation-title {
line-height: 24px;
color: #000000;
font-size: 22px;
}
.confirmation-content {
font-size: 14px;
color: #565656;
line-height: 24px;
display: inline-block;
vertical-align: middle;
width: 80%;
white-space: pre-wrap;
}
.batchInfoUl {
padding: 20px;
list-style-type: none;
}
.batchInfoUl li {
line-height: 24px;
border-bottom: 1px solid #e8e8e8;
}
.batchInfoUl li span:first-child {
padding-right: 20px;
width: 240px;
display: inline-block;
color: #666;
text-overflow: ellipsis;
overflow: hidden;
vertical-align: middle;
}
.batchInfoUl li span:last-child {
width: 220px;
display: inline-block;
color: #666;
}
.batchInfoUl li span i {
display: inline-block;
line-height: 1.2em;
font-size: 0.8em;
color: #999;
}
.batchInfoUl li span a {
cursor: pointer;
text-decoration: underline;
}

View File

@ -18,14 +18,12 @@ import { ConfirmationMessage } from './confirmation-message';
import { ConfirmationAcknowledgement } from './confirmation-state-message';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
import { CONFIRMATION_DIALOG_TEMPLATE } from './confirmation-dialog.component.html';
import { CONFIRMATION_DIALOG_STYLE } from './confirmation-dialog.component.css';
import {BatchInfo} from './confirmation-batch-message';
@Component({
selector: 'confirmation-dialog',
template: CONFIRMATION_DIALOG_TEMPLATE,
styles: [ CONFIRMATION_DIALOG_STYLE ]
templateUrl: './confirmation-dialog.component.html',
styleUrls: [ './confirmation-dialog.component.scss' ]
})
export class ConfirmationDialogComponent {

View File

@ -1,4 +1,3 @@
export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="createEditDestinationOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{modalTitle}}</h3>
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
@ -61,4 +60,4 @@ export const CREATE_EDIT_ENDPOINT_TEMPLATE: string = `
<button type="button" class="btn btn-outline" (click)="onCancel()" [disabled]="inProgress">{{ 'BUTTON.CANCEL' | translate }}</button>
<button type="submit" class="btn btn-primary" (click)="onSubmit()" [disabled]="!isValid">{{ 'BUTTON.OK' | translate }}</button>
</div>
</clr-modal>`;
</clr-modal>

View File

@ -1,4 +1,3 @@
export const CREATE_EDIT_ENDPOINT_STYLE = `
.form-group-label-override {
font-size: 14px;
font-weight: 400;
@ -7,5 +6,4 @@ export const CREATE_EDIT_ENDPOINT_STYLE = `
clr-tooltip {
top: 3px;
position: relative;
}
`;
}

View File

@ -27,15 +27,15 @@ describe('CreateEditEndpointComponent (inline template)', () => {
let comp: CreateEditEndpointComponent;
let fixture: ComponentFixture<CreateEditEndpointComponent>;
let config: IServiceConfig = {
systemInfoEndpoint: '/api/endpoints/testing'
};
let endpointService: EndpointService;
let spy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@ -57,11 +57,11 @@ describe('CreateEditEndpointComponent (inline template)', () => {
beforeEach(()=>{
fixture = TestBed.createComponent(CreateEditEndpointComponent);
comp = fixture.componentInstance;
endpointService = fixture.debugElement.injector.get(EndpointService);
spy = spyOn(endpointService, 'getEndpoint').and.returnValue(Promise.resolve(mockData));
fixture.detectChanges();
comp.openCreateEditTarget(true, 1);
fixture.detectChanges();
});

View File

@ -32,9 +32,6 @@ import { Endpoint } from '../service/interface';
import { TranslateService } from '@ngx-translate/core';
import { CREATE_EDIT_ENDPOINT_STYLE } from './create-edit-endpoint.component.css';
import { CREATE_EDIT_ENDPOINT_TEMPLATE } from './create-edit-endpoint.component.html';
import { toPromise, clone, compareValue, isEmptyObject } from '../utils';
import { Subscription } from 'rxjs/Subscription';
@ -43,8 +40,8 @@ const FAKE_PASSWORD = 'rjGcfuRu';
@Component({
selector: 'hbr-create-edit-endpoint',
template: CREATE_EDIT_ENDPOINT_TEMPLATE,
styles: [CREATE_EDIT_ENDPOINT_STYLE]
templateUrl: './create-edit-endpoint.component.html',
styleUrls: ['./create-edit-endpoint.component.scss']
})
export class CreateEditEndpointComponent implements AfterViewChecked, OnDestroy {
modalTitle: string;

View File

@ -1,30 +0,0 @@
export const CREATE_EDIT_LABEL_STYLE: string = `
.form-group-label-override {
font-size: 14px;
font-weight: 400;
}
form{margin-bottom:-10px;padding-top:0; margin-top: 20px;width: 100%;background-color: #eee; border:1px solid #ccc;}
form .form-group{display:inline-flex;padding-left: 70px;}
form .form-group>label:first-child{width: auto;}
section{padding:.5rem 0;}
section> label{margin-left: 20px;}
.colorDrop {display:inline-block;position: relative; width: 132px;}
.colorDrop .colorPanel{position:absolute; width:166px; padding:6px; background-color: white; border: 1px solid #ccc; z-index:10;}
.btnColor{
margin: 0 !important;
padding: 0 !important;
width: 26px;
height:22px;
min-width: 26px;}
.colorPanel span{margin: 5px 4px; width:30px;height:24px; text-align: center;line-height: 24px;font-size:12px; border:1px solid #A1A1A1;}
.closePanel{ display: block;
left: 138px;
position: relative;
font-size: 18px;
width: 10px;
line-height: 8px;
cursor: pointer;
text-decoration: none;}
`;

View File

@ -1,4 +1,3 @@
export const CREATE_EDIT_LABEL_TEMPLATE: string = `
<div>
<form #labelForm="ngForm" [hidden]="!formShow">
<section>
@ -34,4 +33,4 @@ export const CREATE_EDIT_LABEL_TEMPLATE: string = `
</label>
</section>
</form>
</div>`;
</div>

View File

@ -0,0 +1,74 @@
.form-group-label-override {
font-size: 14px;
font-weight: 400;
}
form {
margin-bottom: -10px;
padding-top: 0;
margin-top: 20px;
width: 100%;
background-color: #eee;
border: 1px solid #ccc;
}
form .form-group {
display: inline-flex;
padding-left: 70px;
}
form .form-group>label:first-child {
width: auto;
}
section {
padding: .5rem 0;
}
section>label {
margin-left: 20px;
}
.colorDrop {
display: inline-block;
position: relative;
width: 132px;
}
.colorDrop .colorPanel {
position: absolute;
width: 166px;
padding: 6px;
background-color: white;
border: 1px solid #ccc;
z-index: 10;
}
.btnColor {
margin: 0 !important;
padding: 0 !important;
width: 26px;
height: 22px;
min-width: 26px;
}
.colorPanel span {
margin: 5px 4px;
width: 30px;
height: 24px;
text-align: center;
line-height: 24px;
font-size: 12px;
border: 1px solid #A1A1A1;
}
.closePanel {
display: block;
left: 138px;
position: relative;
font-size: 18px;
width: 10px;
line-height: 8px;
cursor: pointer;
text-decoration: none;
}

View File

@ -19,12 +19,8 @@ import {
Input, OnInit, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef
} from '@angular/core';
import {Label} from '../service/interface';
import { CREATE_EDIT_LABEL_STYLE } from './create-edit-label.component.css';
import { CREATE_EDIT_LABEL_TEMPLATE } from './create-edit-label.component.html';
import {toPromise, clone, compareValue} from '../utils';
import {LabelService} from "../service/label.service";
@ -35,8 +31,8 @@ import {LabelColor} from "../shared/shared.const";
@Component({
selector: 'hbr-create-edit-label',
template: CREATE_EDIT_LABEL_TEMPLATE,
styles: [CREATE_EDIT_LABEL_STYLE],
templateUrl: './create-edit-label.component.html',
styleUrls: ['./create-edit-label.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})

View File

@ -1,70 +0,0 @@
export const CREATE_EDIT_RULE_STYLE: string = `
/**
* Created by pengf on 9/28/2017.
*/
.select{
width: 186px;
}
.select .optionMore{
background-color: #bfbaba;
height: 1.6em;
font-size: 1.2em;
cursor: pointer;
text-align: center;
}
.hideFilter{ display: none;}
h4{
color: #666;
}
.colorRed{color: red;}
.colorRed a{text-decoration: underline;color: #007CBB;}
.alertLabel{display:block; margin-top:2px; line-height:1em; font-size:12px;}
.inputWidth{width: 270px;}
.endpointSelect{ width: 270px; margin-right: 20px;}
.filterSelect{width: 315px;}
.filterSelect clr-icon{margin-left: 15px;}
.filterSelect label{width: 136px;}
.filterSelect label input{width: 100%;}
.pull-left{float: left;}
.padLeft0{padding-left: 0;}
.floatSetPar{display: inline-block; width: 120px;margin-right: 10px;}
.floatSet {display: inline-block; width: 82px;margin-right: 4px;}
.form-group{ min-height: 36px;}
.projectInput{float: left;position: relative;}
.switchIcon{width:20px;height:20px; margin-top: 10px;margin-left: 10px; cursor: pointer;}
.addEndpoint{ margin-top: .25em !important;padding-left:2px;padding-right:2px;min-width:58px;margin-right:0}
.shadow{position: absolute;top: 8px;}
.is-solid{cursor: pointer;}
.selectBox{
position: absolute;
width: 100%;
height: auto;
margin-top:-0.25rem;
border: 1px solid #ccc;
background-color: white;
border: 1px solid rgba(0,0,0,.15);
border-right-width: 2px;
border-bottom-width: 2px;
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
z-index: 100;
}
.selectBox ul li{
list-style: none;
padding: 3px 20px
cursor: pointer;
}
.selectBox ul li:hover{
color: #262626;
background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8);
background-repeat: repeat-x;
}
.form-group-override{
padding-left: 170px !important;
}
.form-group>label:first-child{font-size:14px; width:6.5rem;}
.goLink{color:blue; border-bottom:1px solid blue; line-height:14px; cursor:pointer;}
`;

View File

@ -1,4 +1,3 @@
export const CREATE_EDIT_RULE_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="createEditRuleOpened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
<h3 class="modal-title">{{headerTitle | translate}}</h3>
<hbr-inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></hbr-inline-alert>
@ -131,4 +130,4 @@ export const CREATE_EDIT_RULE_TEMPLATE: string = `
<button type="button" id="ruleBtnCancel" class="btn btn-outline" [disabled]="this.inProgress" (click)="onCancel()">{{ 'BUTTON.CANCEL' | translate }}</button>
<button type="submit" id="ruleBtnOk" class="btn btn-primary" (click)="onSubmit()" [disabled]="!ruleForm.valid || !isValid || !hasFormChange()">{{ 'BUTTON.SAVE' | translate }}</button>
</div>
</clr-modal>`;
</clr-modal>

View File

@ -0,0 +1,157 @@
.select {
width: 186px;
}
.select .optionMore {
background-color: #bfbaba;
height: 1.6em;
font-size: 1.2em;
cursor: pointer;
text-align: center;
}
.hideFilter {
display: none;
}
h4 {
color: #666;
}
.colorRed {
color: red;
}
.colorRed a {
text-decoration: underline;
color: #007CBB;
}
.alertLabel {
display: block;
margin-top: 2px;
line-height: 1em;
font-size: 12px;
}
.inputWidth {
width: 270px;
}
.endpointSelect {
width: 270px;
margin-right: 20px;
}
.filterSelect {
width: 315px;
}
.filterSelect clr-icon {
margin-left: 15px;
}
.filterSelect label {
width: 136px;
}
.filterSelect label input {
width: 100%;
}
.pull-left {
float: left;
}
.padLeft0 {
padding-left: 0;
}
.floatSetPar {
display: inline-block;
width: 120px;
margin-right: 10px;
}
.floatSet {
display: inline-block;
width: 82px;
margin-right: 4px;
}
.form-group {
min-height: 36px;
}
.projectInput {
float: left;
position: relative;
}
.switchIcon {
width: 20px;
height: 20px;
margin-top: 10px;
margin-left: 10px;
cursor: pointer;
}
.addEndpoint {
margin-top: .25em !important;
padding-left: 2px;
padding-right: 2px;
min-width: 58px;
margin-right: 0
}
.shadow {
position: absolute;
top: 8px;
}
.is-solid {
cursor: pointer;
}
.selectBox {
position: absolute;
width: 100%;
height: auto;
margin-top: -0.25rem;
border: 1px solid #ccc;
background-color: white;
border: 1px solid rgba(0, 0, 0, .15);
border-right-width: 2px;
border-bottom-width: 2px;
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
z-index: 100;
}
.selectBox ul li {
list-style: none;
padding: 3px 20px;
cursor: pointer;
}
.selectBox ul li:hover {
color: #262626;
background-image: linear-gradient(180deg, #f5f5f5 0, #e8e8e8);
background-repeat: repeat-x;
}
.form-group-override {
padding-left: 170px !important;
}
.form-group>label:first-child {
font-size: 14px;
width: 6.5rem;
}
.goLink {
color: blue;
border-bottom: 1px solid blue;
line-height: 14px;
cursor: pointer;
}

View File

@ -21,8 +21,6 @@ import {Router, ActivatedRoute} from "@angular/router";
import {compareValue, isEmptyObject, toPromise} from "../utils";
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import {ReplicationService} from "../service/replication.service";
import {CREATE_EDIT_RULE_TEMPLATE} from "./create-edit-rule.component.html";
import {CREATE_EDIT_RULE_STYLE} from "./create-edit-rule.component.css";
import {ErrorHandler} from "../error-handler/error-handler";
import {TranslateService} from "@ngx-translate/core";
import {EndpointService} from "../service/endpoint.service";
@ -34,8 +32,8 @@ const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@Component ({
selector: 'hbr-create-edit-rule',
template: CREATE_EDIT_RULE_TEMPLATE,
styles: [CREATE_EDIT_RULE_STYLE]
templateUrl: './create-edit-rule.component.html',
styleUrls: ['./create-edit-rule.component.scss']
})

View File

@ -1,9 +1,7 @@
export const DATETIME_PICKER_TEMPLATE: string = `
<clr-icon shape="date"></clr-icon>
<label aria-haspopup="true" role="tooltip" [class.invalid]="dateInvalid" class="tooltip tooltip-validation tooltip-sm">
<input type="date" #searchTime="ngModel" [(ngModel)]="dateInput" name="searchTime" placeholder="dd/mm/yyyy" dateValidator (change)="doSearch()">
<span *ngIf="dateInvalid" class="tooltip-content">
{{'AUDIT_LOG.INVALID_DATE' | translate }}
</span>
</label>
`;
</label>

View File

@ -1,11 +1,9 @@
import {Component, Input, Output, EventEmitter, ViewChild, OnChanges} from '@angular/core';
import { NgModel } from '@angular/forms';
import { DATETIME_PICKER_TEMPLATE } from './datetime-picker.component.html';
@Component({
selector: 'hbr-datetime',
template: DATETIME_PICKER_TEMPLATE
templateUrl: './datetime-picker.component.html'
})
export class DatePickerComponent implements OnChanges{

View File

@ -1,21 +0,0 @@
export const ENDPOINT_STYLE: string = `
.option-left {
padding-left: 16px;
margin-top: -6px;
}
.option-right {
padding-right: 16px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.rightPos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
height: 24px;}
`;

View File

@ -1,4 +1,3 @@
export const ENDPOINT_TEMPLATE = `
<div>
<div class="row" style="position:relative;">
<div>
@ -41,5 +40,4 @@ export const ENDPOINT_TEMPLATE = `
</div>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
<hbr-create-edit-endpoint (reload)="reload($event)"></hbr-create-edit-endpoint>
</div>
`;
</div>

View File

@ -0,0 +1,24 @@
.option-left {
padding-left: 16px;
margin-top: -6px;
}
.option-right {
padding-right: 16px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.rightPos {
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
height: 24px;
}

View File

@ -29,9 +29,6 @@ import { Subscription } from 'rxjs/Subscription';
import { CreateEditEndpointComponent } from '../create-edit-endpoint/create-edit-endpoint.component';
import { ENDPOINT_STYLE } from './endpoint.component.css';
import { ENDPOINT_TEMPLATE } from './endpoint.component.html';
import { toPromise, CustomComparator } from '../utils';
import { State, Comparator } from 'clarity-angular';
@ -40,8 +37,8 @@ import {Observable} from "rxjs/Observable";
@Component({
selector: 'hbr-endpoint',
template: ENDPOINT_TEMPLATE,
styles: [ENDPOINT_STYLE],
templateUrl: './endpoint.component.html',
styleUrls: ['./endpoint.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EndpointComponent implements OnInit, OnDestroy {

View File

@ -0,0 +1,6 @@
<span>
<clr-icon shape="search" size="20" class="search-btn" [class.filter-icon]="isShowSearchBox" (click)="onClick()"></clr-icon>
<input [hidden]="!isShowSearchBox" type="text" style="padding-left: 15px;" (keyup)="valueChange()" (focus)="inputFocus()"
placeholder="{{placeHolder}}" [(ngModel)]="currentValue" />
<span class="filter-divider" *ngIf="withDivider"></span>
</span>

View File

@ -0,0 +1,25 @@
.filter-icon {
position: relative;
right: -12px;
}
.filter-divider {
display: inline-block;
height: 16px;
width: 2px;
background-color: #cccccc;
padding-top: 12px;
padding-bottom: 12px;
position: relative;
top: 9px;
margin-right: 6px;
margin-left: 6px;
}
.search-btn {
cursor: pointer;
}
.search-btn:hover {
color: #007CBB;
}

View File

@ -18,13 +18,11 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { FILTER_TEMPLATE, FILTER_STYLES } from './filter.template';
@Component({
selector: 'hbr-filter',
styles: [FILTER_STYLES],
template: FILTER_TEMPLATE
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.scss']
})
export class FilterComponent implements OnInit {

View File

@ -1,39 +0,0 @@
/**
* Define template resources for filter component
*/
export const FILTER_TEMPLATE: string = `
<span>
<clr-icon shape="search" size="20" class="search-btn" [class.filter-icon]="isShowSearchBox" (click)="onClick()"></clr-icon>
<input [hidden]="!isShowSearchBox" type="text" style="padding-left: 15px;" (keyup)="valueChange()" (focus)="inputFocus()" placeholder="{{placeHolder}}" [(ngModel)]="currentValue"/>
<span class="filter-divider" *ngIf="withDivider"></span>
</span>
`;
export const FILTER_STYLES: string = `
.filter-icon {
position: relative;
right: -12px;
}
.filter-divider {
display: inline-block;
height: 16px;
width: 2px;
background-color: #cccccc;
padding-top: 12px;
padding-bottom: 12px;
position: relative;
top: 9px;
margin-right: 6px;
margin-left: 6px;
}
.search-btn {
cursor: pointer;
}
.search-btn:hover {
color: #007CBB;
}
`;

View File

@ -1,4 +1,3 @@
export const GRIDVIEW_TEMPLATE = `
<div class="grid-content" (scroll)="onScroll($event)">
<div class="items" [ngStyle]="itemsHolderStyle" #itemsHolder >
<span *ngFor="let item of items;let i = index; trackBy:trackByFn" class='card-item' [ngStyle]="cardStyles[i]" #cardItem
@ -14,5 +13,4 @@ export const GRIDVIEW_TEMPLATE = `
<span class="vertical-helper"></span>
<div class="spinner"></div>
</div>
</div>
`
</div>

View File

@ -1,10 +1,3 @@
// Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
// This software is released under MIT license.
// The full license information can be found in LICENSE in the root directory of this project.
// @import 'node_modules/admiral-ui-common/css/mixins';
export const GRIDVIEW_STYLE = `
.grid-content {
position: relative;
top: 36px;
@ -37,10 +30,10 @@ export const GRIDVIEW_STYLE = `
left: 0;
right: 0;
bottom: 0;
@include animation(fadein 0.4s);
text-align: center;
background-color: rgba(255, 255, 255, 0.5);
}
.central-block-loading-more {
position: relative;
z-index: 10;
@ -48,10 +41,10 @@ export const GRIDVIEW_STYLE = `
left: 0;
right: 0;
bottom: 0;
@include animation(fadein 0.4s);
text-align: center;
background-color: rgba(255, 255, 255, 0.5);
}
.vertical-helper {
display: inline-block;
height: 100%;
@ -62,6 +55,4 @@ export const GRIDVIEW_STYLE = `
width: 100px;
height: 100px;
vertical-align: middle;
}
`
}

View File

@ -16,14 +16,12 @@ import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { TranslateService } from '@ngx-translate/core';
import { GRIDVIEW_TEMPLATE } from './grid-view.component.html';
import { GRIDVIEW_STYLE } from './grid-view.component.css';
import { ScrollPosition } from '../service/interface'
@Component({
selector: 'hbr-gridview',
template: GRIDVIEW_TEMPLATE,
styles: [GRIDVIEW_STYLE],
templateUrl: './grid-view.component.html',
styleUrls: ['./grid-view.component.scss'],
encapsulation: ViewEncapsulation.None
})
/**

View File

@ -4,9 +4,6 @@ import { LOG_DIRECTIVES } from './log/index';
import { FILTER_DIRECTIVES } from './filter/index';
import { ENDPOINT_DIRECTIVES } from './endpoint/index';
import { REPOSITORY_DIRECTIVES } from './repository/index';
import { REPOSITORY_STACKVIEW_DIRECTIVES } from './repository-stackview/index';
import { REPOSITORY_LISTVIEW_DIRECTIVES } from './repository-listview/index';
import { TAG_DIRECTIVES } from './tag/index';
import { REPLICATION_DIRECTIVES } from './replication/index';
@ -167,8 +164,6 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
FILTER_DIRECTIVES,
ENDPOINT_DIRECTIVES,
REPOSITORY_DIRECTIVES,
REPOSITORY_STACKVIEW_DIRECTIVES,
REPOSITORY_LISTVIEW_DIRECTIVES,
TAG_DIRECTIVES,
CREATE_EDIT_ENDPOINT_DIRECTIVES,
CONFIRMATION_DIALOG_DIRECTIVES,
@ -193,8 +188,6 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
FILTER_DIRECTIVES,
ENDPOINT_DIRECTIVES,
REPOSITORY_DIRECTIVES,
REPOSITORY_STACKVIEW_DIRECTIVES,
REPOSITORY_LISTVIEW_DIRECTIVES,
TAG_DIRECTIVES,
CREATE_EDIT_ENDPOINT_DIRECTIVES,
CONFIRMATION_DIALOG_DIRECTIVES,

View File

@ -9,7 +9,6 @@ export * from './endpoint/index';
export * from './repository/index';
export * from './create-edit-endpoint/index';
export * from './create-edit-rule/index';
export * from './repository-stackview/index';
export * from './tag/index';
export * from './list-replication-rule/index';
export * from './replication/index';

View File

@ -1,4 +1,3 @@
export const INLINE_ALERT_TEMPLATE: string = `
<clr-alert [clrAlertType]="inlineAlertType" [clrAlertClosable]="inlineAlertClosable" [(clrAlertClosed)]="alertClose" [clrAlertAppLevel]="useAppLevelStyle">
<div class="alert-item">
<span class="alert-text" [class.alert-text-blink]="blinking">
@ -9,5 +8,4 @@ export const INLINE_ALERT_TEMPLATE: string = `
<button class="btn btn-sm btn-link alert-btn-link" (click)="confirmCancel()">{{'BUTTON.YES' | translate}}</button>
</div>
</div>
</clr-alert>
`;
</clr-alert>

View File

@ -1,4 +1,3 @@
export const INLINE_ALERT_STYLE: string = `
.alert-text-blink {
color: red;
font-weight: bolder;
@ -12,5 +11,4 @@ export const INLINE_ALERT_STYLE: string = `
}
:host >>> .alert-icon-wrapper{
display: inline;
}
`;
}

View File

@ -18,13 +18,10 @@ import { errorHandler } from '../shared/shared.utils';
import { Observable } from 'rxjs/Rx';
import { Subscription } from "rxjs";
import { INLINE_ALERT_STYLE } from './inline-alert.component.css';
import { INLINE_ALERT_TEMPLATE } from './inline-alert.component.html';
@Component({
selector: 'hbr-inline-alert',
template: INLINE_ALERT_TEMPLATE,
styles: [ INLINE_ALERT_STYLE ]
templateUrl: './inline-alert.component.html',
styleUrls: [ './inline-alert.component.scss' ]
})
export class InlineAlertComponent {
inlineAlertType: string = 'alert-danger';

View File

@ -1,4 +1,3 @@
export const JOB_LOG_VIEWER_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalSize]="'xl'">
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{title | translate }}</h3>
<div class="modal-body">
@ -12,20 +11,4 @@ export const JOB_LOG_VIEWER_TEMPLATE: string = `
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="close()">{{ 'BUTTON.CLOSE' | translate}}</button>
</div>
</clr-modal>
`;
export const JOB_LOG_VIEWER_STYLES: string = `
.log-viewer-title {
line-height: 24px;
color: #000000;
font-size: 22px;
}
.loading-back {
height: 358px;
display: flex;
align-items: center;
justify-content: center;
}
`;
</clr-modal>

View File

@ -0,0 +1,12 @@
.log-viewer-title {
line-height: 24px;
color: #000000;
font-size: 22px;
}
.loading-back {
height: 358px;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -13,7 +13,6 @@
// limitations under the License.
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from '@angular/core';
import { JOB_LOG_VIEWER_TEMPLATE, JOB_LOG_VIEWER_STYLES } from './job-log-viewer.component.template';
import { JobLogService } from '../service/index';
import { ErrorHandler } from '../error-handler/index';
import { toPromise } from '../utils';
@ -22,8 +21,8 @@ const supportSet: string[] = ["replication", "scan"];
@Component({
selector: 'job-log-viewer',
template: JOB_LOG_VIEWER_TEMPLATE,
styles: [JOB_LOG_VIEWER_STYLES],
templateUrl: './job-log-viewer.component.html',
styleUrls: ['./job-log-viewer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})

View File

@ -0,0 +1,5 @@
<label class="label" [ngStyle]="{'background-color': labelColor?.color, 'color': labelColor?.textColor, 'border': labelColor?.color == '#FFFFFF'? '1px solid #A1A1A1': 'none'}" [style.max-width.px]="labelWidth">
<clr-icon *ngIf="label.scope=='p'" shape="organization"></clr-icon>
<clr-icon *ngIf="label.scope=='g'" shape="administrator"></clr-icon>
{{label.name}}
</label>

View File

@ -0,0 +1,17 @@
.label {
border: none;
color: #222;
display: inline-block;
justify-content: flex-start;
overflow: hidden;
text-overflow: ellipsis;
line-height: .875rem;
}
.label clr-icon {
margin-right: 3px;
}
.btn-group .dropdown-menu clr-icon {
display: block;
}

View File

@ -18,15 +18,14 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { LABEL_PIEICE_TEMPLATE, LABEL_PIEICE_STYLES } from './label-piece.template';
import {Label} from "../service/interface";
import {LabelColor} from "../shared/shared.const";
@Component({
selector: 'hbr-label-piece',
styles: [LABEL_PIEICE_STYLES],
template: LABEL_PIEICE_TEMPLATE
templateUrl: './label-piece.component.html',
styleUrls: ['./label-piece.component.scss']
})
export class LabelPieceComponent implements OnInit, OnChanges {

View File

@ -1,22 +0,0 @@
/**
* Define template resources for filter component
*/
export const LABEL_PIEICE_TEMPLATE: string = `
<label class="label" [ngStyle]="{'background-color': labelColor?.color, 'color': labelColor?.textColor, 'border': labelColor?.color == '#FFFFFF'? '1px solid #A1A1A1': 'none'}" [style.max-width.px]="labelWidth">
<clr-icon *ngIf="label.scope=='p'" shape="organization"></clr-icon>
<clr-icon *ngIf="label.scope=='g'" shape="administrator"></clr-icon>
{{label.name}}
</label>
`;
export const LABEL_PIEICE_STYLES: string = `
.label{border: none; color:#222;
display: inline-block;
justify-content: flex-start;
overflow: hidden;
text-overflow: ellipsis;
line-height: .875rem;}
.label clr-icon{ margin-right: 3px;}
.btn-group .dropdown-menu clr-icon{display:block;}
`;

View File

@ -1,21 +0,0 @@
export const LABEL_STYLE: string = `
.option-left {
padding-left: 16px;
margin-top: -6px;
}
.option-right {
padding-right: 16px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.rightPos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
height: 24px;}
`;

View File

@ -1,4 +1,3 @@
export const LABEL_TEMPLATE = `
<div>
<div class="row" style="position:relative;">
<div>
@ -39,5 +38,4 @@ export const LABEL_TEMPLATE = `
</div>
</div>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</div>
`;
</div>

View File

@ -0,0 +1,24 @@
.option-left {
padding-left: 16px;
margin-top: -6px;
}
.option-right {
padding-right: 16px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.rightPos {
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
height: 24px;
}

View File

@ -15,8 +15,6 @@ import {
Component, OnInit, OnDestroy, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef,
Input
} from '@angular/core';
import {LABEL_TEMPLATE} from "./label.component.html";
import {LABEL_STYLE} from "./label.component.css";
import {Label} from "../service/interface";
import {LabelDefaultService, LabelService} from "../service/label.service";
import {toPromise} from "../utils";
@ -30,8 +28,8 @@ import {TranslateService} from "@ngx-translate/core";
import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component";
@Component({
selector: 'hbr-label',
template: LABEL_TEMPLATE,
styles: [LABEL_STYLE],
templateUrl: './label.component.html',
styleUrls: ['./label.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LabelComponent implements OnInit {

View File

@ -1,2 +0,0 @@
export const LIST_REPLICATION_RULE_CSS = `
`

View File

@ -1,4 +1,3 @@
export const LIST_REPLICATION_RULE_TEMPLATE: string = `
<div style="padding-bottom: 15px;">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" [clDgRowSelection]="true">
<clr-dg-action-bar style="height: 24px;">
@ -36,5 +35,4 @@ export const LIST_REPLICATION_RULE_TEMPLATE: string = `
</clr-dg-footer>
</clr-datagrid>
<confirmation-dialog #deletionConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="deletionConfirm($event)"></confirmation-dialog>
</div>
`;
</div>

View File

@ -41,15 +41,13 @@ import { toPromise, CustomComparator } from '../utils';
import { State, Comparator } from 'clarity-angular';
import { LIST_REPLICATION_RULE_TEMPLATE } from './list-replication-rule.component.html';
import { LIST_REPLICATION_RULE_CSS } from './list-replication-rule.component.css';
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
import {Observable} from "rxjs/Observable";
@Component({
selector: 'hbr-list-replication-rule',
template: LIST_REPLICATION_RULE_TEMPLATE,
styles: [LIST_REPLICATION_RULE_CSS],
templateUrl: './list-replication-rule.component.html',
styleUrls: ['./list-replication-rule.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListReplicationRuleComponent implements OnInit, OnChanges {

View File

@ -1,8 +1,3 @@
/**
* Define the inline template and styles with ts variables
*/
export const LOG_TEMPLATE: string = `
<div>
<h2 class="h2-log-override" *ngIf="withTitle">{{'SIDE_NAV.LOGS' | translate}}</h2>
<div class="row flex-items-xs-between flex-items-xs-bottom">
@ -16,10 +11,11 @@ export const LOG_TEMPLATE: string = `
<option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option>
</select>
</div>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)" (openFlag)="openFilter($event)" [currentValue]="currentTerm"></hbr-filter>
<hbr-filter [withDivider]="true" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"
(openFlag)="openFilter($event)" [currentValue]="currentTerm"></hbr-filter>
<span (click)="refresh()" class="refresh-btn">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="!inProgress"></span>
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="spinner spinner-inline" [hidden]="!inProgress"></span>
</span>
</div>
</div>
@ -39,64 +35,10 @@ export const LOG_TEMPLATE: string = `
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
{{'AUDIT_LOG.OF' | translate}} {{pagination.totalItems}} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'AUDIT_LOG.OF' | translate}} {{pagination.totalItems}} {{'AUDIT_LOG.ITEMS'
| translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
`;
export const LOG_STYLES: string = `
.h2-log-override {
margin-top: 0px !important;
}
.action-head-pos {
padding-right: 18px;
height: 24px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.custom-lines-button {
padding: 0px !important;
min-width: 25px !important;
}
.lines-button-toggole {
font-size: 16px;
text-decoration: underline;
}
.log-select {
width: 130px;
display: inline-block;
top: 1px;
}
.item-divider {
height: 24px;
display: inline-block;
width: 1px;
background-color: #ccc;
opacity: 0.55;
margin-left: 12px;
top: 8px;
position: relative;
}
.rightPos {
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
.filterTag{float:left;margin-top:8px;}
`;
</div>

View File

@ -0,0 +1,55 @@
.h2-log-override {
margin-top: 0px !important;
}
.action-head-pos {
padding-right: 18px;
height: 24px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
.custom-lines-button {
padding: 0px !important;
min-width: 25px !important;
}
.lines-button-toggole {
font-size: 16px;
text-decoration: underline;
}
.log-select {
width: 130px;
display: inline-block;
top: 1px;
}
.item-divider {
height: 24px;
display: inline-block;
width: 1px;
background-color: #ccc;
opacity: 0.55;
margin-left: 12px;
top: 8px;
position: relative;
}
.rightPos {
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
.filterTag {
float: left;
margin-top: 8px;
}

View File

@ -22,7 +22,6 @@ import {
import { ErrorHandler } from '../error-handler/index';
import { Observable } from 'rxjs/Observable';
import { toPromise, CustomComparator } from '../utils';
import { LOG_TEMPLATE, LOG_STYLES } from './recent-log.template';
import {
DEFAULT_PAGE_SIZE,
calculatePage,
@ -34,8 +33,8 @@ import { Comparator, State } from 'clarity-angular';
@Component({
selector: 'hbr-log',
styles: [LOG_STYLES],
template: LOG_TEMPLATE
templateUrl: './recent-log.component.html',
styleUrls: ['./recent-log.component.scss']
})
export class RecentLogComponent implements OnInit {

View File

@ -1,9 +0,0 @@
export const PROJECT_POLICY_CONFIG_STYLE = `#severity-blk div
{
display: inline-block;
}
.select {
width: 120px;
}
`;

View File

@ -1,4 +1,3 @@
export const PROJECT_POLICY_CONFIG_TEMPLATE = `
<form #projectPolicyForm="ngForm">
<section class="form-block">
<div class="form-group">
@ -48,4 +47,4 @@ export const PROJECT_POLICY_CONFIG_TEMPLATE = `
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges() || !hasProjectAdminRole">{{'BUTTON.CANCEL' | translate}}</button>
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
</section>
</form>`;
</form>

View File

@ -0,0 +1,8 @@
#severity-blk div
{
display: inline-block;
}
.select {
width: 120px;
}

View File

@ -1,7 +1,5 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { PROJECT_POLICY_CONFIG_TEMPLATE } from './project-policy-config.component.html';
import { PROJECT_POLICY_CONFIG_STYLE } from './project-policy-config.component.css';
import { toPromise, compareValue, clone } from '../utils';
import { ProjectService } from '../service/project.service';
import { ErrorHandler } from '../error-handler/error-handler';
@ -42,8 +40,8 @@ export class ProjectPolicy {
@Component({
selector: 'hbr-project-policy-config',
template: PROJECT_POLICY_CONFIG_TEMPLATE,
styles: [PROJECT_POLICY_CONFIG_STYLE],
templateUrl: './project-policy-config.component.html',
styleUrls: ['./project-policy-config.component.scss']
})
export class ProjectPolicyConfigComponent implements OnInit {
onGoing = false;

View File

@ -1,7 +1,5 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { COPY_INPUT_HTML } from './copy-input.html';
import { PUSH_IMAGE_STYLE } from './push-image.css';
export const enum CopyStatus {
NORMAL, SUCCESS, ERROR
@ -9,8 +7,8 @@ export const enum CopyStatus {
@Component({
selector: 'hbr-copy-input',
styles: [PUSH_IMAGE_STYLE],
template: COPY_INPUT_HTML,
templateUrl: './copy-input.coponent.html',
styleUrls: ['./push-image.scss'],
providers: []
})

View File

@ -1,4 +1,3 @@
export const COPY_INPUT_HTML = `
<div>
<div class="command-title" *ngIf="!iconMode">
{{headerTitle}}
@ -14,5 +13,4 @@ export const COPY_INPUT_HTML = `
<clr-icon shape="copy" [class.is-success]="isCopied" [class.is-error]="hasCopyError" class="info-tips-icon" size="24" [ngxClipboard]="inputTarget1" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)"></clr-icon>
</span>
</div>
</div>
`;
</div>

View File

@ -1,4 +1,3 @@
export const PUSH_IMAGE_HTML: string = `
<div>
<clr-dropdown>
<button class="btn btn-link btn-font" clrDropdownToggle (click)="onclick()">
@ -32,5 +31,4 @@ export const PUSH_IMAGE_HTML: string = `
</div>
</clr-dropdown-menu>
</clr-dropdown>
</div>
`;
</div>

View File

@ -2,13 +2,11 @@ import { Component, Input, ViewChild } from '@angular/core';
import { CopyInputComponent } from './copy-input.component';
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { PUSH_IMAGE_STYLE } from './push-image.css';
import { PUSH_IMAGE_HTML } from './push-image.html';
@Component({
selector: 'hbr-push-image-button',
template: PUSH_IMAGE_HTML,
styles: [PUSH_IMAGE_STYLE],
templateUrl: './push-image.component.html',
styleUrls: ['./push-image.scss'],
providers: []
})

View File

@ -1,4 +1,3 @@
export const PUSH_IMAGE_STYLE: string = `
.commands-container {
min-width: 360px;
max-width: 720px;
@ -45,5 +44,4 @@ export const PUSH_IMAGE_STYLE: string = `
}
.hide{
display:none;
}
`;
}

View File

@ -1,4 +1,3 @@
export const REPLICATION_TEMPLATE: string = `
<div class="row" style="position:relative;">
<div>
<div class="row flex-items-xs-between rightPos">
@ -75,4 +74,4 @@ export const REPLICATION_TEMPLATE: string = `
<job-log-viewer #replicationLogViewer></job-log-viewer>
<hbr-create-edit-rule *ngIf="isSystemAdmin" [projectId]="projectId" [projectName]="projectName" (goToRegistry)="goRegistry()" (reload)="reloadRules($event)"></hbr-create-edit-rule>
<confirmation-dialog #replicationConfirmDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmReplication($event)"></confirmation-dialog>
</div>`;
</div>

View File

@ -1,4 +1,3 @@
export const REPLICATION_STYLE: string = `
.refresh-btn {
cursor: pointer;
}
@ -28,4 +27,4 @@ export const REPLICATION_STYLE: string = `
margin-top: 5px;
z-index: 100;
height: 32px;
}`;
}

View File

@ -34,9 +34,6 @@ import {
import { Comparator } from 'clarity-angular';
import { REPLICATION_TEMPLATE } from './replication.component.html';
import { REPLICATION_STYLE } from './replication.component.css';
import { JobLogViewerComponent } from '../job-log-viewer/index';
import { State } from "clarity-angular";
import {Observable} from "rxjs/Observable";
@ -81,8 +78,8 @@ export class SearchOption {
@Component({
selector: 'hbr-replication',
template: REPLICATION_TEMPLATE,
styles: [REPLICATION_STYLE]
templateUrl: './replication.component.html',
styleUrls: ['./replication.component.scss']
})
export class ReplicationComponent implements OnInit, OnDestroy {

View File

@ -20,7 +20,7 @@ import { GridViewComponent } from '../gridview/grid-view.component';
@Component({
selector: 'hbr-repository-gridview',
templateUrl: './repository-gridview.component.html',
styleUrls: ['./repository-gridview.component.css'],
styleUrls: ['./repository-gridview.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RepositoryGridviewComponent implements OnChanges, OnInit {

View File

@ -1,6 +0,0 @@
import { Type } from '@angular/core';
import { RepositoryListviewComponent } from './repository-listview.component';
export const REPOSITORY_LISTVIEW_DIRECTIVES: Type<any>[] = [
RepositoryListviewComponent
];

View File

@ -1,8 +0,0 @@
export const REPOSITORY_LISTVIEW_STYLE = `
.rightPos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
`;

View File

@ -1,41 +0,0 @@
export const REPOSITORY_LISTVIEW_TEMPLATE = `
<div>
<div class="row" style="position:relative;">
<div>
<div class="row flex-items-xs-right option-right rightPos">
<div class="flex-xs-middle">
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="selectedChange()">
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]="r">
<clr-dg-cell><a href="javascript:void(0)" (click)="watchRepoClickEvt(r)">{{r.name}}</a></clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="showDBStatusWarning" class="db-status-warning">
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
</span>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<confirmation-dialog #confirmationDialog [batchInfors]="batchDelectionInfos" (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</div>
`;

View File

@ -1,173 +0,0 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '../shared/shared.module';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { RepositoryListviewComponent } from './repository-listview.component';
import { TagComponent } from '../tag/tag.component';
import { FilterComponent } from '../filter/filter.component';
import { ErrorHandler } from '../error-handler/error-handler';
import { Repository, RepositoryItem, Tag, SystemInfo } from '../service/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
import { TagService, TagDefaultService } from '../service/tag.service';
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
import { click } from '../utils';
import {LabelPieceComponent} from "../label-piece/label-piece.component";
describe('RepositoryComponentListView (inline template)', () => {
let compRepo: RepositoryListviewComponent;
let fixtureRepo: ComponentFixture<RepositoryListviewComponent>;
let repositoryService: RepositoryService;
let tagService: TagService;
let systemInfoService: SystemInfoService;
let spyRepos: jasmine.Spy;
let spySystemInfo: jasmine.Spy;
let mockSystemInfo: SystemInfo = {
"with_notary": true,
"with_admiral": false,
"admiral_endpoint": "NA",
"auth_mode": "db_auth",
"registry_url": "10.112.122.56",
"project_creation_restriction": "everyone",
"self_registration": true,
"has_ca_root": false,
"harbor_version": "v1.1.1-rc1-160-g565110d"
};
let mockRepoData: RepositoryItem[] = [
{
"id": 1,
"name": "library/busybox",
"project_id": 1,
"description": "asdfsadf",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
},
{
"id": 2,
"name": "library/nginx",
"project_id": 1,
"description": "asdf",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
}
];
let mockRepo: Repository = {
metadata: {xTotalCount: 2},
data: mockRepoData
};
let mockTagData: Tag[] = [
{
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5",
"size": "2049",
"architecture": "amd64",
"os": "linux",
"docker_version": "1.12.3",
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"created": new Date("2016-11-08T22:41:15.912313785Z"),
"signature": null,
"labels": []
}
];
let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing',
systemInfoEndpoint: '/api/systeminfo/testing',
targetBaseEndpoint: '/api/tag/testing'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
RouterTestingModule
],
declarations: [
RepositoryListviewComponent,
TagComponent,
LabelPieceComponent,
ConfirmationDialogComponent,
FilterComponent,
VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES,
INLINE_ALERT_DIRECTIVES,
JobLogViewerComponent
],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: TagService, useClass: TagDefaultService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService }
]
});
}));
beforeEach(() => {
fixtureRepo = TestBed.createComponent(RepositoryListviewComponent);
compRepo = fixtureRepo.componentInstance;
compRepo.projectId = 1;
compRepo.hasProjectAdminRole = true;
repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
systemInfoService = fixtureRepo.debugElement.injector.get(SystemInfoService);
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
fixtureRepo.detectChanges();
});
it('should create', () => {
expect(compRepo).toBeTruthy();
});
it('should load and render data', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell'));
expect(deRepo).toBeTruthy();
let elRepo: HTMLElement = deRepo.nativeElement;
expect(elRepo).toBeTruthy();
expect(elRepo.textContent).toEqual('library/busybox');
});
}));
it('should filter data by keyword', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
compRepo.doSearchRepoNames('nginx');
fixtureRepo.detectChanges();
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell'));
expect(de).toBeTruthy();
expect(de.length).toEqual(1);
let el: HTMLElement = de[0].nativeElement;
expect(el).toBeTruthy();
expect(el.textContent).toEqual('library/nginx');
});
}));
});

View File

@ -1,354 +0,0 @@
import {
Component,
Input,
Output,
OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
EventEmitter, OnChanges, SimpleChanges
} from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Comparator } from 'clarity-angular';
import { REPOSITORY_LISTVIEW_TEMPLATE } from './repository-listview.component.html';
import { REPOSITORY_LISTVIEW_STYLE } from './repository-listview.component.css';
import {
Repository,
SystemInfo,
SystemInfoService,
RepositoryService,
RequestQueryParams,
RepositoryItem,
TagService
} from '../service/index';
import { ErrorHandler } from '../error-handler/error-handler';
import { toPromise, CustomComparator } from '../utils';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
import { Subscription } from 'rxjs/Subscription';
import { Tag } from '../service/interface';
import { State } from "clarity-angular";
import {
DEFAULT_PAGE_SIZE,
calculatePage,
doFiltering,
doSorting
} from '../utils';
import {BatchInfo, BathInfoChanges} from "../confirmation-dialog/confirmation-batch-message";
import {Observable} from "rxjs/Observable";
@Component({
selector: 'hbr-repository-listview',
template: REPOSITORY_LISTVIEW_TEMPLATE,
styles: [REPOSITORY_LISTVIEW_STYLE],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RepositoryListviewComponent implements OnChanges, OnInit {
signedCon: {[key: string]: any | string[]} = {};
@Input() projectId: number;
@Input() projectName = 'unknown';
@Input() urlPrefix: string;
@Input() hasSignedIn: boolean;
@Input() hasProjectAdminRole: boolean;
@Output() repoClickEvent = new EventEmitter<RepositoryItem>();
lastFilteredRepoName: string;
repositories: RepositoryItem[];
systemInfo: SystemInfo;
selectedRow: RepositoryItem[] = [];
loading = true;
@ViewChild('confirmationDialog')
confirmationDialog: ConfirmationDialogComponent;
batchDelectionInfos: BatchInfo[] = [];
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number');
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number');
pageSize: number = DEFAULT_PAGE_SIZE;
currentPage = 1;
totalCount = 0;
currentState: State;
constructor(
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private repositoryService: RepositoryService,
private systemInfoService: SystemInfoService,
private tagService: TagService,
private ref: ChangeDetectorRef,
private router: Router) { }
public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : '';
}
public get withNotary(): boolean {
return this.systemInfo ? this.systemInfo.with_notary : false;
}
public get withClair(): boolean {
return this.systemInfo ? this.systemInfo.with_clair : false;
}
public get isClairDBReady(): boolean {
return this.systemInfo &&
this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
}
public get showDBStatusWarning(): boolean {
return this.withClair && !this.isClairDBReady;
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['projectId'] && changes['projectId'].currentValue) {
this.refresh();
}
}
ngOnInit(): void {
// Get system info for tag views
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then(systemInfo => this.systemInfo = systemInfo)
.catch(error => this.errorHandler.error(error));
this.lastFilteredRepoName = '';
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.REPOSITORY &&
message.state === ConfirmationState.CONFIRMED) {
let promiseLists: any[] = [];
let repoNames: string[] = message.data.split(',');
repoNames.forEach(repoName => {
promiseLists.push(this.delOperate(repoName));
});
Promise.all(promiseLists).then((item) => {
this.selectedRow = [];
this.refresh();
let st: State = this.getStateAfterDeletion();
if (!st) {
this.refresh();
} else {
this.clrLoad(st);
}
});
}
}
delOperate(repoName: string) {
let findedList = this.batchDelectionInfos.find(data => data.name === repoName);
if (this.signedCon[repoName].length !== 0) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
} else {
return toPromise<number>(this.repositoryService
.deleteRepository(repoName))
.then(
response => {
this.translateService.get('BATCH.DELETED_SUCCESS').subscribe(res => {
findedList = BathInfoChanges(findedList, res);
});
}).catch(error => {
if (error.status === "412") {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
return;
}
if (error.status === 503) {
Observable.forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'),
this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => {
findedList = BathInfoChanges(findedList, res[0], false, true, res[1]);
});
return;
}
this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => {
findedList = BathInfoChanges(findedList, res, false, true);
});
});
}
}
doSearchRepoNames(repoName: string) {
this.lastFilteredRepoName = repoName;
this.currentPage = 1;
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoad(st);
}
saveSignatures(event: {[key: string]: string[]}): void {
Object.assign(this.signedCon, event);
}
deleteRepos(repoLists: RepositoryItem[]) {
if (repoLists && repoLists.length) {
let repoNames: string[] = [];
this.batchDelectionInfos = [];
let repArr: any[] = [];
repoLists.forEach(repo => {
repoNames.push(repo.name);
let initBatchMessage = new BatchInfo();
initBatchMessage.name = repo.name;
this.batchDelectionInfos.push(initBatchMessage);
if (!this.signedCon[repo.name]) {
repArr.push(this.getTagInfo(repo.name));
}
});
Promise.all(repArr).then(() => {
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', '', repoNames.join(','), 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL);
});
}
}
getTagInfo(repoName: string): Promise<void> {
// this.signedNameArr = [];
this.signedCon[repoName] = [];
return toPromise<Tag[]>(this.tagService
.getTags(repoName))
.then(items => {
items.forEach((t: Tag) => {
if (t.signature !== null) {
this.signedCon[repoName].push(t.name);
}
});
})
.catch(error => this.errorHandler.error(error));
}
signedDataSet(repoName: string): void {
let signature = '';
if (this.signedCon[repoName].length === 0) {
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL);
return;
}
signature = this.signedCon[repoName].join(',');
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE);
}
confirmationDialogSet(summaryTitle: string, signature: string, repoName: string, summaryKey: string, button: ConfirmationButtons): void {
this.translateService.get(summaryKey,
{
repoName: repoName,
signedImages: signature,
})
.subscribe((res: string) => {
summaryKey = res;
let message = new ConfirmationMessage(
summaryTitle,
summaryKey,
repoName,
repoName,
ConfirmationTargets.REPOSITORY,
button);
this.confirmationDialog.open(message);
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000);
});
}
selectedChange(): void {
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 2000);
}
refresh() {
this.doSearchRepoNames('');
}
clrLoad(state: State): void {
this.selectedRow = [];
// Keep it for future filtering and sorting
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; }
// Pagination
let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + pageNumber);
params.set("page_size", '' + this.pageSize);
this.loading = true;
toPromise<Repository>(this.repositoryService.getRepositories(
this.projectId,
this.lastFilteredRepoName,
params))
.then((repo: Repository) => {
this.totalCount = repo.metadata.xTotalCount;
this.repositories = repo.data;
this.signedCon = {};
// Do filtering and sorting
this.repositories = doFiltering<RepositoryItem>(this.repositories, state);
this.repositories = doSorting<RepositoryItem>(this.repositories, state);
this.loading = false;
})
.catch(error => {
this.loading = false;
this.errorHandler.error(error);
});
// Force refresh view
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000);
}
getStateAfterDeletion(): State {
let total: number = this.totalCount - 1;
if (total <= 0) { return null; }
let totalPages: number = Math.ceil(total / this.pageSize);
let targetPageNumber: number = this.currentPage;
if (this.currentPage > totalPages) {
targetPageNumber = totalPages; // Should == currentPage -1
}
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = (targetPageNumber - 1) * this.pageSize;
st.page.to = targetPageNumber * this.pageSize - 1;
return st;
}
watchRepoClickEvt(repo: RepositoryItem) {
this.repoClickEvent.emit(repo);
}
}

View File

@ -1,6 +0,0 @@
import { Type } from '@angular/core';
import { RepositoryStackviewComponent } from './repository-stackview.component';
export const REPOSITORY_STACKVIEW_DIRECTIVES: Type<any>[] = [
RepositoryStackviewComponent
];

View File

@ -1,47 +0,0 @@
export const REPOSITORY_STACKVIEW_STYLES: string = `
.option-right {
padding-right: 16px;
}
.sub-grid-custom {
left: 40px;
}
.refresh-btn {
cursor: pointer;
}
.refresh-btn:hover {
color: #007CBB;
}
:host >>> .datagrid .datagrid-body {
overflow-x: hidden;
}
:host >>> .datagrid .datagrid-foot {
border-top: 1px solid #ccc;
}
:host >>> .datagrid .datagrid-body .datagrid-row {
background-color: #ccc;
}
:host >>> .datagrid-body .datagrid-row .datagrid-row-master{
background-color: #FFFFFF;
}
:host >>> .datagrid .datagrid-placeholder-container {
display: none;
}
:host >>> .datagrid-overlay-wrapper{margin-top:24px;}
.db-status-warning {
position: absolute;
left: 24px;
display: inline-block;
}
.rightPos{
position: absolute;
z-index: 100;
right: 35px;
margin-top: 4px;
}
`;

View File

@ -1,42 +0,0 @@
export const REPOSITORY_STACKVIEW_TEMPLATE: string = `
<div>
<div class="row" style="margin-top: 20px; position: relative;">
<div>
<div class="row flex-items-xs-right rightPos">
<div class="flex-xs-middle">
<hbr-push-image-button style="display: inline-block;" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filter)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
<span class="refresh-btn" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></span>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid (clrDgRefresh)="clrLoad($event)" [clrDgLoading]="loading">
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="pullCountComparator">{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPOSITORY.PLACEHOLDER' | translate}}</clr-dg-placeholder>
<clr-dg-row *ngFor="let r of repositories">
<clr-dg-action-overflow [hidden]="!hasProjectAdminRole">
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-overflow>
<clr-dg-cell>{{r.name}}</clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
<hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)" class="sub-grid-custom" [repoName]="r.name" [registryUrl]="registryUrl" [withNotary]="withNotary" [withClair]="withClair" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="true"></hbr-tag>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="showDBStatusWarning" class="db-status-warning">
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}
</span>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<confirmation-dialog #confirmationDialog (confirmAction)="confirmDeletion($event)"></confirmation-dialog>
</div>
`;

View File

@ -1,224 +0,0 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { RepositoryStackviewComponent } from './repository-stackview.component';
import { TagComponent } from '../tag/tag.component';
import { FilterComponent } from '../filter/filter.component';
import { ErrorHandler } from '../error-handler/error-handler';
import {Repository, RepositoryItem, Tag, SystemInfo, Label} from '../service/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { RepositoryService, RepositoryDefaultService } from '../service/repository.service';
import { TagService, TagDefaultService } from '../service/tag.service';
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
import { click } from '../utils';
import {LabelPieceComponent} from "../label-piece/label-piece.component";
import {LabelDefaultService, LabelService} from "../service/label.service";
describe('RepositoryComponentStackview (inline template)', () => {
let compRepo: RepositoryStackviewComponent;
let fixtureRepo: ComponentFixture<RepositoryStackviewComponent>;
let repositoryService: RepositoryService;
let tagService: TagService;
let labelService: LabelService;
let systemInfoService: SystemInfoService;
let spyRepos: jasmine.Spy;
let spyTags: jasmine.Spy;
let spyLabels: jasmine.Spy;
let spySystemInfo: jasmine.Spy;
let mockSystemInfo: SystemInfo = {
"with_notary": true,
"with_admiral": false,
"admiral_endpoint": "NA",
"auth_mode": "db_auth",
"registry_url": "10.112.122.56",
"project_creation_restriction": "everyone",
"self_registration": true,
"has_ca_root": false,
"harbor_version": "v1.1.1-rc1-160-g565110d"
};
let mockRepoData: RepositoryItem[] = [
{
"id": 1,
"name": "library/busybox",
"project_id": 1,
"description": "",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
},
{
"id": 2,
"name": "library/nginx",
"project_id": 1,
"description": "",
"pull_count": 0,
"star_count": 0,
"tags_count": 1
}
];
let mockRepo: Repository = {
metadata: {xTotalCount: 2},
data: mockRepoData
};
let mockTagData: Tag[] = [
{
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
"name": "1.11.5",
"size": "2049",
"architecture": "amd64",
"os": "linux",
"docker_version": "1.12.3",
"author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
"created": new Date("2016-11-08T22:41:15.912313785Z"),
"signature": null,
"labels": []
}
];
let mockLabels: Label[] = [
{
color: "#9b0d54",
creation_time: "",
description: "",
id: 1,
name: "label0-g",
project_id: 0,
scope: "g",
update_time: "",
},
{
color: "#9b0d54",
creation_time: "",
description: "",
id: 2,
name: "label1-g",
project_id: 0,
scope: "g",
update_time: "",
}
];
let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing',
systemInfoEndpoint: '/api/systeminfo/testing',
targetBaseEndpoint: '/api/tag/testing'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule
],
declarations: [
RepositoryStackviewComponent,
TagComponent,
LabelPieceComponent,
ConfirmationDialogComponent,
FilterComponent,
VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES,
INLINE_ALERT_DIRECTIVES,
JobLogViewerComponent
],
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: TagService, useClass: TagDefaultService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService },
{provide: LabelService, useClass: LabelDefaultService}
]
});
}));
beforeEach(() => {
fixtureRepo = TestBed.createComponent(RepositoryStackviewComponent);
compRepo = fixtureRepo.componentInstance;
compRepo.projectId = 1;
compRepo.hasProjectAdminRole = true;
repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
systemInfoService = fixtureRepo.debugElement.injector.get(SystemInfoService);
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
labelService = fixtureRepo.debugElement.injector.get(LabelService);
spyLabels = spyOn(labelService, 'getLabels').and.returnValues(Promise.resolve(mockLabels));
fixtureRepo.detectChanges();
});
it('should create', () => {
expect(compRepo).toBeTruthy();
});
it('should load and render data', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell'));
expect(deRepo).toBeTruthy();
let elRepo: HTMLElement = deRepo.nativeElement;
expect(elRepo).toBeTruthy();
expect(elRepo.textContent).toEqual('library/busybox');
});
}));
it('should filter data by keyword', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
compRepo.doSearchRepoNames('nginx');
fixtureRepo.detectChanges();
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell'));
expect(de).toBeTruthy();
expect(de.length).toEqual(1);
let el: HTMLElement = de[0].nativeElement;
expect(el).toBeTruthy();
expect(el.textContent).toEqual('library/nginx');
});
}));
it('should display embedded tag view when click >', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
let el: HTMLElement = fixtureRepo.nativeElement.querySelector('.datagrid-expandable-caret');
expect(el).toBeTruthy();
let button: HTMLButtonElement = el.querySelector('button');
expect(button).toBeTruthy();
click(button);
fixtureRepo.detectChanges();
let el2: HTMLElement = fixtureRepo.nativeElement.querySelector('.datagrid-row-detail');
expect(el2).toBeTruthy();
let el3: Element = el2.querySelector(".datagrid-cell");
expect(el3).toBeTruthy();
expect(el3.textContent).toEqual('1.11.5');
});
}));
});

View File

@ -1,298 +0,0 @@
import {
Component,
Input,
Output,
OnInit,
ViewChild,
ChangeDetectionStrategy,
ChangeDetectorRef,
EventEmitter, OnChanges, SimpleChanges
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Comparator } from 'clarity-angular';
import { REPOSITORY_STACKVIEW_TEMPLATE } from './repository-stackview.component.html';
import { REPOSITORY_STACKVIEW_STYLES } from './repository-stackview.component.css';
import {
Repository,
SystemInfo,
SystemInfoService,
RepositoryService,
RequestQueryParams,
RepositoryItem
} from '../service/index';
import { ErrorHandler } from '../error-handler/error-handler';
import { toPromise, CustomComparator } from '../utils';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
import { Subscription } from 'rxjs/Subscription';
import { Tag, TagClickEvent } from '../service/interface';
import { State } from "clarity-angular";
import {
DEFAULT_PAGE_SIZE,
calculatePage,
doFiltering,
doSorting
} from '../utils';
import {TagService} from '../service/index';
@Component({
selector: 'hbr-repository-stackview',
template: REPOSITORY_STACKVIEW_TEMPLATE,
styles: [REPOSITORY_STACKVIEW_STYLES],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RepositoryStackviewComponent implements OnChanges, OnInit {
signedCon: {[key: string]: any | string[]} = {};
@Input() projectId: number;
@Input() projectName: string = "unknown";
@Input() hasSignedIn: boolean;
@Input() hasProjectAdminRole: boolean;
@Output() tagClickEvent = new EventEmitter<TagClickEvent>();
lastFilteredRepoName: string;
repositories: RepositoryItem[];
systemInfo: SystemInfo;
loading: boolean = true;
@ViewChild('confirmationDialog')
confirmationDialog: ConfirmationDialogComponent;
pullCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('pull_count', 'number');
tagsCountComparator: Comparator<RepositoryItem> = new CustomComparator<RepositoryItem>('tags_count', 'number');
pageSize: number = DEFAULT_PAGE_SIZE;
currentPage: number = 1;
totalCount: number = 0;
currentState: State;
constructor(
private errorHandler: ErrorHandler,
private translateService: TranslateService,
private repositoryService: RepositoryService,
private systemInfoService: SystemInfoService,
private translate: TranslateService,
private tagService: TagService,
private ref: ChangeDetectorRef) { }
public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : "";
}
public get withNotary(): boolean {
return this.systemInfo ? this.systemInfo.with_notary : false;
}
public get withClair(): boolean {
return this.systemInfo ? this.systemInfo.with_clair : false;
}
public get isClairDBReady(): boolean {
return this.systemInfo &&
this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
}
public get showDBStatusWarning(): boolean {
return this.withClair && !this.isClairDBReady;
}
confirmDeletion(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.REPOSITORY &&
message.state === ConfirmationState.CONFIRMED) {
let repoName = message.data;
toPromise<number>(this.repositoryService
.deleteRepository(repoName))
.then(
response => {
this.refresh();
let st: State = this.getStateAfterDeletion();
if (!st) {
this.refresh();
} else {
this.clrLoad(st);
}
this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS')
.subscribe(res => this.errorHandler.info(res));
}).catch(error => {
if (error.status === "412"){
this.translateService.get('REPOSITORY.TAGS_SIGNED')
.subscribe(res => this.errorHandler.info(res));
return;
}
this.errorHandler.error(error);
});
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['projectId'] && changes['projectId'].currentValue) {
this.refresh();
}
}
ngOnInit(): void {
//Get system info for tag views
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then(systemInfo => this.systemInfo = systemInfo)
.catch(error => this.errorHandler.error(error));
this.lastFilteredRepoName = '';
}
doSearchRepoNames(repoName: string) {
this.lastFilteredRepoName = repoName;
this.currentPage = 1;
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoad(st);
}
saveSignatures(event: {[key: string]: string[]}): void {
Object.assign(this.signedCon, event);
}
deleteRepo(repoName: string) {
if (this.signedCon[repoName]) {
this.signedDataSet(repoName);
} else {
this.getTagInfo(repoName).then(() => {
this.signedDataSet(repoName);
});
}
}
getTagInfo(repoName: string): Promise<void> {
// this.signedNameArr = [];
this.signedCon[repoName] = [];
return toPromise<Tag[]>(this.tagService
.getTags(repoName))
.then(items => {
items.forEach((t: Tag) => {
if (t.signature !== null) {
this.signedCon[repoName].push(t.name);
}
});
})
.catch(error => this.errorHandler.error(error));
}
signedDataSet(repoName: string): void {
let signature: string = '';
if (this.signedCon[repoName].length === 0) {
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL);
return;
}
signature = this.signedCon[repoName].join(',');
this.confirmationDialogSet('REPOSITORY.DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE);
}
confirmationDialogSet(summaryTitle: string, signature: string, repoName: string, summaryKey: string, button: ConfirmationButtons): void {
this.translate.get(summaryKey,
{
repoName: repoName,
signedImages: signature,
})
.subscribe((res: string) => {
summaryKey = res;
let message = new ConfirmationMessage(
summaryTitle,
summaryKey,
repoName,
repoName,
ConfirmationTargets.REPOSITORY,
button);
this.confirmationDialog.open(message);
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000);
});
}
refresh() {
this.doSearchRepoNames("");
}
watchTagClickEvt(tagClickEvt: TagClickEvent): void {
this.tagClickEvent.emit(tagClickEvt);
}
clrLoad(state: State): void {
//Keep it for future filtering and sorting
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; }
//Pagination
let params: RequestQueryParams = new RequestQueryParams();
params.set("page", '' + pageNumber);
params.set("page_size", '' + this.pageSize);
this.loading = true;
toPromise<Repository>(this.repositoryService.getRepositories(
this.projectId,
this.lastFilteredRepoName,
params))
.then((repo: Repository) => {
this.totalCount = repo.metadata.xTotalCount;
this.repositories = repo.data;
this.signedCon = {};
//Do filtering and sorting
this.repositories = doFiltering<RepositoryItem>(this.repositories, state);
this.repositories = doSorting<RepositoryItem>(this.repositories, state);
this.loading = false;
})
.catch(error => {
this.loading = false;
this.errorHandler.error(error);
});
//Force refresh view
let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(() => clearInterval(hnd), 5000);
}
getStateAfterDeletion(): State {
let total: number = this.totalCount - 1;
if (total <= 0) { return null; }
let totalPages: number = Math.ceil(total / this.pageSize);
let targetPageNumber: number = this.currentPage;
if (this.currentPage > totalPages) {
targetPageNumber = totalPages;//Should == currentPage -1
}
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = (targetPageNumber - 1) * this.pageSize;
st.page.to = targetPageNumber * this.pageSize - 1;
return st;
}
}

View File

@ -1,4 +1,3 @@
export const REPOSITORY_TEMPLATE = `
<section class="overview-section">
<div class="title-wrapper">
<div class="title-block arrow-block" *ngIf="withAdmiral">
@ -50,5 +49,4 @@ export const REPOSITORY_TEMPLATE = `
</div>
</section>
</div>
</section>
`;
</section>

View File

@ -1,4 +1,4 @@
export const REPOSITORY_STYLE = `.option-right {
.option-right {
padding-right: 16px;
margin-bottom: 12px;
}
@ -56,5 +56,4 @@ export const REPOSITORY_STYLE = `.option-right {
harbor-tag {
position: relative;
top: 24px;
}
`;
}

View File

@ -6,7 +6,8 @@ import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from '../shared/shared.module';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
import { RepositoryComponent } from './repository.component';
import { RepositoryListviewComponent } from '../repository-listview/repository-listview.component';
import { RepositoryGridviewComponent } from '../repository-gridview/repository-gridview.component';
import { GridViewComponent } from '../gridview/grid-view.component';
import { FilterComponent } from '../filter/filter.component';
import { TagComponent } from '../tag/tag.component';
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
@ -154,7 +155,8 @@ describe('RepositoryComponent (inline template)', () => {
],
declarations: [
RepositoryComponent,
RepositoryListviewComponent,
GridViewComponent,
RepositoryGridviewComponent,
ConfirmationDialogComponent,
FilterComponent,
TagComponent,

View File

@ -23,9 +23,6 @@ import { ConfirmationState, ConfirmationTargets } from '../shared/shared.const';
import { ConfirmationDialogComponent, ConfirmationMessage, ConfirmationAcknowledgement } from '../confirmation-dialog/index';
import { toPromise } from '../utils';
import { REPOSITORY_TEMPLATE } from './repository.component.html';
import { REPOSITORY_STYLE } from './repository.component.css';
const TabLinkContentMap: {[index: string]: string} = {
'repo-info': 'info',
'repo-image': 'image'
@ -33,8 +30,8 @@ const TabLinkContentMap: {[index: string]: string} = {
@Component({
selector: 'hbr-repository',
template: REPOSITORY_TEMPLATE,
styles: [REPOSITORY_STYLE]
templateUrl: './repository.component.html',
styleUrls: ['./repository.component.scss']
})
export class RepositoryComponent implements OnInit {
signedCon: {[key: string]: any | string[]} = {};

View File

@ -1,4 +1,3 @@
export const TAG_DETAIL_HTML: string = `
<div>
<section class="overview-section">
<div class="title-wrapper">
@ -72,5 +71,4 @@ export const TAG_DETAIL_HTML: string = `
<ng-content></ng-content>
</div>
</section>
</div>
`;
</div>

View File

@ -1,4 +1,3 @@
export const TAG_DETAIL_STYLES: string = `
.overview-section {
padding-bottom: 36px;
}
@ -124,6 +123,4 @@ margin-left:20px;}
}
.second-column div, .fourth-column div, .image-detail-value div{
height: 24px;
}
`;
}

View File

@ -1,8 +1,5 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { TAG_DETAIL_STYLES } from './tag-detail.component.css';
import { TAG_DETAIL_HTML } from './tag-detail.component.html';
import { TagService, Tag, VulnerabilitySeverity } from '../service/index';
import { toPromise } from '../utils';
import { ErrorHandler } from '../error-handler/index';
@ -10,8 +7,8 @@ import {Label} from "../service/interface";
@Component({
selector: 'hbr-tag-detail',
styles: [TAG_DETAIL_STYLES],
template: TAG_DETAIL_HTML,
templateUrl: './tag-detail.component.html',
styleUrls: ['./tag-detail.component.scss'],
providers: []
})

Some files were not shown because too many files have changed in this diff Show More