mirror of
https://github.com/goharbor/harbor
synced 2024-09-21 03:13:18 +00:00
Add "expires in" column for robot UI (#16227)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
67bf1638d6
commit
7ff0bf188a
|
@ -60,7 +60,7 @@
|
||||||
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{"SYSTEM_ROBOT.PROJECTS" | translate}}</clr-dg-column>
|
<clr-dg-column>{{"SYSTEM_ROBOT.PROJECTS" | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRES_AT' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRES_IN' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-placeholder>{{
|
<clr-dg-placeholder>{{
|
||||||
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
</span>
|
</span>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.creation_time | harborDatetime: 'short'}}</clr-dg-cell>
|
<clr-dg-cell>{{r.creation_time | harborDatetime: 'short'}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.expires_at === -1?("ROBOT_ACCOUNT.NEVER_EXPIRED" | translate):(r.expires_at * 1000 | harborDatetime: 'short')}}</clr-dg-cell>
|
<clr-dg-cell><app-remaining-time [deadline]="r?.expires_at" [timeDiff]="deltaTime"></app-remaining-time></clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
|
|
@ -12,7 +12,13 @@ import { ProjectService } from "../../../../../ng-swagger-gen/services/project.s
|
||||||
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
||||||
import { OperationService } from "../../../shared/components/operation/operation.service";
|
import { OperationService } from "../../../shared/components/operation/operation.service";
|
||||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { HarborDatetimePipe } from '../../../shared/pipes/harbor-datetime.pipe';
|
||||||
|
|
||||||
describe('SystemRobotAccountsComponent', () => {
|
describe('SystemRobotAccountsComponent', () => {
|
||||||
let component: SystemRobotAccountsComponent;
|
let component: SystemRobotAccountsComponent;
|
||||||
|
@ -119,10 +125,16 @@ describe('SystemRobotAccountsComponent', () => {
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedTestingModule
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
ClarityModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
],
|
],
|
||||||
declarations: [ SystemRobotAccountsComponent ],
|
declarations: [ SystemRobotAccountsComponent, HarborDatetimePipe],
|
||||||
providers: [
|
providers: [
|
||||||
|
TranslateService,
|
||||||
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
{ provide: MessageHandlerService, useValue: fakedMessageHandlerService },
|
||||||
ConfirmationDialogService,
|
ConfirmationDialogService,
|
||||||
OperationService,
|
OperationService,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../
|
||||||
import { errorHandler } from "../../../shared/units/shared.utils";
|
import { errorHandler } from "../../../shared/units/shared.utils";
|
||||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||||
import { RobotPermission } from "../../../../../ng-swagger-gen/models/robot-permission";
|
import { RobotPermission } from "../../../../../ng-swagger-gen/models/robot-permission";
|
||||||
|
import { SysteminfoService } from '../../../../../ng-swagger-gen/services/systeminfo.service';
|
||||||
|
|
||||||
const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
const FIRST_PROJECTS_PAGE_SIZE: number = 100;
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -56,6 +57,7 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||||
searchSub: Subscription;
|
searchSub: Subscription;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
deltaTime: number; // the different between server time and local time
|
||||||
constructor(private robotService: RobotService,
|
constructor(private robotService: RobotService,
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private msgHandler: MessageHandlerService,
|
private msgHandler: MessageHandlerService,
|
||||||
|
@ -63,8 +65,10 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||||
private operationService: OperationService,
|
private operationService: OperationService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
|
private systemInfoService: SysteminfoService
|
||||||
) {}
|
) {}
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.getCurrenTime();
|
||||||
this.loadDataFromBackend();
|
this.loadDataFromBackend();
|
||||||
if (!this.searchSub) {
|
if (!this.searchSub) {
|
||||||
this.searchSub = this.filterComponent.filterTerms.pipe(
|
this.searchSub = this.filterComponent.filterTerms.pipe(
|
||||||
|
@ -124,6 +128,15 @@ export class SystemRobotAccountsComponent implements OnInit, OnDestroy {
|
||||||
this.subscription = null;
|
this.subscription = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getCurrenTime() {
|
||||||
|
this.systemInfoService.getSystemInfo().subscribe(
|
||||||
|
res => {
|
||||||
|
if (res?.current_time) {
|
||||||
|
this.deltaTime = new Date().getTime() - new Date(res?.current_time).getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
loadDataFromBackend() {
|
loadDataFromBackend() {
|
||||||
this.loadingData = true;
|
this.loadingData = true;
|
||||||
this.addBtnState = ClrLoadingState.LOADING;
|
this.addBtnState = ClrLoadingState.LOADING;
|
||||||
|
|
|
@ -172,3 +172,9 @@ export function onlyHasPushPermission(access: Access[]): boolean {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum RobotTimeRemainColor {
|
||||||
|
GREEN = 'green',
|
||||||
|
WARNING = 'yellow',
|
||||||
|
EXPIRED = 'red'
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { Registry } from "../../../../../../../../../ng-swagger-gen/models/regis
|
||||||
import { AppConfigService } from "../../../../../../../services/app-config.service";
|
import { AppConfigService } from "../../../../../../../services/app-config.service";
|
||||||
import { ArtifactListPageService } from '../../artifact-list-page.service';
|
import { ArtifactListPageService } from '../../artifact-list-page.service';
|
||||||
import { ClrLoadingState } from '@clr/angular';
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { Accessory } from "ng-swagger-gen/models/accessory";
|
||||||
|
|
||||||
describe("ArtifactListTabComponent (inline template)", () => {
|
describe("ArtifactListTabComponent (inline template)", () => {
|
||||||
|
|
||||||
|
@ -263,6 +264,16 @@ describe("ArtifactListTabComponent (inline template)", () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
listAccessoriesResponse() {
|
||||||
|
const res: HttpResponse<Array<Accessory>> = new HttpResponse<Array<Accessory>>({
|
||||||
|
headers: new HttpHeaders({'x-total-count': '0'}),
|
||||||
|
body: []
|
||||||
|
});
|
||||||
|
return of(res).pipe(delay(0));
|
||||||
|
},
|
||||||
|
listAccessories() {
|
||||||
|
return of(null).pipe(delay(0));
|
||||||
|
},
|
||||||
listArtifactsResponse: () => {
|
listArtifactsResponse: () => {
|
||||||
if (filtereName === 'sha256:3e33e3e3') {
|
if (filtereName === 'sha256:3e33e3e3') {
|
||||||
return of(
|
return of(
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'ROBOT_ACCOUNT.ENABLED_STATE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{"SYSTEM_ROBOT.PERMISSION_COLUMN" | translate}}</clr-dg-column>
|
<clr-dg-column>{{"SYSTEM_ROBOT.PERMISSION_COLUMN" | translate}}</clr-dg-column>
|
||||||
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
<clr-dg-column [clrDgSortBy]="'creation_time'">{{'ROBOT_ACCOUNT.CREATETION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRES_AT' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'SYSTEM_ROBOT.EXPIRES_IN' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'ROBOT_ACCOUNT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-placeholder>{{
|
<clr-dg-placeholder>{{
|
||||||
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
'SYSTEM_ROBOT.NOT_FOUND' | translate
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.creation_time | harborDatetime: 'short'}}</clr-dg-cell>
|
<clr-dg-cell>{{r.creation_time | harborDatetime: 'short'}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.expires_at === -1?("ROBOT_ACCOUNT.NEVER_EXPIRED" | translate):(r.expires_at * 1000 | harborDatetime: 'short')}}</clr-dg-cell>
|
<clr-dg-cell><app-remaining-time [deadline]="r?.expires_at" [timeDiff]="deltaTime"></app-remaining-time></clr-dg-cell>
|
||||||
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
<clr-dg-cell>{{r.description}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { of, Subscription } from 'rxjs';
|
import { of, Subscription } from 'rxjs';
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||||
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
||||||
import { RobotAccountComponent } from './robot-account.component';
|
import { RobotAccountComponent } from './robot-account.component';
|
||||||
import { UserPermissionService } from "../../../shared/services";
|
import { UserPermissionService } from "../../../shared/services";
|
||||||
|
@ -12,7 +12,13 @@ import { Robot } from "../../../../../ng-swagger-gen/models/robot";
|
||||||
import { delay } from "rxjs/operators";
|
import { delay } from "rxjs/operators";
|
||||||
import { Action, PermissionsKinds, Resource } from "../../left-side-nav/system-robot-accounts/system-robot-util";
|
import { Action, PermissionsKinds, Resource } from "../../left-side-nav/system-robot-accounts/system-robot-util";
|
||||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { HarborDatetimePipe } from '../../../shared/pipes/harbor-datetime.pipe';
|
||||||
|
|
||||||
describe('RobotAccountComponent', () => {
|
describe('RobotAccountComponent', () => {
|
||||||
let component: RobotAccountComponent;
|
let component: RobotAccountComponent;
|
||||||
|
@ -106,9 +112,15 @@ describe('RobotAccountComponent', () => {
|
||||||
NO_ERRORS_SCHEMA
|
NO_ERRORS_SCHEMA
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
SharedTestingModule,
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
ClarityModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
TranslateService,
|
||||||
{
|
{
|
||||||
provide: ActivatedRoute, useValue: {
|
provide: ActivatedRoute, useValue: {
|
||||||
snapshot: {
|
snapshot: {
|
||||||
|
@ -127,7 +139,7 @@ describe('RobotAccountComponent', () => {
|
||||||
{ provide: UserPermissionService, useValue: mockUserPermissionService },
|
{ provide: UserPermissionService, useValue: mockUserPermissionService },
|
||||||
{ provide: RobotService, useValue: fakedRobotService},
|
{ provide: RobotService, useValue: fakedRobotService},
|
||||||
],
|
],
|
||||||
declarations: [RobotAccountComponent]
|
declarations: [RobotAccountComponent, HarborDatetimePipe]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { ConfirmationDialogService } from "../../global-confirmation-dialog/conf
|
||||||
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||||
import { errorHandler } from "../../../shared/units/shared.utils";
|
import { errorHandler } from "../../../shared/units/shared.utils";
|
||||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||||
|
import { SysteminfoService } from '../../../../../ng-swagger-gen/services/systeminfo.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-robot-account",
|
selector: "app-robot-account",
|
||||||
|
@ -55,6 +56,7 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||||
hasRobotReadPermission: boolean;
|
hasRobotReadPermission: boolean;
|
||||||
projectId: number;
|
projectId: number;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
|
deltaTime: number; // the different between server time and local time
|
||||||
constructor(private robotService: RobotService,
|
constructor(private robotService: RobotService,
|
||||||
private msgHandler: MessageHandlerService,
|
private msgHandler: MessageHandlerService,
|
||||||
private operateDialogService: ConfirmationDialogService,
|
private operateDialogService: ConfirmationDialogService,
|
||||||
|
@ -63,8 +65,10 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
|
private systemInfoService: SysteminfoService
|
||||||
) {}
|
) {}
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.getCurrenTime();
|
||||||
this.projectId = +this.route.snapshot.parent.parent.params["id"];
|
this.projectId = +this.route.snapshot.parent.parent.params["id"];
|
||||||
let resolverData = this.route.snapshot.parent.parent.data;
|
let resolverData = this.route.snapshot.parent.parent.data;
|
||||||
if (resolverData) {
|
if (resolverData) {
|
||||||
|
@ -121,6 +125,15 @@ export class RobotAccountComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getCurrenTime() {
|
||||||
|
this.systemInfoService.getSystemInfo().subscribe(
|
||||||
|
res => {
|
||||||
|
if (res?.current_time) {
|
||||||
|
this.deltaTime = new Date().getTime() - new Date(res?.current_time).getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
getPermissionsList(): void {
|
getPermissionsList(): void {
|
||||||
let permissionsList = [];
|
let permissionsList = [];
|
||||||
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<span [style.color]= 'color'>{{timeRemain | translate}}</span>
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { RemainingTimeComponent } from './remaining-time.component';
|
||||||
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
import { Project } from '../../../../../ng-swagger-gen/models/project';
|
||||||
|
import { RobotTimeRemainColor } from '../../../base/left-side-nav/system-robot-accounts/system-robot-util';
|
||||||
|
import { SharedTestingModule } from '../../shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
describe('RemainingTimeComponent', () => {
|
||||||
|
let component: TestHostComponent;
|
||||||
|
let fixture: ComponentFixture<TestHostComponent>;
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
SharedTestingModule
|
||||||
|
],
|
||||||
|
declarations: [TestHostComponent, RemainingTimeComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TestHostComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should show green color', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component?.remainingTimeComponent.color).toEqual(RobotTimeRemainColor.GREEN);
|
||||||
|
expect(component?.remainingTimeComponent.timeRemain).toEqual('ROBOT_ACCOUNT.NEVER_EXPIRED');
|
||||||
|
});
|
||||||
|
it('should show yellow color', () => {
|
||||||
|
component.deltaTime = 0;
|
||||||
|
component.expires_at = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 29).getTime() / 1000;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component?.remainingTimeComponent.color).toEqual(RobotTimeRemainColor.WARNING);
|
||||||
|
expect(component?.remainingTimeComponent.timeRemain).toEqual('29d 0h 0m');
|
||||||
|
});
|
||||||
|
it('should show red color', () => {
|
||||||
|
component.deltaTime = 0;
|
||||||
|
component.expires_at = new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 31).getTime() / 1000;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component?.remainingTimeComponent.color).toEqual(RobotTimeRemainColor.EXPIRED);
|
||||||
|
expect(component?.remainingTimeComponent.timeRemain).toEqual('SYSTEM_ROBOT.EXPIRED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// mock a TestHostComponent for ListProjectROComponent
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<app-remaining-time [deadline]="expires_at" [timeDiff]="deltaTime"></app-remaining-time>`
|
||||||
|
})
|
||||||
|
class TestHostComponent {
|
||||||
|
@ViewChild(RemainingTimeComponent)
|
||||||
|
remainingTimeComponent: RemainingTimeComponent;
|
||||||
|
expires_at: number = -1;
|
||||||
|
deltaTime: number = 100;
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy, SimpleChanges, OnChanges,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { RobotTimeRemainColor } from '../../../base/left-side-nav/system-robot-accounts/system-robot-util';
|
||||||
|
const SEC: number = 1000;
|
||||||
|
const MIN: number = 60 * 1000;
|
||||||
|
const DAY: number = 1000 * 60 * 60 * 24;
|
||||||
|
const HOUR: number = 1000 * 60 * 60;
|
||||||
|
const WARNING_DAYS = 30;
|
||||||
|
@Component({
|
||||||
|
selector: 'app-remaining-time',
|
||||||
|
templateUrl: 'remaining-time.component.html',
|
||||||
|
styleUrls: ['./remaining-time.component.scss']
|
||||||
|
})
|
||||||
|
export class RemainingTimeComponent implements OnInit, OnDestroy, OnChanges {
|
||||||
|
color: string;
|
||||||
|
timeRemain: string;
|
||||||
|
@Input()
|
||||||
|
timeDiff: number; // the different between server time and local time, unit millisecond, localTime - serverTime
|
||||||
|
@Input()
|
||||||
|
deadline: number; // unit second
|
||||||
|
intelVal: any;
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (!this.intelVal) {
|
||||||
|
this.intelVal = setInterval(() => {
|
||||||
|
this.refreshTimeRemain();
|
||||||
|
}, MIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.intelVal) {
|
||||||
|
clearInterval(this.intelVal);
|
||||||
|
this.intelVal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes && changes["timeDiff"] && changes["deadline"]) {
|
||||||
|
this.refreshTimeRemain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshTimeRemain() {
|
||||||
|
if (this.timeDiff !== null && this.deadline !== null) {
|
||||||
|
if (this.deadline === -1) {
|
||||||
|
this.color = RobotTimeRemainColor.GREEN;
|
||||||
|
this.timeRemain = 'ROBOT_ACCOUNT.NEVER_EXPIRED';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const time = new Date(this.deadline * SEC).getTime() - new Date(new Date().getTime() - this.timeDiff).getTime();
|
||||||
|
if (time > 0) {
|
||||||
|
const days = Math.floor(time / DAY);
|
||||||
|
const hours = Math.floor((time % DAY) / HOUR);
|
||||||
|
const minutes = Math.floor((time % HOUR) / MIN);
|
||||||
|
this.timeRemain = `${days}d ${hours}h ${minutes}m`;
|
||||||
|
if (days >= WARNING_DAYS) {
|
||||||
|
this.color = RobotTimeRemainColor.GREEN;
|
||||||
|
} else {
|
||||||
|
this.color = RobotTimeRemainColor.WARNING;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.color = RobotTimeRemainColor.EXPIRED;
|
||||||
|
this.timeRemain = 'SYSTEM_ROBOT.EXPIRED';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,6 +78,7 @@ import localeFr from '@angular/common/locales/fr';
|
||||||
import localePt from '@angular/common/locales/pt-PT';
|
import localePt from '@angular/common/locales/pt-PT';
|
||||||
import localeTr from '@angular/common/locales/tr';
|
import localeTr from '@angular/common/locales/tr';
|
||||||
import localeDe from '@angular/common/locales/de';
|
import localeDe from '@angular/common/locales/de';
|
||||||
|
import { RemainingTimeComponent } from './components/remaining-time/remaining-time.component';
|
||||||
// add locale data for supported languages ['en-us', 'zh-cn', 'zh-tw', 'es-es', 'fr-fr', 'pt-br', 'tr-tr', 'de-de'];
|
// add locale data for supported languages ['en-us', 'zh-cn', 'zh-tw', 'es-es', 'fr-fr', 'pt-br', 'tr-tr', 'de-de'];
|
||||||
// en-us defaulted supported
|
// en-us defaulted supported
|
||||||
registerLocaleData(zh_cn, 'zh-cn');
|
registerLocaleData(zh_cn, 'zh-cn');
|
||||||
|
@ -153,7 +154,8 @@ ClarityIcons.add({"robot-head": `
|
||||||
ListChartVersionRoComponent,
|
ListChartVersionRoComponent,
|
||||||
DatePickerComponent,
|
DatePickerComponent,
|
||||||
ImageNameInputComponent,
|
ImageNameInputComponent,
|
||||||
HarborDatetimePipe
|
HarborDatetimePipe,
|
||||||
|
RemainingTimeComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
@ -191,7 +193,8 @@ ClarityIcons.add({"robot-head": `
|
||||||
ListChartVersionRoComponent,
|
ListChartVersionRoComponent,
|
||||||
DatePickerComponent,
|
DatePickerComponent,
|
||||||
ImageNameInputComponent,
|
ImageNameInputComponent,
|
||||||
HarborDatetimePipe
|
HarborDatetimePipe,
|
||||||
|
RemainingTimeComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: EndpointService, useClass: EndpointDefaultService },
|
{provide: EndpointService, useClass: EndpointDefaultService },
|
||||||
|
|
|
@ -1704,7 +1704,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart Label"
|
"HELM_LABEL": "Helm Chart Label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1704,7 +1704,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1703,7 +1703,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1672,7 +1672,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1700,7 +1700,9 @@
|
||||||
"STOP": "Parar",
|
"STOP": "Parar",
|
||||||
"LIST": "Listar",
|
"LIST": "Listar",
|
||||||
"REPOSITORY": "Repositório",
|
"REPOSITORY": "Repositório",
|
||||||
"HELM_LABEL": "Marcador do Helm Chart"
|
"HELM_LABEL": "Marcador do Helm Chart",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1704,7 +1704,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
|
@ -1702,7 +1702,9 @@
|
||||||
"STOP": "停止",
|
"STOP": "停止",
|
||||||
"LIST": "查询",
|
"LIST": "查询",
|
||||||
"REPOSITORY": "仓库",
|
"REPOSITORY": "仓库",
|
||||||
"HELM_LABEL": "Helm Chart 标签"
|
"HELM_LABEL": "Helm Chart 标签",
|
||||||
|
"EXPIRES_IN": "有效期剩余",
|
||||||
|
"EXPIRED": "已过期"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "删除附件确认",
|
"DELETION_TITLE_ACCESSORY": "删除附件确认",
|
||||||
|
|
|
@ -1689,7 +1689,9 @@
|
||||||
"STOP": "Stop",
|
"STOP": "Stop",
|
||||||
"LIST": "List",
|
"LIST": "List",
|
||||||
"REPOSITORY": "Repository",
|
"REPOSITORY": "Repository",
|
||||||
"HELM_LABEL": "Helm Chart label"
|
"HELM_LABEL": "Helm Chart label",
|
||||||
|
"EXPIRES_IN": "Expires in",
|
||||||
|
"EXPIRED": "Expired"
|
||||||
},
|
},
|
||||||
"ACCESSORY": {
|
"ACCESSORY": {
|
||||||
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
"DELETION_TITLE_ACCESSORY": "Confirm Accessory Deletion",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user