Spaces:
Sleeping
Sleeping
Ming Wang commited on
Commit ·
602851d
1
Parent(s): 3a888be
Phase 1: frontend UI skeleton
Browse files- frontend/.claude/CLAUDE.md +55 -0
- frontend/.cursor/rules/cursor.mdc +60 -0
- frontend/.editorconfig +17 -0
- frontend/.gemini/GEMINI.md +55 -0
- frontend/.github/copilot-instructions.md +55 -0
- frontend/.gitignore +44 -0
- frontend/.junie/guidelines.md +55 -0
- frontend/.prettierrc +12 -0
- frontend/.vscode/extensions.json +4 -0
- frontend/.vscode/launch.json +20 -0
- frontend/.vscode/mcp.json +9 -0
- frontend/.vscode/tasks.json +42 -0
- frontend/.windsurf/rules/guidelines.md +55 -0
- frontend/AGENTS.md +55 -0
- frontend/README.md +59 -0
- frontend/angular.json +97 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +32 -0
- frontend/public/favicon.ico +0 -0
- frontend/src/app/app.config.ts +8 -0
- frontend/src/app/app.html +1 -0
- frontend/src/app/app.scss +0 -0
- frontend/src/app/app.ts +10 -0
- frontend/src/app/core/mocks/alerts.mock.ts +45 -0
- frontend/src/app/core/mocks/kpi.mock.ts +41 -0
- frontend/src/app/dashboard/alert-list/alert-list.html +23 -0
- frontend/src/app/dashboard/alert-list/alert-list.scss +92 -0
- frontend/src/app/dashboard/alert-list/alert-list.ts +24 -0
- frontend/src/app/dashboard/chart-placeholder/chart-placeholder.html +9 -0
- frontend/src/app/dashboard/chart-placeholder/chart-placeholder.scss +37 -0
- frontend/src/app/dashboard/chart-placeholder/chart-placeholder.ts +13 -0
- frontend/src/app/dashboard/dashboard.html +30 -0
- frontend/src/app/dashboard/dashboard.scss +71 -0
- frontend/src/app/dashboard/dashboard.ts +17 -0
- frontend/src/app/dashboard/kpi-card/kpi-card.html +18 -0
- frontend/src/app/dashboard/kpi-card/kpi-card.scss +63 -0
- frontend/src/app/dashboard/kpi-card/kpi-card.ts +27 -0
- frontend/src/app/shared/styles/_tokens.scss +27 -0
- frontend/src/index.html +20 -0
- frontend/src/main.ts +6 -0
- frontend/src/styles.scss +40 -0
- frontend/tsconfig.app.json +15 -0
- frontend/tsconfig.json +30 -0
- frontend/tsconfig.spec.json +15 -0
frontend/.claude/CLAUDE.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/.cursor/rules/cursor.mdc
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
context: true
|
| 3 |
+
priority: high
|
| 4 |
+
scope: project
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 8 |
+
|
| 9 |
+
## TypeScript Best Practices
|
| 10 |
+
|
| 11 |
+
- Use strict type checking
|
| 12 |
+
- Prefer type inference when the type is obvious
|
| 13 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 14 |
+
|
| 15 |
+
## Angular Best Practices
|
| 16 |
+
|
| 17 |
+
- Always use standalone components over NgModules
|
| 18 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 19 |
+
- Use signals for state management
|
| 20 |
+
- Implement lazy loading for feature routes
|
| 21 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 22 |
+
- Use `NgOptimizedImage` for all static images.
|
| 23 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 24 |
+
|
| 25 |
+
## Accessibility Requirements
|
| 26 |
+
|
| 27 |
+
- It MUST pass all AXE checks.
|
| 28 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 29 |
+
|
| 30 |
+
### Components
|
| 31 |
+
|
| 32 |
+
- Keep components small and focused on a single responsibility
|
| 33 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 34 |
+
- Use `computed()` for derived state
|
| 35 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 36 |
+
- Prefer inline templates for small components
|
| 37 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 38 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 39 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 40 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 41 |
+
|
| 42 |
+
## State Management
|
| 43 |
+
|
| 44 |
+
- Use signals for local component state
|
| 45 |
+
- Use `computed()` for derived state
|
| 46 |
+
- Keep state transformations pure and predictable
|
| 47 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 48 |
+
|
| 49 |
+
## Templates
|
| 50 |
+
|
| 51 |
+
- Keep templates simple and avoid complex logic
|
| 52 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 53 |
+
- Use the async pipe to handle observables
|
| 54 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 55 |
+
|
| 56 |
+
## Services
|
| 57 |
+
|
| 58 |
+
- Design services around a single responsibility
|
| 59 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 60 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/.editorconfig
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Editor configuration, see https://editorconfig.org
|
| 2 |
+
root = true
|
| 3 |
+
|
| 4 |
+
[*]
|
| 5 |
+
charset = utf-8
|
| 6 |
+
indent_style = space
|
| 7 |
+
indent_size = 2
|
| 8 |
+
insert_final_newline = true
|
| 9 |
+
trim_trailing_whitespace = true
|
| 10 |
+
|
| 11 |
+
[*.ts]
|
| 12 |
+
quote_type = single
|
| 13 |
+
ij_typescript_use_double_quotes = false
|
| 14 |
+
|
| 15 |
+
[*.md]
|
| 16 |
+
max_line_length = off
|
| 17 |
+
trim_trailing_whitespace = false
|
frontend/.gemini/GEMINI.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/.github/copilot-instructions.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# Compiled output
|
| 4 |
+
/dist
|
| 5 |
+
/tmp
|
| 6 |
+
/out-tsc
|
| 7 |
+
/bazel-out
|
| 8 |
+
|
| 9 |
+
# Node
|
| 10 |
+
/node_modules
|
| 11 |
+
npm-debug.log
|
| 12 |
+
yarn-error.log
|
| 13 |
+
|
| 14 |
+
# IDEs and editors
|
| 15 |
+
.idea/
|
| 16 |
+
.project
|
| 17 |
+
.classpath
|
| 18 |
+
.c9/
|
| 19 |
+
*.launch
|
| 20 |
+
.settings/
|
| 21 |
+
*.sublime-workspace
|
| 22 |
+
|
| 23 |
+
# Visual Studio Code
|
| 24 |
+
.vscode/*
|
| 25 |
+
!.vscode/settings.json
|
| 26 |
+
!.vscode/tasks.json
|
| 27 |
+
!.vscode/launch.json
|
| 28 |
+
!.vscode/extensions.json
|
| 29 |
+
!.vscode/mcp.json
|
| 30 |
+
.history/*
|
| 31 |
+
|
| 32 |
+
# Miscellaneous
|
| 33 |
+
/.angular/cache
|
| 34 |
+
.sass-cache/
|
| 35 |
+
/connect.lock
|
| 36 |
+
/coverage
|
| 37 |
+
/libpeerconnection.log
|
| 38 |
+
testem.log
|
| 39 |
+
/typings
|
| 40 |
+
__screenshots__/
|
| 41 |
+
|
| 42 |
+
# System files
|
| 43 |
+
.DS_Store
|
| 44 |
+
Thumbs.db
|
frontend/.junie/guidelines.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/.prettierrc
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"printWidth": 100,
|
| 3 |
+
"singleQuote": true,
|
| 4 |
+
"overrides": [
|
| 5 |
+
{
|
| 6 |
+
"files": "*.html",
|
| 7 |
+
"options": {
|
| 8 |
+
"parser": "angular"
|
| 9 |
+
}
|
| 10 |
+
}
|
| 11 |
+
]
|
| 12 |
+
}
|
frontend/.vscode/extensions.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
| 3 |
+
"recommendations": ["angular.ng-template"]
|
| 4 |
+
}
|
frontend/.vscode/launch.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
| 3 |
+
"version": "0.2.0",
|
| 4 |
+
"configurations": [
|
| 5 |
+
{
|
| 6 |
+
"name": "ng serve",
|
| 7 |
+
"type": "chrome",
|
| 8 |
+
"request": "launch",
|
| 9 |
+
"preLaunchTask": "npm: start",
|
| 10 |
+
"url": "http://localhost:4200/"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"name": "ng test",
|
| 14 |
+
"type": "chrome",
|
| 15 |
+
"request": "launch",
|
| 16 |
+
"preLaunchTask": "npm: test",
|
| 17 |
+
"url": "http://localhost:9876/debug.html"
|
| 18 |
+
}
|
| 19 |
+
]
|
| 20 |
+
}
|
frontend/.vscode/mcp.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// For more information, visit: https://angular.dev/ai/mcp
|
| 3 |
+
"servers": {
|
| 4 |
+
"angular-cli": {
|
| 5 |
+
"command": "npx",
|
| 6 |
+
"args": ["-y", "@angular/cli", "mcp"]
|
| 7 |
+
}
|
| 8 |
+
}
|
| 9 |
+
}
|
frontend/.vscode/tasks.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
| 3 |
+
"version": "2.0.0",
|
| 4 |
+
"tasks": [
|
| 5 |
+
{
|
| 6 |
+
"type": "npm",
|
| 7 |
+
"script": "start",
|
| 8 |
+
"isBackground": true,
|
| 9 |
+
"problemMatcher": {
|
| 10 |
+
"owner": "typescript",
|
| 11 |
+
"pattern": "$tsc",
|
| 12 |
+
"background": {
|
| 13 |
+
"activeOnStart": true,
|
| 14 |
+
"beginsPattern": {
|
| 15 |
+
"regexp": "Changes detected"
|
| 16 |
+
},
|
| 17 |
+
"endsPattern": {
|
| 18 |
+
"regexp": "bundle generation (complete|failed)"
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"type": "npm",
|
| 25 |
+
"script": "test",
|
| 26 |
+
"isBackground": true,
|
| 27 |
+
"problemMatcher": {
|
| 28 |
+
"owner": "typescript",
|
| 29 |
+
"pattern": "$tsc",
|
| 30 |
+
"background": {
|
| 31 |
+
"activeOnStart": true,
|
| 32 |
+
"beginsPattern": {
|
| 33 |
+
"regexp": "Changes detected"
|
| 34 |
+
},
|
| 35 |
+
"endsPattern": {
|
| 36 |
+
"regexp": "bundle generation (complete|failed)"
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
]
|
| 42 |
+
}
|
frontend/.windsurf/rules/guidelines.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/AGENTS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
| 3 |
+
|
| 4 |
+
## TypeScript Best Practices
|
| 5 |
+
|
| 6 |
+
- Use strict type checking
|
| 7 |
+
- Prefer type inference when the type is obvious
|
| 8 |
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
| 9 |
+
|
| 10 |
+
## Angular Best Practices
|
| 11 |
+
|
| 12 |
+
- Always use standalone components over NgModules
|
| 13 |
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
| 14 |
+
- Use signals for state management
|
| 15 |
+
- Implement lazy loading for feature routes
|
| 16 |
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
| 17 |
+
- Use `NgOptimizedImage` for all static images.
|
| 18 |
+
- `NgOptimizedImage` does not work for inline base64 images.
|
| 19 |
+
|
| 20 |
+
## Accessibility Requirements
|
| 21 |
+
|
| 22 |
+
- It MUST pass all AXE checks.
|
| 23 |
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
| 24 |
+
|
| 25 |
+
### Components
|
| 26 |
+
|
| 27 |
+
- Keep components small and focused on a single responsibility
|
| 28 |
+
- Use `input()` and `output()` functions instead of decorators
|
| 29 |
+
- Use `computed()` for derived state
|
| 30 |
+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
| 31 |
+
- Prefer inline templates for small components
|
| 32 |
+
- Prefer Reactive forms instead of Template-driven ones
|
| 33 |
+
- Do NOT use `ngClass`, use `class` bindings instead
|
| 34 |
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
| 35 |
+
- When using external templates/styles, use paths relative to the component TS file.
|
| 36 |
+
|
| 37 |
+
## State Management
|
| 38 |
+
|
| 39 |
+
- Use signals for local component state
|
| 40 |
+
- Use `computed()` for derived state
|
| 41 |
+
- Keep state transformations pure and predictable
|
| 42 |
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
| 43 |
+
|
| 44 |
+
## Templates
|
| 45 |
+
|
| 46 |
+
- Keep templates simple and avoid complex logic
|
| 47 |
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
| 48 |
+
- Use the async pipe to handle observables
|
| 49 |
+
- Do not assume globals like (`new Date()`) are available.
|
| 50 |
+
|
| 51 |
+
## Services
|
| 52 |
+
|
| 53 |
+
- Design services around a single responsibility
|
| 54 |
+
- Use the `providedIn: 'root'` option for singleton services
|
| 55 |
+
- Use the `inject()` function instead of constructor injection
|
frontend/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend
|
| 2 |
+
|
| 3 |
+
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.3.
|
| 4 |
+
|
| 5 |
+
## Development server
|
| 6 |
+
|
| 7 |
+
To start a local development server, run:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
ng serve
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
| 14 |
+
|
| 15 |
+
## Code scaffolding
|
| 16 |
+
|
| 17 |
+
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
ng generate component component-name
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
ng generate --help
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
## Building
|
| 30 |
+
|
| 31 |
+
To build the project run:
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
ng build
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
| 38 |
+
|
| 39 |
+
## Running unit tests
|
| 40 |
+
|
| 41 |
+
To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
ng test
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
## Running end-to-end tests
|
| 48 |
+
|
| 49 |
+
For end-to-end (e2e) testing, run:
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
ng e2e
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
| 56 |
+
|
| 57 |
+
## Additional Resources
|
| 58 |
+
|
| 59 |
+
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
frontend/angular.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
| 3 |
+
"version": 1,
|
| 4 |
+
"cli": {
|
| 5 |
+
"packageManager": "npm"
|
| 6 |
+
},
|
| 7 |
+
"newProjectRoot": "projects",
|
| 8 |
+
"projects": {
|
| 9 |
+
"frontend": {
|
| 10 |
+
"projectType": "application",
|
| 11 |
+
"schematics": {
|
| 12 |
+
"@schematics/angular:component": {
|
| 13 |
+
"style": "scss",
|
| 14 |
+
"skipTests": true
|
| 15 |
+
},
|
| 16 |
+
"@schematics/angular:class": {
|
| 17 |
+
"skipTests": true
|
| 18 |
+
},
|
| 19 |
+
"@schematics/angular:directive": {
|
| 20 |
+
"skipTests": true
|
| 21 |
+
},
|
| 22 |
+
"@schematics/angular:guard": {
|
| 23 |
+
"skipTests": true
|
| 24 |
+
},
|
| 25 |
+
"@schematics/angular:interceptor": {
|
| 26 |
+
"skipTests": true
|
| 27 |
+
},
|
| 28 |
+
"@schematics/angular:pipe": {
|
| 29 |
+
"skipTests": true
|
| 30 |
+
},
|
| 31 |
+
"@schematics/angular:resolver": {
|
| 32 |
+
"skipTests": true
|
| 33 |
+
},
|
| 34 |
+
"@schematics/angular:service": {
|
| 35 |
+
"skipTests": true
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"root": "",
|
| 39 |
+
"sourceRoot": "src",
|
| 40 |
+
"prefix": "app",
|
| 41 |
+
"architect": {
|
| 42 |
+
"build": {
|
| 43 |
+
"builder": "@angular/build:application",
|
| 44 |
+
"options": {
|
| 45 |
+
"browser": "src/main.ts",
|
| 46 |
+
"tsConfig": "tsconfig.app.json",
|
| 47 |
+
"inlineStyleLanguage": "scss",
|
| 48 |
+
"assets": [
|
| 49 |
+
{
|
| 50 |
+
"glob": "**/*",
|
| 51 |
+
"input": "public"
|
| 52 |
+
}
|
| 53 |
+
],
|
| 54 |
+
"styles": [
|
| 55 |
+
"src/styles.scss"
|
| 56 |
+
]
|
| 57 |
+
},
|
| 58 |
+
"configurations": {
|
| 59 |
+
"production": {
|
| 60 |
+
"budgets": [
|
| 61 |
+
{
|
| 62 |
+
"type": "initial",
|
| 63 |
+
"maximumWarning": "500kB",
|
| 64 |
+
"maximumError": "1MB"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"type": "anyComponentStyle",
|
| 68 |
+
"maximumWarning": "4kB",
|
| 69 |
+
"maximumError": "8kB"
|
| 70 |
+
}
|
| 71 |
+
],
|
| 72 |
+
"outputHashing": "all"
|
| 73 |
+
},
|
| 74 |
+
"development": {
|
| 75 |
+
"optimization": false,
|
| 76 |
+
"extractLicenses": false,
|
| 77 |
+
"sourceMap": true
|
| 78 |
+
}
|
| 79 |
+
},
|
| 80 |
+
"defaultConfiguration": "production"
|
| 81 |
+
},
|
| 82 |
+
"serve": {
|
| 83 |
+
"builder": "@angular/build:dev-server",
|
| 84 |
+
"configurations": {
|
| 85 |
+
"production": {
|
| 86 |
+
"buildTarget": "frontend:build:production"
|
| 87 |
+
},
|
| 88 |
+
"development": {
|
| 89 |
+
"buildTarget": "frontend:build:development"
|
| 90 |
+
}
|
| 91 |
+
},
|
| 92 |
+
"defaultConfiguration": "development"
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"scripts": {
|
| 5 |
+
"ng": "ng",
|
| 6 |
+
"start": "ng serve",
|
| 7 |
+
"build": "ng build",
|
| 8 |
+
"watch": "ng build --watch --configuration development",
|
| 9 |
+
"test": "ng test"
|
| 10 |
+
},
|
| 11 |
+
"private": true,
|
| 12 |
+
"packageManager": "npm@10.9.4",
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"@angular/cdk": "^21.2.3",
|
| 15 |
+
"@angular/common": "^21.2.0",
|
| 16 |
+
"@angular/compiler": "^21.2.0",
|
| 17 |
+
"@angular/core": "^21.2.0",
|
| 18 |
+
"@angular/forms": "^21.2.0",
|
| 19 |
+
"@angular/material": "^21.2.3",
|
| 20 |
+
"@angular/platform-browser": "^21.2.0",
|
| 21 |
+
"@angular/router": "^21.2.0",
|
| 22 |
+
"rxjs": "~7.8.0",
|
| 23 |
+
"tslib": "^2.3.0"
|
| 24 |
+
},
|
| 25 |
+
"devDependencies": {
|
| 26 |
+
"@angular/build": "^21.2.3",
|
| 27 |
+
"@angular/cli": "^21.2.3",
|
| 28 |
+
"@angular/compiler-cli": "^21.2.0",
|
| 29 |
+
"prettier": "^3.8.1",
|
| 30 |
+
"typescript": "~5.9.2"
|
| 31 |
+
}
|
| 32 |
+
}
|
frontend/public/favicon.ico
ADDED
|
|
frontend/src/app/app.config.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
| 2 |
+
|
| 3 |
+
export const appConfig: ApplicationConfig = {
|
| 4 |
+
providers: [
|
| 5 |
+
provideBrowserGlobalErrorListeners(),
|
| 6 |
+
|
| 7 |
+
]
|
| 8 |
+
};
|
frontend/src/app/app.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<app-dashboard />
|
frontend/src/app/app.scss
ADDED
|
File without changes
|
frontend/src/app/app.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component } from '@angular/core';
|
| 2 |
+
import { Dashboard } from './dashboard/dashboard';
|
| 3 |
+
|
| 4 |
+
@Component({
|
| 5 |
+
selector: 'app-root',
|
| 6 |
+
imports: [Dashboard],
|
| 7 |
+
templateUrl: './app.html',
|
| 8 |
+
styleUrl: './app.scss',
|
| 9 |
+
})
|
| 10 |
+
export class App {}
|
frontend/src/app/core/mocks/alerts.mock.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface AlertData {
|
| 2 |
+
id: number;
|
| 3 |
+
timestamp: string;
|
| 4 |
+
severity: 'critical' | 'warning' | 'info';
|
| 5 |
+
message: string;
|
| 6 |
+
source: string;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
export const ALERT_MOCKS: AlertData[] = [
|
| 10 |
+
{
|
| 11 |
+
id: 1,
|
| 12 |
+
timestamp: '2026-03-24T10:32:00Z',
|
| 13 |
+
severity: 'critical',
|
| 14 |
+
message: 'Node B-12 unresponsive for 5 minutes',
|
| 15 |
+
source: 'Network Monitor',
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
id: 2,
|
| 19 |
+
timestamp: '2026-03-24T10:28:00Z',
|
| 20 |
+
severity: 'warning',
|
| 21 |
+
message: 'CPU usage above 85% on Cluster A',
|
| 22 |
+
source: 'Resource Monitor',
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
id: 3,
|
| 26 |
+
timestamp: '2026-03-24T10:15:00Z',
|
| 27 |
+
severity: 'info',
|
| 28 |
+
message: 'AI detected anomalous traffic pattern in Zone 3',
|
| 29 |
+
source: 'AI Insights',
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
id: 4,
|
| 33 |
+
timestamp: '2026-03-24T09:58:00Z',
|
| 34 |
+
severity: 'warning',
|
| 35 |
+
message: 'SSL certificate expires in 7 days for api.example.com',
|
| 36 |
+
source: 'Cert Monitor',
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
id: 5,
|
| 40 |
+
timestamp: '2026-03-24T09:45:00Z',
|
| 41 |
+
severity: 'info',
|
| 42 |
+
message: 'Auto-scaling triggered: +2 instances in Region US-East',
|
| 43 |
+
source: 'AI Insights',
|
| 44 |
+
},
|
| 45 |
+
];
|
frontend/src/app/core/mocks/kpi.mock.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface KpiData {
|
| 2 |
+
title: string;
|
| 3 |
+
value: string;
|
| 4 |
+
unit?: string;
|
| 5 |
+
trend: 'up' | 'down' | 'stable';
|
| 6 |
+
trendLabel: string;
|
| 7 |
+
icon: string;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export const KPI_MOCKS: KpiData[] = [
|
| 11 |
+
{
|
| 12 |
+
title: 'Network Health',
|
| 13 |
+
value: '98.2',
|
| 14 |
+
unit: '%',
|
| 15 |
+
trend: 'up',
|
| 16 |
+
trendLabel: '+1.5% from last hour',
|
| 17 |
+
icon: 'hub',
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
title: 'Critical Incidents',
|
| 21 |
+
value: '3',
|
| 22 |
+
trend: 'down',
|
| 23 |
+
trendLabel: '-2 from yesterday',
|
| 24 |
+
icon: 'error_outline',
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
title: 'Threat Severity',
|
| 28 |
+
value: 'Low',
|
| 29 |
+
trend: 'stable',
|
| 30 |
+
trendLabel: 'No change',
|
| 31 |
+
icon: 'shield',
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
title: 'Site Availability',
|
| 35 |
+
value: '99.97',
|
| 36 |
+
unit: '%',
|
| 37 |
+
trend: 'up',
|
| 38 |
+
trendLabel: '+0.03% this week',
|
| 39 |
+
icon: 'cloud_done',
|
| 40 |
+
},
|
| 41 |
+
];
|
frontend/src/app/dashboard/alert-list/alert-list.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<mat-card class="alert-card" appearance="outlined">
|
| 2 |
+
<mat-card-content>
|
| 3 |
+
<h3 class="alert-title">Recent Alerts & AI Insights</h3>
|
| 4 |
+
<ul class="alert-list">
|
| 5 |
+
@for (alert of alerts(); track alert.id) {
|
| 6 |
+
<li class="alert-item">
|
| 7 |
+
<mat-icon [class]="'severity-icon severity-' + alert.severity">
|
| 8 |
+
{{ severityIcon(alert.severity) }}
|
| 9 |
+
</mat-icon>
|
| 10 |
+
<div class="alert-body">
|
| 11 |
+
<span class="alert-message">{{ alert.message }}</span>
|
| 12 |
+
<span class="alert-meta">
|
| 13 |
+
{{ alert.source }} · {{ alert.timestamp | date: 'HH:mm' }}
|
| 14 |
+
</span>
|
| 15 |
+
</div>
|
| 16 |
+
<span [class]="'severity-badge severity-' + alert.severity">
|
| 17 |
+
{{ alert.severity }}
|
| 18 |
+
</span>
|
| 19 |
+
</li>
|
| 20 |
+
}
|
| 21 |
+
</ul>
|
| 22 |
+
</mat-card-content>
|
| 23 |
+
</mat-card>
|
frontend/src/app/dashboard/alert-list/alert-list.scss
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.alert-card {
|
| 6 |
+
border-radius: var(--pf-radius-md);
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
.alert-title {
|
| 10 |
+
font: var(--mat-sys-title-medium);
|
| 11 |
+
color: var(--mat-sys-on-surface);
|
| 12 |
+
margin: 0 0 var(--pf-spacing-md);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.alert-list {
|
| 16 |
+
list-style: none;
|
| 17 |
+
margin: 0;
|
| 18 |
+
padding: 0;
|
| 19 |
+
display: flex;
|
| 20 |
+
flex-direction: column;
|
| 21 |
+
gap: var(--pf-spacing-sm);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.alert-item {
|
| 25 |
+
display: flex;
|
| 26 |
+
align-items: flex-start;
|
| 27 |
+
gap: var(--pf-spacing-md);
|
| 28 |
+
padding: var(--pf-spacing-md);
|
| 29 |
+
border-radius: var(--pf-radius-sm);
|
| 30 |
+
background: var(--mat-sys-surface-container-low);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.severity-icon {
|
| 34 |
+
font-size: 20px;
|
| 35 |
+
width: 20px;
|
| 36 |
+
height: 20px;
|
| 37 |
+
flex-shrink: 0;
|
| 38 |
+
margin-top: 2px;
|
| 39 |
+
|
| 40 |
+
&.severity-critical {
|
| 41 |
+
color: var(--pf-severity-critical);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
&.severity-warning {
|
| 45 |
+
color: var(--pf-severity-warning);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
&.severity-info {
|
| 49 |
+
color: var(--pf-severity-info);
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.alert-body {
|
| 54 |
+
flex: 1;
|
| 55 |
+
display: flex;
|
| 56 |
+
flex-direction: column;
|
| 57 |
+
gap: var(--pf-spacing-xs);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.alert-message {
|
| 61 |
+
font: var(--mat-sys-body-medium);
|
| 62 |
+
color: var(--mat-sys-on-surface);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.alert-meta {
|
| 66 |
+
font: var(--mat-sys-label-small);
|
| 67 |
+
color: var(--mat-sys-on-surface-variant);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.severity-badge {
|
| 71 |
+
flex-shrink: 0;
|
| 72 |
+
font: var(--mat-sys-label-small);
|
| 73 |
+
padding: var(--pf-spacing-xs) var(--pf-spacing-sm);
|
| 74 |
+
border-radius: var(--pf-radius-sm);
|
| 75 |
+
text-transform: uppercase;
|
| 76 |
+
letter-spacing: 0.5px;
|
| 77 |
+
|
| 78 |
+
&.severity-critical {
|
| 79 |
+
background: rgba(211, 47, 47, 0.1);
|
| 80 |
+
color: var(--pf-severity-critical);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
&.severity-warning {
|
| 84 |
+
background: rgba(237, 108, 2, 0.1);
|
| 85 |
+
color: var(--pf-severity-warning);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
&.severity-info {
|
| 89 |
+
background: rgba(2, 136, 209, 0.1);
|
| 90 |
+
color: var(--pf-severity-info);
|
| 91 |
+
}
|
| 92 |
+
}
|
frontend/src/app/dashboard/alert-list/alert-list.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, input } from '@angular/core';
|
| 2 |
+
import { DatePipe } from '@angular/common';
|
| 3 |
+
import { MatCard, MatCardContent } from '@angular/material/card';
|
| 4 |
+
import { MatIcon } from '@angular/material/icon';
|
| 5 |
+
import { AlertData } from '../../core/mocks/alerts.mock';
|
| 6 |
+
|
| 7 |
+
@Component({
|
| 8 |
+
selector: 'app-alert-list',
|
| 9 |
+
imports: [DatePipe, MatCard, MatCardContent, MatIcon],
|
| 10 |
+
templateUrl: './alert-list.html',
|
| 11 |
+
styleUrl: './alert-list.scss',
|
| 12 |
+
})
|
| 13 |
+
export class AlertList {
|
| 14 |
+
alerts = input.required<AlertData[]>();
|
| 15 |
+
|
| 16 |
+
protected severityIcon(severity: AlertData['severity']): string {
|
| 17 |
+
const icons: Record<string, string> = {
|
| 18 |
+
critical: 'error',
|
| 19 |
+
warning: 'warning',
|
| 20 |
+
info: 'info',
|
| 21 |
+
};
|
| 22 |
+
return icons[severity];
|
| 23 |
+
}
|
| 24 |
+
}
|
frontend/src/app/dashboard/chart-placeholder/chart-placeholder.html
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<mat-card class="chart-card" appearance="outlined">
|
| 2 |
+
<mat-card-content>
|
| 3 |
+
<h3 class="chart-title">{{ title() }}</h3>
|
| 4 |
+
<div class="chart-area">
|
| 5 |
+
<mat-icon class="chart-icon">bar_chart</mat-icon>
|
| 6 |
+
<span class="chart-label">Chart library not yet installed</span>
|
| 7 |
+
</div>
|
| 8 |
+
</mat-card-content>
|
| 9 |
+
</mat-card>
|
frontend/src/app/dashboard/chart-placeholder/chart-placeholder.scss
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.chart-card {
|
| 6 |
+
height: 100%;
|
| 7 |
+
border-radius: var(--pf-radius-md);
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.chart-title {
|
| 11 |
+
font: var(--mat-sys-title-medium);
|
| 12 |
+
color: var(--mat-sys-on-surface);
|
| 13 |
+
margin: 0 0 var(--pf-spacing-md);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.chart-area {
|
| 17 |
+
display: flex;
|
| 18 |
+
flex-direction: column;
|
| 19 |
+
align-items: center;
|
| 20 |
+
justify-content: center;
|
| 21 |
+
min-height: 200px;
|
| 22 |
+
border: 2px dashed var(--mat-sys-outline-variant);
|
| 23 |
+
border-radius: var(--pf-radius-sm);
|
| 24 |
+
gap: var(--pf-spacing-sm);
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.chart-icon {
|
| 28 |
+
font-size: 48px;
|
| 29 |
+
width: 48px;
|
| 30 |
+
height: 48px;
|
| 31 |
+
color: var(--mat-sys-outline);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.chart-label {
|
| 35 |
+
font: var(--mat-sys-body-medium);
|
| 36 |
+
color: var(--mat-sys-on-surface-variant);
|
| 37 |
+
}
|
frontend/src/app/dashboard/chart-placeholder/chart-placeholder.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, input } from '@angular/core';
|
| 2 |
+
import { MatCard, MatCardContent } from '@angular/material/card';
|
| 3 |
+
import { MatIcon } from '@angular/material/icon';
|
| 4 |
+
|
| 5 |
+
@Component({
|
| 6 |
+
selector: 'app-chart-placeholder',
|
| 7 |
+
imports: [MatCard, MatCardContent, MatIcon],
|
| 8 |
+
templateUrl: './chart-placeholder.html',
|
| 9 |
+
styleUrl: './chart-placeholder.scss',
|
| 10 |
+
})
|
| 11 |
+
export class ChartPlaceholder {
|
| 12 |
+
title = input.required<string>();
|
| 13 |
+
}
|
frontend/src/app/dashboard/dashboard.html
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<section class="dashboard">
|
| 2 |
+
<header class="dashboard-header">
|
| 3 |
+
<h1 class="dashboard-title">PulseFabric Monitor</h1>
|
| 4 |
+
<p class="dashboard-subtitle">Real-time infrastructure monitoring dashboard</p>
|
| 5 |
+
</header>
|
| 6 |
+
|
| 7 |
+
<div class="kpi-grid">
|
| 8 |
+
@for (kpi of kpis; track kpi.title) {
|
| 9 |
+
<app-kpi-card
|
| 10 |
+
[title]="kpi.title"
|
| 11 |
+
[value]="kpi.value"
|
| 12 |
+
[unit]="kpi.unit ?? ''"
|
| 13 |
+
[trend]="kpi.trend"
|
| 14 |
+
[trendLabel]="kpi.trendLabel"
|
| 15 |
+
[icon]="kpi.icon"
|
| 16 |
+
/>
|
| 17 |
+
}
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<div class="chart-grid">
|
| 21 |
+
<app-chart-placeholder title="Network Traffic (24h)" />
|
| 22 |
+
<app-chart-placeholder title="Incident Trend (7d)" />
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<app-alert-list [alerts]="alerts" />
|
| 26 |
+
|
| 27 |
+
<div id="ai-dropzone" class="ai-dropzone">
|
| 28 |
+
<span class="dropzone-label">AI Widget Drop Zone</span>
|
| 29 |
+
</div>
|
| 30 |
+
</section>
|
frontend/src/app/dashboard/dashboard.scss
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.dashboard {
|
| 6 |
+
max-width: var(--pf-content-max-width);
|
| 7 |
+
margin: 0 auto;
|
| 8 |
+
padding: var(--pf-spacing-xl);
|
| 9 |
+
display: flex;
|
| 10 |
+
flex-direction: column;
|
| 11 |
+
gap: var(--pf-spacing-xl);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.dashboard-header {
|
| 15 |
+
padding: var(--pf-spacing-lg) 0;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.dashboard-title {
|
| 19 |
+
font: var(--mat-sys-headline-large);
|
| 20 |
+
color: var(--mat-sys-on-surface);
|
| 21 |
+
margin: 0;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.dashboard-subtitle {
|
| 25 |
+
font: var(--mat-sys-body-large);
|
| 26 |
+
color: var(--mat-sys-on-surface-variant);
|
| 27 |
+
margin: var(--pf-spacing-xs) 0 0;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.kpi-grid {
|
| 31 |
+
display: grid;
|
| 32 |
+
grid-template-columns: repeat(4, 1fr);
|
| 33 |
+
gap: var(--pf-spacing-lg);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.chart-grid {
|
| 37 |
+
display: grid;
|
| 38 |
+
grid-template-columns: repeat(2, 1fr);
|
| 39 |
+
gap: var(--pf-spacing-lg);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.ai-dropzone {
|
| 43 |
+
min-height: 120px;
|
| 44 |
+
border: 2px dashed var(--mat-sys-outline-variant);
|
| 45 |
+
border-radius: var(--pf-radius-md);
|
| 46 |
+
display: flex;
|
| 47 |
+
align-items: center;
|
| 48 |
+
justify-content: center;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.dropzone-label {
|
| 52 |
+
font: var(--mat-sys-body-medium);
|
| 53 |
+
color: var(--mat-sys-outline);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
@media (max-width: 1024px) {
|
| 57 |
+
.kpi-grid {
|
| 58 |
+
grid-template-columns: repeat(2, 1fr);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
@media (max-width: 600px) {
|
| 63 |
+
.kpi-grid,
|
| 64 |
+
.chart-grid {
|
| 65 |
+
grid-template-columns: 1fr;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.dashboard {
|
| 69 |
+
padding: var(--pf-spacing-md);
|
| 70 |
+
}
|
| 71 |
+
}
|
frontend/src/app/dashboard/dashboard.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component } from '@angular/core';
|
| 2 |
+
import { KpiCard } from './kpi-card/kpi-card';
|
| 3 |
+
import { ChartPlaceholder } from './chart-placeholder/chart-placeholder';
|
| 4 |
+
import { AlertList } from './alert-list/alert-list';
|
| 5 |
+
import { KPI_MOCKS } from '../core/mocks/kpi.mock';
|
| 6 |
+
import { ALERT_MOCKS } from '../core/mocks/alerts.mock';
|
| 7 |
+
|
| 8 |
+
@Component({
|
| 9 |
+
selector: 'app-dashboard',
|
| 10 |
+
imports: [KpiCard, ChartPlaceholder, AlertList],
|
| 11 |
+
templateUrl: './dashboard.html',
|
| 12 |
+
styleUrl: './dashboard.scss',
|
| 13 |
+
})
|
| 14 |
+
export class Dashboard {
|
| 15 |
+
protected readonly kpis = KPI_MOCKS;
|
| 16 |
+
protected readonly alerts = ALERT_MOCKS;
|
| 17 |
+
}
|
frontend/src/app/dashboard/kpi-card/kpi-card.html
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<mat-card class="kpi-card" appearance="outlined">
|
| 2 |
+
<mat-card-content>
|
| 3 |
+
<div class="kpi-header">
|
| 4 |
+
<mat-icon class="kpi-icon">{{ icon() }}</mat-icon>
|
| 5 |
+
<span class="kpi-title">{{ title() }}</span>
|
| 6 |
+
</div>
|
| 7 |
+
<div class="kpi-value">
|
| 8 |
+
{{ value() }}
|
| 9 |
+
@if (unit()) {
|
| 10 |
+
<span class="kpi-unit">{{ unit() }}</span>
|
| 11 |
+
}
|
| 12 |
+
</div>
|
| 13 |
+
<div class="kpi-trend" [class]="'kpi-trend trend-' + trend()">
|
| 14 |
+
<mat-icon class="trend-icon">{{ trendIcon() }}</mat-icon>
|
| 15 |
+
<span>{{ trendLabel() }}</span>
|
| 16 |
+
</div>
|
| 17 |
+
</mat-card-content>
|
| 18 |
+
</mat-card>
|
frontend/src/app/dashboard/kpi-card/kpi-card.scss
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.kpi-card {
|
| 6 |
+
height: 100%;
|
| 7 |
+
border-radius: var(--pf-radius-md);
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.kpi-header {
|
| 11 |
+
display: flex;
|
| 12 |
+
align-items: center;
|
| 13 |
+
gap: var(--pf-spacing-sm);
|
| 14 |
+
margin-bottom: var(--pf-spacing-md);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.kpi-icon {
|
| 18 |
+
color: var(--mat-sys-primary);
|
| 19 |
+
font-size: 20px;
|
| 20 |
+
width: 20px;
|
| 21 |
+
height: 20px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.kpi-title {
|
| 25 |
+
font: var(--mat-sys-title-small);
|
| 26 |
+
color: var(--mat-sys-on-surface-variant);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.kpi-value {
|
| 30 |
+
font: var(--mat-sys-display-small);
|
| 31 |
+
color: var(--mat-sys-on-surface);
|
| 32 |
+
margin-bottom: var(--pf-spacing-sm);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.kpi-unit {
|
| 36 |
+
font: var(--mat-sys-title-medium);
|
| 37 |
+
color: var(--mat-sys-on-surface-variant);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.kpi-trend {
|
| 41 |
+
display: flex;
|
| 42 |
+
align-items: center;
|
| 43 |
+
gap: var(--pf-spacing-xs);
|
| 44 |
+
font: var(--mat-sys-label-medium);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.trend-icon {
|
| 48 |
+
font-size: 16px;
|
| 49 |
+
width: 16px;
|
| 50 |
+
height: 16px;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.trend-up {
|
| 54 |
+
color: #2e7d32;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.trend-down {
|
| 58 |
+
color: var(--pf-severity-critical);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.trend-stable {
|
| 62 |
+
color: var(--mat-sys-on-surface-variant);
|
| 63 |
+
}
|
frontend/src/app/dashboard/kpi-card/kpi-card.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, computed, input } from '@angular/core';
|
| 2 |
+
import { MatCard, MatCardContent } from '@angular/material/card';
|
| 3 |
+
import { MatIcon } from '@angular/material/icon';
|
| 4 |
+
|
| 5 |
+
@Component({
|
| 6 |
+
selector: 'app-kpi-card',
|
| 7 |
+
imports: [MatCard, MatCardContent, MatIcon],
|
| 8 |
+
templateUrl: './kpi-card.html',
|
| 9 |
+
styleUrl: './kpi-card.scss',
|
| 10 |
+
})
|
| 11 |
+
export class KpiCard {
|
| 12 |
+
title = input.required<string>();
|
| 13 |
+
value = input.required<string>();
|
| 14 |
+
unit = input<string>();
|
| 15 |
+
trend = input.required<'up' | 'down' | 'stable'>();
|
| 16 |
+
trendLabel = input.required<string>();
|
| 17 |
+
icon = input.required<string>();
|
| 18 |
+
|
| 19 |
+
protected trendIcon = computed(() => {
|
| 20 |
+
const icons: Record<string, string> = {
|
| 21 |
+
up: 'trending_up',
|
| 22 |
+
down: 'trending_down',
|
| 23 |
+
stable: 'trending_flat',
|
| 24 |
+
};
|
| 25 |
+
return icons[this.trend()];
|
| 26 |
+
});
|
| 27 |
+
}
|
frontend/src/app/shared/styles/_tokens.scss
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
// Spacing
|
| 3 |
+
--pf-spacing-xs: 4px;
|
| 4 |
+
--pf-spacing-sm: 8px;
|
| 5 |
+
--pf-spacing-md: 16px;
|
| 6 |
+
--pf-spacing-lg: 24px;
|
| 7 |
+
--pf-spacing-xl: 32px;
|
| 8 |
+
--pf-spacing-2xl: 48px;
|
| 9 |
+
|
| 10 |
+
// Border radius
|
| 11 |
+
--pf-radius-sm: 8px;
|
| 12 |
+
--pf-radius-md: 12px;
|
| 13 |
+
--pf-radius-lg: 16px;
|
| 14 |
+
|
| 15 |
+
// Shadows
|
| 16 |
+
--pf-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
| 17 |
+
--pf-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 18 |
+
|
| 19 |
+
// Layout
|
| 20 |
+
--pf-content-max-width: 1400px;
|
| 21 |
+
--pf-header-height: 64px;
|
| 22 |
+
|
| 23 |
+
// Severity colors
|
| 24 |
+
--pf-severity-critical: #d32f2f;
|
| 25 |
+
--pf-severity-warning: #ed6c02;
|
| 26 |
+
--pf-severity-info: #0288d1;
|
| 27 |
+
}
|
frontend/src/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<title>PulseFabric Monitor</title>
|
| 6 |
+
<base href="/" />
|
| 7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 8 |
+
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
| 9 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 10 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 11 |
+
<link
|
| 12 |
+
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
|
| 13 |
+
rel="stylesheet"
|
| 14 |
+
/>
|
| 15 |
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
| 16 |
+
</head>
|
| 17 |
+
<body>
|
| 18 |
+
<app-root></app-root>
|
| 19 |
+
</body>
|
| 20 |
+
</html>
|
frontend/src/main.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { bootstrapApplication } from '@angular/platform-browser';
|
| 2 |
+
import { appConfig } from './app/app.config';
|
| 3 |
+
import { App } from './app/app';
|
| 4 |
+
|
| 5 |
+
bootstrapApplication(App, appConfig)
|
| 6 |
+
.catch((err) => console.error(err));
|
frontend/src/styles.scss
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Include theming for Angular Material with `mat.theme()`.
|
| 2 |
+
// This Sass mixin will define CSS variables that are used for styling Angular Material
|
| 3 |
+
// components according to the Material 3 design spec.
|
| 4 |
+
// Learn more about theming and how to use it for your application's
|
| 5 |
+
// custom components at https://material.angular.dev/guide/theming
|
| 6 |
+
@use '@angular/material' as mat;
|
| 7 |
+
@use 'app/shared/styles/tokens';
|
| 8 |
+
|
| 9 |
+
html {
|
| 10 |
+
height: 100%;
|
| 11 |
+
@include mat.theme(
|
| 12 |
+
(
|
| 13 |
+
color: (
|
| 14 |
+
primary: mat.$violet-palette,
|
| 15 |
+
tertiary: mat.$blue-palette,
|
| 16 |
+
),
|
| 17 |
+
typography: Roboto,
|
| 18 |
+
density: 0,
|
| 19 |
+
)
|
| 20 |
+
);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
body {
|
| 24 |
+
// Default the application to a light color theme. This can be changed to
|
| 25 |
+
// `dark` to enable the dark color theme, or to `light dark` to defer to the
|
| 26 |
+
// user's system settings.
|
| 27 |
+
color-scheme: light;
|
| 28 |
+
|
| 29 |
+
// Set a default background, font and text colors for the application using
|
| 30 |
+
// Angular Material's system-level CSS variables. Learn more about these
|
| 31 |
+
// variables at https://material.angular.dev/guide/system-variables
|
| 32 |
+
background-color: var(--mat-sys-surface);
|
| 33 |
+
color: var(--mat-sys-on-surface);
|
| 34 |
+
font: var(--mat-sys-body-medium);
|
| 35 |
+
|
| 36 |
+
// Reset the user agent margin.
|
| 37 |
+
margin: 0;
|
| 38 |
+
height: 100%;
|
| 39 |
+
}
|
| 40 |
+
/* You can add global styles to this file, and also import other style files */
|
frontend/tsconfig.app.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
| 2 |
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
| 3 |
+
{
|
| 4 |
+
"extends": "./tsconfig.json",
|
| 5 |
+
"compilerOptions": {
|
| 6 |
+
"outDir": "./out-tsc/app",
|
| 7 |
+
"types": []
|
| 8 |
+
},
|
| 9 |
+
"include": [
|
| 10 |
+
"src/**/*.ts"
|
| 11 |
+
],
|
| 12 |
+
"exclude": [
|
| 13 |
+
"src/**/*.spec.ts"
|
| 14 |
+
]
|
| 15 |
+
}
|
frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
| 2 |
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
| 3 |
+
{
|
| 4 |
+
"compileOnSave": false,
|
| 5 |
+
"compilerOptions": {
|
| 6 |
+
"strict": true,
|
| 7 |
+
"noImplicitOverride": true,
|
| 8 |
+
"noPropertyAccessFromIndexSignature": true,
|
| 9 |
+
"noImplicitReturns": true,
|
| 10 |
+
"noFallthroughCasesInSwitch": true,
|
| 11 |
+
"skipLibCheck": true,
|
| 12 |
+
"isolatedModules": true,
|
| 13 |
+
"experimentalDecorators": true,
|
| 14 |
+
"importHelpers": true,
|
| 15 |
+
"target": "ES2022",
|
| 16 |
+
"module": "preserve"
|
| 17 |
+
},
|
| 18 |
+
"angularCompilerOptions": {
|
| 19 |
+
"enableI18nLegacyMessageIdFormat": false,
|
| 20 |
+
"strictInjectionParameters": true,
|
| 21 |
+
"strictInputAccessModifiers": true,
|
| 22 |
+
"strictTemplates": true
|
| 23 |
+
},
|
| 24 |
+
"files": [],
|
| 25 |
+
"references": [
|
| 26 |
+
{
|
| 27 |
+
"path": "./tsconfig.app.json"
|
| 28 |
+
}
|
| 29 |
+
]
|
| 30 |
+
}
|
frontend/tsconfig.spec.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
| 2 |
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
| 3 |
+
{
|
| 4 |
+
"extends": "./tsconfig.json",
|
| 5 |
+
"compilerOptions": {
|
| 6 |
+
"outDir": "./out-tsc/spec",
|
| 7 |
+
"types": [
|
| 8 |
+
"vitest/globals"
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
"include": [
|
| 12 |
+
"src/**/*.d.ts",
|
| 13 |
+
"src/**/*.spec.ts"
|
| 14 |
+
]
|
| 15 |
+
}
|