committed by
khalil-bot
parent
ad911b7479
commit
781959240b
61
.github/workflows/README.md
vendored
Normal file
61
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# GitHub Actions Workflows
|
||||
|
||||
## Angular CI Pipeline
|
||||
|
||||
### Triggers
|
||||
- **Push**: Runs on all branches when changes are made to `ui/` folder
|
||||
- **Pull Request**: Runs when PR targets `main` branch
|
||||
|
||||
### Pipeline Stages
|
||||
|
||||
1. **Checkout** - Clone repository
|
||||
2. **Setup Node.js** - Install Node 20.x with npm cache
|
||||
3. **Install Dependencies** - Run `npm ci`
|
||||
4. **Type Check** - Run `npx tsc --noEmit`
|
||||
5. **Lint** - Run `npm run lint`
|
||||
6. **Format Check** - Run `npm run format:check`
|
||||
7. **Build Dev** - Build development configuration
|
||||
8. **Build Prod** - Build production configuration
|
||||
9. **Test** - Run unit tests with coverage
|
||||
10. **Upload Artifacts** - Save build outputs and coverage reports
|
||||
|
||||
|
||||
### Local Testing
|
||||
|
||||
Before pushing, run locally:
|
||||
```bash
|
||||
cd ui
|
||||
npm ci
|
||||
npx tsc --noEmit
|
||||
npm run lint
|
||||
npm run format:check
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Node version mismatch:**
|
||||
```bash
|
||||
# Use Node 20.x locally
|
||||
nvm use 20
|
||||
```
|
||||
|
||||
**Cache issues:**
|
||||
```bash
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
**Lint errors:**
|
||||
```bash
|
||||
# Auto-fix
|
||||
npm run lint -- --fix
|
||||
```
|
||||
|
||||
**Format errors:**
|
||||
```bash
|
||||
# Auto-format
|
||||
npm run format
|
||||
```
|
||||
100
.github/workflows/angular-ci.yml
vendored
Normal file
100
.github/workflows/angular-ci.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: Angular CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**' # Run on all branches
|
||||
paths:
|
||||
- 'ui/**'
|
||||
- '.github/workflows/angular-ci.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'ui/**'
|
||||
- '.github/workflows/angular-ci.yml'
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test Angular App
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./ui
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
|
||||
steps:
|
||||
# Checkout code
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Setup Node.js
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './ui/package-lock.json'
|
||||
|
||||
# Install dependencies
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Check TypeScript compilation
|
||||
- name: TypeScript compilation check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
# Run linting
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
continue-on-error: true # Don't fail build on lint warnings
|
||||
|
||||
# Check code formatting
|
||||
- name: Check Prettier formatting
|
||||
run: npm run format:check
|
||||
|
||||
# Build development
|
||||
- name: Build (Development)
|
||||
run: npm run build -- --configuration=development
|
||||
|
||||
# Build production
|
||||
- name: Build (Production)
|
||||
run: npm run build -- --configuration=production
|
||||
|
||||
# Run unit tests (when available)
|
||||
- name: Run unit tests
|
||||
run: npm run test -- --watch=false --browsers=ChromeHeadless --code-coverage
|
||||
continue-on-error: true # Don't fail yet (no tests implemented)
|
||||
|
||||
# Upload coverage (when tests exist)
|
||||
- name: Upload coverage reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: ui/coverage/
|
||||
retention-days: 7
|
||||
continue-on-error: true
|
||||
|
||||
# Upload build artifacts
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angular-build
|
||||
path: ui/dist/
|
||||
retention-days: 7
|
||||
|
||||
# Summary
|
||||
- name: Build summary
|
||||
if: success()
|
||||
run: |
|
||||
echo "Build successful!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Build Details" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Node version: ${{ matrix.node-version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Angular version: 18.x" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Build time: $(date)" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["projects/**/*"],
|
||||
"ignorePatterns": [
|
||||
"projects/**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json"],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
@@ -30,15 +30,16 @@
|
||||
"prefix": "app",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": "off"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/template/recommended"
|
||||
"plugin:@angular-eslint/template/recommended",
|
||||
"plugin:@angular-eslint/template/accessibility"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "npm",
|
||||
"analytics": false
|
||||
"analytics": false,
|
||||
"schematicCollections": [
|
||||
"@angular-eslint/schematics"
|
||||
]
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
@@ -119,6 +122,15 @@
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1614
ui/package-lock.json
generated
1614
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,10 @@
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"format": "prettier --write \"src/**/*.{ts,html,scss,json}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,html,scss,json}\""
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -28,13 +31,18 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.2",
|
||||
"@angular-eslint/builder": "21.0.1",
|
||||
"@angular-eslint/eslint-plugin": "21.0.1",
|
||||
"@angular-eslint/eslint-plugin-template": "21.0.1",
|
||||
"@angular-eslint/schematics": "21.0.1",
|
||||
"@angular-eslint/template-parser": "21.0.1",
|
||||
"@angular/cli": "^18.0.2",
|
||||
"@angular/compiler-cli": "^18.0.0",
|
||||
"@types/chart.js": "^4.0.1",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"eslint": "^10.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.11.0",
|
||||
"@typescript-eslint/parser": "7.11.0",
|
||||
"eslint": "8.57.1",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
providers: [provideRouter(routes)],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
@@ -14,16 +17,16 @@ describe('AppComponent', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'dashboard' title`, () => {
|
||||
it(`should have the 'PI_E2EEDA Dashboard' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('dashboard');
|
||||
expect(app.title).toEqual('PI_E2EEDA Dashboard');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
it('should render header', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, dashboard');
|
||||
expect(compiled.querySelector('app-header')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,9 +7,8 @@ import { HeaderComponent } from './components/header/header.component';
|
||||
standalone: true,
|
||||
imports: [RouterOutlet, HeaderComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
styleUrl: './app.component.scss',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'PI_E2EEDA Dashboard';
|
||||
|
||||
}
|
||||
|
||||
@@ -5,5 +5,9 @@ import { routes } from './app.routes';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()]
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideAnimationsAsync(),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,22 +4,24 @@ export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/dashboard',
|
||||
pathMatch: 'full'
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadComponent: () =>
|
||||
import('./components/room-map/room-map.component').then(m => m.RoomMapComponent),
|
||||
title: 'Dashboard - PI_E2EEDA'
|
||||
title: 'Dashboard - PI_E2EEDA',
|
||||
},
|
||||
{
|
||||
path: 'room/:id',
|
||||
loadComponent: () =>
|
||||
import('./components/room-details-panel/room-details-panel.component').then(m => m.RoomDetailsPanelComponent),
|
||||
title: 'Room Details - PI_E2EEDA'
|
||||
import('./components/room-details-panel/room-details-panel.component').then(
|
||||
m => m.RoomDetailsPanelComponent
|
||||
),
|
||||
title: 'Room Details - PI_E2EEDA',
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: '/dashboard'
|
||||
}
|
||||
redirectTo: '/dashboard',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,8 +8,6 @@ import { RouterModule } from '@angular/router';
|
||||
imports: [CommonModule, RouterModule],
|
||||
templateUrl: './header.component.html',
|
||||
styleUrl: './header.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class HeaderComponent {
|
||||
|
||||
}
|
||||
export class HeaderComponent {}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<div class="legend">
|
||||
<h3>Air Quality (CO2)</h3>
|
||||
<div class="legend-items">
|
||||
<div class="legend-item" *ngFor="let level of levels">
|
||||
@for (level of levels; track level.label) {
|
||||
<div class="legend-item">
|
||||
<div class="color-box" [style.background-color]="level.color"></div>
|
||||
<span class="label">{{ level.label }}</span>
|
||||
<span class="range">{{ level.range }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
interface CO2Level {
|
||||
label: string;
|
||||
@@ -10,10 +10,10 @@ interface CO2Level {
|
||||
@Component({
|
||||
selector: 'app-legend',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [CommonModule],
|
||||
templateUrl: './legend.component.html',
|
||||
styleUrl: './legend.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class LegendComponent {
|
||||
levels: CO2Level[] = [
|
||||
@@ -22,7 +22,6 @@ export class LegendComponent {
|
||||
{ label: 'Moderate', range: '1000-1200 ppm', color: '#ffc107' },
|
||||
{ label: 'Poor', range: '1200-1500 ppm', color: '#ff9800' },
|
||||
{ label: 'Very Poor', range: '1500-2000 ppm', color: '#ff5722' },
|
||||
{ label: 'Critical', range: '> 2000 ppm', color: '#f44336' }
|
||||
{ label: 'Critical', range: '> 2000 ppm', color: '#f44336' },
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
@Component({
|
||||
@@ -7,13 +7,9 @@ import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
imports: [CommonModule, RouterModule],
|
||||
templateUrl: './room-details-panel.component.html',
|
||||
styleUrl: './room-details-panel.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RoomDetailsPanelComponent {
|
||||
roomId: string | null = null;
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
this.roomId = this.route.snapshot.paramMap.get('id');
|
||||
}
|
||||
|
||||
private route = inject(ActivatedRoute);
|
||||
roomId: string | null = this.route.snapshot.paramMap.get('id');
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
imports: [CommonModule],
|
||||
templateUrl: './room-map.component.html',
|
||||
styleUrl: './room-map.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RoomMapComponent {
|
||||
|
||||
}
|
||||
export class RoomMapComponent {}
|
||||
|
||||
@@ -7,5 +7,5 @@ export const environment = {
|
||||
apiUrl: 'https://YOUR_DOMAIN/api',
|
||||
wsUrl: 'wss://YOUR_DOMAIN/ws',
|
||||
influxUrl: '',
|
||||
logLevel: 'error'
|
||||
logLevel: 'error',
|
||||
};
|
||||
|
||||
@@ -3,5 +3,5 @@ export const environment = {
|
||||
apiUrl: 'http://localhost:8080',
|
||||
wsUrl: 'ws://localhost:8080',
|
||||
influxUrl: '',
|
||||
logLevel: 'debug'
|
||||
logLevel: 'debug',
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8" />
|
||||
<title>Dashboard</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
<app-root></app-root>
|
||||
|
||||
@@ -2,5 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user