+
-
-
-
+
+
diff --git a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts
index f62ed2e634..1547930e35 100644
--- a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts
+++ b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.spec.ts
@@ -59,7 +59,7 @@ describe("NotesDashboardComponent", () => {
component.ngOnInit();
tick();
- expect(component.dataSource.data).toHaveSize(3);
+ expect(component.entries).toHaveSize(3);
}));
});
@@ -92,9 +92,9 @@ describe("NotesDashboardComponent", () => {
tick();
- expect(component.dataSource.data).toHaveSize(3);
+ expect(component.entries).toHaveSize(3);
- expect(component.dataSource.data[0]).toEqual({
+ expect(component.entries[0]).toEqual({
entityId: "5",
daysSinceLastNote: 50,
moreThanDaysSince: false,
@@ -110,11 +110,11 @@ describe("NotesDashboardComponent", () => {
component.ngOnInit();
tick();
- expect(component.dataSource.data).toHaveSize(1);
+ expect(component.entries).toHaveSize(1);
- expect(component.dataSource.data[0].entityId).toBe(childId1);
- expect(component.dataSource.data[0].moreThanDaysSince).toBeTrue();
- expect(component.dataSource.data[0].daysSinceLastNote).toBeFinite();
+ expect(component.entries[0].entityId).toBe(childId1);
+ expect(component.entries[0].moreThanDaysSince).toBeTrue();
+ expect(component.entries[0].daysSinceLastNote).toBeFinite();
}));
it("should load notes related to the configured entity", () => {
diff --git a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts
index 1329575419..32ea5ff410 100644
--- a/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts
+++ b/src/app/child-dev-project/notes/dashboard-widgets/notes-dashboard/notes-dashboard.component.ts
@@ -1,24 +1,16 @@
-import {
- AfterViewInit,
- Component,
- Input,
- OnInit,
- ViewChild,
-} from "@angular/core";
+import { Component, Input, OnInit } from "@angular/core";
import { ChildrenService } from "../../../children/children.service";
import moment from "moment";
-import { MatTableDataSource, MatTableModule } from "@angular/material/table";
-import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
+import { MatTableModule } from "@angular/material/table";
import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator";
import { Child } from "../../../children/model/child";
import { EntityRegistry } from "../../../../core/entity/database-entity.decorator";
import { EntityConstructor } from "../../../../core/entity/model/entity";
import { DecimalPipe, NgIf } from "@angular/common";
import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component";
-import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component";
-import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component";
import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget";
import { Note } from "../../model/note";
+import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component";
interface NotesDashboardConfig {
entity?: string;
@@ -38,20 +30,18 @@ interface NotesDashboardConfig {
selector: "app-no-recent-notes-dashboard",
templateUrl: "./notes-dashboard.component.html",
styleUrls: ["./notes-dashboard.component.scss"],
+ standalone: true,
imports: [
NgIf,
MatTableModule,
DisplayEntityComponent,
DecimalPipe,
- MatPaginatorModule,
- DashboardWidgetComponent,
- WidgetContentComponent,
+ DashboardListWidgetComponent,
],
- standalone: true,
})
export class NotesDashboardComponent
extends DashboardWidget
- implements OnInit, AfterViewInit, NotesDashboardConfig
+ implements OnInit, NotesDashboardConfig
{
static getRequiredEntities(config: NotesDashboardConfig) {
return config?.entity || Note.ENTITY_TYPE;
@@ -78,14 +68,10 @@ export class NotesDashboardComponent
/**
* Entities displayed in the template with additional "daysSinceLastNote" field
*/
- dataSource = new MatTableDataSource
();
+ entries: EntityWithRecentNoteInfo[];
subtitle: string;
- @ViewChild("paginator") paginator: MatPaginator;
-
- isLoading = true;
-
constructor(
private childrenService: ChildrenService,
private entities: EntityRegistry,
@@ -116,10 +102,6 @@ export class NotesDashboardComponent
}
}
- ngAfterViewInit() {
- this.dataSource.paginator = this.paginator;
- }
-
private async loadConcernedEntities(
filter: (stat: [string, number]) => boolean,
dayRangeBoundary: number,
@@ -133,12 +115,10 @@ export class NotesDashboardComponent
this._entity.ENTITY_TYPE,
queryRange,
);
- this.dataSource.data = Array.from(recentNotesMap)
+ this.entries = Array.from(recentNotesMap)
.filter(filter)
.map((stat) => statsToEntityWithRecentNoteInfo(stat, queryRange))
.sort((a, b) => order * (b.daysSinceLastNote - a.daysSinceLastNote));
-
- this.isLoading = false;
}
get tooltip(): string {
diff --git a/src/app/core/config/dynamic-components/dynamic-component.directive.ts b/src/app/core/config/dynamic-components/dynamic-component.directive.ts
index ea92b0b1c8..3a40bd8012 100644
--- a/src/app/core/config/dynamic-components/dynamic-component.directive.ts
+++ b/src/app/core/config/dynamic-components/dynamic-component.directive.ts
@@ -48,11 +48,12 @@ export class DynamicComponentDirective implements OnChanges {
this.appDynamicComponent.component,
)();
} catch (e) {
- this.logger.error(
- `Failed to load dynamic component:\n${JSON.stringify(
+ this.logger.error({
+ message: `Failed to load dynamic component:\n${JSON.stringify(
this.appDynamicComponent,
)}`,
- );
+ error: e,
+ });
// abort if component failed to load
return;
}
diff --git a/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts b/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts
index 49b62a4d6b..135941522d 100644
--- a/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts
+++ b/src/app/core/config/dynamic-routing/not-found/not-found.component.spec.ts
@@ -11,7 +11,7 @@ describe("NotFoundComponent", () => {
let mockLogging: jasmine.SpyObj;
beforeEach(async () => {
- mockLogging = jasmine.createSpyObj(LoggingService.name, ["warn"]);
+ mockLogging = jasmine.createSpyObj(LoggingService.name, ["debug"]);
await TestBed.configureTestingModule({
imports: [NotFoundComponent, RouterTestingModule],
providers: [
@@ -32,8 +32,8 @@ describe("NotFoundComponent", () => {
});
it("should call logging service with current route", () => {
- expect(mockLogging.warn).toHaveBeenCalledWith(
- "Could not find component for route: /some/path",
+ expect(mockLogging.debug).toHaveBeenCalledWith(
+ "Could not find route: /some/path",
);
});
});
diff --git a/src/app/core/config/dynamic-routing/not-found/not-found.component.ts b/src/app/core/config/dynamic-routing/not-found/not-found.component.ts
index 3cf6987ac6..e83ad0db73 100644
--- a/src/app/core/config/dynamic-routing/not-found/not-found.component.ts
+++ b/src/app/core/config/dynamic-routing/not-found/not-found.component.ts
@@ -18,8 +18,10 @@ export class NotFoundComponent implements OnInit {
) {}
ngOnInit() {
- this.loggingService.warn(
- "Could not find component for route: " + this.location.pathname,
- );
+ if (!this.location.pathname.endsWith("/404")) {
+ this.loggingService.debug(
+ "Could not find route: " + this.location.pathname,
+ );
+ }
}
}
diff --git a/src/app/core/logging/logging.service.ts b/src/app/core/logging/logging.service.ts
index 63eee4030a..a91e5fd709 100644
--- a/src/app/core/logging/logging.service.ts
+++ b/src/app/core/logging/logging.service.ts
@@ -1,8 +1,8 @@
import { Injectable } from "@angular/core";
import { LogLevel } from "./log-level";
import * as Sentry from "@sentry/browser";
-import { environment } from "../../../environments/environment";
import { BrowserOptions, SeverityLevel } from "@sentry/browser";
+import { environment } from "../../../environments/environment";
/* eslint-disable no-console */
@@ -123,7 +123,11 @@ export class LoggingService {
private logToRemoteMonitoring(message: any, logLevel: LogLevel) {
if (logLevel === LogLevel.ERROR) {
- Sentry.captureException(message);
+ if (message instanceof Error) {
+ Sentry.captureException(message);
+ } else {
+ Sentry.captureException(new Error(message?.error ?? message), message);
+ }
} else {
Sentry.captureMessage(message, this.translateLogLevel(logLevel));
}
diff --git a/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts b/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts
new file mode 100644
index 0000000000..cc44dc4353
--- /dev/null
+++ b/src/app/core/permissions/permission-guard/abstract-permission.guard.spec.ts
@@ -0,0 +1,46 @@
+import { TestBed } from "@angular/core/testing";
+import { Router, Routes } from "@angular/router";
+import { AbstractPermissionGuard } from "./abstract-permission.guard";
+import { Injectable } from "@angular/core";
+import { DynamicComponentConfig } from "../../config/dynamic-components/dynamic-component-config.interface";
+
+@Injectable()
+class TestPermissionGuard extends AbstractPermissionGuard {
+ constructor(router: Router) {
+ super(router);
+ }
+
+ protected async canAccessRoute(
+ routeData: DynamicComponentConfig,
+ ): Promise {
+ return routeData?.config;
+ }
+}
+
+describe("EntityPermissionGuard", () => {
+ let guard: TestPermissionGuard;
+
+ let testRoutes: Routes;
+
+ beforeEach(() => {
+ testRoutes = [{ path: "**", data: { config: true } }];
+
+ TestBed.configureTestingModule({
+ providers: [
+ TestPermissionGuard,
+ { provide: Router, useValue: { config: testRoutes } },
+ ],
+ });
+ guard = TestBed.inject(TestPermissionGuard);
+ });
+
+ it("should be created", () => {
+ expect(guard).toBeTruthy();
+ });
+
+ it("should get route config also for '**' path", async () => {
+ const result = await guard.checkRoutePermissions("url");
+
+ expect(result).toBeTrue();
+ });
+});
diff --git a/src/app/core/permissions/permission-guard/abstract-permission.guard.ts b/src/app/core/permissions/permission-guard/abstract-permission.guard.ts
index a59e038f59..cd0d0e76d6 100644
--- a/src/app/core/permissions/permission-guard/abstract-permission.guard.ts
+++ b/src/app/core/permissions/permission-guard/abstract-permission.guard.ts
@@ -63,6 +63,7 @@ export abstract class AbstractPermissionGuard implements CanActivate {
function isPathMatch(genericPath: string, path: string) {
const routeRegex = genericPath
+ .replace(/\*/g, ".*") // allow for wildcard routes in regex
.split("/")
// replace params with wildcard regex
.map((part) => (part.startsWith(":") ? "[^/]*" : part))
diff --git a/src/app/core/session/auth/keycloak/keycloak-auth.service.ts b/src/app/core/session/auth/keycloak/keycloak-auth.service.ts
index 4dd986844f..b4560a8339 100644
--- a/src/app/core/session/auth/keycloak/keycloak-auth.service.ts
+++ b/src/app/core/session/auth/keycloak/keycloak-auth.service.ts
@@ -57,6 +57,10 @@ export class KeycloakAuthService {
}
private processToken(token: string): SessionInfo {
+ if (!token) {
+ throw new Error("No token received from Keycloak");
+ }
+
this.accessToken = token;
this.logSuccessfulAuth();
const parsedToken: ParsedJWT = parseJwt(this.accessToken);
diff --git a/src/app/core/support/support/support.component.ts b/src/app/core/support/support/support.component.ts
index f29da86cf9..997737b433 100644
--- a/src/app/core/support/support/support.component.ts
+++ b/src/app/core/support/support/support.component.ts
@@ -133,7 +133,7 @@ export class SupportComponent implements OnInit {
user: { name: this.sessionInfo.name },
level: "debug",
extra: {
- currentUser: this.currentUser.getId(),
+ currentUser: this.currentUser?.getId(),
currentSyncState: this.currentSyncState,
lastSync: this.lastSync,
lastRemoteLogin: this.lastRemoteLogin,
diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html
index 9824d9f747..ea31f4a2af 100644
--- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html
+++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.html
@@ -1,51 +1,36 @@
-
-
-
-
- no birthdays in the next {{ threshold - 1 }} days
-
-
- 0" class="table-wrapper">
-
-
- Name |
- DateOfBirth |
- AfterBirthdayAge |
-
-
-
-
- |
-
-
-
- {{ entity.birthday | date: "E dd.MM" }}
- |
-
-
- {{ entity.newAge }} yrs |
-
-
-
-
-
-
-
-
+
+
+
+ Name |
+ DateOfBirth |
+ AfterBirthdayAge |
+
+
+
+
+ |
+
+
+
+ {{ entity.birthday | date: "E dd.MM" }}
+ |
+
+
+ {{ entity.newAge }} yrs |
+
+
+
+
+
diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts
index f058325831..1903e95c5d 100644
--- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts
+++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.spec.ts
@@ -65,7 +65,7 @@ describe("BirthdayDashboardComponent", () => {
tick();
const expectedNextBirthday = birthdaySoon.add(10, "years");
- expect(component.dataSource.data).toEqual([
+ expect(component.entries).toEqual([
{ entity: child1, birthday: expectedNextBirthday.toDate(), newAge: 10 },
]);
}));
@@ -90,7 +90,7 @@ describe("BirthdayDashboardComponent", () => {
const expectedFirstBirthday = firstBirthday.add(12, "years");
const expectedSecondBirthday = secondBirthday.add(15, "years");
- expect(component.dataSource.data).toEqual([
+ expect(component.entries).toEqual([
{ entity: child1, birthday: expectedFirstBirthday.toDate(), newAge: 12 },
{ entity: child2, birthday: expectedSecondBirthday.toDate(), newAge: 15 },
]);
@@ -123,7 +123,7 @@ describe("BirthdayDashboardComponent", () => {
component.ngOnInit();
tick();
- expect(component.dataSource.data).toEqual([
+ expect(component.entries).toEqual([
{
entity: e1,
birthday: moment().add(1, "day").startOf("day").toDate(),
diff --git a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts
index 69610e9273..c53d14b9f7 100644
--- a/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts
+++ b/src/app/features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard/birthday-dashboard.component.ts
@@ -1,21 +1,14 @@
-import {
- AfterViewInit,
- Component,
- Input,
- OnInit,
- ViewChild,
-} from "@angular/core";
+import { Component, Input, OnInit } from "@angular/core";
import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service";
import { Child } from "../../../../child-dev-project/children/model/child";
import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator";
-import { MatTableDataSource, MatTableModule } from "@angular/material/table";
-import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
+import { MatTableModule } from "@angular/material/table";
import { Entity } from "../../../../core/entity/model/entity";
import { DatePipe, NgIf } from "@angular/common";
import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component";
-import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component";
-import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component";
+
import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget";
+import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component";
interface BirthdayDashboardConfig {
entities: EntityPropertyMap;
@@ -27,26 +20,23 @@ interface BirthdayDashboardConfig {
selector: "app-birthday-dashboard",
templateUrl: "./birthday-dashboard.component.html",
styleUrls: ["./birthday-dashboard.component.scss"],
+ standalone: true,
imports: [
NgIf,
MatTableModule,
DisplayEntityComponent,
DatePipe,
- MatPaginatorModule,
- DashboardWidgetComponent,
- WidgetContentComponent,
+ DashboardListWidgetComponent,
],
- standalone: true,
})
export class BirthdayDashboardComponent
extends DashboardWidget
- implements BirthdayDashboardConfig, OnInit, AfterViewInit
+ implements BirthdayDashboardConfig, OnInit
{
static getRequiredEntities(config: BirthdayDashboardConfig) {
return config?.entities ? Object.keys(config.entities) : Child.ENTITY_TYPE;
}
- @ViewChild(MatPaginator) paginator: MatPaginator;
private readonly today: Date;
/**
@@ -64,8 +54,7 @@ export class BirthdayDashboardComponent
*/
@Input() threshold = 32;
- dataSource = new MatTableDataSource();
- isLoading = true;
+ entries: EntityWithBirthday[];
constructor(private entityMapper: EntityMapperService) {
super();
@@ -91,12 +80,7 @@ export class BirthdayDashboardComponent
data.sort(
(a, b) => this.daysUntil(a.birthday) - this.daysUntil(b.birthday),
);
- this.dataSource.data = data;
- this.isLoading = false;
- }
-
- ngAfterViewInit() {
- this.dataSource.paginator = this.paginator;
+ this.entries = data;
}
private getNextBirthday(dateOfBirth: Date): Date {
diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html
index cbf45a7d32..e1debc6bc1 100644
--- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html
+++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html
@@ -1,13 +1,13 @@
-
-
+
@@ -50,5 +50,5 @@
-
-
+
+
diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss
index 36511c292b..3ac3adfea4 100644
--- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss
+++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.scss
@@ -15,4 +15,8 @@
position: absolute;
left: 0;
bottom: 0;
+ margin: 4px;
+}
+.widget-container {
+ position: relative;
}
diff --git a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts
index 71d7268521..b852f7e699 100644
--- a/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts
+++ b/src/app/features/dashboard-widgets/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.ts
@@ -13,25 +13,23 @@ import { MatTableModule } from "@angular/material/table";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatButtonModule } from "@angular/material/button";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
-import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component";
-import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component";
import { SyncStateSubject } from "../../../../core/session/session-type";
import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget";
+import { DashboardListWidgetComponent } from "../../../../core/dashboard/dashboard-list-widget/dashboard-list-widget.component";
@Component({
selector: "app-progress-dashboard",
templateUrl: "./progress-dashboard.component.html",
styleUrls: ["./progress-dashboard.component.scss"],
+ standalone: true,
imports: [
PercentPipe,
MatTableModule,
MatProgressBarModule,
MatButtonModule,
FontAwesomeModule,
- DashboardWidgetComponent,
- WidgetContentComponent,
+ DashboardListWidgetComponent,
],
- standalone: true,
})
@DynamicComponent("ProgressDashboard")
export class ProgressDashboardComponent