Merge branch 'master' into return_labels_in_chart_api

This commit is contained in:
Steven Zou 2018-09-20 17:55:48 +08:00
commit d4c423ea8e
28 changed files with 667 additions and 36 deletions

View File

@ -376,7 +376,7 @@ gosec:
$(GOPATH)/bin/gosec -fmt=json -out=harbor_gas_output.json -quiet ./... | true ; \
fi
go_check: misspell golint govet gofmt
go_check: misspell golint govet gofmt commentfmt
gofmt:
@echo checking gofmt...
@ -387,6 +387,16 @@ gofmt:
exit 1; \
fi
commentfmt:
@echo checking comment format...
@res=$$(find . -type d \( -path ./src/vendor -o -path ./tests \) -prune -o -name '*.go' -print | xargs egrep '(^|\s)\/\/(\S)'); \
if [ -n "$${res}" ]; then \
echo checking comment format fail.. ; \
echo missing whitespace between // and comment body;\
echo "$${res}"; \
exit 1; \
fi
misspell:
@echo checking misspell...
@find . -type d \( -path ./src/vendor -o -path ./tests \) -prune -o -name '*.go' -print | xargs misspell -error

View File

@ -15,7 +15,7 @@ import (
const (
maxWorkers = 10
//Keep consistent with 'helm search' command
// Keep consistent with 'helm search' command
searchMaxScore = 25
)
@ -102,15 +102,15 @@ LOOP:
waitGroup.Add(1)
go func(ns string) {
defer func() {
waitGroup.Done() //done
//Return the worker back to the pool
waitGroup.Done() // done
// Return the worker back to the pool
workerPool <- struct{}{}
}()
indexFile, err := c.getIndexYamlWithNS(ns)
if err != nil {
if len(errorChan) == 0 {
//Only need one error as failure signal
// Only need one error as failure signal
errorChan <- err
}
return

View File

@ -299,13 +299,13 @@ func (a testapi) LogGet(user usrInfo) (int, []apilib.AccessLog, error) {
return code, successPayload, err
}
// //Delete a repository or a tag in a repository.
// //Delete a repository or a tag in a repository.
// //This endpoint let user delete repositories and tags with repo name and tag.\n
// //@param repoName The name of repository which will be deleted.
// //@param tag Tag of a repository.
// //@return void
// //func (a testapi) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
// // Delete a repository or a tag in a repository.
// // Delete a repository or a tag in a repository.
// // This endpoint let user delete repositories and tags with repo name and tag.\n
// // @param repoName The name of repository which will be deleted.
// // @param tag Tag of a repository.
// // @return void
// // func (a testapi) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
// func (a testapi) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) {
// _sling := sling.New().Delete(a.basePath)

View File

@ -31,7 +31,7 @@ import (
func TestSearch(t *testing.T) {
fmt.Println("Testing Search(SearchGet) API")
//Use mock chart search handler
// Use mock chart search handler
searchHandler = func(string, []string) ([]*search.Result, error) {
results := []*search.Result{}
results = append(results, &search.Result{
@ -178,7 +178,7 @@ func TestSearch(t *testing.T) {
_, exist = repositories["search-2/hello-world"]
assert.True(t, exist)
//Search chart
// Search chart
err = handleAndParse(&testingRequest{
method: http.MethodGet,
url: "/api/search",
@ -193,6 +193,6 @@ func TestSearch(t *testing.T) {
require.Equal(t, 1, len(result.Chart))
require.Equal(t, "library/harbor", result.Chart[0].Name)
//Restore chart search handler
// Restore chart search handler
searchHandler = nil
}

View File

@ -13,7 +13,7 @@ import {
clone
} from '../utils';
import { ErrorHandler } from '../error-handler/index';
import { SystemSettingsComponent, VulnerabilityConfigComponent } from './index';
import { SystemSettingsComponent, VulnerabilityConfigComponent} from './index';
import { Configuration } from './config';
@Component({

View File

@ -1,6 +1,5 @@
import { Component, Input, Output, EventEmitter, ViewChild, Inject } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Configuration } from '../config';
import { SERVICE_CONFIG, IServiceConfig } from '../../service.config';

View File

@ -14,10 +14,8 @@
</label>
<label>
<label for="color">{{'LABEL.COLOR' | translate}}</label>
<clr-dropdown [clrCloseMenuOnItemClick]="false">
<button type="button" class="btn btn-outline btnColor btn-sm" clrDropdownTrigger>
<clr-icon shape="angle down"></clr-icon>
</button>
<clr-dropdown [clrCloseMenuOnItemClick]="false" clrDropdownTrigger>
<clr-icon shape="caret down" class="btn btn-outline btnColor btn-sm"></clr-icon>
<clr-dropdown-menu *clrIfOpen>
<label class="dropdown-item" (click)="labelModel.color=i.color" *ngFor="let i of labelColor"
[class.borderSty]="i.color == '#FFFFFF'" [ngStyle]="{'background-color': i.color, 'color': i.textColor }">Aa</label>

View File

@ -11,7 +11,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"typeRoots": ["node_modules/@types"],
"lib": [
"dom",
"es2015"

View File

@ -4,7 +4,7 @@
"description": "Harbor UI with Clarity",
"angular-cli": {},
"scripts": {
"start": "ng serve --aot --ssl true --ssl-key ssl/server.key --ssl-cert ssl/server.crt --host 0.0.0.0 --proxy-config proxy.config.json",
"start": "ng serve --ssl true --ssl-key ssl/server.key --ssl-cert ssl/server.crt --host 0.0.0.0 --proxy-config proxy.config.json",
"lint": "tslint \"src/**/*.ts\"",
"lint:lib": "tslint \"lib/**/*.ts\" -e \"lib/dist/**/*\" ",
"test": "ng test harbor-portal",

View File

@ -20,12 +20,21 @@ import { HarborRoutingModule } from './harbor-routing.module';
import { SharedModule } from './shared/shared.module';
import { AccountModule } from './account/account.module';
import { ConfigurationModule } from './config/config.module';
import { registerLocaleData } from '@angular/common';
import { TranslateService } from "@ngx-translate/core";
import { AppConfigService } from './app-config.service';
import {SkinableConfig} from "./skinable-config.service";
import { SkinableConfig } from "./skinable-config.service";
import { ProjectConfigComponent } from './project/project-config/project-config.component';
import zh from '@angular/common/locales/zh-Hans';
import es from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
registerLocaleData(zh, 'zh-cn');
registerLocaleData(es, 'es-es');
registerLocaleData(localeFr, 'fr-fr');
export function initConfig(configService: AppConfigService, skinableService: SkinableConfig) {
return () => {
skinableService.getCustomFile();

View File

@ -18,6 +18,9 @@
<li role="presentation" class="nav-item" *ngIf="withClair">
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>{{'CONFIG.VULNERABILITY' | translate}}</button>
</li>
<li role="presentation" class="nav-item" *ngIf="hasAdminRole">
<button id="config-gc" class="btn btn-link nav-link" aria-controls="gc" [class.active]='isCurrentTabLink("config-gc")' type="button" (click)='tabLinkClick("config-gc")'>{{'CONFIG.GC' | translate}}</button>
</li>
</ul>
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
<config-auth [allConfig]="allConfig"></config-auth>
@ -34,6 +37,9 @@
<section id="vulnerability" *ngIf="withClair" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
</section>
<section id="gc" *ngIf="hasAdminRole" role="tabpanel" aria-labelledby="config-gc" [hidden]='!isCurrentTabContent("gc")'>
<gc-config></gc-config>
</section>
<div>
<button type="button" class="btn btn-primary" (click)="save()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="hideBtn" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subscription } from "rxjs";
import { Configuration, StringValueItem, SystemSettingsComponent, VulnerabilityConfigComponent } from '@harbor/ui';
import { Configuration, StringValueItem, SystemSettingsComponent, VulnerabilityConfigComponent} from '@harbor/ui';
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';
import { SessionService } from '../shared/session.service';
@ -24,6 +24,7 @@ import { MessageHandlerService } from '../shared/message-handler/message-handler
import { AppConfigService } from '../app-config.service';
import { ConfigurationAuthComponent } from './auth/config-auth.component';
import { ConfigurationEmailComponent } from './email/config-email.component';
import { GcComponent} from './gc/gc.component';
import { ConfigurationService } from './config.service';
@ -34,6 +35,7 @@ const TabLinkContentMap = {
'config-email': 'email',
'config-system': 'system_settings',
'config-vulnerability': 'vulnerability',
'config-gc': 'gc',
'config-label': 'system_label',
};
@ -53,6 +55,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
@ViewChild(GcComponent) gcConfig: GcComponent;
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
@ -201,7 +204,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
}
public get hideBtn(): boolean {
return this.currentTabId === 'config-label';
return this.currentTabId === 'config-label' || this.currentTabId === 'config-gc';
}
public get hideMailTestingSpinner(): boolean {

View File

@ -19,6 +19,12 @@ import { ConfigurationComponent } from './config.component';
import { ConfigurationService } from './config.service';
import { ConfigurationAuthComponent } from './auth/config-auth.component';
import { ConfigurationEmailComponent } from './email/config-email.component';
import { GcComponent } from './gc/gc.component';
import { GcRepoService } from './gc/gc.service';
import { GcApiRepository } from './gc/gc.api.repository';
import { GcViewModelFactory } from './gc/gc.viewmodel.factory';
import { GcUtility } from './gc/gc.utility';
@NgModule({
imports: [
@ -29,8 +35,9 @@ import { ConfigurationEmailComponent } from './email/config-email.component';
ConfigurationComponent,
ConfigurationAuthComponent,
ConfigurationEmailComponent,
GcComponent
],
exports: [ConfigurationComponent],
providers: [ConfigurationService]
providers: [ConfigurationService, GcRepoService, GcApiRepository, GcViewModelFactory, GcUtility ]
})
export class ConfigurationModule { }

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@Injectable()
export class GcApiRepository {
constructor(
private http: Http,
) {
}
public postSchedule(param): Observable<any> {
return this.http.post("/api/system/gc/schedule", param)
.pipe(catchError(err => Observable.throw(err)));
}
public putSchedule(param): Observable<any> {
return this.http.put("/api/system/gc/schedule", param)
.pipe(catchError(err => Observable.throw(err)));
}
public getSchedule(): Observable<any> {
return this.http.get("/api/system/gc/schedule")
.pipe(catchError(err => Observable.throw(err)))
.pipe(map(response => response.json()));
}
public getLog(id): Observable<any> {
return this.http.get("/api/system/gc/" + id + "/log")
.pipe(catchError(err => Observable.throw(err)));
}
public getStatus(id): Observable<any> {
return this.http.get("/api/system/gc/" + id)
.pipe(catchError(err => Observable.throw(err)))
.pipe(map(response => response.json()));
}
public getJobs(): Observable<any> {
return this.http.get("/api/system/gc")
.pipe(catchError(err => Observable.throw(err)))
.pipe(map(response => response.json()));
}
}

View File

@ -0,0 +1,50 @@
<div class="normal-wrapper flex-layout" *ngIf="!isEditMode">
<span>{{'GC.CURRENT_SCHEDULE' | translate}}</span>
<span>{{(originScheduleType ? 'SCHEDULE.'+ originScheduleType.toUpperCase(): "") | translate}}</span>
<span [hidden]="originScheduleType!==SCHEDULE_TYPE.WEEKLY">{{'GC.ON' | translate}} {{originWeekDay.text | translate}}</span>
<span [hidden]="originScheduleType===SCHEDULE_TYPE.NONE">{{'GC.AT' | translate}} {{originOffTime.text}}</span>
<button class="btn btn-outline" (click)="editSchedule()">{{'BUTTON.EDIT' | translate}}</button>
</div>
<div class="setting-wrapper flex-layout" *ngIf="isEditMode">
<label for="gcPolicy">{{'CONFIG.GC' | translate}}</label>
<div class="select">
<select id="gcPolicy" name="gcPolicy" [(ngModel)]="scheduleType">
<option [value]="SCHEDULE_TYPE.NONE">{{'SCHEDULE.NONE' | translate}}</option>
<option [value]="SCHEDULE_TYPE.DAILY">{{'SCHEDULE.DAILY' | translate}}</option>
<option [value]="SCHEDULE_TYPE.WEEKLY">{{'SCHEDULE.WEEKLY' | translate}}</option>
</select>
</div>
<section [hidden]="scheduleType!== SCHEDULE_TYPE.WEEKLY" class="select day-selector-wrapper">
<span>{{'GC.ON' | translate}}</span>
<select id="daySelector" name="daySelector" [(ngModel)]="weekDay">
<option *ngFor="let d of weekDays" [ngValue]="d">{{d.text | translate}}</option>
</select>
</section>
<section [hidden]="scheduleType===SCHEDULE_TYPE.NONE">
<span>{{'GC.AT' | translate}}</span>
<input type="time" name="dailyTimePicker" required [(ngModel)]="dailyTime" />
</section>
<button class="btn btn-primary btn-sm" (click)="scheduleGc()">{{'BUTTON.SAVE' | translate}}</button>
<button class="btn btn-primary btn-sm" (click)="isEditMode = false" >{{'BUTTON.CANCEL' | translate}}</button>
</div>
<button class="btn btn-success btn-sm gc-start-btn" (click)="gcNow()">{{'GC.GC_NOW' | translate}}</button>
<div class="job-header">{{'GC.JOB_LIST' | translate}}</div>
<clr-datagrid>
<clr-dg-column>{{'GC.JOB_ID' | translate}}</clr-dg-column>
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'START_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'END_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'DETAILS' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]='job'>
<clr-dg-cell>{{job.id }}</clr-dg-cell>
<clr-dg-cell>{{'SCHEDULE.'+ job.type.toUpperCase() | translate }}</clr-dg-cell>
<clr-dg-cell>{{job.status.toUpperCase() | translate}}</clr-dg-cell>
<clr-dg-cell>{{job.createTime | date:'medium'}}</clr-dg-cell>
<clr-dg-cell>{{job.updateTime | date:'medium'}}</clr-dg-cell>
<clr-dg-cell>
<a target="_blank" href="/api/system/gc/{{job.id}}/log">{{'GC.LOG_DETAIL' | translate}}</a>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{'GC.LATEST_JOBS' | translate :{param: jobs.length} }}</clr-dg-footer>
</clr-datagrid>

View File

@ -0,0 +1,20 @@
.flex-layout {
display: flex;
align-items: center;
margin:20px 0;
> * {
margin-right:35px;
}
}
.gc-start-btn {
width:150px;
}
.job-header {
margin:20px 0 -10px 0;
}
.day-selector-wrapper {
display: flex;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GcComponent } from './gc.component';
describe('GcComponent', () => {
let component: GcComponent;
let fixture: ComponentFixture<GcComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GcComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GcComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,134 @@
import { Component, Input, Output, EventEmitter, ViewChild, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { GcJobViewModel, WeekDay } from "./gcLog";
import { GcViewModelFactory } from "./gc.viewmodel.factory";
import { GcRepoService } from "./gc.service";
import { WEEKDAYS, SCHEDULE_TYPE } from './gc.const';
import { GcUtility } from './gc.utility';
import { ErrorHandler } from '@harbor/ui';
@Component({
selector: 'gc-config',
templateUrl: './gc.component.html',
styleUrls: ['./gc.component.scss']
})
export class GcComponent implements OnInit {
jobs: Array<GcJobViewModel> = [];
schedule: any;
originScheduleType: string;
originOffTime: any = { value: null, text: "" };
originWeekDay: any = { value: null, text: "" };
scheduleType: string;
isEditMode: boolean = false;
weekDays = WEEKDAYS;
SCHEDULE_TYPE = SCHEDULE_TYPE;
weekDay: WeekDay = WEEKDAYS[0];
dailyTime: string;
constructor(private gcRepoService: GcRepoService,
private gcViewModelFactory: GcViewModelFactory,
private gcUtility: GcUtility,
private errorHandler: ErrorHandler,
private translate: TranslateService) {
translate.setDefaultLang('en-us');
}
ngOnInit() {
this.getCurrentSchedule();
this.getJobs();
}
getCurrentSchedule() {
this.gcRepoService.getSchedule().subscribe(schedule => {
this.initSchedule(schedule);
});
}
private initSchedule(schedule: any) {
if (schedule && schedule.length > 0) {
this.schedule = schedule[0];
const cron = this.schedule.schedule;
this.originScheduleType = cron.type;
this.originWeekDay = this.weekDays[cron.weekday];
let dailyTime = this.gcUtility.getDailyTime(cron.offtime);
this.originOffTime = { value: cron.offtime, text: dailyTime };
} else {
this.originScheduleType = SCHEDULE_TYPE.NONE;
}
}
editSchedule() {
this.isEditMode = true;
this.scheduleType = this.originScheduleType;
if (this.originWeekDay.value) {
this.weekDay = this.originWeekDay;
} else {
this.weekDay = this.weekDays[0];
}
if (this.originOffTime.value) {
this.dailyTime = this.originOffTime.text;
} else {
this.dailyTime = "00:00";
}
}
getJobs() {
this.gcRepoService.getJobs().subscribe(jobs => {
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
});
}
gcNow(): void {
this.gcRepoService.manualGc().subscribe(response => {
this.translate.get('GC.MSG_SUCCESS').subscribe((res: string) => {
this.errorHandler.info(res);
});
this.getJobs();
}, error => {
this.errorHandler.error(error);
});
}
scheduleGc(): void {
let offTime = this.gcUtility.getOffTime(this.dailyTime);
if (this.schedule) {
this.gcRepoService.putScheduleGc(this.scheduleType, offTime, this.weekDay.value).subscribe(response => {
this.translate.get('GC.MSG_SCHEDULE_RESET').subscribe((res: string) => {
this.errorHandler.info(res);
});
this.originScheduleType = this.scheduleType;
this.originWeekDay = this.weekDay;
this.originOffTime = { value: offTime, text: this.dailyTime };
this.isEditMode = false;
this.getJobs();
}, error => {
this.translate.get('GC.MSG_ERROR').subscribe((res: string) => {
this.errorHandler.info(res);
});
});
} else {
this.gcRepoService.postScheduleGc(this.scheduleType, offTime, this.weekDay.value).subscribe(response => {
this.translate.get('GC.MSG_SCHEDULE_SET').subscribe((res: string) => {
this.errorHandler.info(res);
});
this.schedule = {
schedule: {
type: this.scheduleType,
offTime: offTime,
weekDay: this.weekDay.value
}
};
this.originScheduleType = this.scheduleType;
this.originWeekDay = this.weekDay;
this.originOffTime = { value: offTime, text: this.dailyTime };
this.isEditMode = false;
this.getJobs();
}, error => {
this.translate.get('GC.MSG_ERROR').subscribe((res: string) => {
this.errorHandler.info(res);
});
});
}
}
}

View File

@ -0,0 +1,20 @@
export const WEEKDAYS = [
{value: 0, text: "WEEKLY.MONDAY"},
{value: 1, text: "WEEKLY.TUESDAY"},
{value: 2, text: "WEEKLY.WEDNESDAY"},
{value: 3, text: "WEEKLY.THURSDAY"},
{value: 4, text: "WEEKLY.FRIDAY"},
{value: 5, text: "WEEKLY.SATURDAY"},
{value: 6, text: "WEEKLY.SUNDAY"}
];
export const SCHEDULE_TYPE = {
NONE: "None",
DAILY: "Daily",
WEEKLY: "Weekly"
};

View File

@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable, Subscription, Subject, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { GcApiRepository } from './gc.api.repository';
import { ErrorHandler } from '@harbor/ui';
import { GcJobData } from './gcLog';
@Injectable()
export class GcRepoService {
constructor(private http: Http,
private gcApiRepository: GcApiRepository,
private errorHandler: ErrorHandler) {
}
public manualGc(): Observable <any> {
let param = {
"schedule": {
"type": "Manual"
}
};
return this.gcApiRepository.postSchedule(param);
}
public getJobs(): Observable <GcJobData []> {
return this.gcApiRepository.getJobs();
}
public getLog(id): Observable <any> {
return this.gcApiRepository.getLog(id);
}
public getSchedule(): Observable <any> {
return this.gcApiRepository.getSchedule();
}
public postScheduleGc(type, offTime, weekday ?): Observable <any> {
let param = {
"schedule": {
"type": type,
"offtime": offTime,
}
};
if (weekday) {
param.schedule["weekday"] = weekday;
}
return this.gcApiRepository.postSchedule(param);
}
public putScheduleGc(type, offTime, weekday ?): Observable <any> {
let param = {
"schedule": {
"type": type,
"offtime": offTime,
}
};
if (weekday) {
param.schedule["weekday"] = weekday;
}
return this.gcApiRepository.putSchedule(param);
}
}

View File

@ -0,0 +1,62 @@
import { Injectable } from '@angular/core';
const ONE_HOUR_SECONDS: number = 3600;
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
@Injectable()
export class GcUtility {
private _localTime: Date = new Date();
public getOffTime(v: string) {
let values: string[] = v.split(":");
if (!values || values.length !== 2) {
return;
}
let hours: number = +values[0];
let minutes: number = +values[1];
// Convert to UTC time
let timezoneOffset: number = this._localTime.getTimezoneOffset();
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
utcTimes += timezoneOffset * 60;
if (utcTimes < 0) {
utcTimes += ONE_DAY_SECONDS;
}
if (utcTimes >= ONE_DAY_SECONDS) {
utcTimes -= ONE_DAY_SECONDS;
}
return utcTimes;
}
public getDailyTime(v: number ) {
let timeOffset: number = 0; // seconds
timeOffset = + v;
// Convert to current time
let timezoneOffset: number = this._localTime.getTimezoneOffset();
// Local time
timeOffset = timeOffset - timezoneOffset * 60;
if (timeOffset < 0) {
timeOffset = timeOffset + ONE_DAY_SECONDS;
}
if (timeOffset >= ONE_DAY_SECONDS) {
timeOffset -= ONE_DAY_SECONDS;
}
// To time string
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
let timeStr: string = "" + hours;
if (hours < 10) {
timeStr = "0" + timeStr;
}
if (minutes < 10) {
timeStr += ":0";
} else {
timeStr += ":";
}
timeStr += minutes;
return timeStr;
}
}

View File

@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { GcJobData, GcJobViewModel } from './gcLog';
@Injectable()
export class GcViewModelFactory {
public createJobViewModel(jobs: GcJobData[]): GcJobViewModel[] {
let gcViewModels: GcJobViewModel[] = [];
for (let job of jobs) {
let createTime = new Date(job.creation_time);
let updateTime = new Date(job.update_time);
gcViewModels.push({
id: job.id,
type: job.schedule.type,
status: job.job_status,
createTime: createTime,
updateTime: updateTime,
details: null
});
}
return gcViewModels;
}
}

View File

@ -0,0 +1,37 @@
export class GcJobData {
id: number;
job_name: string;
job_kind: string;
schedule: Schedule;
job_status: string;
job_uuid: string;
creation_time: string;
update_time: string;
delete: boolean;
}
export class Schedule {
type: string;
weekday: number;
offtime: number;
}
export class GcJobViewModel {
id: number;
type: string;
status: string;
createTime: Date;
updateTime: Date;
details: string;
}
export class WeekDay {
value: number;
text: string;
}
export class GcScheduleViewModel {
type: string;
weekDay: string;
dailyTime: string;
}

View File

@ -555,6 +555,7 @@
"REPO_READ_ONLY": "Repository Read Only",
"SYSTEM": "System Settings",
"VULNERABILITY": "Vulnerability",
"GC": "Garbage Collection",
"CONFIRM_TITLE": "Confirm to cancel",
"CONFIRM_SUMMARY": "Some changes have not been saved. Do you want to discard them?",
"SAVE_SUCCESS": "Configuration has been successfully saved.",
@ -810,5 +811,33 @@
"PRECONDITION_FAILED": "We are unable to perform your action because of a precondition failure.",
"SERVER_ERROR": "We are unable to perform your action because internal server errors have occurred.",
"INCONRRECT_OLD_PWD": "The old password is incorrect.",
"UNKNOWN": "n/a"
"UNKNOWN": "n/a",
"STATUS":"Status",
"START_TIME": "Start Time",
"END_TIME": "End Time",
"DETAILS":"Details",
"PENDING":"Pending",
"FINISHED":"Finished",
"SCHEDULE": {
"NONE": "None",
"DAILY": "Daily",
"WEEKLY": "Weekly",
"MANUAL": "Manual"
},
"GC": {
"CURRENT_SCHEDULE": "Current Schedule",
"ON": "on",
"AT": "at",
"GC_NOW": "GC NOW",
"JOB_LIST":"GC Jobs List",
"JOB_ID":"Job ID",
"TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs",
"LOG_DETAIL":"Log Details",
"MSG_SUCCESS":"Garbage Collection Successful",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
}
}

View File

@ -554,6 +554,7 @@
"REPO_READ_ONLY": "Repository Read Only",
"SYSTEM": "Opciones del Sistema",
"VULNERABILITY": "Vulnerability",
"GC": "Garbage Collection",
"CONFIRM_TITLE": "Confirma cancelación",
"CONFIRM_SUMMARY": "Algunos cambios no han sido guardados aún. ¿Quiere descartarlos?",
"SAVE_SUCCESS": "La configuración ha sido guardada satisfactoriamente.",
@ -595,7 +596,9 @@
"ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.",
"SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday.",
"VERIFY_CERT": "Verify Cert from LDAP Server",
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. "
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
"GC_POLICY": ""
},
"LDAP": {
"URL": "LDAP URL",
@ -805,5 +808,32 @@
"PRECONDITION_FAILED": "No hemos podido llevar a cabo la acción debido a un error de precondición.",
"SERVER_ERROR": "No hemos podido llevar a cabo la acción debido a un error interno.",
"INCONRRECT_OLD_PWD": "La contraseña antigua no es correcta.",
"UNKNOWN": "n/a"
"UNKNOWN": "n/a",
"STATUS":"Status",
"START_TIME": "Start Time",
"END_TIME": "End Time",
"DETAILS":"Details",
"PENDING":"Pending",
"FINISHED":"Finished",
"SCHEDULE": {
"NONE": "None",
"DAILY": "Daily",
"WEEKLY": "Weekly",
"MANUAL": "Manual"
},
"GC": {
"CURRENT_SCHEDULE": "Current Schedule",
"ON": "on",
"AT": "at",
"GC_NOW": "GC NOW",
"JOB_LIST":"GC Jobs List",
"JOB_ID":"Job ID",
"TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs",
"LOG_DETAIL":"Log Details",
"MSG_SUCCESS":"Garbage Collection Successful",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
}
}

View File

@ -553,6 +553,7 @@
"PRO_CREATION_ADMIN": "Les Administrateurs Seulement",
"ROOT_CERT": "Enregistrer le Certificat Racine",
"ROOT_CERT_LINK": "Télécharger",
"GC": "Garbage Collection",
"TOOLTIP": {
"SELF_REGISTRATION_ENABLE": "Activer l'inscription.",
"SELF_REGISTRATION_DISABLE": "Désactiver l'inscription.",
@ -566,7 +567,8 @@
"PRO_CREATION_RESTRICTION": "L'indicateur pour définir quels utilisateurs ont le droit de créer des projets. Par défaut, tout le monde peut créer un projet. Définissez sur 'Administrateur Seulement' pour que seul un administrateur puisse créer un projet.",
"ROOT_CERT_DOWNLOAD": "Téléchargez le certificat racine du dépôt.",
"SCANNING_POLICY": "Définissez la politique d'analyse des images en fonction des différentes exigences. 'Aucune' : pas de politique active; 'Tousles jours à' : déclenchement du balayage à l'heure spécifiée tous les jours.",
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. "
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
"GC_POLICY": ""
},
"LDAP": {
"URL": "URL LDAP",
@ -769,5 +771,32 @@
"PRECONDITION_FAILED": "Nous ne pouvons pas exécuter votre action en raison d'un échec de conditions préalables.",
"SERVER_ERROR": "Nous ne sommes pas en mesure d'exécuter votre action parce que des erreurs internes de serveur se sont produites.",
"INCONRRECT_OLD_PWD": "L'ancien mot de passe est incorrect.",
"UNKNOWN": "n. d."
"UNKNOWN": "n. d.",
"STATUS":"Status",
"START_TIME": "Start Time",
"END_TIME": "End Time",
"DETAILS":"Details",
"PENDING":"Pending",
"FINISHED":"Finished",
"SCHEDULE": {
"NONE": "None",
"DAILY": "Daily",
"WEEKLY": "Weekly",
"MANUAL": "Manual"
},
"GC": {
"CURRENT_SCHEDULE": "Current Schedule",
"ON": "on",
"AT": "at",
"GC_NOW": "GC NOW",
"JOB_LIST":"GC Jobs List",
"JOB_ID":"Job ID",
"TRIGGER_TYPE": "Trigger Type",
"LATEST_JOBS": "Latest {{param}} Jobs",
"LOG_DETAIL":"Log Details",
"MSG_SUCCESS":"Garbage Collection Successful",
"MSG_SCHEDULE_SET":"Garbage Collection schedule has been set",
"MSG_SCHEDULE_RESET":"Garbage Collection schedule has been reset",
"MSG_ERROR":"Can not do Garbase Collection too often,please try again later."
}
}

View File

@ -554,6 +554,7 @@
"REPO_READ_ONLY": "仓库只读",
"SYSTEM": "系统设置",
"VULNERABILITY": "漏洞",
"GC": "垃圾清理",
"CONFIRM_TITLE": "确认取消",
"CONFIRM_SUMMARY": "配置项有改动, 确定取消?",
"SAVE_SUCCESS": "变更的配置项成功保存。",
@ -806,5 +807,32 @@
"PRECONDITION_FAILED": "验证前置条件失败, 无法执行操作。",
"SERVER_ERROR": "服务器出现内部错误,请求无法完成。",
"INCONRRECT_OLD_PWD": "旧密码不正确。",
"UNKNOWN": "未知"
"UNKNOWN": "未知",
"STATUS":"状态",
"START_TIME": "开始时间",
"END_TIME": "结束时间",
"DETAILS":"详情",
"PENDING":"未开始",
"FINISHED":"已完成",
"SCHEDULE": {
"NONE": "无",
"DAILY": "每天",
"WEEKLY": "每周",
"MANUAL": "手动"
},
"GC": {
"CURRENT_SCHEDULE": "当前定时任务",
"ON": " ",
"AT": " ",
"GC_NOW": "立即清理垃圾",
"JOB_LIST":"任务列表",
"JOB_ID":"任务ID",
"TRIGGER_TYPE": "触发类型",
"LATEST_JOBS": "最新的 {{param}} 个任务",
"LOG_DETAIL":"日志详情",
"MSG_SUCCESS":"垃圾回收成功",
"MSG_SCHEDULE_SET":"垃圾回收定时任务设置成功",
"MSG_SCHEDULE_RESET":"垃圾回收定时任务已被重置",
"MSG_ERROR":"您的垃圾回收请求提交过于频繁,请稍候重试"
}
}

View File

@ -156,7 +156,7 @@ fi
# harbor-offline-installer-latest.tgz
#
set -e
if [ $upload_build == true ] && [ $rc -eq 0 ]; then
if [ $upload_build == true ]; then
cp $harbor_build_bundle harbor-offline-installer-latest.tgz
uploader $harbor_build_bundle $harbor_target_bucket
uploader harbor-offline-installer-latest.tgz $harbor_target_bucket
@ -168,13 +168,13 @@ fi
#
# latest.build file holds the latest offline installer url, it must be sure that the installer has been uploaded successfull.
#
if [ $upload_latest_build == true ] && [ $upload_bundle_success == true ] && [ $rc -eq 0 ]; then
if [ $upload_latest_build == true ] && [ $upload_bundle_success == true ]; then
echo 'https://storage.googleapis.com/'$harbor_target_bucket/$harbor_build_bundle > $latest_build_file
uploader $latest_build_file $harbor_target_bucket
fi
## --------------------------------------------- Upload securego results ------------------------------------------
if [ $DRONE_BUILD_EVENT == "push" ] && [ $rc -eq 0 ]; then
if [ $DRONE_BUILD_EVENT == "push" ]; then
go get github.com/securego/gosec/cmd/gosec
go get github.com/dghubble/sling
make gosec -e GOSECRESULTS=harbor-gosec-results-latest.json