Add webhook UI

Signed-off-by: 方彬 <fang_bingo@163.com>
This commit is contained in:
方彬 2019-08-08 17:58:49 +08:00
parent 9cbcc93e8a
commit 44ca591582
28 changed files with 823 additions and 22 deletions

View File

@ -87,6 +87,7 @@ export class Configuration {
token_expiration: NumberValueItem;
scan_all_policy: ComplexValueItem;
read_only: BoolValueItem;
notification_enable: BoolValueItem;
http_authproxy_endpoint?: StringValueItem;
http_authproxy_tokenreview_endpoint?: StringValueItem;
http_authproxy_verify_cert?: BoolValueItem;
@ -140,6 +141,7 @@ export class Configuration {
}
}, true);
this.read_only = new BoolValueItem(false, true);
this.notification_enable = new BoolValueItem(false, true);
this.http_authproxy_endpoint = new StringValueItem("", true);
this.http_authproxy_tokenreview_endpoint = new StringValueItem("", true);
this.http_authproxy_verify_cert = new BoolValueItem(false, true);

View File

@ -142,9 +142,21 @@
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="webhookNotificationEnabled">{{'CONFIG.WEBHOOK_NOTIFICATION_ENABLED' | translate}}</label>
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox name="webhookNotificationEnabled" id="webhookNotificationEnabled" [ngModel]="systemSettings.notification_enable.value"
(ngModelChange)="setWebhookNotificationEnabledValue($event)" [ngModel]="systemSettings.notification_enable.value"/>
<label>
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right read-tooltip">
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
<span class="tooltip-content">{{'CONFIG.TOOLTIP.WEBHOOK_TOOLTIP' | translate}}</span>
</a>
</label>
</clr-checkbox-wrapper>
</div>
</section>
</form>
<div>

View File

@ -108,7 +108,7 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
let changes = {};
for (let prop in allChanges) {
if (prop === 'token_expiration' || prop === 'read_only' || prop === 'project_creation_restriction'
|| prop === 'robot_token_duration') {
|| prop === 'robot_token_duration' || prop === 'notification_enable') {
changes[prop] = allChanges[prop];
}
}
@ -119,6 +119,10 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
this.systemSettings.read_only.value = $event;
}
setWebhookNotificationEnabledValue($event: any) {
this.systemSettings.notification_enable.value = $event;
}
disabled(prop: any): boolean {
return !(prop && prop.editable);
}

View File

@ -23,10 +23,18 @@
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.ENABLE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="5">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="confirm()">{{'BUTTON.DISABLE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="6">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.REPLICATE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="5">
<ng-template [ngSwitchCase]="7">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()" [hidden]="isDelete">{{'BUTTON.STOP' | translate}}</button>
</ng-template>

View File

@ -144,5 +144,12 @@ export const USERSTATICPERMISSION = {
"PUSH": "push"
}
},
"WEBHOOK": {
"KEY": "notification-policy",
"VALUE": {
"LIST": "list",
"READ": "read",
}
},
};

View File

