mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-30 22:24:14 +01:00
Improve webhook UI according to the UX
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
0319baabcb
commit
ba5fd67b08
@ -27,7 +27,7 @@
|
||||
<!-- notify type -->
|
||||
<clr-select-container>
|
||||
<label class="required">{{'WEBHOOK.NOTIFY_TYPE' | translate}}</label>
|
||||
<select clrSelect name="notifyType" id="notify_type" [(ngModel)]="webhook.targets[0].type" [disabled]="checking">
|
||||
<select class="width-238" clrSelect name="notifyType" id="notify_type" [(ngModel)]="webhook.targets[0].type" [disabled]="checking">
|
||||
<option *ngFor="let type of metadata?.notify_type" value="{{type}}">{{type}}</option>
|
||||
</select>
|
||||
</clr-select-container>
|
||||
@ -35,9 +35,9 @@
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label required">{{'WEBHOOK.EVENT_TYPE' | translate}}</label>
|
||||
<div class="clr-control-container clr-control-inline" [class.clr-error]="!hasEventType()">
|
||||
<div class="clr-checkbox-wrapper" *ngFor="let item of metadata?.event_type">
|
||||
<div class="clr-checkbox-wrapper width-7rem" *ngFor="let item of metadata?.event_type">
|
||||
<input type="checkbox" id="{{item}}" name="eventTypes" value="{{item}}" class="clr-checkbox" (change)="setEventType(item)" [checked]="getEventType(item)">
|
||||
<label for="{{item}}" class="clr-control-label">{{item}}</label>
|
||||
<label for="{{item}}" class="clr-control-label">{{eventTypeToText(item)}}</label>
|
||||
</div>
|
||||
<div class="clr-subtext-wrapper" *ngIf="!hasEventType()">
|
||||
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
|
||||
@ -92,12 +92,12 @@
|
||||
</form>
|
||||
<div class="mt-1" *ngIf="!isModify">
|
||||
<button type="button" id="webhook-test-add" [clrLoading]="checkBtnState" class="btn btn-outline" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
|
||||
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" id="add-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" id="new-webhook-continue" class="btn btn-primary" [disabled]="!isValid" (click)="add()">{{'BUTTON.ADD' | translate}}</button>
|
||||
</div>
|
||||
<div class="mt-1 bottom-btn" *ngIf="isModify">
|
||||
<button type="button" [clrLoading]="checkBtnState" class="btn btn-outline" id="webhook-test" (click)="onTestEndpoint()" [disabled]="checking || enpointURL.errors">{{'WEBHOOK.TEST_ENDPOINT_BUTTON' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" id="edit-webhook-cancel" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" id="edit-webhook-save" [disabled]="!isValid" (click)="save()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
@ -11,3 +11,9 @@
|
||||
.width-238 {
|
||||
width: 238px;
|
||||
}
|
||||
.width-7rem {
|
||||
width: 7rem;
|
||||
}
|
||||
.clr-control-label {
|
||||
width: 9rem !important;
|
||||
}
|
@ -27,6 +27,9 @@ describe('AddWebhookFormComponent', () => {
|
||||
},
|
||||
testEndpoint() {
|
||||
return of(null);
|
||||
},
|
||||
eventTypeToText(eventType: string) {
|
||||
return eventType;
|
||||
}
|
||||
};
|
||||
const mockMessageHandlerService = {
|
||||
|
@ -129,4 +129,7 @@ export class AddWebhookFormComponent implements OnInit {
|
||||
&& this.webhook.event_types
|
||||
&& this.webhook.event_types.length > 0;
|
||||
}
|
||||
eventTypeToText(eventType: string): string {
|
||||
return this.webhookService.eventTypeToText(eventType);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
:host::ng-deep.modal-dialog {
|
||||
width: 27rem;
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
<clr-dg-column>{{'WEBHOOK.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.LAST_TRIGGERED' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let item of lastTriggers">
|
||||
<clr-dg-cell>{{item.event_type}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{eventTypeToText(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>
|
||||
|
@ -5,6 +5,8 @@ import { SharedModule } from "../../../shared/shared.module";
|
||||
import { LastTriggerComponent } from "./last-trigger.component";
|
||||
import { LastTrigger } from "../webhook";
|
||||
import { SimpleChange } from "@angular/core";
|
||||
import { of } from "rxjs";
|
||||
import { WebhookService } from "../webhook.service";
|
||||
|
||||
describe('LastTriggerComponent', () => {
|
||||
const mokedTriggers: LastTrigger[] = [
|
||||
@ -23,6 +25,11 @@ describe('LastTriggerComponent', () => {
|
||||
last_trigger_time: null
|
||||
}
|
||||
];
|
||||
const mockWebhookService = {
|
||||
eventTypeToText(eventType: string) {
|
||||
return eventType;
|
||||
}
|
||||
};
|
||||
let component: LastTriggerComponent;
|
||||
let fixture: ComponentFixture<LastTriggerComponent>;
|
||||
beforeEach(() => {
|
||||
@ -35,6 +42,7 @@ describe('LastTriggerComponent', () => {
|
||||
declarations: [
|
||||
LastTriggerComponent
|
||||
],
|
||||
providers: [{ provide: WebhookService, useValue: mockWebhookService }]
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
OnInit, SimpleChanges
|
||||
} from "@angular/core";
|
||||
import { LastTrigger } from "../webhook";
|
||||
import { WebhookService } from "../webhook.service";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -14,7 +15,7 @@ export class LastTriggerComponent implements OnInit , OnChanges {
|
||||
@Input() inputLastTriggers: LastTrigger[];
|
||||
@Input() webhookName: string;
|
||||
lastTriggers: LastTrigger[] = [];
|
||||
constructor() {
|
||||
constructor(private webhookService: WebhookService) {
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes && changes['inputLastTriggers']) {
|
||||
@ -28,4 +29,7 @@ export class LastTriggerComponent implements OnInit , OnChanges {
|
||||
}
|
||||
ngOnInit(): void {
|
||||
}
|
||||
eventTypeToText(eventType: string): string {
|
||||
return this.webhookService.eventTypeToText(eventType);
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
<clr-icon class="margin-top-0" size="16" shape="pencil"></clr-icon>
|
||||
<span id="edit-webhook" class="margin-left-10">{{'BUTTON.EDIT' | translate}}</span>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem (click)="deleteWebhook()"
|
||||
class="btn btn-secondary" [disabled]="!(selectedRow && selectedRow.length >= 1)">
|
||||
<clr-icon class="margin-top-0" size="16" shape="times"></clr-icon>
|
||||
@ -49,9 +50,9 @@
|
||||
</div>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column [clrDgField]="'name'">{{'WEBHOOK.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column class="width-340">{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.ENABLED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.NOTIFY_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column class="min-width-340">{{'WEBHOOK.TARGET' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.EVENT_TYPES' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'WEBHOOK.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
@ -60,8 +61,6 @@
|
||||
</clr-dg-placeholder>
|
||||
<clr-dg-row *clrDgItems="let w of webhookList" [clrDgItem]="w">
|
||||
<clr-dg-cell>{{w.name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{w?.targets[0].type}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{w?.targets[0].address}}</clr-dg-cell>
|
||||
<clr-dg-cell [ngSwitch]="w.enabled">
|
||||
<div *ngSwitchCase="true" class="icon-wrap">
|
||||
<clr-icon shape="check-circle" size="20" class="is-success enabled-icon"></clr-icon>
|
||||
@ -72,10 +71,12 @@
|
||||
<span>{{'WEBHOOK.DISABLED' | translate}}</span>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{w?.targets[0].type}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{w?.targets[0].address}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<div class="cell" *ngIf="w?.event_types?.length">
|
||||
<div class="bar-state">
|
||||
<span class="label" *ngIf="w?.event_types[0]">{{w?.event_types[0]}}</span>
|
||||
<span class="label" *ngIf="w?.event_types[0]">{{eventTypeToText(w?.event_types[0])}}</span>
|
||||
</div>
|
||||
<div class="signpost-item" [hidden]="w?.event_types?.length<=1">
|
||||
<div class="trigger-item">
|
||||
@ -84,7 +85,7 @@
|
||||
<clr-signpost-content [clrPosition]="'left-top'" *clrIfOpen>
|
||||
<div>
|
||||
<div *ngFor="let e of w?.event_types" class="bar-state">
|
||||
<span class="label not-scan">{{e}}</span>
|
||||
<span class="label not-scan">{{eventTypeToText(e)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</clr-signpost-content>
|
||||
|
@ -55,6 +55,6 @@
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.width-340 {
|
||||
.min-width-340 {
|
||||
min-width: 340px!important;
|
||||
}
|
@ -73,6 +73,9 @@ describe('WebhookComponent', () => {
|
||||
},
|
||||
editWebhook() {
|
||||
return of(true);
|
||||
},
|
||||
eventTypeToText(eventType: string) {
|
||||
return eventType;
|
||||
}
|
||||
};
|
||||
const mockActivatedRoute = {
|
||||
|
@ -83,7 +83,13 @@ export class WebhookComponent implements OnInit {
|
||||
response => {
|
||||
this.metadata = response;
|
||||
if (this.metadata && this.metadata.event_type) {
|
||||
this.metadata.event_type.sort();
|
||||
// sort by text
|
||||
this.metadata.event_type.sort((a: string, b: string) => {
|
||||
if (this.eventTypeToText(a) === this.eventTypeToText(b)) {
|
||||
return 0;
|
||||
}
|
||||
return this.eventTypeToText(a) > this.eventTypeToText(b) ? 1 : -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
@ -222,4 +228,7 @@ export class WebhookComponent implements OnInit {
|
||||
);
|
||||
this.confirmationDialogComponent.open(msg);
|
||||
}
|
||||
eventTypeToText(eventType: string): string {
|
||||
return this.webhookService.eventTypeToText(eventType);
|
||||
}
|
||||
}
|
||||
|
@ -15,4 +15,11 @@ describe('WebhookService', () => {
|
||||
it('should be created', inject([WebhookService], (service: WebhookService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
it('function eventTypeToText should work', inject([WebhookService], (service: WebhookService) => {
|
||||
expect(service).toBeTruthy();
|
||||
const eventType: string = 'REPLICATION';
|
||||
expect(service.eventTypeToText(eventType)).toEqual('Replication finished');
|
||||
const mockedEventType: string = 'TEST';
|
||||
expect(service.eventTypeToText(mockedEventType)).toEqual('TEST');
|
||||
}));
|
||||
});
|
||||
|
@ -18,6 +18,20 @@ import { HttpClient } from "@angular/common/http";
|
||||
import { Webhook, LastTrigger } from "./webhook";
|
||||
import { CURRENT_BASE_HREF } from "../../../lib/utils/utils";
|
||||
|
||||
const EVENT_TYPES_TEXT_MAP = {
|
||||
'REPLICATION': 'Replication finished',
|
||||
'PUSH_ARTIFACT': 'Artifact pushed',
|
||||
'PULL_ARTIFACT': 'Artifact pulled',
|
||||
'DELETE_ARTIFACT': 'Artifact deleted',
|
||||
'DOWNLOAD_CHART': 'Chart downloaded',
|
||||
'UPLOAD_CHART': 'Chart uploaded',
|
||||
'DELETE_CHART': 'Chart deleted',
|
||||
'QUOTA_EXCEED': 'Quota exceed',
|
||||
'QUOTA_WARNING': 'Quota near threshold',
|
||||
'SCANNING_FAILED': 'Scanning failed',
|
||||
'SCANNING_COMPLETED': 'Scanning finished',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class WebhookService {
|
||||
constructor(private http: HttpClient) { }
|
||||
@ -66,4 +80,11 @@ export class WebhookService {
|
||||
.get(`${CURRENT_BASE_HREF}/projects/${projectId}/webhook/events`)
|
||||
.pipe(catchError(error => observableThrowError(error)));
|
||||
}
|
||||
|
||||
public eventTypeToText(eventType: string): string {
|
||||
if (EVENT_TYPES_TEXT_MAP[eventType]) {
|
||||
return EVENT_TYPES_TEXT_MAP[eventType];
|
||||
}
|
||||
return eventType;
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +393,7 @@
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"NAME": "Name",
|
||||
"TARGET": "Target",
|
||||
"TARGET": "Endpoint URL",
|
||||
"EVENT_TYPES": "Event types",
|
||||
"DESCRIPTION": "Description",
|
||||
"NO_WEBHOOK": "No Webhook",
|
||||
|
@ -394,7 +394,7 @@
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"NAME": "Name",
|
||||
"TARGET": "Target",
|
||||
"TARGET": "Endpoint URL",
|
||||
"EVENT_TYPES": "Event types",
|
||||
"DESCRIPTION": "Description",
|
||||
"NO_WEBHOOK": "No Webhook",
|
||||
|
@ -385,7 +385,7 @@
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"NAME": "Name",
|
||||
"TARGET": "Target",
|
||||
"TARGET": "Endpoint URL",
|
||||
"EVENT_TYPES": "Event types",
|
||||
"DESCRIPTION": "Description",
|
||||
"NO_WEBHOOK": "No Webhook",
|
||||
|
@ -423,7 +423,7 @@
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"NAME": "Name",
|
||||
"TARGET": "Target",
|
||||
"TARGET": "Endpoint URL",
|
||||
"EVENT_TYPES": "Event types",
|
||||
"DESCRIPTION": "Description",
|
||||
"NO_WEBHOOK": "No Webhook",
|
||||
|
@ -393,7 +393,7 @@
|
||||
"ENABLE": "Enable",
|
||||
"DISABLE": "Disable",
|
||||
"NAME": "Name",
|
||||
"TARGET": "Target",
|
||||
"TARGET": "Endpoint URL",
|
||||
"EVENT_TYPES": "Event types",
|
||||
"DESCRIPTION": "Description",
|
||||
"NO_WEBHOOK": "No Webhook",
|
||||
|
Loading…
Reference in New Issue
Block a user