Added history to frontend

This commit is contained in:
Sven Heidemann 2025-04-18 11:03:13 +02:00
parent 1824ff7564
commit 165c140a25
25 changed files with 461 additions and 29 deletions

View File

@ -54,7 +54,7 @@ class DbModelABC(ABC):
from data.schemas.administration.user_dao import userDao from data.schemas.administration.user_dao import userDao
return await userDao.get_by_id(self._editor_id) return await userDao.find_single_by({"id": self._editor_id})
@property @property
def created(self) -> datetime: def created(self) -> datetime:

View File

@ -1,6 +1,6 @@
import { DbModel } from 'src/app/model/entities/db-model'; import { DbModelWithHistory } from 'src/app/model/entities/db-model';
export interface Domain extends DbModel { export interface Domain extends DbModelWithHistory {
name: string; name: string;
} }

View File

@ -1,7 +1,7 @@
import { DbModel } from 'src/app/model/entities/db-model'; import { DbModelWithHistory } from 'src/app/model/entities/db-model';
import { Role } from 'src/app/model/entities/role'; import { Role } from 'src/app/model/entities/role';
export interface Group extends DbModel { export interface Group extends DbModelWithHistory {
name: string; name: string;
roles: Role[]; roles: Role[];
} }

View File

@ -1,8 +1,8 @@
import { DbModel } from 'src/app/model/entities/db-model'; import { DbModelWithHistory } from 'src/app/model/entities/db-model';
import { Group } from 'src/app/model/entities/group'; import { Group } from 'src/app/model/entities/group';
import { Domain } from 'src/app/model/entities/domain'; import { Domain } from 'src/app/model/entities/domain';
export interface ShortUrl extends DbModel { export interface ShortUrl extends DbModelWithHistory {
shortUrl: string; shortUrl: string;
targetUrl: string; targetUrl: string;
description: string; description: string;

View File

@ -11,7 +11,10 @@ import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort } from 'src/app/model/graphql/filter/sort.model'; import { Sort } from 'src/app/model/graphql/filter/sort.model';
import { Apollo, gql } from 'apollo-angular'; import { Apollo, gql } from 'apollo-angular';
import { QueryResult } from 'src/app/model/entities/query-result'; import { QueryResult } from 'src/app/model/entities/query-result';
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query'; import {
DB_HISTORY_MODEL_FRAGMENT,
DB_MODEL_FRAGMENT,
} from 'src/app/model/graphql/db-model.query';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { SpinnerService } from 'src/app/service/spinner.service'; import { SpinnerService } from 'src/app/service/spinner.service';
import { import {
@ -19,10 +22,11 @@ import {
DomainCreateInput, DomainCreateInput,
DomainUpdateInput, DomainUpdateInput,
} from 'src/app/model/entities/domain'; } from 'src/app/model/entities/domain';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
@Injectable() @Injectable()
export class DomainsDataService export class DomainsDataService
extends PageDataService<Domain> extends PageWithHistoryDataService<Domain>
implements implements
Create<Domain, DomainCreateInput>, Create<Domain, DomainCreateInput>,
Update<Domain, DomainUpdateInput>, Update<Domain, DomainUpdateInput>,
@ -109,6 +113,40 @@ export class DomainsDataService
.pipe(map(result => result.data.domains.nodes[0])); .pipe(map(result => result.data.domains.nodes[0]));
} }
loadHistory(id: number) {
return this.apollo
.query<{ domains: QueryResult<Domain> }>({
query: gql`
query getDomainHistory($id: Int) {
domains(filter: { id: { equal: $id } }) {
count
totalCount
nodes {
history {
id
name
...DB_HISTORY_MODEL
}
}
}
}
${DB_HISTORY_MODEL_FRAGMENT}
`,
variables: {
id,
},
})
.pipe(
catchError(err => {
this.spinner.hide();
throw err;
})
)
.pipe(map(result => result.data?.domains?.nodes?.[0]?.history ?? []));
}
onChange(): Observable<void> { onChange(): Observable<void> {
return this.apollo return this.apollo
.subscribe<{ domainChange: void }>({ .subscribe<{ domainChange: void }>({
@ -238,6 +276,10 @@ export class DomainsDataService
provide: PageDataService, provide: PageDataService,
useClass: DomainsDataService, useClass: DomainsDataService,
}, },
{
provide: PageWithHistoryDataService,
useClass: DomainsDataService,
},
DomainsDataService, DomainsDataService,
]; ];
} }

