mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-29 21:54:13 +01:00
Use new query model to get audit logs (#11113)
* Use new query model to get audit logs leverage the query builder to build query, remove the old style query string Signed-off-by: wang yan <wangyan@vmware.com> * Switch to new API for project log page Signed-off-by: AllForNothing <sshijun@vmware.com> Co-authored-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
b0e87b46e4
commit
050967f95f
@ -546,38 +546,9 @@ paths:
|
|||||||
operationId: listAuditLogs
|
operationId: listAuditLogs
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/requestId'
|
- $ref: '#/parameters/requestId'
|
||||||
|
- $ref: '#/parameters/query'
|
||||||
- $ref: '#/parameters/page'
|
- $ref: '#/parameters/page'
|
||||||
- $ref: '#/parameters/pageSize'
|
- $ref: '#/parameters/pageSize'
|
||||||
- name: username
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: Username of the operator.
|
|
||||||
- name: resource
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: The identity of resource
|
|
||||||
- name: resource_type
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: The type of resource, artifact/tag/repository
|
|
||||||
- name: operation
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: The operation, create/delete
|
|
||||||
- name: begin_timestamp
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: The begin timestamp
|
|
||||||
- name: end_timestamp
|
|
||||||
in: query
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: The end timestamp
|
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Success
|
description: Success
|
||||||
|
@ -32,14 +32,17 @@ type PushArtifactEventMetadata struct {
|
|||||||
|
|
||||||
// Resolve to the event from the metadata
|
// Resolve to the event from the metadata
|
||||||
func (p *PushArtifactEventMetadata) Resolve(event *event.Event) error {
|
func (p *PushArtifactEventMetadata) Resolve(event *event.Event) error {
|
||||||
data := &event2.PushArtifactEvent{
|
ae := &event2.ArtifactEvent{
|
||||||
ArtifactEvent: &event2.ArtifactEvent{
|
|
||||||
EventType: event2.TopicPushArtifact,
|
EventType: event2.TopicPushArtifact,
|
||||||
Repository: p.Artifact.RepositoryName,
|
Repository: p.Artifact.RepositoryName,
|
||||||
Artifact: p.Artifact,
|
Artifact: p.Artifact,
|
||||||
Tags: []string{p.Tag},
|
|
||||||
OccurAt: time.Now(),
|
OccurAt: time.Now(),
|
||||||
},
|
}
|
||||||
|
if p.Tag != "" {
|
||||||
|
ae.Tags = []string{p.Tag}
|
||||||
|
}
|
||||||
|
data := &event2.PushArtifactEvent{
|
||||||
|
ArtifactEvent: ae,
|
||||||
}
|
}
|
||||||
ctx, exist := security.FromContext(p.Ctx)
|
ctx, exist := security.FromContext(p.Ctx)
|
||||||
if exist {
|
if exist {
|
||||||
@ -59,14 +62,17 @@ type PullArtifactEventMetadata struct {
|
|||||||
|
|
||||||
// Resolve to the event from the metadata
|
// Resolve to the event from the metadata
|
||||||
func (p *PullArtifactEventMetadata) Resolve(event *event.Event) error {
|
func (p *PullArtifactEventMetadata) Resolve(event *event.Event) error {
|
||||||
data := &event2.PullArtifactEvent{
|
ae := &event2.ArtifactEvent{
|
||||||
ArtifactEvent: &event2.ArtifactEvent{
|
|
||||||
EventType: event2.TopicPullArtifact,
|
EventType: event2.TopicPullArtifact,
|
||||||
Repository: p.Artifact.RepositoryName,
|
Repository: p.Artifact.RepositoryName,
|
||||||
Artifact: p.Artifact,
|
Artifact: p.Artifact,
|
||||||
Tags: []string{p.Tag},
|
|
||||||
OccurAt: time.Now(),
|
OccurAt: time.Now(),
|
||||||
},
|
}
|
||||||
|
if p.Tag != "" {
|
||||||
|
ae.Tags = []string{p.Tag}
|
||||||
|
}
|
||||||
|
data := &event2.PullArtifactEvent{
|
||||||
|
ArtifactEvent: ae,
|
||||||
}
|
}
|
||||||
ctx, exist := security.FromContext(p.Ctx)
|
ctx, exist := security.FromContext(p.Ctx)
|
||||||
if exist {
|
if exist {
|
||||||
|
@ -30,16 +30,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 datagrid-margin-top ">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 datagrid-margin-top ">
|
||||||
<clr-datagrid (clrDgRefresh)="retrievePage()">
|
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="retrieve()">
|
||||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.REPOSITORY_NAME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.RESOURCE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.TAGS' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.RESOURCE_TYPE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.OPERATION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.TIMESTAMP' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *ngFor="let l of auditLogs">
|
<clr-dg-row *ngFor="let l of auditLogs">
|
||||||
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
<clr-dg-cell>{{l.username}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.repo_name}}</clr-dg-cell>
|
<clr-dg-cell>{{l.resource}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.repo_tag}}</clr-dg-cell>
|
<clr-dg-cell>{{l.resource_type}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
<clr-dg-cell>{{l.operation}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
|
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
|
@ -2,16 +2,19 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { AuditLogComponent } from './audit-log.component';
|
import { AuditLogComponent } from './audit-log.component';
|
||||||
import { ClarityModule } from '@clr/angular';
|
import { ClarityModule } from '@clr/angular';
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { AuditLogService } from './audit-log.service';
|
|
||||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||||
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { delay } from 'rxjs/operators';
|
import { delay } from 'rxjs/operators';
|
||||||
import { HarborLibraryModule } from "../../lib/harbor-library.module";
|
import { HarborLibraryModule } from "../../lib/harbor-library.module";
|
||||||
|
import { AuditLog } from "../../../ng-swagger-gen/models/audit-log";
|
||||||
|
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||||
|
import { ProjectService } from "../../../ng-swagger-gen/services/project.service";
|
||||||
|
import { click } from "../../lib/utils/utils";
|
||||||
|
import { FormsModule } from "@angular/forms";
|
||||||
|
|
||||||
describe('AuditLogComponent', () => {
|
describe('AuditLogComponent', () => {
|
||||||
let component: AuditLogComponent;
|
let component: AuditLogComponent;
|
||||||
@ -19,27 +22,59 @@ describe('AuditLogComponent', () => {
|
|||||||
const mockMessageHandlerService = {
|
const mockMessageHandlerService = {
|
||||||
handleError: () => {}
|
handleError: () => {}
|
||||||
};
|
};
|
||||||
const mockAuditLogService = {
|
|
||||||
listAuditLogs: () => {
|
|
||||||
return of({
|
|
||||||
headers: new Map().set('x-total-count', 0),
|
|
||||||
body: []
|
|
||||||
}).pipe(delay(0));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mockActivatedRoute = {
|
const mockActivatedRoute = {
|
||||||
|
parent: {
|
||||||
|
snapshot: {
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snapshot: {
|
||||||
|
data: null
|
||||||
|
},
|
||||||
data: of({
|
data: of({
|
||||||
auditLogResolver: ""
|
auditLogResolver: ""
|
||||||
}).pipe(delay(0)),
|
}).pipe(delay(0))
|
||||||
snapshot: {
|
};
|
||||||
parent: {
|
const mockRouter = null;
|
||||||
params: {
|
const mockedAuditLogs: AuditLog [] = [];
|
||||||
id: 1
|
for (let i = 0; i < 18; i++) {
|
||||||
|
let item: AuditLog = {
|
||||||
|
id: 234 + i,
|
||||||
|
resource: "myProject/Demo" + i,
|
||||||
|
resource_type: "N/A",
|
||||||
|
operation: "create",
|
||||||
|
op_time: "2017-04-11T10:26:22Z",
|
||||||
|
username: "user91" + i
|
||||||
|
};
|
||||||
|
mockedAuditLogs.push(item);
|
||||||
}
|
}
|
||||||
|
const fakedAuditlogService = {
|
||||||
|
getLogsResponse(params: ProjectService.GetLogsParams) {
|
||||||
|
if (params.q && params.q.indexOf('Demo0') !== -1) {
|
||||||
|
return of(new HttpResponse({
|
||||||
|
body: mockedAuditLogs.slice(0, 1),
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
"x-total-count": "18"
|
||||||
|
})
|
||||||
|
})).pipe(delay(0));
|
||||||
|
}
|
||||||
|
if (params.page <= 1) {
|
||||||
|
return of(new HttpResponse({
|
||||||
|
body: mockedAuditLogs.slice(0, 15),
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
"x-total-count": "18"
|
||||||
|
})
|
||||||
|
})).pipe(delay(0));
|
||||||
|
} else {
|
||||||
|
return of(new HttpResponse({
|
||||||
|
body: mockedAuditLogs.slice(15),
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
"x-total-count": "18"
|
||||||
|
})
|
||||||
|
})).pipe(delay(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const mockRouter = null;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -60,7 +95,7 @@ describe('AuditLogComponent', () => {
|
|||||||
TranslateService,
|
TranslateService,
|
||||||
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
|
||||||
{ provide: Router, useValue: mockRouter },
|
{ provide: Router, useValue: mockRouter },
|
||||||
{ provide: AuditLogService, useValue: mockAuditLogService },
|
{ provide: ProjectService, useValue: fakedAuditlogService },
|
||||||
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
{ provide: MessageHandlerService, useValue: mockMessageHandlerService },
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -76,4 +111,57 @@ describe('AuditLogComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
it('should get data from AccessLogService', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => { // wait for async getRecentLogs
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.auditLogs).toBeTruthy();
|
||||||
|
expect(component.auditLogs.length).toEqual(15);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should render data to view', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
let de: DebugElement = fixture.debugElement.query(del => del.classes['datagrid-cell']);
|
||||||
|
expect(de).toBeTruthy();
|
||||||
|
let el: HTMLElement = de.nativeElement;
|
||||||
|
expect(el).toBeTruthy();
|
||||||
|
expect(el.textContent.trim()).toEqual('user910');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
it('should support pagination', async () => {
|
||||||
|
fixture.autoDetectChanges(true);
|
||||||
|
await fixture.whenStable();
|
||||||
|
let el: HTMLButtonElement = fixture.nativeElement.querySelector('.pagination-next');
|
||||||
|
expect(el).toBeTruthy();
|
||||||
|
el.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
expect(component.currentPage).toEqual(2);
|
||||||
|
expect(component.auditLogs.length).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support filtering list by keywords', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
let el: HTMLElement = fixture.nativeElement.querySelector('.search-btn');
|
||||||
|
expect(el).toBeTruthy("Not found search icon");
|
||||||
|
click(el);
|
||||||
|
fixture.detectChanges();
|
||||||
|
let el2: HTMLInputElement = fixture.nativeElement.querySelector('input');
|
||||||
|
expect(el2).toBeTruthy("Not found input");
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.doSearchAuditLogs("Demo0");
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.auditLogs).toBeTruthy();
|
||||||
|
expect(component.auditLogs.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@ -11,17 +11,14 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { NgModel } from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { AuditLog } from './audit-log';
|
|
||||||
import { SessionUser } from '../shared/session-user';
|
import { SessionUser } from '../shared/session-user';
|
||||||
|
|
||||||
import { AuditLogService } from './audit-log.service';
|
|
||||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
||||||
|
import { ProjectService } from "../../../ng-swagger-gen/services/project.service";
|
||||||
import { State } from '../../lib/services/interface';
|
import { AuditLog } from "../../../ng-swagger-gen/models/audit-log";
|
||||||
|
import { Project } from "../project/project";
|
||||||
|
import { finalize } from "rxjs/operators";
|
||||||
|
|
||||||
const optionalSearch: {} = { 0: 'AUDIT_LOG.ADVANCED', 1: 'AUDIT_LOG.SIMPLE' };
|
const optionalSearch: {} = { 0: 'AUDIT_LOG.ADVANCED', 1: 'AUDIT_LOG.SIMPLE' };
|
||||||
|
|
||||||
@ -55,8 +52,13 @@ export class AuditLogComponent implements OnInit {
|
|||||||
search: SearchOption = new SearchOption();
|
search: SearchOption = new SearchOption();
|
||||||
currentUser: SessionUser;
|
currentUser: SessionUser;
|
||||||
projectId: number;
|
projectId: number;
|
||||||
queryParam: AuditLog = new AuditLog();
|
projectName: string;
|
||||||
|
queryUsername: string;
|
||||||
|
queryStartTime: string;
|
||||||
|
queryEndTime: string;
|
||||||
|
queryOperation: string[] = [];
|
||||||
auditLogs: AuditLog[];
|
auditLogs: AuditLog[];
|
||||||
|
loading: boolean = true;
|
||||||
|
|
||||||
toggleName = optionalSearch;
|
toggleName = optionalSearch;
|
||||||
currentOption: number = 0;
|
currentOption: number = 0;
|
||||||
@ -82,25 +84,60 @@ export class AuditLogComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private auditLogService: AuditLogService,
|
private auditLogService: ProjectService,
|
||||||
private messageHandlerService: MessageHandlerService) {
|
private messageHandlerService: MessageHandlerService) {
|
||||||
// Get current user from registered resolver.
|
// Get current user from registered resolver.
|
||||||
this.route.data.subscribe(data => this.currentUser = <SessionUser>data['auditLogResolver']);
|
this.route.data.subscribe(data => this.currentUser = <SessionUser>data['auditLogResolver']);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
const resolverData = this.route.parent.snapshot.data;
|
||||||
this.queryParam.project_id = this.projectId;
|
if (resolverData) {
|
||||||
this.queryParam.page_size = this.pageSize;
|
const pro: Project = <Project>resolverData['projectResolver'];
|
||||||
|
this.projectName = pro.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private retrieve(): void {
|
retrieve() {
|
||||||
|
const arr: string[] = [];
|
||||||
|
if (this.queryUsername) {
|
||||||
|
arr.push(`username=~${this.queryUsername}`);
|
||||||
|
}
|
||||||
|
if (this.queryStartTime && this.queryEndTime) {
|
||||||
|
arr.push(`op_time=[${this.queryStartTime}~${this.queryEndTime}]`);
|
||||||
|
} else {
|
||||||
|
if (this.queryStartTime) {
|
||||||
|
arr.push(`op_time=[${this.queryStartTime}~]`);
|
||||||
|
}
|
||||||
|
if (this.queryEndTime) {
|
||||||
|
arr.push(`op_time=[~${this.queryEndTime}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.queryOperation && this.queryOperation.length > 0) {
|
||||||
|
arr.push(`operation={${this.queryOperation.join(' ')}}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const param: ProjectService.GetLogsParams = {
|
||||||
|
projectName: this.projectName,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
page: this.currentPage,
|
||||||
|
};
|
||||||
|
if (arr && arr.length > 0) {
|
||||||
|
param.q = encodeURIComponent(arr.join(','));
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
this.auditLogService
|
this.auditLogService
|
||||||
.listAuditLogs(this.queryParam)
|
.getLogsResponse(param)
|
||||||
|
.pipe(finalize(() => this.loading = false))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.totalRecordCount = Number.parseInt(response.headers.get('x-total-count'));
|
// Get total count
|
||||||
|
if (response.headers) {
|
||||||
|
let xHeader: string = response.headers.get("x-total-count");
|
||||||
|
if (xHeader) {
|
||||||
|
this.totalRecordCount = Number.parseInt(xHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.auditLogs = response.body;
|
this.auditLogs = response.body;
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@ -108,24 +145,18 @@ export class AuditLogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
retrievePage() {
|
|
||||||
this.queryParam.page = this.currentPage;
|
|
||||||
this.retrieve();
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearchAuditLogs(searchUsername: string): void {
|
doSearchAuditLogs(searchUsername: string): void {
|
||||||
this.queryParam.username = searchUsername;
|
this.queryUsername = searchUsername;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearchByStartTime(fromTimestamp: string): void {
|
doSearchByStartTime(fromTimestamp: string): void {
|
||||||
this.queryParam.begin_timestamp = fromTimestamp;
|
this.queryStartTime = fromTimestamp;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearchByEndTime(toTimestamp: string): void {
|
doSearchByEndTime(toTimestamp: string): void {
|
||||||
this.queryParam.end_timestamp = toTimestamp;
|
this.queryEndTime = toTimestamp;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +165,7 @@ export class AuditLogComponent implements OnInit {
|
|||||||
let operationFilter: string[] = [];
|
let operationFilter: string[] = [];
|
||||||
for (let filterOption of this.filterOptions) {
|
for (let filterOption of this.filterOptions) {
|
||||||
if (filterOption.checked) {
|
if (filterOption.checked) {
|
||||||
operationFilter.push('operation=' + filterOption.key);
|
operationFilter.push(filterOption.key);
|
||||||
} else {
|
} else {
|
||||||
selectAll = false;
|
selectAll = false;
|
||||||
}
|
}
|
||||||
@ -142,7 +173,7 @@ export class AuditLogComponent implements OnInit {
|
|||||||
if (selectAll) {
|
if (selectAll) {
|
||||||
operationFilter = [];
|
operationFilter = [];
|
||||||
}
|
}
|
||||||
this.queryParam.keywords = operationFilter.join('&');
|
this.queryOperation = operationFilter;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { TestBed, inject } from '@angular/core/testing';
|
|
||||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
|
||||||
import { AuditLogService } from './audit-log.service';
|
|
||||||
|
|
||||||
describe('AuditLogService', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
HttpClientTestingModule
|
|
||||||
],
|
|
||||||
providers: [AuditLogService]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', inject([AuditLogService], (service: AuditLogService) => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,65 +0,0 @@
|
|||||||
|
|
||||||
import {throwError as observableThrowError, Observable } from "rxjs";
|
|
||||||
|
|
||||||
import {map, catchError} from 'rxjs/operators';
|
|
||||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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 { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
|
||||||
import { AuditLog } from './audit-log';
|
|
||||||
import {
|
|
||||||
buildHttpRequestOptions,
|
|
||||||
buildHttpRequestOptionsWithObserveResponse,
|
|
||||||
CURRENT_BASE_HREF
|
|
||||||
} from "../../lib/utils/utils";
|
|
||||||
import { RequestQueryParams } from "../../lib/services";
|
|
||||||
|
|
||||||
export const logEndpoint = CURRENT_BASE_HREF + '/logs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuditLogService {
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
|
||||||
|
|
||||||
listAuditLogs(queryParam: AuditLog): Observable<any> {
|
|
||||||
let params: HttpParams = new HttpParams({fromString: queryParam.keywords});
|
|
||||||
if (queryParam.begin_timestamp) {
|
|
||||||
params = params.set('begin_timestamp', <string>queryParam.begin_timestamp);
|
|
||||||
}
|
|
||||||
if (queryParam.end_timestamp) {
|
|
||||||
params = params.set('end_timestamp', <string>queryParam.end_timestamp);
|
|
||||||
}
|
|
||||||
if (queryParam.username) {
|
|
||||||
params = params.set('username', queryParam.username);
|
|
||||||
}
|
|
||||||
if (queryParam.page) {
|
|
||||||
params = params.set('page', <string>queryParam.page);
|
|
||||||
}
|
|
||||||
if (queryParam.page_size) {
|
|
||||||
params = params.set('page_size', <string>queryParam.page_size);
|
|
||||||
}
|
|
||||||
return this.http
|
|
||||||
.get<HttpResponse<AuditLog[]>>(`${ CURRENT_BASE_HREF }/projects/${queryParam.project_id}/logs`
|
|
||||||
, buildHttpRequestOptionsWithObserveResponse(params)).pipe(
|
|
||||||
catchError(error => observableThrowError(error)), );
|
|
||||||
}
|
|
||||||
|
|
||||||
getRecentLogs(lines: number): Observable<AuditLog[]> {
|
|
||||||
let params: RequestQueryParams = new RequestQueryParams();
|
|
||||||
params = params.set('page_size', '' + lines);
|
|
||||||
return this.http.get(logEndpoint, buildHttpRequestOptions(params)).pipe(
|
|
||||||
map(response => response as AuditLog[]),
|
|
||||||
catchError(error => observableThrowError(error)), );
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// 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.
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"log_id": 3,
|
|
||||||
"user_id": 0,
|
|
||||||
"project_id": 0,
|
|
||||||
"repo_name": "library/mysql",
|
|
||||||
"repo_tag": "5.6",
|
|
||||||
"guid": "",
|
|
||||||
"operation": "push",
|
|
||||||
"op_time": "2017-02-14T09:22:58Z",
|
|
||||||
"username": "admin",
|
|
||||||
"keywords": "",
|
|
||||||
"BeginTime": "0001-01-01T00:00:00Z",
|
|
||||||
"begin_timestamp": 0,
|
|
||||||
"EndTime": "0001-01-01T00:00:00Z",
|
|
||||||
"end_timestamp": 0
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
export class AuditLog {
|
|
||||||
log_id: number | string;
|
|
||||||
project_id: number | string;
|
|
||||||
username: string;
|
|
||||||
repo_name: string;
|
|
||||||
repo_tag: string;
|
|
||||||
operation: string;
|
|
||||||
op_time: Date;
|
|
||||||
begin_timestamp: number | string;
|
|
||||||
end_timestamp: number | string;
|
|
||||||
keywords: string;
|
|
||||||
page: number | string;
|
|
||||||
page_size: number | string;
|
|
||||||
fromTime: string;
|
|
||||||
toTime: string;
|
|
||||||
}
|
|
@ -14,7 +14,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AuditLogComponent } from './audit-log.component';
|
import { AuditLogComponent } from './audit-log.component';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { AuditLogService } from './audit-log.service';
|
|
||||||
import { LogPageComponent } from './log-page.component';
|
import { LogPageComponent } from './log-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -23,7 +22,6 @@ import { LogPageComponent } from './log-page.component';
|
|||||||
AuditLogComponent,
|
AuditLogComponent,
|
||||||
LogPageComponent
|
LogPageComponent
|
||||||
],
|
],
|
||||||
providers: [AuditLogService],
|
|
||||||
exports: [
|
exports: [
|
||||||
AuditLogComponent,
|
AuditLogComponent,
|
||||||
LogPageComponent]
|
LogPageComponent]
|
||||||
|
@ -33,29 +33,10 @@ export class DatePickerComponent implements OnChanges {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertDate(strDate: string): string {
|
|
||||||
if (
|
|
||||||
/^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d$/.test(
|
|
||||||
strDate
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
let parts = strDate.split(/[-\/]/);
|
|
||||||
strDate =
|
|
||||||
parts[2] /*Year*/ + "-" + parts[0] /*Month*/+ "-" + parts[1] /*Date*/;
|
|
||||||
}
|
|
||||||
return strDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
doSearch() {
|
doSearch() {
|
||||||
let searchTerm: string = "";
|
let searchTerm: string = "";
|
||||||
if (this.searchTime.valid && this.dateInput) {
|
if (this.searchTime.valid && this.dateInput) {
|
||||||
let timestamp: number =
|
searchTerm = this.searchTime.value;
|
||||||
new Date(this.convertDate(this.searchTime.value)).getTime() / 1000;
|
|
||||||
if (this.oneDayOffset) {
|
|
||||||
timestamp += 3600 * 24;
|
|
||||||
}
|
|
||||||
searchTerm = timestamp.toString();
|
|
||||||
}
|
}
|
||||||
this.search.emit(searchTerm);
|
this.search.emit(searchTerm);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<select id="selectKey" (change)="selectFilterKey($event)">
|
<select id="selectKey" (change)="selectFilterKey($event)">
|
||||||
<option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option>
|
<option value="username">{{"AUDIT_LOG.USERNAME" | translate | lowercase}}</option>
|
||||||
<option value="resource">{{"AUDIT_LOG.RESOURCE" | translate | lowercase}}</option>
|
<option value="resource">{{"AUDIT_LOG.RESOURCE" | translate | lowercase}}</option>
|
||||||
<option value="resourceType">{{"AUDIT_LOG.RESOURCE_TYPE" | translate | lowercase}}</option>
|
<option value="resource_type">{{"AUDIT_LOG.RESOURCE_TYPE" | translate | lowercase}}</option>
|
||||||
<option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option>
|
<option value="operation">{{"AUDIT_LOG.OPERATION" | translate | lowercase}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,6 @@ import { of } from 'rxjs';
|
|||||||
import { AuditLog } from "../../../../ng-swagger-gen/models/audit-log";
|
import { AuditLog } from "../../../../ng-swagger-gen/models/audit-log";
|
||||||
import { AuditlogService } from "../../../../ng-swagger-gen/services/auditlog.service";
|
import { AuditlogService } from "../../../../ng-swagger-gen/services/auditlog.service";
|
||||||
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
import { HttpHeaders, HttpResponse } from "@angular/common/http";
|
||||||
import ListAuditLogsParams = AuditlogService.ListAuditLogsParams;
|
|
||||||
import { delay } from "rxjs/operators";
|
import { delay } from "rxjs/operators";
|
||||||
|
|
||||||
describe('RecentLogComponent (inline template)', () => {
|
describe('RecentLogComponent (inline template)', () => {
|
||||||
@ -39,9 +38,9 @@ describe('RecentLogComponent (inline template)', () => {
|
|||||||
mockedAuditLogs.push(item);
|
mockedAuditLogs.push(item);
|
||||||
}
|
}
|
||||||
const fakedAuditlogService = {
|
const fakedAuditlogService = {
|
||||||
listAuditLogsResponse(params: ListAuditLogsParams) {
|
listAuditLogsResponse(params: AuditlogService.ListAuditLogsParams) {
|
||||||
if (params && params.username) {
|
if (params && params.q) {
|
||||||
if (params.username === 'demo0') {
|
if (params.q.indexOf('demo0') !== -1) {
|
||||||
return of(new HttpResponse({
|
return of(new HttpResponse({
|
||||||
body: mockedAuditLogs.slice(0, 1),
|
body: mockedAuditLogs.slice(0, 1),
|
||||||
headers: new HttpHeaders({
|
headers: new HttpHeaders({
|
||||||
|
@ -77,7 +77,7 @@ export class RecentLogComponent implements OnInit {
|
|||||||
pageSize: this.pageSize
|
pageSize: this.pageSize
|
||||||
};
|
};
|
||||||
if (this.currentTerm && this.currentTerm !== "") {
|
if (this.currentTerm && this.currentTerm !== "") {
|
||||||
params[this.defaultFilter] = this.currentTerm;
|
params.q = encodeURIComponent(`${this.defaultFilter}=~${this.currentTerm}`);
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.logService.listAuditLogsResponse(params).pipe(finalize(() => (this.loading = false)))
|
this.logService.listAuditLogsResponse(params).pipe(finalize(() => (this.loading = false)))
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"github.com/go-openapi/runtime/middleware"
|
"github.com/go-openapi/runtime/middleware"
|
||||||
"github.com/goharbor/harbor/src/pkg/audit"
|
"github.com/goharbor/harbor/src/pkg/audit"
|
||||||
"github.com/goharbor/harbor/src/pkg/q"
|
|
||||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||||
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/auditlog"
|
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/auditlog"
|
||||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/auditlog"
|
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/auditlog"
|
||||||
@ -26,28 +25,9 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
|
|||||||
// if !a.HasPermission(ctx, rbac.ActionList, rbac.ResourceLog) {
|
// if !a.HasPermission(ctx, rbac.ActionList, rbac.ResourceLog) {
|
||||||
// return a.SendError(ctx, ierror.ForbiddenError(nil))
|
// return a.SendError(ctx, ierror.ForbiddenError(nil))
|
||||||
// }
|
// }
|
||||||
keywords := make(map[string]interface{})
|
query, err := a.BuildQuery(ctx, params.Q, params.Page, params.PageSize)
|
||||||
query := &q.Query{
|
if err != nil {
|
||||||
Keywords: keywords,
|
return a.SendError(ctx, err)
|
||||||
}
|
|
||||||
// TODO support fuzzy match and start end time
|
|
||||||
if params.Username != nil {
|
|
||||||
query.Keywords["Username"] = *(params.Username)
|
|
||||||
}
|
|
||||||
if params.Operation != nil {
|
|
||||||
query.Keywords["Operation"] = *(params.Operation)
|
|
||||||
}
|
|
||||||
if params.Resource != nil {
|
|
||||||
query.Keywords["Resource"] = *(params.Resource)
|
|
||||||
}
|
|
||||||
if params.ResourceType != nil {
|
|
||||||
query.Keywords["ResourceType"] = *(params.ResourceType)
|
|
||||||
}
|
|
||||||
if params.Page != nil {
|
|
||||||
query.PageNumber = *(params.Page)
|
|
||||||
}
|
|
||||||
if params.PageSize != nil {
|
|
||||||
query.PageSize = *(params.PageSize)
|
|
||||||
}
|
}
|
||||||
total, err := a.auditMgr.Count(ctx, query)
|
total, err := a.auditMgr.Count(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -148,6 +148,7 @@ func (r *repositoryAPI) DeleteRepository(ctx context.Context, params operation.D
|
|||||||
notification.AddEvent(ctx, &metadata.DeleteRepositoryEventMetadata{
|
notification.AddEvent(ctx, &metadata.DeleteRepositoryEventMetadata{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Repository: repository.Name,
|
Repository: repository.Name,
|
||||||
|
ProjectID: repository.ProjectID,
|
||||||
})
|
})
|
||||||
|
|
||||||
return operation.NewDeleteRepositoryOK()
|
return operation.NewDeleteRepositoryOK()
|
||||||
|
Loading…
Reference in New Issue
Block a user