diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json
index 526897977..e9ba3349c 100644
--- a/src/portal/src/i18n/lang/en-us-lang.json
+++ b/src/portal/src/i18n/lang/en-us-lang.json
@@ -456,7 +456,9 @@
"FILTER_PLACEHOLDER": "Filter Logs",
"INVALID_DATE": "Invalid date.",
"OF": "of",
- "NOT_FOUND": "We couldn't find any logs!"
+ "NOT_FOUND": "We couldn't find any logs!",
+ "RESOURCE": "Resource",
+ "RESOURCE_TYPE": "Resource Type"
},
"REPLICATION": {
"YES": "Yes",
diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json
index d89783a3f..220c48723 100644
--- a/src/portal/src/i18n/lang/es-es-lang.json
+++ b/src/portal/src/i18n/lang/es-es-lang.json
@@ -456,7 +456,9 @@
"FILTER_PLACEHOLDER": "Filtrar logs",
"INVALID_DATE": "Fecha invalida.",
"OF": "of",
- "NOT_FOUND": "No pudimos encontrar ningún registro!"
+ "NOT_FOUND": "No pudimos encontrar ningún registro!",
+ "RESOURCE": "Resource",
+ "RESOURCE_TYPE": "Resource Type"
},
"REPLICATION": {
"YES": "Yes",
diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json
index 53bcd7ea3..b7c9c52b4 100644
--- a/src/portal/src/i18n/lang/fr-fr-lang.json
+++ b/src/portal/src/i18n/lang/fr-fr-lang.json
@@ -448,7 +448,9 @@
"FILTER_PLACEHOLDER": "Filtrer les Logs",
"INVALID_DATE": "Date invalide.",
"OF": "de",
- "NOT_FOUND": "Nous n'avons trouvé aucun journal!"
+ "NOT_FOUND": "Nous n'avons trouvé aucun journal!",
+ "RESOURCE": "Resource",
+ "RESOURCE_TYPE": "Resource Type"
},
"REPLICATION": {
"YES": "Yes",
diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json
index 2b45bf5fa..41f365b6b 100644
--- a/src/portal/src/i18n/lang/pt-br-lang.json
+++ b/src/portal/src/i18n/lang/pt-br-lang.json
@@ -454,7 +454,9 @@
"FILTER_PLACEHOLDER": "Filtrar Logs",
"INVALID_DATE": "Data inválida.",
"OF": "de",
- "NOT_FOUND": "Nós não encontramos nenhum registro!"
+ "NOT_FOUND": "Nós não encontramos nenhum registro!",
+ "RESOURCE": "Resource",
+ "RESOURCE_TYPE": "Resource Type"
},
"REPLICATION": {
"YES": "Yes",
diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json
index 95e22a660..b59b93d65 100644
--- a/src/portal/src/i18n/lang/tr-tr-lang.json
+++ b/src/portal/src/i18n/lang/tr-tr-lang.json
@@ -455,7 +455,10 @@
"ITEMS": "adet",
"FILTER_PLACEHOLDER": "Günlükleri Filtrele",
"INVALID_DATE": "Geçersiz tarih.",
- "OF": "of"
+ "OF": "of",
+ "NOT_FOUND": "We couldn't find any logs!",
+ "RESOURCE": "Resource",
+ "RESOURCE_TYPE": "Resource Type"
},
"REPLICATION": {
"YES": "Evet",
diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json
index 6fb27e1ab..a0af4a3a1 100644
--- a/src/portal/src/i18n/lang/zh-cn-lang.json
+++ b/src/portal/src/i18n/lang/zh-cn-lang.json
@@ -455,7 +455,9 @@
"FILTER_PLACEHOLDER": "过滤日志",
"INVALID_DATE": "无效日期。",
"OF": "共计",
- "NOT_FOUND": "未发现任何日志!"
+ "NOT_FOUND": "未发现任何日志!",
+ "RESOURCE": "资源",
+ "RESOURCE_TYPE": "资源类型"
},
"REPLICATION": {
"YES": "是",
diff --git a/src/portal/src/lib/components/log/recent-log.component.html b/src/portal/src/lib/components/log/recent-log.component.html
index 6ad11483b..6381c095d 100644
--- a/src/portal/src/lib/components/log/recent-log.component.html
+++ b/src/portal/src/lib/components/log/recent-log.component.html
@@ -6,8 +6,8 @@
@@ -20,17 +20,17 @@
-
+
{{'AUDIT_LOG.USERNAME' | translate}}
- {{'AUDIT_LOG.REPOSITORY_NAME' | translate}}
- {{'AUDIT_LOG.TAGS' | translate}}
+ {{'AUDIT_LOG.RESOURCE' | translate}}
+ {{'AUDIT_LOG.RESOURCE_TYPE' | translate}}
{{'AUDIT_LOG.OPERATION' | translate}}
{{'AUDIT_LOG.TIMESTAMP' | translate}}
{{ 'AUDIT_LOG.NOT_FOUND' | translate }}
{{l.username}}
- {{l.repo_name}}
- {{l.repo_tag}}
+ {{l.resource}}
+ {{l.resource_type}}
{{l.operation}}
{{l.op_time | date: 'short'}}
diff --git a/src/portal/src/lib/components/log/recent-log.component.spec.ts b/src/portal/src/lib/components/log/recent-log.component.spec.ts
index d1f58797c..decf0f802 100644
--- a/src/portal/src/lib/components/log/recent-log.component.spec.ts
+++ b/src/portal/src/lib/components/log/recent-log.component.spec.ts
@@ -1,41 +1,79 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
-import { AccessLog, AccessLogItem, RequestQueryParams } from '../../services';
-
import { RecentLogComponent } from './recent-log.component';
-import { AccessLogService, AccessLogDefaultService } from '../../services/access-log.service';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
import { ErrorHandler } from '../../utils/error-handler';
import { SharedModule } from '../../utils/shared/shared.module';
import { FilterComponent } from '../filter/filter.component';
-
import { click, CURRENT_BASE_HREF } from '../../utils/utils';
import { of } from 'rxjs';
-import { delay } from 'rxjs/operators';
+import { AuditLog } from "../../../../ng-swagger-gen/models/audit-log";
+import { AuditlogService } from "../../../../ng-swagger-gen/services/auditlog.service";
+import { HttpHeaders, HttpResponse } from "@angular/common/http";
+import ListAuditLogsParams = AuditlogService.ListAuditLogsParams;
+import { delay } from "rxjs/operators";
describe('RecentLogComponent (inline template)', () => {
let component: RecentLogComponent;
let fixture: ComponentFixture;
let serviceConfig: IServiceConfig;
- let logService: AccessLogService;
- let spy: jasmine.Spy;
- let mockItems: AccessLogItem[] = [];
- let mockData: AccessLog = {
- metadata: {
- xTotalCount: 18
- },
- data: []
- };
- let mockData2: AccessLog = {
- metadata: {
- xTotalCount: 1
- },
- data: []
- };
+ let auditlogService: AuditlogService;
let testConfig: IServiceConfig = {
logBaseEndpoint: CURRENT_BASE_HREF + "/logs/testing"
};
-
+ const fakedErrorHandler = {
+ error() {
+ return undefined;
+ }
+ };
+ const mockedAuditLogs: AuditLog [] = [];
+ for (let i = 0; i < 18; i++) {
+ let item: AuditLog = {
+ id: 23 + 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 = {
+ listAuditLogsResponse(params: ListAuditLogsParams) {
+ if (params && params.username) {
+ if (params.username === 'demo0') {
+ return of(new HttpResponse({
+ body: mockedAuditLogs.slice(0, 1),
+ headers: new HttpHeaders({
+ "x-total-count": "18"
+ })
+ })).pipe(delay(0));
+ }
+ return of(new HttpResponse({
+ body: mockedAuditLogs,
+ headers: new HttpHeaders({
+ "x-total-count": "18"
+ })
+ })).pipe(delay(0));
+ } else {
+ 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));
+ }
+ }
+ }
+ };
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@@ -43,9 +81,9 @@ describe('RecentLogComponent (inline template)', () => {
],
declarations: [FilterComponent, RecentLogComponent],
providers: [
- ErrorHandler,
+ { provide: ErrorHandler, useValue: fakedErrorHandler },
+ { provide: AuditlogService, useValue: fakedAuditlogService },
{ provide: SERVICE_CONFIG, useValue: testConfig },
- { provide: AccessLogService, useClass: AccessLogDefaultService }
]
});
@@ -55,38 +93,7 @@ describe('RecentLogComponent (inline template)', () => {
fixture = TestBed.createComponent(RecentLogComponent);
component = fixture.componentInstance;
serviceConfig = TestBed.get(SERVICE_CONFIG);
- logService = fixture.debugElement.injector.get(AccessLogService);
- // Mock data
- for (let i = 0; i < 18; i++) {
- let item: AccessLogItem = {
- log_id: 23 + i,
- user_id: 45 + i,
- project_id: 11 + i,
- repo_name: "myproject/demo" + i,
- repo_tag: "N/A",
- operation: "create",
- op_time: "2017-04-11T10:26:22Z",
- username: "user91" + i
- };
- mockItems.push(item);
- }
- mockData2.data = mockItems.slice(0, 1);
- mockData.data = mockItems;
-
- spy = spyOn(logService, 'getRecentLogs')
- .and.callFake(function (params: RequestQueryParams) {
- if (params && params.get('username')) {
- return of(mockData2);
- } else {
- if (params.get('page') === '1') {
- mockData.data = mockItems.slice(0, 15);
- } else {
- mockData.data = mockItems.slice(15, 18);
- }
- return of(mockData).pipe(delay(0));
- }
- });
-
+ auditlogService = fixture.debugElement.injector.get(AuditlogService);
fixture.detectChanges();
});
@@ -100,15 +107,11 @@ describe('RecentLogComponent (inline template)', () => {
});
it('should get data from AccessLogService', async(() => {
- expect(logService).toBeTruthy();
- expect(spy.calls.any()).toBe(true, 'getRecentLogs called');
-
+ expect(auditlogService).toBeTruthy();
fixture.detectChanges();
-
fixture.whenStable().then(() => { // wait for async getRecentLogs
fixture.detectChanges();
expect(component.recentLogs).toBeTruthy();
- expect(component.logsCache).toBeTruthy();
expect(component.recentLogs.length).toEqual(15);
});
}));
@@ -125,28 +128,16 @@ describe('RecentLogComponent (inline template)', () => {
expect(el.textContent.trim()).toEqual('user910');
});
}));
-
- // Will fail after upgrade to angular 6. todo: need to fix it.
- xit('should support pagination', () => {
+ 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();
-
- fixture.whenStable().then(() => {
- fixture.detectChanges();
-
- let el: HTMLButtonElement = fixture.nativeElement.querySelector('.pagination-next');
- expect(el).toBeTruthy();
- el.click();
- jasmine.clock().tick(100);
- fixture.detectChanges();
-
- fixture.whenStable().then(() => {
- fixture.detectChanges();
-
- let els: HTMLElement[] = fixture.nativeElement.querySelectorAll('.datagrid-row');
- expect(els).toBeTruthy();
- expect(els.length).toEqual(4);
- });
- });
+ await fixture.whenStable();
+ expect(component.currentPage).toEqual(2);
+ expect(component.recentLogs.length).toEqual(3);
});
it('should support filtering list by keywords', async(() => {
@@ -154,22 +145,17 @@ describe('RecentLogComponent (inline template)', () => {
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.doFilter("demo0");
-
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.recentLogs).toBeTruthy();
- expect(component.logsCache).toBeTruthy();
expect(component.recentLogs.length).toEqual(1);
});
});
@@ -177,32 +163,23 @@ describe('RecentLogComponent (inline template)', () => {
it('should support refreshing', async(() => {
fixture.detectChanges();
-
fixture.whenStable().then(() => {
fixture.detectChanges();
-
let el: HTMLButtonElement = fixture.nativeElement.querySelector('.pagination-next');
expect(el).toBeTruthy();
el.click();
-
fixture.detectChanges();
-
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.recentLogs).toBeTruthy();
- expect(component.logsCache).toBeTruthy();
expect(component.recentLogs.length).toEqual(3);
-
let refreshEl: HTMLElement = fixture.nativeElement.querySelector(".refresh-btn");
expect(refreshEl).toBeTruthy("Not found refresh button");
refreshEl.click();
-
fixture.detectChanges();
-
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.recentLogs).toBeTruthy();
- expect(component.logsCache).toBeTruthy();
expect(component.recentLogs.length).toEqual(15);
});
diff --git a/src/portal/src/lib/components/log/recent-log.component.ts b/src/portal/src/lib/components/log/recent-log.component.ts
index de4876cb3..104a9a0e6 100644
--- a/src/portal/src/lib/components/log/recent-log.component.ts
+++ b/src/portal/src/lib/components/log/recent-log.component.ts
@@ -12,20 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Input } from '@angular/core';
-import { Comparator, State } from '../../services/interface';
-
-import {
- AccessLogService,
- AccessLog,
- AccessLogItem,
- RequestQueryParams
-} from '../../services';
import { ErrorHandler } from '../../utils/error-handler';
-import { CustomComparator } from '../../utils/utils';
-import {
- DEFAULT_PAGE_SIZE,
-} from '../../utils/utils';
import { finalize } from "rxjs/operators";
+import { AuditlogService } from "../../../../ng-swagger-gen/services/auditlog.service";
+import { AuditLog } from "../../../../ng-swagger-gen/models/audit-log";
+import ListAuditLogsParams = AuditlogService.ListAuditLogsParams;
@Component({
selector: 'hbr-log',
@@ -34,8 +25,7 @@ import { finalize } from "rxjs/operators";
})
export class RecentLogComponent implements OnInit {
- recentLogs: AccessLogItem[] = [];
- logsCache: AccessLog;
+ recentLogs: AuditLog[] = [];
loading: boolean = true;
currentTerm: string;
defaultFilter = "username";
@@ -43,17 +33,13 @@ export class RecentLogComponent implements OnInit {
@Input() withTitle: boolean = false;
pageSize: number = 15;
currentPage: number = 1; // Double bound to pagination component
+ totalCount: number = 0;
constructor(
- private logService: AccessLogService,
+ private logService: AuditlogService,
private errorHandler: ErrorHandler) { }
ngOnInit(): void {
}
-
- public get totalCount(): number {
- return this.logsCache && this.logsCache.metadata ? this.logsCache.metadata.xTotalCount : 0;
- }
-
public get inProgress(): boolean {
return this.loading;
}
@@ -66,7 +52,8 @@ export class RecentLogComponent implements OnInit {
this.currentTerm = terms.trim();
this.loading = true;
this.currentPage = 1;
- this.load({page: {}});
+ this.totalCount = 0;
+ this.load();
}
public refresh(): void {
@@ -74,11 +61,7 @@ export class RecentLogComponent implements OnInit {
}
openFilter(isOpen: boolean): void {
- if (isOpen) {
- this.isOpenFilterTag = true;
- } else {
- this.isOpenFilterTag = false;
- }
+ this.isOpenFilterTag = isOpen;
}
selectFilterKey($event: any): void {
@@ -86,21 +69,27 @@ export class RecentLogComponent implements OnInit {
this.doFilter(this.currentTerm);
}
- load(state) {
- if (!state || !state.page) {
- return;
- }
+ load() {
// Keep it for future filter
// this.currentState = state;
- let params: RequestQueryParams = new RequestQueryParams().set("page", '' + this.currentPage).set("page_size", '' + this.pageSize);
+ const params: ListAuditLogsParams = {
+ page: this.currentPage,
+ pageSize: this.pageSize
+ };
if (this.currentTerm && this.currentTerm !== "") {
- params = params.set(this.defaultFilter, this.currentTerm);
+ params[this.defaultFilter] = this.currentTerm;
}
this.loading = true;
- this.logService.getRecentLogs(params).pipe(finalize(() => (this.loading = false)))
+ this.logService.listAuditLogsResponse(params).pipe(finalize(() => (this.loading = false)))
.subscribe(response => {
- this.logsCache = response; // Keep the data
- this.recentLogs = response.data;
+ // Get total count
+ if (response.headers) {
+ let xHeader: string = response.headers.get("x-total-count");
+ if (xHeader) {
+ this.totalCount = parseInt(xHeader, 0);
+ }
+ }
+ this.recentLogs = response.body as AuditLog[];
}, error => {
this.errorHandler.error(error);
});
diff --git a/src/portal/src/lib/services/permission.service.ts b/src/portal/src/lib/services/permission.service.ts
index 3f1f1e832..05ddb7d2b 100644
--- a/src/portal/src/lib/services/permission.service.ts
+++ b/src/portal/src/lib/services/permission.service.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import { Injectable } from '@angular/core';
-import { Observable, forkJoin, of, throwError as observableThrowError } from "rxjs";
-import { map, tap, publishReplay, refCount } from "rxjs/operators";
+import { Observable, forkJoin} from "rxjs";
+import { map, share } from "rxjs/operators";
import { HttpClient } from '@angular/common/http';
import { CacheObservable } from "../utils/cache-util";
import { CURRENT_BASE_HREF } from "../utils/utils";
@@ -46,6 +46,8 @@ export abstract class UserPermissionService {
// @dynamic
@Injectable()
export class UserPermissionDefaultService extends UserPermissionService {
+ // to prevent duplicate permissions HTTP requests
+ private _sharedPermissionObservableMap: {[key: string]: Observable>} = {};
constructor(
private http: HttpClient,
) {
@@ -55,7 +57,12 @@ export class UserPermissionDefaultService extends UserPermissionService {
@CacheObservable({ maxAge: 1000 * 60 })
private getPermissions(scope: string, relative?: boolean): Observable> {
const url = `${ CURRENT_BASE_HREF }/users/current/permissions?scope=${scope}&relative=${relative ? 'true' : 'false'}`;
- return this.http.get>(url);
+ if (this._sharedPermissionObservableMap[url]) {
+ return this._sharedPermissionObservableMap[url];
+ } else {
+ this._sharedPermissionObservableMap[url] = this.http.get>(url).pipe(share());
+ return this._sharedPermissionObservableMap[url];
+ }
}
private hasPermission(permission: Permission, scope: string, relative?: boolean): Observable {