View File

@ -8,6 +8,7 @@ import { DomainsPage } from 'src/app/modules/admin/domains/domains.page';
import { DomainFormPageComponent } from 'src/app/modules/admin/domains/form-page/domain-form-page.component'; import { DomainFormPageComponent } from 'src/app/modules/admin/domains/form-page/domain-form-page.component';
import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service'; import { DomainsDataService } from 'src/app/modules/admin/domains/domains.data.service';
import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns'; import { DomainsColumns } from 'src/app/modules/admin/domains/domains.columns';
import { HistoryComponent } from 'src/app/modules/admin/domains/history/history.component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -31,12 +32,20 @@ const routes: Routes = [
permissions: [PermissionsEnum.apiKeysUpdate], permissions: [PermissionsEnum.apiKeysUpdate],
}, },
}, },
{
path: 'history/:historyId',
component: HistoryComponent,
canActivate: [PermissionGuard],
data: {
permissions: [PermissionsEnum.domains],
},
},
], ],
}, },
]; ];
@NgModule({ @NgModule({
declarations: [DomainsPage, DomainFormPageComponent], declarations: [DomainsPage, DomainFormPageComponent, HistoryComponent],
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
providers: [DomainsDataService.provide(), DomainsColumns.provide()], providers: [DomainsDataService.provide(), DomainsColumns.provide()],
}) })

View File

@ -11,6 +11,7 @@
[(skip)]="skip" [(skip)]="skip"
[(take)]="take" [(take)]="take"
(load)="load()" (load)="load()"
[history]="true"
[create]="true" [create]="true"
[update]="true" [update]="true"
(delete)="delete($event)" (delete)="delete($event)"

View File

@ -0,0 +1,3 @@
<app-history-sidebar
[loadHistory]="loadHistory"
[columns]="columns"></app-history-sidebar>

View File

@ -0,0 +1,65 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { AuthService } from 'src/app/service/auth.service';
import { KeycloakService } from 'keycloak-angular';
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
import { ToastService } from 'src/app/service/toast.service';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { PageDataService } from 'src/app/core/base/page.data.service';
import { IpListDataService } from 'src/app/modules/admin/tools/ip-list/ip-list.data.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PageColumns } from 'src/app/core/base/page.columns';
import { MockPageColumns } from 'src/app/modules/shared/test/page.columns.mock';
describe('HistoryComponent', () => {
let component: HistoryComponent<unknown>;
let fixture: ComponentFixture<HistoryComponent<unknown>>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HistoryComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
TranslateModule.forRoot(),
],
providers: [
AuthService,
KeycloakService,
ErrorHandlingService,
ToastService,
MessageService,
ConfirmationService,
{
provide: ActivatedRoute,
useValue: {
snapshot: { params: { historyId: '3' } },
},
},
{
provide: PageDataService,
useClass: IpListDataService,
},
{
provide: PageColumns,
useClass: MockPageColumns,
},
],
}).compileComponents();
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
component.loadHistory = (id: number) => of([]);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
import { PageColumns } from 'src/app/core/base/page.columns';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrl: './history.component.scss',
})
export class HistoryComponent<T> {
loadHistory = (id: number) => this.data.loadHistory(id);
columns = this._columns.get();
constructor(
public data: PageWithHistoryDataService<T>,
private _columns: PageColumns<T>
) {}
}

View File

