mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-27 19:17:47 +01:00
Update project summary page (#14874)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
e553cbe795
commit
0a8ff4c1f9
@ -1,18 +1,15 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SignInComponent } from './sign-in.component';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { SessionService } from '../../shared/services/session.service';
|
||||
import { CookieService } from 'ngx-cookie';
|
||||
import { SkinableConfig } from "../../services/skinable-config.service";
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { of } from "rxjs";
|
||||
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { SharedTestingModule } from "../../shared/shared.module";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
|
||||
describe('SignInComponent', () => {
|
||||
let component: SignInComponent;
|
||||
@ -20,21 +17,23 @@ describe('SignInComponent', () => {
|
||||
const mockedSessionService = {
|
||||
signIn() {
|
||||
return of(true);
|
||||
},
|
||||
getCurrentUser() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
const mockedUserPermissionService = {
|
||||
clearPermissionCache() {
|
||||
}
|
||||
};
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
RouterTestingModule,
|
||||
ClarityModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule
|
||||
SharedTestingModule
|
||||
],
|
||||
declarations: [SignInComponent],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{ provide: UserPermissionService, useValue: mockedUserPermissionService},
|
||||
{ provide: SessionService, useValue: mockedSessionService},
|
||||
{
|
||||
provide: AppConfigService, useValue: {
|
||||
@ -42,6 +41,11 @@ describe('SignInComponent', () => {
|
||||
return of({
|
||||
|
||||
});
|
||||
},
|
||||
isIntegrationMode() {
|
||||
},
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -28,6 +28,7 @@ import {modalEvents} from "../../base/modal-events.const";
|
||||
import {AboutDialogComponent} from "../../shared/components/about-dialog/about-dialog.component";
|
||||
import { CommonRoutes, CONFIG_AUTH_MODE } from "../../shared/entities/shared.const";
|
||||
import { SignInCredential } from "./sign-in-credential";
|
||||
import { UserPermissionService } from "../../shared/services";
|
||||
|
||||
// Define status flags for signing in states
|
||||
export const signInStatusNormal = 0;
|
||||
@ -74,7 +75,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private appConfigService: AppConfigService,
|
||||
private cookie: CookieService,
|
||||
private skinableConfig: SkinableConfig) { }
|
||||
private skinableConfig: SkinableConfig,
|
||||
private userPermissionService: UserPermissionService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// custom skin
|
||||
@ -244,7 +246,8 @@ export class SignInComponent implements AfterViewChecked, OnInit {
|
||||
// Set status
|
||||
// Keep it ongoing to keep the button 'disabled'
|
||||
// this.signInStatus = signInStatusNormal;
|
||||
|
||||
// clear permissions cache
|
||||
this.userPermissionService.clearPermissionCache();
|
||||
// Remeber me
|
||||
this.remeberMe();
|
||||
|
||||
|
@ -69,7 +69,7 @@ export class StatisticsPanelComponent implements OnInit, OnDestroy {
|
||||
if (this.originalCopy) {
|
||||
return getSizeNumber(this.originalCopy.total_storage_consumption);
|
||||
}
|
||||
return null;
|
||||
return 0;
|
||||
}
|
||||
getSizeUnit(): number | string {
|
||||
if (this.originalCopy) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
<div>
|
||||
<div class="breadcrumb">
|
||||
<a (click)="gotoProjectList()"> {{ 'SIDE_NAV.PROJECTS'| translate}} </a>
|
||||
<
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="gotoProjectList()">{{ 'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="gotoChartList()">{{ projectName }}</a>
|
||||
<
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="gotoChartVersion()">{{ 'HELM_CHART.CHARTVERSIONS'| translate}}</a>
|
||||
</div>
|
||||
<hbr-chart-detail [projectId]="projectId" [project]="project" [chartName]="chartName" [chartVersion]="chartVersion"
|
||||
|
@ -3,7 +3,12 @@
|
||||
cursor: pointer;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
margin: 5px;
|
||||
}
|
||||
.breadcrumb {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.back-icon {
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
<div>
|
||||
<div class="breadcrumb">
|
||||
<a href="javascript:void(0)" (click)="gotoProjectList()"> {{ 'SIDE_NAV.PROJECTS'| translate}} </a>
|
||||
<
|
||||
<span class="back-icon"><</span>
|
||||
<a href="javascript:void(0)" (click)="gotoProjectList()">{{ 'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<span class="back-icon"><</span>
|
||||
<a href="javascript:void(0)" (click)="gotoChartList()">{{ projectName }}</a>
|
||||
</div>
|
||||
<hbr-helm-chart-version
|
||||
|
@ -3,7 +3,12 @@
|
||||
cursor: pointer;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
margin: 5px;
|
||||
}
|
||||
.breadcrumb {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.back-icon {
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -1,13 +1,58 @@
|
||||
<a *ngIf="hasSignedIn" (click)="backToProject()" class="backStyle"> {{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<span class="back-icon"><</span>
|
||||
<a *ngIf="hasSignedIn" (click)="backToProject()" class="backStyle">{{'PROJECT_DETAIL.PROJECTS' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']"> {{'SEARCH.BACK' | translate}}</a>
|
||||
|
||||
<h1 class="custom-h2" sub-header-title>
|
||||
<clr-icon *ngIf="isProxyCacheProject" shape="cloud-traffic" size="30"></clr-icon>
|
||||
<span class="ml-05">{{currentProject.name}}</span>
|
||||
<span class="ml-05 role-label" *ngIf="isMember">{{roleName | translate}}</span>
|
||||
</h1>
|
||||
<div class="clr-row mt-0 line-height-10" *ngIf="isProxyCacheProject">
|
||||
<span class="proxy-cache">{{ 'PROJECT.PROXY_CACHE' | translate }}</span>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4">
|
||||
<h1 class="custom-h2 center" sub-header-title>
|
||||
<clr-icon *ngIf="isProxyCacheProject" shape="cloud-traffic" size="30"></clr-icon>
|
||||
<clr-icon *ngIf="!isProxyCacheProject" shape="organization" size="30"></clr-icon>
|
||||
<span class="ml-05">{{currentProject.name}}</span>
|
||||
<div class="divider filter-divider"></div>
|
||||
<span class="role-label" *ngIf="isMember">{{roleName | translate}}</span>
|
||||
</h1>
|
||||
<div class="clr-row mt-0 line-height-10" *ngIf="isProxyCacheProject">
|
||||
<span class="proxy-cache">{{ 'PROJECT.PROXY_CACHE' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-col-8 flex-end">
|
||||
<div class="card">
|
||||
<div class="card-block container">
|
||||
<div class="head">{{'PROJECT.ACCESS_LEVEL' | translate}}</div>
|
||||
<div class="storage-used font-weight-700">
|
||||
<div>
|
||||
<h3 class="mt-0">{{ (currentProject?.metadata?.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" *ngIf="hasQuotaReadPermission">
|
||||
<div class="card-block container">
|
||||
<div class="head">{{'STATISTICS.STORAGE_USED' | translate }}</div>
|
||||
<div class="storage-used font-weight-700">
|
||||
<div *ngIf="projectQuota">
|
||||
<h3 class="mt-0 clr-display-inline-block">
|
||||
<span class="size-number">{{getSizeNumber()}}</span>
|
||||
<span *ngIf="getSizeNumber()">{{getSizeUnit()}}</span>
|
||||
</h3>
|
||||
<span class="of">{{ 'QUOTA.OF' | translate }}</span>
|
||||
<span>{{ projectQuota?.hard?.storage ===-1? ('QUOTA.UNLIMITED' | translate) : getIntegerAndUnit(projectQuota?.hard?.storage, projectQuota?.used?.storage).partNumberHard }}</span>
|
||||
<span>{{ projectQuota?.hard?.storage ===-1? '': getIntegerAndUnit(projectQuota?.hard?.storage, projectQuota?.used?.storage).partCharacterHard }}</span>
|
||||
<div>
|
||||
<div class="progress-block progress-min-width progress-div">
|
||||
<div class="progress success"
|
||||
[class.danger]="projectQuota?.hard?.storage!==-1?projectQuota?.used?.storage/projectQuota?.hard?.storage>quotaDangerCoefficient:false"
|
||||
[class.warning]="projectQuota?.hard?.storage!==-1?projectQuota?.used?.storage/projectQuota?.hard?.storage<=quotaDangerCoefficient&&projectQuota?.used?.storage/projectQuota?.hard?.storage>=quotaWarningCoefficient:false">
|
||||
<progress
|
||||
value="{{projectQuota?.hard?.storage===-1? 0 : projectQuota?.used?.storage}}"
|
||||
max="{{projectQuota?.hard?.storage}}" data-displayval="100%"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-tabs id="project-tabs" class="tabs" [class.in-overflow]="isTabLinkInOverFlow()">
|
||||
<ng-container *ngFor="let tab of tabLinkNavList;let i=index">
|
||||
|
@ -12,10 +12,10 @@
|
||||
}
|
||||
|
||||
.role-label {
|
||||
color: #cccccc;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
letter-spacing: 0.01em;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.backStyle {
|
||||
color: #007cbb;
|
||||
@ -54,3 +54,83 @@ button {
|
||||
.line-height-10 {
|
||||
line-height: 10px;
|
||||
}
|
||||
.back-icon {
|
||||
margin-right: 5px;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
}
|
||||
.flex-end {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.card{
|
||||
width: auto;
|
||||
min-width: 10rem;
|
||||
margin-right: 0.5rem;
|
||||
margin-top: 0;
|
||||
height: 6rem;
|
||||
}
|
||||
.head {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 18px;
|
||||
}
|
||||
.font-weight-700 {
|
||||
font-weight: 700;
|
||||
}
|
||||
.storage-used {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 8rem;
|
||||
}
|
||||
.size-number {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.margin-right-5px {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.divider {
|
||||
height: 24px;
|
||||
width: 1px;
|
||||
margin-right: 24px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
|
||||
.progress,
|
||||
.progress-static {
|
||||
progress {
|
||||
max-height: 0.48rem;
|
||||
}
|
||||
}
|
||||
:host::ng-deep {
|
||||
.progress {
|
||||
&.warning>progress {
|
||||
color: orange;
|
||||
|
||||
&::-webkit-progress-value {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.of {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ import { UserPermissionService, USERSTATICPERMISSION } from "../../../shared/ser
|
||||
import { ErrorHandler } from "../../../shared/units/error-handler";
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { DOWN, SHOW_ELLIPSIS_WIDTH, UP } from './project-detail.const';
|
||||
import { ProjectService } from "../../../../../ng-swagger-gen/services/project.service";
|
||||
import { ProjectSummaryQuota } from "../../../../../ng-swagger-gen/models/project-summary-quota";
|
||||
import { QUOTA_DANGER_COEFFICIENT, QUOTA_WARNING_COEFFICIENT, QuotaUnits } from "../../../shared/entities/shared.const";
|
||||
import { clone, GetIntegerAndUnit, getSizeNumber, getSizeUnit } from "../../../shared/units/utils";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -48,6 +52,7 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
hasWebhookListPermission: boolean;
|
||||
hasScannerReadPermission: boolean;
|
||||
hasP2pProviderReadPermission: boolean;
|
||||
hasQuotaReadPermission: boolean = false;
|
||||
tabLinkNavList = [
|
||||
{
|
||||
linkName: "summary",
|
||||
@ -126,7 +131,11 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
private _subject = new Subject<string>();
|
||||
private _subscription: Subscription;
|
||||
isProxyCacheProject: boolean = false;
|
||||
projectQuota: ProjectSummaryQuota;
|
||||
quotaDangerCoefficient: number = QUOTA_DANGER_COEFFICIENT;
|
||||
quotaWarningCoefficient: number = QUOTA_WARNING_COEFFICIENT;
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private sessionService: SessionService,
|
||||
@ -202,14 +211,30 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
USERSTATICPERMISSION.SCANNER.KEY, USERSTATICPERMISSION.SCANNER.VALUE.READ));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.P2P_PROVIDER.KEY, USERSTATICPERMISSION.P2P_PROVIDER.VALUE.READ));
|
||||
permissionsList.push(this.userPermissionService.getPermission(projectId,
|
||||
USERSTATICPERMISSION.QUOTA.KEY, USERSTATICPERMISSION.QUOTA.VALUE.READ));
|
||||
|
||||
forkJoin(...permissionsList).subscribe(Rules => {
|
||||
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
|
||||
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
|
||||
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission,
|
||||
this.hasScannerReadPermission, this.hasP2pProviderReadPermission] = Rules;
|
||||
this.hasScannerReadPermission, this.hasP2pProviderReadPermission, this.hasQuotaReadPermission] = Rules;
|
||||
if (this.hasQuotaReadPermission) {
|
||||
this.getQuotaInfo();
|
||||
}
|
||||
}, error => this.errorHandler.error(error));
|
||||
}
|
||||
getQuotaInfo() {
|
||||
this.projectService.getProjectSummary({
|
||||
projectNameOrId: this.projectId.toString()
|
||||
}).subscribe(res => {
|
||||
if (res && res.quota) {
|
||||
this.projectQuota = res.quota;
|
||||
}
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
public get isSessionValid(): boolean {
|
||||
return this.sessionService.getCurrentUser() != null;
|
||||
@ -274,4 +299,20 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
// 3. Hide overflowed tabs
|
||||
this.resetTabsForDownSize();
|
||||
}
|
||||
getIntegerAndUnit(hardValue, usedValue) {
|
||||
return GetIntegerAndUnit(hardValue, clone(QuotaUnits), usedValue, clone(QuotaUnits));
|
||||
}
|
||||
|
||||
getSizeNumber(): number | string {
|
||||
if (this.projectQuota && this.projectQuota.used && this.projectQuota.used.storage) {
|
||||
return getSizeNumber(this.projectQuota.used.storage);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
getSizeUnit(): number | string {
|
||||
if (this.projectQuota) {
|
||||
return getSizeUnit(this.projectQuota.used.storage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@
|
||||
|
||||
<clr-dg-footer>
|
||||
<div class="report">
|
||||
<i>{{'VULNERABILITY.REPORTED_BY' | translate: {scanner: getScannerInfo(scanner)} }}</i>
|
||||
<i *ngIf="scanner">{{'VULNERABILITY.REPORTED_BY' | translate: {scanner: getScannerInfo(scanner)} }}</i>
|
||||
</div>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="25" [clrDgTotalItems]="scanningResults?.length">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<div class="breadcrumb" *ngIf="!withAdmiral">
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="goProBack()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="watchGoBackEvt(projectId)">{{projectName}}</a>
|
||||
|
@ -8,5 +8,6 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
.back-icon {
|
||||
color: gray;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div class="arrow-block" *ngIf="!withAdmiral">
|
||||
<span class="back-icon margin-right-5px"><</span>
|
||||
<a class="pl-0" (click)="goBackPro()">{{'SIDE_NAV.PROJECTS'| translate}}</a>
|
||||
<span class="back-icon"><</span>
|
||||
<a (click)="goBackRep()">{{projectName}}</a>
|
||||
|
@ -7,7 +7,8 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
.back-icon {
|
||||
color: gray;
|
||||
color: #007cbb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.margin-top-5px {
|
||||
@ -28,3 +29,6 @@
|
||||
.margin-left-10px {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.margin-right-5px {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import {
|
||||
CARD_VIEW_LOCALSTORAGE_KEY,
|
||||
ConfirmationButtons,
|
||||
ConfirmationState,
|
||||
ConfirmationTargets
|
||||
ConfirmationTargets, FALSE_STR, TRUE_STR
|
||||
} from "../../../shared/entities/shared.const";
|
||||
import { operateChanges, OperateInfo, OperationState } from "../../../shared/components/operation/operate";
|
||||
import {
|
||||
@ -54,8 +54,6 @@ import { errorHandler } from "../../../shared/units/shared.utils";
|
||||
import { ConfirmationAcknowledgement } from "../../global-confirmation-dialog/confirmation-state-message";
|
||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||
|
||||
const TRUE_STR: string = 'true';
|
||||
const FALSE_STR: string = 'false';
|
||||
@Component({
|
||||
selector: "hbr-repository-gridview",
|
||||
templateUrl: "./repository-gridview.component.html",
|
||||
|
@ -1,64 +1,138 @@
|
||||
<div class="summary summary-dark display-flex" *ngIf="summaryInformation">
|
||||
<div class="summary-left">
|
||||
<div class="display-flex project-detail pt-05" *ngIf="summaryInformation?.registry">
|
||||
<h5 class="mt-0 width-7-5">{{'PROJECT.PROXY_CACHE_ENDPOINT' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li id="endpoint">{{summaryInformation?.registry?.name}}-{{summaryInformation?.registry?.url}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="display-flex project-detail pt-05">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_REPOSITORY' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.repo_count}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="display-flex project-detail pt-05" *ngIf="withHelmChart">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_HELM_CHART' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.chart_count}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="showProjectMemberInfo" class="display-flex project-detail pt-05">
|
||||
<h5 class="mt-0 width-7-5">{{'SUMMARY.PROJECT_MEMBER' | translate}}</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ summaryInformation?.project_admin_count?summaryInformation?.project_admin_count:0 }} {{'SUMMARY.ADMIN' | translate}}</li>
|
||||
<li>{{ summaryInformation?.maintainer_count?summaryInformation?.maintainer_count:0 }} {{'SUMMARY.MAINTAINER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.developer_count?summaryInformation?.developer_count:0 }} {{'SUMMARY.DEVELOPER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.guest_count?summaryInformation?.guest_count:0 }} {{'SUMMARY.GUEST' | translate}}</li>
|
||||
<li>{{ summaryInformation?.limited_guest_count?summaryInformation?.limited_guest_count:0 }} {{'SUMMARY.LIMITED_GUEST' | translate}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showQuotaInfo && summaryInformation?.quota" class="summary-right pt-05">
|
||||
<div class="display-flex project-detail">
|
||||
<h5 class="mt-0">{{'SUMMARY.PROJECT_QUOTAS' | translate}}</h5>
|
||||
<div class="ml-1">
|
||||
<div class="display-flex quotas-progress">
|
||||
<label class="mr-1">{{'SUMMARY.STORAGE_CONSUMPTION' | translate}}</label>
|
||||
<label class="progress-label">
|
||||
{{ summaryInformation?.quota?.hard?.storage !== -1 ?(getIntegerAndUnit(summaryInformation?.quota?.hard?.storage, summaryInformation?.quota?.used?.storage).partNumberUsed
|
||||
+ getIntegerAndUnit(summaryInformation?.quota?.hard?.storage, summaryInformation?.quota?.used?.storage).partCharacterUsed) : getSuitableUnit(summaryInformation?.quota?.used?.storage)}}
|
||||
|
||||
<!-- {{ getSuitableUnit(summaryInformation?.quota?.used?.storage) }} -->
|
||||
{{ 'QUOTA.OF' | translate }}
|
||||
{{ summaryInformation?.quota?.hard?.storage ===-1? ('QUOTA.UNLIMITED' | translate) : getIntegerAndUnit(summaryInformation?.quota?.hard?.storage, summaryInformation?.quota?.used?.storage).partNumberHard }}
|
||||
{{ summaryInformation?.quota?.hard?.storage ===-1? '': getIntegerAndUnit(summaryInformation?.quota?.hard?.storage, summaryInformation?.quota?.used?.storage).partCharacterHard }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="progress-block progress-min-width progress-div">
|
||||
<div class="progress success"
|
||||
[class.danger]="summaryInformation?.quota?.hard?.storage!==-1?summaryInformation?.quota?.used?.storage/summaryInformation?.quota?.hard?.storage>quotaDangerCoefficient:false"
|
||||
[class.warning]="summaryInformation?.quota?.hard?.storage!==-1?summaryInformation?.quota?.used?.storage/summaryInformation?.quota?.hard?.storage<=quotaDangerCoefficient&&summaryInformation?.quota?.used?.storage/summaryInformation?.quota?.hard?.storage>=quotaWarningCoefficient:false">
|
||||
<progress
|
||||
value="{{summaryInformation?.quota?.hard?.storage===-1? 0 : summaryInformation?.quota?.used?.storage}}"
|
||||
max="{{summaryInformation?.quota?.hard?.storage}}" data-displayval="100%"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row card-row mt-1">
|
||||
<span class="card-btn mr-5px" (click)="showCard(true)" (mouseenter)="mouseEnter('card') "
|
||||
(mouseleave)="mouseLeave('card')">
|
||||
<clr-icon size="24" [ngClass]="{'is-highlight': isCardView || isHovering('card') }"
|
||||
shape="view-cards"></clr-icon>
|
||||
</span>
|
||||
<span class="list-btn" (click)="showCard(false)" (mouseenter)="mouseEnter('list') "
|
||||
(mouseleave)="mouseLeave('list')">
|
||||
<clr-icon size="24" [ngClass]="{'is-highlight': !isCardView || isHovering('list') }"
|
||||
shape="view-list"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div *ngIf="!summaryInformation" class="clr-row mt-2 center">
|
||||
<span class="spinner spinner-md"></span>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!isCardView">
|
||||
<div *ngIf="summaryInformation">
|
||||
<div class="pt-05 flex" *ngIf="summaryInformation?.registry">
|
||||
<h4 class="mt-0 title-width">{{'PROJECT.PROXY_CACHE_ENDPOINT' | translate}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li id="endpoint">{{summaryInformation?.registry?.name}}-{{summaryInformation?.registry?.url}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pt-05 flex">
|
||||
<h4 class="mt-0 title-width">{{'SUMMARY.PROJECT_REPOSITORY' | translate}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.repo_count ? summaryInformation?.repo_count : 0}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pt-05 flex" *ngIf="withHelmChart">
|
||||
<h4 class="mt-0 title-width">{{'SUMMARY.PROJECT_HELM_CHART' | translate}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{summaryInformation?.chart_count ? summaryInformation?.chart_count : 0}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="showProjectMemberInfo" class="pt-05 flex">
|
||||
<h4 class="mt-0 title-width">{{'SUMMARY.PROJECT_MEMBER' | translate}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{ summaryInformation?.project_admin_count ? summaryInformation?.project_admin_count : 0 }} {{'SUMMARY.ADMIN' | translate}}</li>
|
||||
<li>{{ summaryInformation?.maintainer_count ? summaryInformation?.maintainer_count : 0 }} {{'SUMMARY.MAINTAINER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.developer_count ? summaryInformation?.developer_count : 0 }} {{'SUMMARY.DEVELOPER' | translate}}</li>
|
||||
<li>{{ summaryInformation?.guest_count ? summaryInformation?.guest_count : 0 }} {{'SUMMARY.GUEST' | translate}}</li>
|
||||
<li>{{ summaryInformation?.limited_guest_count ? summaryInformation?.limited_guest_count : 0 }} {{'SUMMARY.LIMITED_GUEST' | translate}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isCardView && summaryInformation">
|
||||
<div class="container">
|
||||
<div class="card clickable" *ngIf="hasReadRepoPermission">
|
||||
<div class="card-header">
|
||||
<div>{{"PROJECT_DETAIL.REPOSITORIES" | translate}}</div>
|
||||
<div class="clr-row number">{{summaryInformation?.repo_count ? summaryInformation?.repo_count : 0}}</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'REPOSITORY.NAME' | translate}}</div>
|
||||
<div class="clr-col-2 column">{{'REPOSITORY.ARTIFACTS_COUNT' | translate}}</div>
|
||||
<div class="clr-col-2 column">{{'REPOSITORY.PULL_COUNT' | translate}}</div>
|
||||
<div class="clr-col-4 column">{{'REPOSITORY.LAST_MODIFIED' | translate}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="clr-row" *ngFor="let item of repos">
|
||||
<div class="clr-col-4 ellipsis">
|
||||
<a href="javascript:void(0)" (click)="goIntoRepo(item)">{{item.name}}</a>
|
||||
</div>
|
||||
<div class="clr-col-2">{{item.artifact_count?item.artifact_count:0}}</div>
|
||||
<div class="clr-col-2">{{item.pull_count?item.pull_count:0}}</div>
|
||||
<div class="clr-col-4 ellipsis">{{item.update_time | harborDatetime: 'short'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-link" (click)="goToRepos()">{{'SUMMARY.SEE_ALL' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card clickable" *ngIf="hasReadChartPermission">
|
||||
<div class="card-header">
|
||||
<div>{{"PROJECT_DETAIL.HELMCHART" | translate}}</div>
|
||||
<div class="clr-row number">{{summaryInformation?.chart_count ? summaryInformation?.chart_count : 0}}</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'HELM_CHART.NAME' | translate}}</div>
|
||||
<div class="clr-col-2 column">{{'HELM_CHART.STATUS' | translate}}</div>
|
||||
<div class="clr-col-2 column">{{'HELM_CHART.CHARTVERSIONS' | translate}}</div>
|
||||
<div class="clr-col-4 column">{{'HELM_CHART.CREATED' | translate}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="clr-row" *ngFor="let chart of charts">
|
||||
<div class="clr-col-4 ellipsis chart-name">
|
||||
<img class="size-24 mr-5px" [src]="chart.icon ?chart.icon:chartDefaultIcon" (error)="getDefaultIcon(chart);" />
|
||||
<a href="javascript:void(0)" (click)="onChartClick(chart.name)">{{ chart.name }}</a>
|
||||
</div>
|
||||
<div class="clr-col-2">{{ getStatusString(chart) | translate }}</div>
|
||||
<div class="clr-col-2">{{ chart.total_versions }}</div>
|
||||
<div class="clr-col-4 ellipsis">{{ chart.created | harborDatetime: 'short' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-link" (click)="goToCharts()">{{'SUMMARY.SEE_ALL' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card clickable" *ngIf="showProjectMemberInfo">
|
||||
<div class="card-header no-underline">
|
||||
<div>{{"PROJECT_DETAIL.USERS" | translate}}</div>
|
||||
<div class="clr-row number">{{getTotalMembers()}}</div>
|
||||
</div>
|
||||
<div class="card-block">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'SUMMARY.ADMIN' | translate}}</div>
|
||||
<div class="clr-col-8">{{ summaryInformation?.project_admin_count ? summaryInformation?.project_admin_count : 0 }}</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'SUMMARY.MAINTAINER' | translate}}</div>
|
||||
<div class="clr-col-8">{{ summaryInformation?.maintainer_count ? summaryInformation?.maintainer_count : 0 }}</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'SUMMARY.DEVELOPER' | translate}}</div>
|
||||
<div class="clr-col-8">{{ summaryInformation?.developer_count ? summaryInformation?.developer_count : 0 }}</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'SUMMARY.GUEST' | translate}}</div>
|
||||
<div class="clr-col-8">{{ summaryInformation?.guest_count ? summaryInformation?.guest_count : 0 }}</div>
|
||||
</div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4 column">{{'SUMMARY.LIMITED_GUEST' | translate}}</div>
|
||||
<div class="clr-col-8">{{ summaryInformation?.limited_guest_count ? summaryInformation?.limited_guest_count : 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-link" (click)="goToMembers()">{{'SUMMARY.SEE_ALL' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -1,63 +1,67 @@
|
||||
.summary {
|
||||
color: #000;
|
||||
padding-right: 0.3rem;
|
||||
font-size: 13px;
|
||||
justify-content: space-between;
|
||||
.summary-left {
|
||||
padding-top: .25rem;
|
||||
.project-detail {
|
||||
width: 17rem;
|
||||
min-height: 1.45rem;
|
||||
|
||||
ul {
|
||||
width: 8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.summary-right {
|
||||
.quotas-progress {
|
||||
min-width: 10rem;
|
||||
justify-content: space-between;
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
.width-7-5 {
|
||||
width: 7.5rem;
|
||||
}
|
||||
.display-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.progress,
|
||||
.progress-static {
|
||||
progress {
|
||||
max-height: 0.48rem;
|
||||
}
|
||||
}
|
||||
::ng-deep {
|
||||
.progress {
|
||||
&.warning>progress {
|
||||
color: orange;
|
||||
|
||||
&::-webkit-progress-value {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
&::-moz-progress-bar {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title-width {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.pt-05 {
|
||||
padding-top: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
justify-content: flex-end;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
.mr-5px {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.card {
|
||||
width: 24rem;
|
||||
margin-right: 1rem;
|
||||
flex-grow:0;
|
||||
flex-shrink:0;
|
||||
}
|
||||
.number {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3rem;
|
||||
color: #266aac;
|
||||
height: 4rem;
|
||||
}
|
||||
.column {
|
||||
font-size: 10px;
|
||||
font-weight: bolder;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.card-block {
|
||||
min-height: 6rem;
|
||||
}
|
||||
.size-24 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.chart-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.no-underline {
|
||||
border-bottom-style: none;
|
||||
}
|
||||
.center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -80,13 +80,21 @@ describe('SummaryComponent', () => {
|
||||
{
|
||||
provide: ActivatedRoute, useValue: {
|
||||
paramMap: of({ get: (key) => 'value' }),
|
||||
snapshot: {
|
||||
parent: {
|
||||
parent: {
|
||||
snapshot: {
|
||||
data: {
|
||||
projectResolver: {registry_id: 3}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
parent: {
|
||||
snapshot: {
|
||||
params: { id: 1 },
|
||||
data: {
|
||||
projectResolver: {registry_id: 3}
|
||||
},
|
||||
}
|
||||
}},
|
||||
}
|
||||
@ -107,10 +115,30 @@ describe('SummaryComponent', () => {
|
||||
|
||||
it('should show proxy cache endpoint', async () => {
|
||||
component.summaryInformation = mockedSummaryInformation;
|
||||
component.isCardView = false;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const endpoint: HTMLElement = fixture.nativeElement.querySelector("#endpoint");
|
||||
expect(endpoint).toBeTruthy();
|
||||
expect(endpoint.innerText).toEqual("test-https://test.com");
|
||||
});
|
||||
|
||||
it('should show card view', async () => {
|
||||
component.summaryInformation = mockedSummaryInformation;
|
||||
component.isCardView = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const container: HTMLElement = fixture.nativeElement.querySelector(".container");
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show three cards', async () => {
|
||||
component.summaryInformation = mockedSummaryInformation;
|
||||
component.isCardView = true;
|
||||
component.hasReadChartPermission = true;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const cards = fixture.nativeElement.querySelectorAll(".card");
|
||||
expect(cards.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AppConfigService } from "../../../services/app-config.service";
|
||||
import { QUOTA_DANGER_COEFFICIENT, QUOTA_WARNING_COEFFICIENT, QuotaUnits } from "../../../shared/entities/shared.const";
|
||||
import {
|
||||
Endpoint,
|
||||
ProjectService,
|
||||
@ -9,7 +8,18 @@ import {
|
||||
USERSTATICPERMISSION
|
||||
} from '../../../shared/services';
|
||||
import { ErrorHandler } from "../../../shared/units/error-handler";
|
||||
import { clone, GetIntegerAndUnit, getSuitableUnit as getSuitableUnitFn } from "../../../shared/units/utils";
|
||||
import {
|
||||
DefaultHelmIcon,
|
||||
FALSE_STR,
|
||||
PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY,
|
||||
TRUE_STR
|
||||
} from "../../../shared/entities/shared.const";
|
||||
import { RepositoryService } from "../../../../../ng-swagger-gen/services/repository.service";
|
||||
import { Project } from "../../../../../ng-swagger-gen/models/project";
|
||||
import { Repository } from "../../../../../ng-swagger-gen/models/repository";
|
||||
import { HelmChartItem } from "../helm-chart/helm-chart-detail/helm-chart.interface.service";
|
||||
import { HelmChartService } from "../helm-chart/helm-chart-detail/helm-chart.service";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'summary',
|
||||
@ -17,50 +27,169 @@ import { clone, GetIntegerAndUnit, getSuitableUnit as getSuitableUnitFn } from "
|
||||
styleUrls: ['./summary.component.scss']
|
||||
})
|
||||
export class SummaryComponent implements OnInit {
|
||||
showProjectMemberInfo: boolean;
|
||||
showQuotaInfo: boolean;
|
||||
|
||||
showProjectMemberInfo: boolean = false;
|
||||
hasReadRepoPermission: boolean = false;
|
||||
hasReadChartPermission: boolean = false;
|
||||
projectId: number;
|
||||
projectName: string;
|
||||
summaryInformation: any;
|
||||
quotaDangerCoefficient: number = QUOTA_DANGER_COEFFICIENT;
|
||||
quotaWarningCoefficient: number = QUOTA_WARNING_COEFFICIENT;
|
||||
endpoint: Endpoint;
|
||||
isCardView: boolean = true;
|
||||
cardHover: boolean = false;
|
||||
listHover: boolean = false;
|
||||
repos: Repository[] = [];
|
||||
charts: HelmChartItem[] = [];
|
||||
chartDefaultIcon: string = DefaultHelmIcon;
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private userPermissionService: UserPermissionService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private appConfigService: AppConfigService,
|
||||
private route: ActivatedRoute,
|
||||
) { }
|
||||
private repoService: RepositoryService,
|
||||
private router: Router,
|
||||
private helmChartService: HelmChartService,
|
||||
) {
|
||||
if (localStorage) {
|
||||
if (!localStorage.getItem(PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY)) {
|
||||
localStorage.setItem(PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY, FALSE_STR);
|
||||
}
|
||||
this.isCardView = localStorage.getItem(PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY) === TRUE_STR;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.projectId = this.route.parent.parent.snapshot.params['id'];
|
||||
const resolverData = this.route.snapshot.parent.parent.data;
|
||||
if (resolverData) {
|
||||
let project = <Project>resolverData["projectResolver"];
|
||||
this.projectName = project.name;
|
||||
}
|
||||
const permissions = [
|
||||
{resource: USERSTATICPERMISSION.MEMBER.KEY, action: USERSTATICPERMISSION.MEMBER.VALUE.LIST},
|
||||
{resource: USERSTATICPERMISSION.QUOTA.KEY, action: USERSTATICPERMISSION.QUOTA.VALUE.READ},
|
||||
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.LIST},
|
||||
{resource: USERSTATICPERMISSION.HELM_CHART.KEY, action: USERSTATICPERMISSION.HELM_CHART.VALUE.LIST}
|
||||
];
|
||||
|
||||
this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array<boolean>) => {
|
||||
this.showProjectMemberInfo = results[0];
|
||||
this.showQuotaInfo = results[1];
|
||||
this.hasReadRepoPermission = results[1];
|
||||
this.hasReadChartPermission = results[2];
|
||||
});
|
||||
|
||||
this.projectService.getProjectSummary(this.projectId).subscribe(res => {
|
||||
this.summaryInformation = res;
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
if (this.isCardView) {
|
||||
this.getDataForCardView();
|
||||
}
|
||||
}
|
||||
getSuitableUnit(value) {
|
||||
const QuotaUnitsCopy = clone(QuotaUnits);
|
||||
return getSuitableUnitFn(value, QuotaUnitsCopy);
|
||||
}
|
||||
|
||||
getIntegerAndUnit(hardValue, usedValue) {
|
||||
return GetIntegerAndUnit(hardValue, clone(QuotaUnits), usedValue, clone(QuotaUnits));
|
||||
}
|
||||
|
||||
public get withHelmChart(): boolean {
|
||||
return this.appConfigService.getConfig().with_chartmuseum;
|
||||
}
|
||||
|
||||
showCard(cardView: boolean) {
|
||||
if (this.isCardView === cardView) {
|
||||
return;
|
||||
}
|
||||
this.isCardView = cardView;
|
||||
if (localStorage) {
|
||||
if (this.isCardView) {
|
||||
localStorage.setItem(PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY, TRUE_STR);
|
||||
} else {
|
||||
localStorage.setItem(PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY, FALSE_STR);
|
||||
}
|
||||
}
|
||||
if (this.isCardView) {
|
||||
this.getDataForCardView();
|
||||
}
|
||||
}
|
||||
|
||||
mouseEnter(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = true;
|
||||
} else {
|
||||
this.listHover = true;
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeave(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
this.cardHover = false;
|
||||
} else {
|
||||
this.listHover = false;
|
||||
}
|
||||
}
|
||||
|
||||
isHovering(itemName: string) {
|
||||
if (itemName === "card") {
|
||||
return this.cardHover;
|
||||
} else {
|
||||
return this.listHover;
|
||||
}
|
||||
}
|
||||
getDataForCardView() {
|
||||
this.getTop4Repos();
|
||||
this.getTop4Charts();
|
||||
}
|
||||
getTop4Repos() {
|
||||
if (this.hasReadRepoPermission) {
|
||||
this.repoService.listRepositories({
|
||||
projectName: this.projectName,
|
||||
page: 1,
|
||||
pageSize: 4
|
||||
}).subscribe(res => {
|
||||
this.repos = res;
|
||||
});
|
||||
}
|
||||
}
|
||||
getTop4Charts() {
|
||||
if (this.hasReadChartPermission) {
|
||||
this.helmChartService.getHelmCharts(this.projectName).subscribe(res => {
|
||||
if (res && res.length) {
|
||||
this.charts = res.slice(0, 4);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
goIntoRepo(repoEvt: Repository): void {
|
||||
const linkUrl = ['harbor', 'projects', repoEvt.project_id, 'repositories', repoEvt.name.substr(this.projectName.length + 1)];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
goToRepos() {
|
||||
const linkUrl = ['harbor', 'projects', this.projectId, 'repositories'];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
getDefaultIcon(chart: HelmChartItem) {
|
||||
chart.icon = this.chartDefaultIcon;
|
||||
}
|
||||
getStatusString(chart: HelmChartItem) {
|
||||
if (chart.deprecated) {
|
||||
return "HELM_CHART.DEPRECATED";
|
||||
} else {
|
||||
return "HELM_CHART.ACTIVE";
|
||||
}
|
||||
}
|
||||
onChartClick(chartName: string) {
|
||||
const linkUrl = ['harbor', 'projects', this.projectId, 'helm-charts', chartName, 'versions'];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
goToCharts() {
|
||||
const linkUrl = ['harbor', 'projects', this.projectId, 'helm-charts'];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
goToMembers() {
|
||||
const linkUrl = ['harbor', 'projects', this.projectId, 'members'];
|
||||
this.router.navigate(linkUrl);
|
||||
}
|
||||
getTotalMembers(): number {
|
||||
if (this.summaryInformation) {
|
||||
return +(this.summaryInformation.project_admin_count ? this.summaryInformation.project_admin_count : 0) +
|
||||
+(this.summaryInformation.maintainer_count ? this.summaryInformation.maintainer_count : 0) +
|
||||
+(this.summaryInformation.developer_count ? this.summaryInformation.developer_count : 0) +
|
||||
+(this.summaryInformation.guest_count ? this.summaryInformation.guest_count : 0) +
|
||||
+(this.summaryInformation.limited_guest_count ? this.summaryInformation.limited_guest_count : 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -241,8 +241,13 @@ export enum ResourceType {
|
||||
REPOSITORY_TAG = 3,
|
||||
}
|
||||
|
||||
export const TRUE_STR: string = 'true';
|
||||
export const FALSE_STR: string = 'false';
|
||||
|
||||
export const CARD_VIEW_LOCALSTORAGE_KEY = 'card-view';
|
||||
|
||||
export const PROJECT_SUMMARY_CARD_VIEW_LOCALSTORAGE_KEY = 'project_card-view';
|
||||
|
||||
export enum ScheduleType {
|
||||
NONE = "None",
|
||||
DAILY = "Daily",
|
||||
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Observable, forkJoin} from "rxjs";
|
||||
import { map, share } from "rxjs/operators";
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { CacheObservable } from "../units/cache-util";
|
||||
import { CacheObservable, FlushAll } from "../units/cache-util";
|
||||
import { CURRENT_BASE_HREF } from "../units/utils";
|
||||
|
||||
|
||||
@ -90,5 +90,7 @@ export class UserPermissionDefaultService extends UserPermissionService {
|
||||
}
|
||||
|
||||
public clearPermissionCache() {
|
||||
this._sharedPermissionObservableMap = {};
|
||||
FlushAll();
|
||||
}
|
||||
}
|
||||
|
@ -810,7 +810,8 @@
|
||||
"MAINTAINER": "Maintainer(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Einige Änderungen wurden noch nicht gespeichert. Sollen diese verworfen werden?"
|
||||
|
@ -810,7 +810,8 @@
|
||||
"MAINTAINER": "Maintainer(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet. Do you want to cancel?"
|
||||
|
@ -811,7 +811,8 @@
|
||||
"MAINTAINER": "Maintainer(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algunos cambios no se han guardado aún. ¿Quiere cancelar?"
|
||||
|
@ -796,7 +796,8 @@
|
||||
"MAINTAINER": "Maintainer(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Certaines modifications ne sont pas encore enregistrées. Voulez-vous annuler ?"
|
||||
|
@ -807,7 +807,8 @@
|
||||
"MAINTAINER": "Maintainer(s)",
|
||||
"DEVELOPER": "Developer(s)",
|
||||
"GUEST": "Guest(s)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Algumas alterações ainda não foram salvas. Você deseja cancelar?"
|
||||
|
@ -810,7 +810,8 @@
|
||||
"MAINTAINER": "Uzman(lar)",
|
||||
"DEVELOPER": "Geliştirici(ler)",
|
||||
"GUEST": "Misafir(ler)",
|
||||
"LIMITED_GUEST": "Limited guest(s)"
|
||||
"LIMITED_GUEST": "Limited guest(s)",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Bazı değişiklikler henüz kaydedilmedi. İptal etmek istiyor musun?"
|
||||
|
@ -812,7 +812,8 @@
|
||||
"MAINTAINER": "维护人员",
|
||||
"DEVELOPER": "开发者",
|
||||
"GUEST": "访客",
|
||||
"LIMITED_GUEST": "受限访客"
|
||||
"LIMITED_GUEST": "受限访客",
|
||||
"SEE_ALL": "查看全部"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认是否取消?"
|
||||
|
@ -808,7 +808,8 @@
|
||||
"MAINTAINER": "維護人員",
|
||||
"DEVELOPER": "開發者",
|
||||
"GUEST": "訪客",
|
||||
"LIMITED_GUEST": "受限訪客"
|
||||
"LIMITED_GUEST": "受限訪客",
|
||||
"SEE_ALL": "SEE ALL"
|
||||
},
|
||||
"ALERT":{
|
||||
"FORM_CHANGE_CONFIRMATION": "表單內容改變,確認是否取消?"
|
||||
|
Loading…
Reference in New Issue
Block a user