@ -69,7 +69,7 @@ export const FilterType = {
};
export const enum ConfirmationButtons {
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, REPLICATE_CANCEL, STOP_CANCEL
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, ENABLE_CANCEL, DISABLE_CANCEL, REPLICATE_CANCEL, STOP_CANCEL
}
export const QuotaUnits = [
{

View File

@ -49,6 +49,7 @@ import { ProjectComponent } from './project/project.component';
import { ProjectDetailComponent } from './project/project-detail/project-detail.component';
import { MemberComponent } from './project/member/member.component';
import { RobotAccountComponent } from './project/robot-account/robot-account.component';
import { WebhookComponent } from './project/webhook/webhook.component';
import { ProjectLabelComponent } from "./project/project-label/project-label.component";
import { ProjectConfigComponent } from './project/project-config/project-config.component';
import { ProjectRoutingResolver } from './project/project-routing-resolver.service';
@ -207,7 +208,11 @@ const harborRoutes: Routes = [
{
path: 'tag-retention',
component: TagRetentionComponent
}
},
{
path: 'webhook',
component: WebhookComponent
},
]
},
{

View File

@ -28,6 +28,9 @@
<li class="nav-item" *ngIf="hasTagRetentionPermission">
<a class="nav-link" routerLink="tag-retention" routerLinkActive="active">{{'TAG_RETENTION.TAG_RETENTION' | translate}}</a>
</li>
<li class="nav-item" *ngIf="hasWebhookListPermission">
<a class="nav-link" routerLink="webhook" routerLinkActive="active">{{'PROJECT_DETAIL.WEBHOOKS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSessionValid && (hasConfigurationListPermission)">
<a class="nav-link" routerLink="configs" routerLinkActive="active">{{'PROJECT_DETAIL.CONFIG' | translate}}</a>
</li>

View File

@ -44,6 +44,7 @@ export class ProjectDetailComponent implements OnInit {
hasConfigurationListPermission: boolean;
hasRobotListPermission: boolean;
hasTagRetentionPermission: boolean;
hasWebhookListPermission: boolean;
constructor(
private route: ActivatedRoute,
private router: Router,
@ -86,11 +87,12 @@ export class ProjectDetailComponent implements OnInit {
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.TAG_RETENTION.KEY, USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.WEBHOOK.KEY, USERSTATICPERMISSION.WEBHOOK.VALUE.LIST));
forkJoin(...permissionsList).subscribe(Rules => {
[this.hasProjectReadPermission, this.hasLogListPermission, this.hasConfigurationListPermission, this.hasMemberListPermission
, this.hasLabelListPermission, this.hasRepositoryListPermission, this.hasHelmChartsListPermission, this.hasRobotListPermission
, this.hasLabelCreatePermission, this.hasTagRetentionPermission] = Rules;
, this.hasLabelCreatePermission, this.hasTagRetentionPermission, this.hasWebhookListPermission] = Rules;
}, error => this.errorHandler.error(error));
}

View File

@ -43,7 +43,10 @@ import { AddHttpAuthGroupComponent } from './member/add-http-auth-group/add-http
import { TagRetentionComponent } from "./tag-retention/tag-retention.component";
import { AddRuleComponent } from "./tag-retention/add-rule/add-rule.component";
import { TagRetentionService } from "./tag-retention/tag-retention.service";
import { WebhookService } from './webhook/webhook.service';
import { WebhookComponent } from './webhook/webhook.component';
import { AddWebhookComponent } from './webhook/add-webhook/add-webhook.component';
import { AddWebhookFormComponent } from './webhook/add-webhook-form/add-webhook-form.component';
@NgModule({
imports: [
@ -70,9 +73,12 @@ import { TagRetentionService } from "./tag-retention/tag-retention.service";
AddHttpAuthGroupComponent,
TagRetentionComponent,
AddRuleComponent,
WebhookComponent,
AddWebhookComponent,
AddWebhookFormComponent,
],
exports: [ProjectComponent, ListProjectComponent],
providers: [ProjectRoutingResolver, MemberService, RobotService, TagRetentionService]
providers: [ProjectRoutingResolver, MemberService, RobotService, TagRetentionService, WebhookService]
})
export class ProjectModule {

View File

@ -0,0 +1,46 @@
<div class="align-center">
<form #webhookForm="ngForm">
<section class="form-block webhook-section">
<!-- endpoint URL -->
<div class="form-group">
<label for="edit_endpoint_url" class="col-md-3 form-group-label-override required">{{'WEBHOOK.ENDPOINT_URL' | translate}}</label>
<label for="edit_endpoint_url" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-bottom-left"
[class.invalid]="enpointURL.errors && (enpointURL.dirty || enpointURL.touched)" [class.valid]="enpointURL.valid">
<input type="text" id="edit_endpoint_url" [disabled]="checking" [(ngModel)]="webhookTarget.address"
size="25" name="edit_endpoint_url" #enpointURL="ngModel" required placeholder="http(s)://192.168.1.1">
<span class="tooltip-content" *ngIf="enpointURL.errors && enpointURL.errors.required && (enpointURL.dirty || enpointURL.touched)">
{{ 'WEBHOOK.URL_IS_REQUIRED' | translate }}
</span>
</label>
</div>
<!-- auth_header -->
<div class="form-group">
<label for="auth_header" class="col-md-3 form-group-label-override">{{ 'WEBHOOK.AUTH_HEADER' |
translate }}</label>
<input type="text" id="auth_header" [disabled]="checking"
[(ngModel)]="webhookTarget.auth_header" size="28" name="auth_header">
</div>
<!-- verify remote cert -->
<div class="form-group">
<label for="verify_remote_cert">{{'WEBHOOK.VERIFY_REMOTE_CERT' | translate}}</label>
<input type="checkbox" [disabled]="checking" clrCheckbox name="verify_remote_cert" id="verify_remote_cert"
(ngModelChange)="setCertValue($event)" [ngModel]="!webhookTarget.skip_cert_verify" class="clr-checkbox"/>
<clr-tooltip class="icon-tooltip">
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
<clr-tooltip-content clrPosition="top-right" clrSize="md" *clrIfOpen>
{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate}}
</clr-tooltip-content>
</clr-tooltip>
</div>
</section>
</form>
<div *ngIf="!isModify">
<button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.CONTINUE' | translate}}</button>
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
</div>
<div *ngIf="isModify">
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="!isValid" (click)="onSubmit()">{{'BUTTON.SAVE' | translate}}</button>
</div>
</div>

View File

@ -0,0 +1,12 @@
.align-center {
text-align: center;
}
.webhook-section {
margin-left: calc(50% - 10rem);
text-align: left;
}
.icon-tooltip {
margin-top: 4px;
}

View File

@ -0,0 +1,112 @@
import {
Component,
OnInit,
OnChanges,
Input,
ViewChild,
Output,
EventEmitter,
SimpleChanges
} from "@angular/core";
import { Webhook, Target } from "../webhook";
import { NgForm } from "@angular/forms";
import {ClrLoadingState} from "@clr/angular";
import { finalize } from "rxjs/operators";
import { WebhookService } from "../webhook.service";
import { WebhookEventTypes } from '../../../shared/shared.const';
import { MessageHandlerService } from "../../../shared/message-handler/message-handler.service";
@Component({
selector: 'add-webhook-form',
templateUrl: './add-webhook-form.component.html',
styleUrls: ['./add-webhook-form.component.scss']
})
export class AddWebhookFormComponent implements OnInit, OnChanges {
closable: boolean = true;
staticBackdrop: boolean = true;
checking: boolean = false;
checkBtnState: ClrLoadingState = ClrLoadingState.DEFAULT;
webhookForm: NgForm;
submitting: boolean = false;
webhookTarget: Target = new Target();
@Input() projectId: number;
@Input() webhook: Webhook;
@Input() isModify: boolean;
@Input() isOpen: boolean;
@Output() edit = new EventEmitter<boolean>();
@Output() close = new EventEmitter<boolean>();
@ViewChild("webhookForm") currentForm: NgForm;
constructor(
private webhookService: WebhookService,
private messageHandlerService: MessageHandlerService
) { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges) {
if (changes['isOpen'] && changes['isOpen'].currentValue) {
Object.assign(this.webhookTarget, this.webhook.targets[0]);
}
}
onTestEndpoint() {
this.checkBtnState = ClrLoadingState.LOADING;
this.checking = true;
this.webhookService
.testEndpoint(this.projectId, {
targets: [this.webhookTarget]
})
.pipe(finalize(() => (this.checking = false)))
.subscribe(
response => {
this.checkBtnState = ClrLoadingState.SUCCESS;
},
error => {
this.checkBtnState = ClrLoadingState.DEFAULT;
this.messageHandlerService.handleError(error);
}
);
}
onCancel() {
this.close.emit(false);
this.currentForm.reset();
}
onSubmit() {
const rx = this.isModify
? this.webhookService.editWebhook(this.projectId, this.webhook.id, Object.assign(this.webhook, { targets: [this.webhookTarget] }))
: this.webhookService.createWebhook(this.projectId, {
targets: [this.webhookTarget],
event_types: Object.keys(WebhookEventTypes).map(key => WebhookEventTypes[key]),
enabled: true,
});
rx.pipe(finalize(() => (this.submitting = false)))
.subscribe(
response => {
this.edit.emit(this.isModify);
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
setCertValue($event: any): void {
this.webhookTarget.skip_cert_verify = !$event;
}
public get isValid(): boolean {
return (
this.currentForm &&
this.currentForm.valid &&
!this.submitting &&
!this.checking
);
}
}

View File

@ -0,0 +1,13 @@
<clr-modal [(clrModalOpen)]="isOpen" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
<h3 class="modal-title">{{'WEBHOOK.EDIT_WEBHOOK' | translate}}</h3>
<div class="modal-body">
<div>{{'WEBHOOK.EDIT_WEBHOOK_DESC' | translate}}</div>
<add-webhook-form [projectId]="projectId"
[isModify]="true"
[isOpen]="isOpen"
[webhook]="webhook"
(edit)="closeModal($event)"
(close)="closeModal($event)"
></add-webhook-form>
</div>
</clr-modal>

View File

@ -0,0 +1,49 @@
import {
Component,
OnInit,
Input,
ViewChild,
Output,
EventEmitter,
} from "@angular/core";
import { Webhook } from "../webhook";
import { AddWebhookFormComponent } from "../add-webhook-form/add-webhook-form.component";
@Component({
selector: 'add-webhook',
templateUrl: './add-webhook.component.html',
styleUrls: ['./add-webhook.component.scss']
})
export class AddWebhookComponent implements OnInit {
isOpen: boolean = false;
closable: boolean = true;
staticBackdrop: boolean = true;
@Input() projectId: number;
@Input() webhook: Webhook;
@Output() modify = new EventEmitter<boolean>();
@ViewChild(AddWebhookFormComponent)
addWebhookFormComponent: AddWebhookFormComponent;
constructor() { }
ngOnInit() {
}
openAddWebhookModal() {
this.isOpen = true;
}
onCancel() {
this.isOpen = false;
}
closeModal(isModified: boolean): void {
if (isModified) {
this.modify.emit(true);
}
this.isOpen = false;
}
}

View File

@ -0,0 +1,51 @@
<div class="row">
<div *ngIf="!showCreate">
<div>
<div class="row flex-items-xs-between rightPos">
<div class="flex-xs-middle option-left">
<div>
<span class="endpoint-label">Webhook endpoint</span>: {{endpoint}}
<button class="btn btn-link" (click)="openAddWebhookModal()">{{'WEBHOOK.EDIT_BUTTON' | translate}}</button>
</div>
<div [ngSwitch]="isEnabled">
<button *ngSwitchCase="false" class="btn btn-link" (click)="switchWebhookStatus(true)">{{'WEBHOOK.ENABLED_BUTTON' | translate}}</button>
<button *ngSwitchCase="true" class="btn btn-link disabled-btn" (click)="switchWebhookStatus(false)">{{'WEBHOOK.DISABLED_BUTTON' | translate}}</button>
</div>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 datagrid-margin-top">
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-column>{{'WEBHOOK.TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
<clr-dg-column>{{'WEBHOOK.LAST_TRIGGERED' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let item of lastTriggers">
<clr-dg-cell>{{item.event_type}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="item.enabled">
<div *ngSwitchCase="true" class="icon-wrap">
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
<span>{{'WEBHOOK.ENABLED' | translate}}</span>
</div>
<div *ngSwitchCase="false" class="icon-wrap">
<clr-icon shape="exclamation-triangle" size="20" class="is-warning"></clr-icon>
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
</div>
</clr-dg-cell>
<clr-dg-cell>{{item.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{item.last_trigger_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="lastTriggerCount">1 - {{lastTriggerCount}} {{'WEBHOOK.OF' | translate}} </span> {{lastTriggerCount}} {{'WEBHOOK.ITEMS' | translate}}
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
<div *ngIf="showCreate">
<h2 class="create-text create-text-title">{{'WEBHOOK.CREATE_WEBHOOK' | translate}}</h2>
<p class="create-text">{{'WEBHOOK.CREATE_WEBHOOK_DESC' | translate}}</p>
<add-webhook-form class="webhook-form-wrap" [projectId]="projectId" [webhook]="webhook" [isModify]="false" (edit)="editWebhook($event)"></add-webhook-form>
</div>
<add-webhook [projectId]="projectId" [webhook]="webhook" (modify)="editWebhook($event)"></add-webhook>
<confirmation-dialog #confirmationDialogComponent (confirmAction)="confirmSwitch($event)"></confirmation-dialog>
</div>

View File

@ -0,0 +1,37 @@
.label-top {
top: 12px;
}
.icon-wrap {
height: 14px;
}
.webhook-form-wrap {
width: 19rem;
margin: 0 auto;
}
.create-text {
margin: 0 auto;
width: 19rem;
}
.create-text-title {
margin-top: 1rem;
}
.endpoint-label {
font-weight: bold;
}
.disabled-btn {
color: #e12200;
}
.disabled-btn:hover {
color: #c92100;
}
.enabled-icon {
margin: -2px 5px 0 0;
}

View File

@ -0,0 +1,154 @@
// 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 { finalize } from "rxjs/operators";
import { TranslateService } from '@ngx-translate/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { AddWebhookComponent } from "./add-webhook/add-webhook.component";
import { AddWebhookFormComponent } from "./add-webhook-form/add-webhook-form.component";
import { ActivatedRoute } from '@angular/router';
import { Webhook, LastTrigger } from './webhook';
import { WebhookService } from './webhook.service';
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
import { Project } from '../project';
import {
ConfirmationTargets,
ConfirmationState,
ConfirmationButtons
} from "../../shared/shared.const";
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../../shared/confirmation-dialog/confirmation-state-message";
import { ConfirmationDialogComponent } from "../../shared/confirmation-dialog/confirmation-dialog.component";
@Component({
templateUrl: './webhook.component.html',
styleUrls: ['./webhook.component.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush
})
export class WebhookComponent implements OnInit {
@ViewChild(AddWebhookComponent)
addWebhookComponent: AddWebhookComponent;
@ViewChild(AddWebhookFormComponent)
addWebhookFormComponent: AddWebhookFormComponent;
@ViewChild("confirmationDialogComponent")
confirmationDialogComponent: ConfirmationDialogComponent;
webhook: Webhook;
endpoint: string = '';
lastTriggers: LastTrigger[] = [];
lastTriggerCount: number = 0;
isEnabled: boolean;
loading: boolean = false;
showCreate: boolean = false;
projectId: number;
projectName: string;
constructor(
private route: ActivatedRoute,
private translate: TranslateService,
private webhookService: WebhookService,
private messageHandlerService: MessageHandlerService) {}
ngOnInit() {
this.projectId = +this.route.snapshot.parent.params['id'];
let resolverData = this.route.snapshot.parent.data;
if (resolverData) {
let project = <Project>(resolverData["projectResolver"]);
this.projectName = project.name;
}
this.getData(this.projectId);
}
getData(projectId: number) {
this.getLastTriggers(projectId);
this.getWebhook(projectId);
}
getLastTriggers(projectId: number) {
this.loading = true;
this.webhookService
.listLastTrigger(projectId)
.pipe(finalize(() => (this.loading = false)))
.subscribe(
response => {
this.lastTriggers = response;
this.lastTriggerCount = response.length;
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
getWebhook(projectId: number) {
this.webhookService
.listWebhook(projectId)
.subscribe(
response => {
if (response.length) {
this.webhook = response[0];
this.endpoint = this.webhook.targets[0].address;
this.isEnabled = this.webhook.enabled;
this.showCreate = false;
} else {
this.showCreate = true;
}
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
switchWebhookStatus(enabled = false) {
let content = '';
this.translate.get(
enabled
? 'WEBHOOK.ENABLED_WEBHOOK_SUMMARY'
: 'WEBHOOK.DISABLED_WEBHOOK_SUMMARY'
).subscribe((res) => content = res + this.projectName);
let message = new ConfirmationMessage(
enabled ? 'WEBHOOK.ENABLED_WEBHOOK_TITLE' : 'WEBHOOK.DISABLED_WEBHOOK_TITLE',
content,
'',
{},
ConfirmationTargets.WEBHOOK,
enabled ? ConfirmationButtons.ENABLE_CANCEL : ConfirmationButtons.DISABLE_CANCEL
);
this.confirmationDialogComponent.open(message);
}
confirmSwitch(message: ConfirmationAcknowledgement) {
if (message &&
message.source === ConfirmationTargets.WEBHOOK &&
message.state === ConfirmationState.CONFIRMED) {
this.webhookService
.editWebhook(this.projectId, this.webhook.id, Object.assign({}, this.webhook, { enabled: !this.isEnabled }))
.subscribe(
response => {
this.getData(this.projectId);
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
}
editWebhook(isModify: boolean): void {
this.getData(this.projectId);
}
openAddWebhookModal(): void {
this.addWebhookComponent.openAddWebhookModal();
}
}

View File

@ -0,0 +1,56 @@
// 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 { throwError as observableThrowError, Observable } from "rxjs";
import { map, catchError } from "rxjs/operators";
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Webhook, LastTrigger } from "./webhook";
@Injectable()
export class WebhookService {
constructor(private http: HttpClient) { }
public listWebhook(projectId: number): Observable<Webhook[]> {
return this.http
.get(`/api/projects/${projectId}/webhook/policies`)
.pipe(map(response => response as Webhook[]))
.pipe(catchError(error => observableThrowError(error)));
}
public listLastTrigger(projectId: number): Observable<LastTrigger[]> {
return this.http
.get(`/api/projects/${projectId}/webhook/lasttrigger`)
.pipe(map(response => response as LastTrigger[]))
.pipe(catchError(error => observableThrowError(error)));
}
public editWebhook(projectId: number, policyId: number, data: any): Observable<any> {
return this.http
.put(`/api/projects/${projectId}/webhook/policies/${policyId}`, data)
.pipe(catchError(error => observableThrowError(error)));
}
public createWebhook(projectId: number, data: any): Observable<any> {
return this.http
.post(`/api/projects/${projectId}/webhook/policies`, data)
.pipe(catchError(error => observableThrowError(error)));
}
public testEndpoint(projectId: number, param): Observable<any> {
return this.http
.post(`/api/projects/${projectId}/webhook/policies/test`, param)
.pipe(catchError(error => observableThrowError(error)));
}
}

View File

@ -0,0 +1,35 @@
import { WebhookEventTypes } from '../../shared/shared.const';
export class Webhook {
id: number;
name: string;
project_id: number;
description: string;
targets: Target[];
event_types: WebhookEventTypes[];
creator: string;
creation_time: Date;
update_time: Date;
enabled: boolean;
}
export class Target {
type: string;
address: string;
attachment: string;
auth_header: string;
skip_cert_verify: boolean;
constructor () {
this.type = 'http';
this.address = '';
this.skip_cert_verify = true;
}
}
export class LastTrigger {
enabled: boolean;
event_type: string;
creation_time: Date;
last_trigger_time: Date;
}

View File

@ -22,7 +22,15 @@
<ng-template [ngSwitchCase]="3">
<button type="button" class="btn btn-primary" (click)="cancel()">{{'BUTTON.CLOSE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="4">
<ng-template [ngSwitchCase]="4">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="confirm()">{{'BUTTON.ENABLE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="5">
<button type="button" class="btn btn-outline" (click)="cancel()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-danger" (click)="confirm()">{{'BUTTON.DISABLE' | translate}}</button>
</ng-template>
<ng-template [ngSwitchCase]="6">
<button type="button" class="btn btn-outline" (click)="cancel()" [hidden]="isDelete">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" class="btn btn-primary" [hidden]="isDelete">{{'BUTTON.SWITCH' | translate}}</button>
<button type="button" class="btn btn-primary" (click)="cancel()" [disabled]="'true'" [hidden]="!isDelete">{{'BUTTON.CLOSE' | translate}}</button>

View File

@ -39,7 +39,8 @@ export const enum ConfirmationTargets {
CONFIG_ROUTE,
CONFIG_TAB,
HELM_CHART,
HELM_CHART_VERSION
HELM_CHART_VERSION,
WEBHOOK
}
export const enum ActionType {
@ -53,7 +54,7 @@ export const enum ConfirmationState {
NA, CONFIRMED, CANCEL
}
export const enum ConfirmationButtons {
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, SWITCH_CANCEL
CONFIRM_CANCEL, YES_NO, DELETE_CANCEL, CLOSE, ENABLE_CANCEL, DISABLE_CANCEL, SWITCH_CANCEL
}
export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' };
@ -80,3 +81,14 @@ export enum ResourceType {
CHART_VERSION = 2,
REPOSITORY_TAG = 3,
}
export enum WebhookEventTypes {
DOWNLOAD_CHART = "downloadChart",
DELETE_CHART = "deleteChart",
UPLOAD_CHART = "uploadChart",
DELETE_IMAGE = "deleteImage",
PULL_IMAGE = "pullImage",
PUSH_IMAGE = "pushImage",
SCANNING_FAILED = "scanningFailed",
SCANNING_COMPLETED = "scanningCompleted",
}

View File

@ -45,7 +45,10 @@
"UPLOAD": "Upload",
"NO_FILE": "No file selected",
"ADD": "ADD",
"RUN": "RUN"
"RUN": "RUN",
"CONTINUE": "CONTINUE",
"ENABLE": "ENABLE",
"DISABLE": "DISABLE"
},
"BATCH": {
"DELETED_SUCCESS": "Deleted successfully",
@ -237,7 +240,8 @@
"PROJECTS": "Projects",
"CONFIG": "Configuration",
"HELMCHART": "Helm Charts",
"ROBOT_ACCOUNTS": "Robot Accounts"
"ROBOT_ACCOUNTS": "Robot Accounts",
"WEBHOOKS": "Webhooks"
},
"PROJECT_CONFIG": {
"REGISTRY": "Project registry",
@ -335,6 +339,34 @@
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
"EXPORT_TO_FILE" : "export to file"
},
"WEBHOOK": {
"EDIT_BUTTON": "EDIT",
"ENABLED_BUTTON": "ENABLED",
"DISABLED_BUTTON": "DISABLED",
"TYPE": "Webhook",
"STATUS": "Status",
"CREATED": "Created",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
"ENDPOINT_URL": "Endpoint URL",
"URL_IS_REQUIRED": "Endpoint URL is required.",
"AUTH_HEADER": "Auth Header",
"VERIFY_REMOTE_CERT": "Verify Remote Certificate",
"TEST_ENDPOINT_BUTTON": "TEST ENDPOINT",
"CANCEL_BUTTON": "CANCEL",
"SAVE_BUTTON": "SAVE",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
},
"GROUP": {
"GROUP": "Group",
"GROUPS": "Groups",
@ -717,6 +749,7 @@
"LABEL": "Labels",
"REPOSITORY": "Repository",
"REPO_READ_ONLY": "Repository Read Only",
"WEBHOOK_NOTIFICATION_ENABLED": "Webhooks enabled",
"SYSTEM": "System Settings",
"PROJECT_QUOTAS": "Project Quotas",
"VULNERABILITY": "Vulnerability",
@ -769,6 +802,7 @@
"VERIFY_CERT": "Verify Cert from LDAP Server",
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalent to 0 0 * * * *.",
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalent to 0 0 0 * * 0.",
"DAILY_CRON":"Run once a day, midnight. Equivalent to 0 0 0 * * *."

View File

@ -45,7 +45,10 @@
"UPLOAD": "Upload",
"NO_FILE": "No file selected",
"ADD": "ADD",
"RUN": "RUN"
"RUN": "RUN",
"CONTINUE": "CONTINUE",
"ENABLE": "ENABLE",
"DISABLE": "DISABLE"
},
"BATCH": {
"DELETED_SUCCESS": "Deleted successfully",
@ -238,7 +241,8 @@
"PROJECTS": "Proyectos",
"CONFIG": "Configuración",
"HELMCHART": "Helm Charts",
"ROBOT_ACCOUNTS": "Robot Accounts"
"ROBOT_ACCOUNTS": "Robot Accounts",
"WEBHOOKS": "Webhooks"
},
"PROJECT_CONFIG": {
"REGISTRY": "Registro de proyectos",
@ -336,6 +340,34 @@
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
"EXPORT_TO_FILE" : "export to file"
},
"WEBHOOK": {
"EDIT_BUTTON": "EDIT",
"ENABLED_BUTTON": "ENABLED",
"DISABLED_BUTTON": "DISABLED",
"TYPE": "Webhook",
"STATUS": "Status",
"CREATED": "Created",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
"ENDPOINT_URL": "Endpoint URL",
"URL_IS_REQUIRED": "Endpoint URL is required.",
"AUTH_HEADER": "Auth Header",
"VERIFY_REMOTE_CERT": "Verify Remote Certificate",
"TEST_ENDPOINT_BUTTON": "TEST ENDPOINT",
"CANCEL_BUTTON": "CANCEL",
"SAVE_BUTTON": "SAVE",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
},
"GROUP": {
"GROUP": "Group",
"GROUPS": "Groups",
@ -769,6 +801,7 @@
"VERIFY_CERT": "Verify Cert from LDAP Server",
"READONLY_TOOLTIP": "In read-only mode, you can not delete repositories or tags or push images. ",
"GC_POLICY": "",
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
"DAILY_CRON":"Run once a day, midnight. Equivalente a 0 0 0 * * *."

View File

@ -42,7 +42,10 @@
"UPLOAD": "Upload",
"NO_FILE": "No file selected",
"ADD": "ADD",
"RUN": "RUN"
"RUN": "RUN",
"CONTINUE": "CONTINUE",
"ENABLE": "ENABLE",
"DISABLE": "DISABLE"
},
"BATCH": {
"DELETED_SUCCESS": "Deleted successfully",
@ -231,7 +234,8 @@
"PROJECTS": "Projets",
"CONFIG": "Configuration",
"HELMCHART": "Helm Charts",
"ROBOT_ACCOUNTS": "Robot Accounts"
"ROBOT_ACCOUNTS": "Robot Accounts",
"WEBHOOKS": "Webhooks"
},
"PROJECT_CONFIG": {
"REGISTRY": "Dépôt du Projet",
@ -328,6 +332,34 @@
"PULL_IS_MUST" : "Pull permission is checked by default and can not be modified.",
"EXPORT_TO_FILE" : "export to file"
},
"WEBHOOK": {
"EDIT_BUTTON": "EDIT",
"ENABLED_BUTTON": "ENABLED",
"DISABLED_BUTTON": "DISABLED",
"TYPE": "Webhook",
"STATUS": "Status",
"CREATED": "Created",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
"ENDPOINT_URL": "Endpoint URL",
"URL_IS_REQUIRED": "Endpoint URL is required.",
"AUTH_HEADER": "Auth Header",
"VERIFY_REMOTE_CERT": "Verify Remote Certificate",
"TEST_ENDPOINT_BUTTON": "TEST ENDPOINT",
"CANCEL_BUTTON": "CANCEL",
"SAVE_BUTTON": "SAVE",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
},
"GROUP": {
"Group": "Group",
"GROUPS": "Groups",
@ -750,6 +782,7 @@
"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. ",
"GC_POLICY": "",
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
"HOURLY_CRON":"Run once an hour, beginning of hour. Équivalent à 0 0 * * * *.",
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Équivalent à 0 0 0 * * 0.",
"DAILY_CRON":"Run once a day, midnight. Équivalent à 0 0 0 * * *."

View File

@ -45,7 +45,10 @@
"UPLOAD": "Upload",
"NO_FILE": "Nenhum arquivo selecionado",
"ADD": "ADD",
"RUN": "RUN"
"RUN": "RUN",
"CONTINUE": "CONTINUE",
"ENABLE": "ENABLE",
"DISABLE": "DISABLE"
},
"BATCH": {
"DELETED_SUCCESS": "Removido com sucesso",
@ -235,7 +238,8 @@
"PROJECTS": "Projetos",
"CONFIG": "Configuração",
"HELMCHART": "Helm Charts",
"ROBOT_ACCOUNTS": "Robot Accounts"
"ROBOT_ACCOUNTS": "Robot Accounts",
"WEBHOOKS": "Webhooks"
},
"PROJECT_CONFIG": {
"REGISTRY": "Registro do Projeto",
@ -362,6 +366,34 @@
"DEVELOPER": "Developer",
"GUEST": "Guest"
},
"WEBHOOK": {
"EDIT_BUTTON": "EDIT",
"ENABLED_BUTTON": "ENABLED",
"DISABLED_BUTTON": "DISABLED",
"TYPE": "Webhook",
"STATUS": "Status",
"CREATED": "Created",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"OF": "of",
"ITEMS": "items",
"LAST_TRIGGERED": "Last Triggered",
"EDIT_WEBHOOK": "Webhook Endpoint",
"CREATE_WEBHOOK": "Getting started with webhooks",
"EDIT_WEBHOOK_DESC": "Specify the endpoint for receiving webhook notifications",
"CREATE_WEBHOOK_DESC": "To get started with webhooks, provide an endpoint and credentials to access the webhook server.",
"ENDPOINT_URL": "Endpoint URL",
"URL_IS_REQUIRED": "Endpoint URL is required.",
"AUTH_HEADER": "Auth Header",
"VERIFY_REMOTE_CERT": "Verify Remote Certificate",
"TEST_ENDPOINT_BUTTON": "TEST ENDPOINT",
"CANCEL_BUTTON": "CANCEL",
"SAVE_BUTTON": "SAVE",
"ENABLED_WEBHOOK_TITLE": "Enable Project Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "Do you want to enable webhooks for project ",
"DISABLED_WEBHOOK_TITLE": "Disable Project Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "Do you want to disable webhooks for project "
},
"AUDIT_LOG": {
"USERNAME": "Nome do usuário",
"REPOSITORY_NAME": "Nome do repositório",
@ -764,6 +796,7 @@
"VERIFY_CERT": "Verificar o Certificado do Servidor LDAP",
"READONLY_TOOLTIP": "Em modo somente leitura, você não pode remover repositórios ou tags ou enviar imagens. ",
"REPO_TOOLTIP": "Usuários não podem efetuar qualquer operação nas imagens nesse modo.",
"WEBHOOK_TOOLTIP": "Enable webhooks to receive callbacks at your designated endpoints when certain actions such as image or chart being pushed, pulled, deleted, scanned are performed",
"HOURLY_CRON":"Run once an hour, beginning of hour. Equivalente a 0 0 * * * *.",
"WEEKLY_CRON":"Run once a week, midnight between Sat/Sun. Equivalente a 0 0 0 * * 0.",
"DAILY_CRON":"Run once a day, midnight. Equivalente a 0 0 0 * * *."

View File

@ -45,7 +45,10 @@
"UPLOAD": "上传",
"NO_FILE": "未选择文件",
"ADD": "添加",
"RUN": "执行"
"RUN": "执行",
"CONTINUE": "继续",
"ENABLE": "启用",
"DISABLE": "关闭"
},
"BATCH": {
"DELETED_SUCCESS": "删除成功",
@ -236,7 +239,8 @@
"PROJECTS": "项目",
"CONFIG": "配置管理",
"HELMCHART": "Helm Charts",
"ROBOT_ACCOUNTS": "机器人账户"
"ROBOT_ACCOUNTS": "机器人账户",
"WEBHOOKS": "Webhooks"
},
"PROJECT_CONFIG": {
"REGISTRY": "项目仓库",
@ -334,6 +338,34 @@
"PULL_IS_MUST" : "拉取权限默认选中且不可修改。",
"EXPORT_TO_FILE" : "导出到文件中"
},
"WEBHOOK": {
"EDIT_BUTTON": "编辑",
"ENABLED_BUTTON": "启用",
"DISABLED_BUTTON": "停用",
"TYPE": "Webhook",
"STATUS": "状态",
"CREATED": "创建时间",
"ENABLED": "启用",
"DISABLED": "停用",
"OF": "共计",
"ITEMS": "条记录",
"LAST_TRIGGERED": "最近触发事件",
"EDIT_WEBHOOK": "Webhook 目标",
"CREATE_WEBHOOK": "创建 Webhooks",
"EDIT_WEBHOOK_DESC": "指定接收 Webhook 通知的目标",
"CREATE_WEBHOOK_DESC": "为了启用 webhook, 请提供 Endpoint 和凭据以访问 Webhook 服务器。",
"ENDPOINT_URL": "Endpoint 地址",
"URL_IS_REQUIRED": "Endpoint 地址必填",
"AUTH_HEADER": "Auth Header",
"VERIFY_REMOTE_CERT": "验证远程证书",
"TEST_ENDPOINT_BUTTON": "测试 ENDPOINT",
"CANCEL_BUTTON": "取消",
"SAVE_BUTTON": "保存",
"ENABLED_WEBHOOK_TITLE": "启用项目的 Webhooks",
"ENABLED_WEBHOOK_SUMMARY": "你希望开启项目的 Webhooks 吗?",
"DISABLED_WEBHOOK_TITLE": "停用项目的 Webhooks",
"DISABLED_WEBHOOK_SUMMARY": "你希望停用项目的 Webhooks 吗?"
},
"GROUP": {
"GROUP": "组",
"GROUPS": "组",
@ -717,6 +749,7 @@
"LABEL": "标签",
"REPOSITORY": "仓库",
"REPO_READ_ONLY": "仓库只读",
"WEBHOOK_NOTIFICATION_ENABLED": "开启 WEBHOOK",
"SYSTEM": "系统设置",
"PROJECT_QUOTAS": "项目定额",
"VULNERABILITY": "漏洞",
@ -769,6 +802,7 @@
"VERIFY_CERT": "检查来自LDAP服务端的证书",
"READONLY_TOOLTIP": "选中,表示正在维护状态,不可删除仓库及标签,也不可以推送镜像。",
"REPO_TOOLTIP": "用户在此模式下无法对图像执行任何操作。",
"WEBHOOK_TOOLTIP": "当执行推送,拉动,删除,扫描图像或图表等特定操作时,启用 webhooks 以在指定端点接收回调",
"HOURLY_CRON":"每小时运行一次。相当于 0 0 * * * *",
"WEEKLY_CRON":"每周一次,周六/周日午夜之间开始。相当于 0 0 * * * *",
"DAILY_CRON":"每天午夜运行一次。相当于 0 0 * * * *"