@ -11,7 +11,10 @@ import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort } from 'src/app/model/graphql/filter/sort.model'; import { Sort } from 'src/app/model/graphql/filter/sort.model';
import { Apollo, gql } from 'apollo-angular'; import { Apollo, gql } from 'apollo-angular';
import { QueryResult } from 'src/app/model/entities/query-result'; import { QueryResult } from 'src/app/model/entities/query-result';
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query'; import {
DB_HISTORY_MODEL_FRAGMENT,
DB_MODEL_FRAGMENT,
} from 'src/app/model/graphql/db-model.query';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { SpinnerService } from 'src/app/service/spinner.service'; import { SpinnerService } from 'src/app/service/spinner.service';
import { import {
@ -19,10 +22,12 @@ import {
GroupCreateInput, GroupCreateInput,
GroupUpdateInput, GroupUpdateInput,
} from 'src/app/model/entities/group'; } from 'src/app/model/entities/group';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
import { Domain } from 'src/app/model/entities/domain';
@Injectable() @Injectable()
export class GroupsDataService export class GroupsDataService
extends PageDataService<Group> extends PageWithHistoryDataService<Group>
implements implements
Create<Group, GroupCreateInput>, Create<Group, GroupCreateInput>,
Update<Group, GroupUpdateInput>, Update<Group, GroupUpdateInput>,
@ -117,6 +122,40 @@ export class GroupsDataService
.pipe(map(result => result.data.groups.nodes[0])); .pipe(map(result => result.data.groups.nodes[0]));
} }
loadHistory(id: number) {
return this.apollo
.query<{ groups: QueryResult<Group> }>({
query: gql`
query getGroupHistory($id: Int) {
groups(filter: { id: { equal: $id } }) {
count
totalCount
nodes {
history {
id
name
...DB_HISTORY_MODEL
}
}
}
}
${DB_HISTORY_MODEL_FRAGMENT}
`,
variables: {
id,
},
})
.pipe(
catchError(err => {
this.spinner.hide();
throw err;
})
)
.pipe(map(result => result.data?.groups?.nodes?.[0]?.history ?? []));
}
onChange(): Observable<void> { onChange(): Observable<void> {
return this.apollo return this.apollo
.subscribe<{ groupChange: void }>({ .subscribe<{ groupChange: void }>({
@ -248,6 +287,10 @@ export class GroupsDataService
provide: PageDataService, provide: PageDataService,
useClass: GroupsDataService, useClass: GroupsDataService,
}, },
{
provide: PageWithHistoryDataService,
useClass: GroupsDataService,
},
GroupsDataService, GroupsDataService,
]; ];
} }

View File

@ -1,22 +1,23 @@
import { NgModule } from "@angular/core"; import { NgModule } from '@angular/core';
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { SharedModule } from "src/app/modules/shared/shared.module"; import { SharedModule } from 'src/app/modules/shared/shared.module';
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from '@angular/router';
import { PermissionGuard } from "src/app/core/guard/permission.guard"; import { PermissionGuard } from 'src/app/core/guard/permission.guard';
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum"; import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
import { GroupsPage } from "src/app/modules/admin/groups/groups.page"; import { GroupsPage } from 'src/app/modules/admin/groups/groups.page';
import { GroupFormPageComponent } from "src/app/modules/admin/groups/form-page/group-form-page.component"; import { GroupFormPageComponent } from 'src/app/modules/admin/groups/form-page/group-form-page.component';
import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service"; import { GroupsDataService } from 'src/app/modules/admin/groups/groups.data.service';
import { GroupsColumns } from "src/app/modules/admin/groups/groups.columns"; import { GroupsColumns } from 'src/app/modules/admin/groups/groups.columns';
import { HistoryComponent } from 'src/app/modules/admin/groups/history/history.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: "", path: '',
title: "Groups", title: 'Groups',
component: GroupsPage, component: GroupsPage,
children: [ children: [
{ {
path: "create", path: 'create',
component: GroupFormPageComponent, component: GroupFormPageComponent,
canActivate: [PermissionGuard], canActivate: [PermissionGuard],
data: { data: {
@ -24,19 +25,27 @@ const routes: Routes = [
}, },
}, },
{ {
path: "edit/:id", path: 'edit/:id',
component: GroupFormPageComponent, component: GroupFormPageComponent,
canActivate: [PermissionGuard], canActivate: [PermissionGuard],
data: { data: {
permissions: [PermissionsEnum.apiKeysUpdate], permissions: [PermissionsEnum.apiKeysUpdate],
}, },
}, },
{
path: 'history/:historyId',
component: HistoryComponent,
canActivate: [PermissionGuard],
data: {
permissions: [PermissionsEnum.domains],
},
},
], ],
}, },
]; ];
@NgModule({ @NgModule({
declarations: [GroupsPage, GroupFormPageComponent], declarations: [GroupsPage, GroupFormPageComponent, HistoryComponent],
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
providers: [GroupsDataService.provide(), GroupsColumns.provide()], providers: [GroupsDataService.provide(), GroupsColumns.provide()],
}) })

