Merge pull request #4899 from ninjadq/mv_string_to_html_n_css

Fix legacy issues that html and css file are written on ts file.
This commit is contained in:
Steven Zou 2018-05-11 15:22:37 +08:00 committed by GitHub
commit 9b01d3a2f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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