View File

@ -11,6 +11,7 @@
[(skip)]="skip" [(skip)]="skip"
[(take)]="take" [(take)]="take"
(load)="load()" (load)="load()"
[history]="true"
[create]="true" [create]="true"
[update]="true" [update]="true"
(delete)="delete($event)" (delete)="delete($event)"

View File

@ -0,0 +1,3 @@
<app-history-sidebar
[loadHistory]="loadHistory"
[columns]="columns"></app-history-sidebar>

View File

@ -0,0 +1,65 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { AuthService } from 'src/app/service/auth.service';
import { KeycloakService } from 'keycloak-angular';
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
import { ToastService } from 'src/app/service/toast.service';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { PageDataService } from 'src/app/core/base/page.data.service';
import { IpListDataService } from 'src/app/modules/admin/tools/ip-list/ip-list.data.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PageColumns } from 'src/app/core/base/page.columns';
import { MockPageColumns } from 'src/app/modules/shared/test/page.columns.mock';
describe('HistoryComponent', () => {
let component: HistoryComponent<unknown>;
let fixture: ComponentFixture<HistoryComponent<unknown>>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HistoryComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
TranslateModule.forRoot(),
],
providers: [
AuthService,
KeycloakService,
ErrorHandlingService,
ToastService,
MessageService,
ConfirmationService,
{
provide: ActivatedRoute,
useValue: {
snapshot: { params: { historyId: '3' } },
},
},
{
provide: PageDataService,
useClass: IpListDataService,
},
{
provide: PageColumns,
useClass: MockPageColumns,
},
],
}).compileComponents();
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
component.loadHistory = (id: number) => of([]);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
import { PageColumns } from 'src/app/core/base/page.columns';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrl: './history.component.scss',
})
export class HistoryComponent<T> {
loadHistory = (id: number) => this.data.loadHistory(id);
columns = this._columns.get();
constructor(
public data: PageWithHistoryDataService<T>,
private _columns: PageColumns<T>
) {}
}

View File

@ -0,0 +1,3 @@
<app-history-sidebar
[loadHistory]="loadHistory"
[columns]="columns"></app-history-sidebar>

View File

@ -0,0 +1,65 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { AuthService } from 'src/app/service/auth.service';
import { KeycloakService } from 'keycloak-angular';
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
import { ToastService } from 'src/app/service/toast.service';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { PageDataService } from 'src/app/core/base/page.data.service';
import { IpListDataService } from 'src/app/modules/admin/tools/ip-list/ip-list.data.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PageColumns } from 'src/app/core/base/page.columns';
import { MockPageColumns } from 'src/app/modules/shared/test/page.columns.mock';
describe('HistoryComponent', () => {
let component: HistoryComponent<unknown>;
let fixture: ComponentFixture<HistoryComponent<unknown>>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HistoryComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
TranslateModule.forRoot(),
],
providers: [
AuthService,
KeycloakService,
ErrorHandlingService,
ToastService,
MessageService,
ConfirmationService,
{
provide: ActivatedRoute,
useValue: {
snapshot: { params: { historyId: '3' } },
},
},
{
provide: PageDataService,
useClass: IpListDataService,
},
{
provide: PageColumns,
useClass: MockPageColumns,
},
],
}).compileComponents();
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
component.loadHistory = (id: number) => of([]);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
import { PageColumns } from 'src/app/core/base/page.columns';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrl: './history.component.scss',
})
export class HistoryComponent<T> {
loadHistory = (id: number) => this.data.loadHistory(id);
columns = this._columns.get();
constructor(
public data: PageWithHistoryDataService<T>,
private _columns: PageColumns<T>
) {}
}

View File

@ -11,7 +11,10 @@ import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort } from 'src/app/model/graphql/filter/sort.model'; import { Sort } from 'src/app/model/graphql/filter/sort.model';
import { Apollo, gql } from 'apollo-angular'; import { Apollo, gql } from 'apollo-angular';
import { QueryResult } from 'src/app/model/entities/query-result'; import { QueryResult } from 'src/app/model/entities/query-result';
import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query'; import {
DB_HISTORY_MODEL_FRAGMENT,
DB_MODEL_FRAGMENT,
} from 'src/app/model/graphql/db-model.query';
import { catchError, map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { SpinnerService } from 'src/app/service/spinner.service'; import { SpinnerService } from 'src/app/service/spinner.service';
import { import {
@ -21,6 +24,7 @@ import {
} from 'src/app/model/entities/short-url'; } from 'src/app/model/entities/short-url';
import { Group } from 'src/app/model/entities/group'; import { Group } from 'src/app/model/entities/group';
import { Domain } from 'src/app/model/entities/domain'; import { Domain } from 'src/app/model/entities/domain';
import { PageWithHistoryDataService } from 'src/app/core/base/page-with-history.data.service';
@Injectable() @Injectable()
export class ShortUrlsDataService export class ShortUrlsDataService
@ -132,6 +136,52 @@ export class ShortUrlsDataService
.pipe(map(result => result.data.shortUrls.nodes[0])); .pipe(map(result => result.data.shortUrls.nodes[0]));
} }
loadHistory(id: number) {
return this.apollo
.query<{ shortUrls: QueryResult<ShortUrl> }>({
query: gql`
query getDomainHistory($id: Int) {
shortUrls(filter: { id: { equal: $id } }) {
count
totalCount
nodes {
history {
id
shortUrl
targetUrl
description
loadingScreen
visits
group {
id
name
}
domain {
id
name
}
...DB_HISTORY_MODEL
}
}
}
}
${DB_HISTORY_MODEL_FRAGMENT}
`,
variables: {
id,
},
})
.pipe(
catchError(err => {
this.spinner.hide();
throw err;
})
)
.pipe(map(result => result.data?.shortUrls?.nodes?.[0]?.history ?? []));
}
onChange(): Observable<void> { onChange(): Observable<void> {
return merge( return merge(
this.apollo this.apollo
@ -329,6 +379,10 @@ export class ShortUrlsDataService
provide: PageDataService, provide: PageDataService,
useClass: ShortUrlsDataService, useClass: ShortUrlsDataService,
}, },
{
provide: PageWithHistoryDataService,
useClass: ShortUrlsDataService,
},
ShortUrlsDataService, ShortUrlsDataService,
]; ];
} }

View File

@ -8,6 +8,7 @@ import { ShortUrlsPage } from 'src/app/modules/admin/short-urls/short-urls.page'
import { ShortUrlFormPageComponent } from 'src/app/modules/admin/short-urls/form-page/short-url-form-page.component'; import { ShortUrlFormPageComponent } from 'src/app/modules/admin/short-urls/form-page/short-url-form-page.component';
import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service'; import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns'; import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
import { HistoryComponent } from 'src/app/modules/admin/short-urls/history/history.component';
const routes: Routes = [ const routes: Routes = [
{ {
@ -31,12 +32,20 @@ const routes: Routes = [
permissions: [PermissionsEnum.shortUrlsUpdate], permissions: [PermissionsEnum.shortUrlsUpdate],
}, },
}, },
{
path: 'history/:historyId',
component: HistoryComponent,
canActivate: [PermissionGuard],
data: {
permissions: [PermissionsEnum.domains],
},
},
], ],
}, },
]; ];
@NgModule({ @NgModule({
declarations: [ShortUrlsPage, ShortUrlFormPageComponent], declarations: [ShortUrlsPage, ShortUrlFormPageComponent, HistoryComponent],
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
providers: [ShortUrlsDataService.provide(), ShortUrlsColumns.provide()], providers: [ShortUrlsDataService.provide(), ShortUrlsColumns.provide()],
}) })

View File

@ -67,6 +67,12 @@
tooltipPosition="left" tooltipPosition="left"
pTooltip="{{ 'table.restore' | translate }}" pTooltip="{{ 'table.restore' | translate }}"
(click)="restore(url)"></p-button> (click)="restore(url)"></p-button>
<p-button
class="icon-btn btn"
icon="pi pi-history"
tooltipPosition="left"
pTooltip="{{ 'table.history' | translate }}"
routerLink="history/{{ url.id }}"></p-button>
</div> </div>
</div> </div>
</div> </div>