Browse Source

add dialog confirm

pull/14/head
PhamY0601 1 year ago
parent
commit
f6fb61e1e9
44 changed files with 1143 additions and 1081 deletions
  1. +13
    -15
      src/app/app-routing.module.ts
  2. +7
    -1
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html
  3. +82
    -0
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss
  4. +213
    -3
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts
  5. +159
    -0
      src/app/modules/homepage/data/fake-data.ts
  6. +6
    -3
      src/app/modules/homepage/home-page.module.ts
  7. +8
    -11
      src/app/modules/homepage/home-page.routing.ts
  8. +47
    -12
      src/app/modules/homepage/homepage/home-page.component.html
  9. +1
    -2
      src/app/modules/homepage/homepage/home-page.component.scss
  10. +9
    -3
      src/app/modules/homepage/homepage/home-page.component.ts
  11. +56
    -1
      src/app/modules/homepage/security-system-details/security-system-details.component.html
  12. +224
    -0
      src/app/modules/homepage/security-system-details/security-system-details.component.scss
  13. +85
    -2
      src/app/modules/homepage/security-system-details/security-system-details.component.ts
  14. +0
    -2
      src/app/modules/overview/camera-stream/camera-stream.component.html
  15. +0
    -5
      src/app/modules/overview/camera-stream/camera-stream.component.scss
  16. +0
    -21
      src/app/modules/overview/camera-stream/camera-stream.component.spec.ts
  17. +0
    -34
      src/app/modules/overview/camera-stream/camera-stream.component.ts
  18. +0
    -7
      src/app/modules/overview/map/map.component.html
  19. +0
    -82
      src/app/modules/overview/map/map.component.scss
  20. +0
    -353
      src/app/modules/overview/map/map.component.ts
  21. +0
    -56
      src/app/modules/overview/overall-ground/overall-ground.component.html
  22. +0
    -225
      src/app/modules/overview/overall-ground/overall-ground.component.scss
  23. +0
    -21
      src/app/modules/overview/overall-ground/overall-ground.component.spec.ts
  24. +0
    -95
      src/app/modules/overview/overall-ground/overall-ground.component.ts
  25. +0
    -28
      src/app/modules/overview/overview.module.ts
  26. +0
    -21
      src/app/modules/overview/overview.routing.ts
  27. +0
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.html
  28. +0
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.scss
  29. +0
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts
  30. +0
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.ts
  31. +29
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.html
  32. +4
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.scss
  33. +21
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts
  34. +58
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.ts
  35. +0
    -1
      src/app/shared/component/layout/layout.component.html
  36. +14
    -21
      src/app/shared/component/shared-component.module.ts
  37. +2
    -25
      src/app/shared/component/slider-range/slider-range.component.html
  38. +2
    -1
      src/app/shared/component/slider-range/slider-range.component.scss
  39. +48
    -10
      src/app/shared/component/slider-range/slider-range.component.ts
  40. +13
    -9
      src/app/shared/services/alarm-sound.service.ts
  41. +27
    -0
      src/app/shared/services/confirm-dialog.service.ts
  42. +1
    -3
      src/app/shared/services/mqtt-client.service.ts
  43. +14
    -8
      src/app/shared/shared-material.module.ts
  44. BIN
      src/assets/sound/alarm_5m.mp3

+ 13
- 15
src/app/app-routing.module.ts View File

@@ -1,28 +1,26 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {LayoutComponent} from "./shared/component/layout/layout.component";
import { LayoutComponent } from './shared/component/layout/layout.component';

const routes: Routes = [
{ path: '',
redirectTo: '/homepage',
pathMatch: 'full'
},
{ path: '',
{ path: '', redirectTo: '/homepage', pathMatch: 'full' },
{
path: '',
component: LayoutComponent,
children: [
{ path: 'homepage',
loadChildren: () => import('./modules/homepage/home-page.module').then(m => m.HomePageModule)
},
{ path: 'overview',
loadChildren: () => import('./modules/overview/overview.module').then(m => m.OverviewModule)
{
path: 'homepage',
loadChildren: () =>
import('./modules/homepage/home-page.module').then(
(m) => m.HomePageModule,
),
},
]
],
},

];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

+ 7
- 1
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html View File

@@ -1 +1,7 @@
<h2 style="text-align: center; padding: 3rem">Centralized Security Management Dashboard</h2>
<div class="map-container">
<h2>Security Monitoring and Control</h2>
<div class="map-frame">
<div id="map"></div>
</div>
</div>


+ 82
- 0
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss View File

@@ -0,0 +1,82 @@
.map-container {
position: absolute;
left: 0;
right: 0;
width: 100%;
height: calc(100% - 7rem);
h2 {
padding: .5rem 3rem 0 ;
text-align: center;
}
}

.map-frame {
height: 100%;
}

#map {
height: 100%;
}
p {
margin: 0 0 0 16px !important;
}
.box-custom {
color: #F33152;
padding: 3px 2px;
background-color: rbg(243,49,82,0.1);
}

::ng-deep.sensor-on {
width: 30px;
height: 30px;
position: relative;
background: linear-gradient(#ff0000, #C70039);
display: flex !important;
justify-content: center;
align-items: center;
border-radius: 50%;
img{
z-index: 9;
}
&:before, &:after {
position: absolute;
content: '';
width: 100%;
height: 100%;
background: #ff0000;
border-radius: 50%;
z-index: 1;
}

&:before {
animation: sensor-on 2s ease-out infinite;
}

&:after {
animation: sensor-on 2s 1s ease-out infinite;
}
}
.sensor-off{
display: inline-block;
}
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
::ng-deep .tooltip {
display: flex;
flex-direction: column;
gap: 5px;
.dynamic-button{
cursor: pointer;
}
}

::ng-deep.icon {
width: 200px;
display: flex;
flex-direction: column;
align-items: center;
}

+ 213
- 3
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts View File

@@ -1,10 +1,220 @@
import { Component } from '@angular/core';
import {
AfterViewInit,
Component,
OnDestroy,
OnInit,
Renderer2,
} from '@angular/core';
import * as L from 'leaflet';
import 'leaflet.markercluster';
import { Subscription, take } from 'rxjs';
import { SocketService } from '../../../shared/services/socket.service';
import { MatDialog } from '@angular/material/dialog';
import { AlarmSoundService } from '../../../shared/services/alarm-sound.service';
import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component';
import { alarmData, alarmDemo } from '../data/fake-data';
import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service';

@Component({
selector: 'app-centralized-security-management',
templateUrl: './centralized-security-management.component.html',
styleUrls: ['./centralized-security-management.component.scss']
styleUrls: ['./centralized-security-management.component.scss'],
})
export class CentralizedSecurityManagementComponent {
export class CentralizedSecurityManagementComponent
implements OnInit, AfterViewInit, OnDestroy
{
private map!: L.Map;
private markers!: L.MarkerClusterGroup;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
data = alarmData;
alarmDemo = alarmDemo;
state1 = false;
state2 = false;
state5 = false;
state6 = false;
isReady = true;

constructor(
private socketService$: SocketService,
private dialog: MatDialog,
private renderer: Renderer2,
private alarmSoundService$: AlarmSoundService,
private confirmDialogService$: ConfirmDialogService,
) {}

ngOnInit() {
this.statusSubscription = this.socketService$.status$.subscribe(
(isConnected) => {
if (isConnected) {
this.socketService$.sendMessage({ id: '0', type: 'get' });
this.messageSubscription = this.socketService$.messages$.subscribe(
(message) => {
this.onMessage(message);
},
);
}
},
);
}

openDialog(): void {
this.dialog.open(CameraDialogComponent, {
width: '80vw',
data: '',
});
}

ngAfterViewInit(): void {
this.initMap();
}

ngOnDestroy(): void {
this.statusSubscription?.unsubscribe();
this.messageSubscription?.unsubscribe();
this.socketService$.close();
this.alarmSoundService$.stopAlarm();
}

initMap(): void {
const mapContainer = document.getElementById('map');
if (mapContainer) {
this.map = L.map('map', {
center: [10.7483, 106.7537],
zoom: 12,
});

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 15,
minZoom: 3,
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(this.map);

this.addIconsToMap();
} else {
console.error('Map container not found');
}
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state5 = message.state5 === '1'; // 1 ON, 0 OFF
this.isReady = message.ready === '1';
// this.alarmSoundService$.startAlarm(true, true, true, this.state2);

this.updateIcons();
}
}

addIconsToMap(): void {
this.markers = L.markerClusterGroup();
this.data.forEach((item) => this.addMarker(item));
this.addMarker(this.alarmDemo, true);
this.map.addLayer(this.markers);
}

addMarker(item: any, isDemo: boolean = false): void {
const icon = isDemo
? this.getIcon(this.state5, this.isReady, this.state1, this.state2)
: this.createIcon(item.warning);
const marker = L.marker(
[item.detail.coordinates.lat, item.detail.coordinates.lng],
{ icon },
).bindPopup(this.popupDetail(item));

marker.on('popupopen', (event) => this.bindPopupEvents(event));

this.markers.addLayer(marker);
}

bindPopupEvents(event: any): void {
const popupContainer = event.popup.getElement();
const button = popupContainer?.querySelector('.dynamic-button');
if (button) {
this.renderer.listen(button, 'click', () => this.openDialog());
}
}

updateIcons(): void {
this.markers.clearLayers();
this.addIconsToMap();
}

popupDetail(item: any): string {
return `
<div class="tooltip" style="width: 200px">
<div style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 5px">
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; max-width: 180px;">${item.title}</div>
<a class="dynamic-button">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> ${item.detail.position}</div>
<div><strong>Tọa độ:</strong> ${item.detail.coordinates.lat}, ${item.detail.coordinates.lng}</div>
<div><strong>Thời gian:</strong> ${item.detail.time}</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>`;
}

createIcon(
active: boolean,
className: string = '',
text: any = [],
): L.Icon<L.IconOptions> | L.DivIcon {
if (text.length < 1) {
return L.icon({
iconUrl: active
? '../../../../assets/images/sensor-on.png'
: '../../../../assets/images/sensor-off.png',
iconSize: [30, 30],
className: className,
});
} else {
let htmlContent = '';
text.forEach((item: any) => {
htmlContent += `<span><b>${item}</b></span><br>`;
});

return L.divIcon({
html: `<div class="icon">
<div style="z-index:9999" class="sensor-on">
<img alt="icon-alarm" src="assets/images/sensor-on.png" style="width: 30px; height: 30px">
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px; margin-top: 10px; text-align: center">
${htmlContent}
</div>
</div>`,
iconSize: [200, 30],
className: className,
});
}
}

getIcon(
isTurnOn: boolean,
isReady: boolean,
fireArm: boolean,
fenceArm: boolean,
): L.Icon<L.IconOptions> | L.DivIcon {
if (isTurnOn && isReady) {
let text = [];
if (fireArm && fenceArm) {
text.push('FIRE ALARM', 'FENCE ALARM');
} else if (fireArm) {
text.push('FIRE ALARM');
} else if (fenceArm) {
text.push('FENCE ALARM');
}
return this.createIcon(true, '', text);
}
return this.createIcon(false);
}
}

+ 159
- 0
src/app/modules/homepage/data/fake-data.ts View File

@@ -0,0 +1,159 @@
export const alarmData = [
{
title: ' Alarm System 1',
detail: {
position: 'Hanoi',
coordinates: {
lat: 21.0285,
lng: 105.8542
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 2',
detail: {
position: 'Hai Phong',
coordinates: {
lat: 20.8449,
lng: 106.6881
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 3',
detail: {
position: 'Ha Long',
coordinates: {
lat: 20.9460,
lng: 107.0740
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 4',
detail: {
position: 'Vinh',
coordinates: {
lat: 18.6796,
lng: 105.6813
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 5',
detail: {
position: 'Dong Hoi',
coordinates: {
lat: 17.4834,
lng: 106.6000
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 6',
detail: {
position: 'Hue',
coordinates: {
lat: 16.4637,
lng: 107.5909
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 7',
detail: {
position: 'Da Nang',
coordinates: {
lat: 16.0471,
lng: 108.2068
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 8',
detail: {
position: 'Quy Nhon',
coordinates: {
lat: 13.7820,
lng: 109.2198
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 9',
detail: {
position: 'Nha Trang',
coordinates: {
lat: 12.2388,
lng: 109.1967
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 10',
detail: {
position: 'Da Lat',
coordinates: {
lat: 11.9416,
lng: 108.4580
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 11',
detail: {
position: 'Ho Chi Minh City',
coordinates: {
lat: 10.8231,
lng: 106.6297
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 12',
detail: {
position: 'Can Tho',
coordinates: {
lat: 10.0452,
lng: 105.7469
},
time: '06-07-2024'
},
warning: true
},

];

export const alarmDemo = {
title: ' Demo Alarm System', // Thêm mới warning
detail: {
position: 'Vinhomes Quận 9',
coordinates: {
lat: 10.7483,
lng: 106.8016
},
time: '06-07-2024'
},
}

+ 6
- 3
src/app/modules/homepage/home-page.module.ts View File

@@ -8,18 +8,21 @@ import {homePageRoutes} from "./home-page.routing";
import {SharedModule} from "../../shared/shared.module";
import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component';
import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component';
import {FormsModule} from "@angular/forms";


@NgModule({
declarations: [
HomePageComponent,
CentralizedSecurityManagementComponent,
SecuritySystemDetailsComponent
SecuritySystemDetailsComponent,
],
imports: [
CommonModule,
RouterModule.forChild(homePageRoutes),
SharedMaterialModule,
SharedModule,]
SharedModule,
FormsModule,
],
})
export class HomePageModule { }
export class HomePageModule {}

+ 8
- 11
src/app/modules/homepage/home-page.routing.ts View File

@@ -1,22 +1,19 @@
import {Routes} from "@angular/router";
import {HomePageComponent} from "./homepage/home-page.component";
import {
CentralizedSecurityManagementComponent
} from "./centralized-security-management/centralized-security-management.component";

import { Routes } from '@angular/router';
import { HomePageComponent } from './homepage/home-page.component';
import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component';
import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component';

export const homePageRoutes: Routes = [
{
path: '',
component: HomePageComponent,
},
},
{
path: 'centralized-security-management',
component: CentralizedSecurityManagementComponent
component: CentralizedSecurityManagementComponent,
},
{
path: 'security-system-details',
component: CentralizedSecurityManagementComponent
}
component: SecuritySystemDetailsComponent,
},
];


+ 47
- 12
src/app/modules/homepage/homepage/home-page.component.html View File

@@ -1,19 +1,54 @@
<div fxLayout="row" fxLayoutGap="30px" style="padding: 3rem 3rem 0">
<button mat-stroked-button routerLink="./centralized-security-management">Centralized Security Management</button>
<button mat-stroked-button routerLink="./security-system-details">Security System Details </button>
</div>
<div fxLayout="row" fxLayoutGap="30px" style="padding: 2rem 3rem 0">
<button mat-stroked-button routerLink="./centralized-security-management">
Centralized Security Management
</button>
<button mat-stroked-button routerLink="./security-system-details">
Security System Details
</button>
</div>

<div class="sound-group">
<h2>WHISTLE TIME: {{timeEffect1}}s</h2>

<mat-card class="sound-group">
<mat-card-header>
<mat-card-title>WHISTLE TIME</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="volume-group">
<app-slider-range [value]="timeEffect1" (valueChange)="timeEffect1 = $event" ></app-slider-range>
<app-slider-range
[value]="whistle.time"
[icon]="'access_alarm'"
(valueChange)="whistle.time = $event"
></app-slider-range>
</div>
</div>
<div class="sound-group">
<h2>24-HOUR ZONE ALARM TIME: {{timeEffect2}}s</h2>
<div class="volume-group">
<app-slider-range [value]="timeEffect2" (valueChange)="timeEffect2 = $event" ></app-slider-range>
<app-slider-range
[value]="alarm.sound"
[(ngModel)]="alarm.sound"
(valueChange)="alarm.sound = $event"
[icon]="alarm.sound == 0 ? 'volume_off' : 'volume_up'"
></app-slider-range>
</div>
</div>
</mat-card-content>
</mat-card>


<mat-card class="sound-group">
<mat-card-header>
<mat-card-title>24-HOUR ZONE ALARM TIME</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="volume-group">
<app-slider-range
[value]="whistle.time"
[icon]="'access_alarm'"
></app-slider-range>
</div>
<div class="volume-group">
<app-slider-range
[value]="whistle.sound"
[(ngModel)]="whistle.sound"
[icon]="whistle.sound == 0 ? 'volume_off' : 'volume_up'"
></app-slider-range>
</div>
</mat-card-content>
</mat-card>

+ 1
- 2
src/app/modules/homepage/homepage/home-page.component.scss View File

@@ -1,6 +1,5 @@
.sound-group{
padding: 3rem;
width: 70%
margin: 2rem 3rem;
}
button{
color: #ff7723 !important;

+ 9
- 3
src/app/modules/homepage/homepage/home-page.component.ts View File

@@ -3,9 +3,15 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-homepage',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.scss']
styleUrls: ['./home-page.component.scss'],
})
export class HomePageComponent {
timeEffect1: any = 10;
timeEffect2: any = 30;
whistle = {
time: 10,
sound: 0,
};
alarm = {
time: 30,
sound: 0,
};
}

+ 56
- 1
src/app/modules/homepage/security-system-details/security-system-details.component.html View File

@@ -1 +1,56 @@
<h2 style="text-align: center; padding: 3rem">Security System Details Dashboard</h2>
<div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px">
<div fxFlex="50" class="map-image">
<div class="card-state">
<img src="assets/images/ground.png">
<div class="state t2" id="State2">
<div>
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state1 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && isReady) }">FIRE ALARM</div>
</div>

<div class="state t3" id="State3">
<div>
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state2 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && isReady) }">FENCE ALARM</div>
</div>
<div class="state t4" id="State4">
<div>
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
<div class="state t5 tooltip" id="State5" >
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div class="state t6 tooltip" id="State6">
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
</div>
<div fxFlex="30" fxLayout="column" fxLayoutGap="50px">
<button [disabled]="!isConnected" mat-flat-button color="{{switchArm ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ switchArm ? 'DISARM' : 'ARM'}}</button>

</div>
</div>

+ 224
- 0
src/app/modules/homepage/security-system-details/security-system-details.component.scss View File

@@ -0,0 +1,224 @@
button {
padding: 30px 50px;
}
.red-bg {
background-color: red !important;
color: white !important;
}

.green-bg {
background-color: green !important;
color: white !important;
}

h1 {
font-size: 1.8rem;
color: white;
}

.topnav {
overflow: hidden;
background-color: #0a1128;
}

body {
margin: 0;
}
p {
text-align: center;
}
.content {
padding: 50px;
}

.card-grid {
max-width: 800px;
margin: 0 auto;
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

.card {
background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, 0.5);
}

.card-title {
font-size: 1.2rem;
font-weight: bold;
color: #034078;
}

.reading {
font-size: 1.2rem;
color: #1282a2;
}

.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

/*.button:hover {background-color: #0f8b8d}*/
.button:active {
background-color: #0f8b8d;
box-shadow: 2px 2px #cdcdcd;
transform: translateY(2px);
}

.state {
font-size: 1.5rem;
color: #8c8c8c;
font-weight: bold;
text-align: center;
}

.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}

.map-image {
img {
height: 100%;
width: 100%;
}
.card-state {
width: 100%;
height: 100%;
position: relative;
}
.state {
position: absolute;
color: red;
&.t1 {
top: 5%;
left: 10%;
}
&.t2 {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.t3 {
top: 5%;
left: 50%;
transform: translate(-50%);
}
&.t4 {
top: 47%;
right: 12%;
}
&.t5 {
top: 88%;
left: 50%;
transform: translate(-50%);
}
&.t6 {
top: 47%;
left: 5%;
width: 100px;
.alarm-text-off {
width: 100px !important;
}
}
}

.sensor-on {
width: 30px;
height: 30px;
position: absolute;
background: linear-gradient(#ff0000, #c70039);
display: flex !important;
justify-content: center;
align-items: center;
border-radius: 50%;
img {
z-index: 9;
}
&:before,
&:after {
position: absolute;
content: "";
width: 100%;
height: 100%;
background: #ff0000;
border-radius: 50%;
z-index: 1;
}

&:before {
animation: sensor-on 2s ease-out infinite;
}

&:after {
animation: sensor-on 2s 1s ease-out infinite;
}
}
.sensor-off {
display: inline-block;
}
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
}
.alarm-text {
font-size: 10px;
padding: 2px 4px;
width: 100px;
margin-top: 10px;
margin-left: -22px;
border-radius: 2px;
&-off {
background: #bfe9f4;
color: #004aad;
}
&-on {
background: #f11e1e;
color: #fff;
}
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
text-align: start;
font-size: 10px;
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #eee;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: -50%;
}

.tooltip:hover .tooltiptext {
visibility: visible;
cursor: pointer;
}
a {
color: blue;
}

+ 85
- 2
src/app/modules/homepage/security-system-details/security-system-details.component.ts View File

@@ -1,10 +1,93 @@
import { Component } from '@angular/core';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subscription} from "rxjs";
import {SocketService} from "../../../shared/services/socket.service";
import {ToastrService} from "ngx-toastr";

@Component({
selector: 'app-security-system-details',
templateUrl: './security-system-details.component.html',
styleUrls: ['./security-system-details.component.scss']
})
export class SecuritySystemDetailsComponent {
export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
isConnected = false;
state1 = false;
state2 = false;
state3 = '';
state4 = '';
state5 = false;
state6 = false;
switchArm = false;
switchWarning = false;
isReady = true;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
private intervalId: any;


constructor(
private socketService$: SocketService,
private toastr: ToastrService
) {
}

ngOnInit() {
// this.socketService$.connect();
this.statusSubscription = this.socketService$.status$.subscribe(isConnected => {
this.isConnected = isConnected;
this.getReadings();
if (this.isConnected) {
this.intervalId = setInterval(() => this.getReadings(), 5000);
this.messageSubscription = this.socketService$.messages$.subscribe(message => {
this.onMessage(message);
});
}
});
}

ngOnDestroy(): void {
if (this.statusSubscription) {
this.statusSubscription.unsubscribe();
}
if (this.messageSubscription) {
this.messageSubscription.unsubscribe();
}
if (this.intervalId) {
clearInterval(this.intervalId);
}
this.socketService$.close();
}

getReadings() {
let str = {id: '0', type: 'get'};
this.socketService$.sendMessage(str);
}

toggleState1() {
this.switchArm = !this.switchArm;
let str = {id: '0', type: 'cmd', state1: this.switchArm.toString()};
this.socketService$.sendMessage(str);
}

getImageSource(): string {
return (this.state5 && this.state6) || this.state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png';
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state3 = message.state3 === '1' ? 'ON' : 'OFF';
this.state4 = message.state4 === '1' ? 'ON' : 'OFF';
this.state5 = message.state5 === '1'; // alarm 9h && 6h // 1 ON, 0 OFF
this.state6 = message.state6 === '1'; // ? 'ON' : 'OFF';

this.switchArm = message.state5 === '1';// alarm 9h && 6h // 1 ON, 0 OFF
this.switchWarning = message.state6 === '1';// alarm 9h && 6h // 1 ON, 0 OFF

this.isReady = message.ready === '1';
if (message.ready === '0' && this.state5) { // not ready and ON arm
this.toastr.warning('System not ready', 'Warning', {timeOut: 5000});
}
}
}
}

+ 0
- 2
src/app/modules/overview/camera-stream/camera-stream.component.html View File

@@ -1,2 +0,0 @@
<h3 style="text-align: center">Camera Stream</h3>
<canvas class="video" #videoPlayer></canvas>

+ 0
- 5
src/app/modules/overview/camera-stream/camera-stream.component.scss View File

@@ -1,5 +0,0 @@
.video {
width: 78% !important;
display: block;
margin: 0 auto
}

+ 0
- 21
src/app/modules/overview/camera-stream/camera-stream.component.spec.ts View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CameraStreamComponent } from './camera-stream.component';

describe('CameraStreamComponent', () => {
let component: CameraStreamComponent;
let fixture: ComponentFixture<CameraStreamComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CameraStreamComponent]
});
fixture = TestBed.createComponent(CameraStreamComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 0
- 34
src/app/modules/overview/camera-stream/camera-stream.component.ts View File

@@ -1,34 +0,0 @@
import {Component, OnInit, ViewChild, ElementRef, AfterViewInit, Renderer2} from '@angular/core';
import {loadPlayer, Player} from "rtsp-relay/browser";

@Component({
selector: 'app-camera-stream',
templateUrl: './camera-stream.component.html',
styleUrls: ['./camera-stream.component.scss']
})
export class CameraStreamComponent implements OnInit, AfterViewInit{
player?: Player;

@ViewChild('videoPlayer')
videoPlayer?: ElementRef<HTMLCanvasElement>;

constructor() {}

ngOnInit() {
}

async ngAfterViewInit() {
const connect = async () => {
this.player = await loadPlayer({
url: 'ws://localhost:8080/stream',
canvas: this.videoPlayer!.nativeElement,
onDisconnect: () => {
setTimeout(connect, 5000); // reconnect after 5 seconds
},
});
};
connect();
}


}

+ 0
- 7
src/app/modules/overview/map/map.component.html View File

@@ -1,7 +0,0 @@
<div class="map-container">
<h2>Security Monitoring and Control</h2>
<div class="map-frame">
<div id="map"></div>
</div>
</div>


+ 0
- 82
src/app/modules/overview/map/map.component.scss View File

@@ -1,82 +0,0 @@
.map-container {
position: absolute;
left: 0;
right: 0;
width: 100%;
height: calc(100% - 7rem);
h2 {
padding: .5rem 3rem 0 ;
text-align: center;
}
}

.map-frame {
height: 100%;
}

#map {
height: 100%;
}
p {
margin: 0 0 0 16px !important;
}
.box-custom {
color: #F33152;
padding: 3px 2px;
background-color: rbg(243,49,82,0.1);
}

::ng-deep.sensor-on {
width: 30px;
height: 30px;
position: relative;
background: linear-gradient(#ff0000, #C70039);
display: flex !important;
justify-content: center;
align-items: center;
border-radius: 50%;
img{
z-index: 9;
}
&:before, &:after {
position: absolute;
content: '';
width: 100%;
height: 100%;
background: #ff0000;
border-radius: 50%;
z-index: 1;
}

&:before {
animation: sensor-on 2s ease-out infinite;
}

&:after {
animation: sensor-on 2s 1s ease-out infinite;
}
}
.sensor-off{
display: inline-block;
}
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
::ng-deep .tooltip {
display: flex;
flex-direction: column;
gap: 5px;
.dynamic-button{
cursor: pointer;
}
}

::ng-deep.icon {
width: 200px;
display: flex;
flex-direction: column;
align-items: center;
}

+ 0
- 353
src/app/modules/overview/map/map.component.ts View File

@@ -1,353 +0,0 @@
import {
AfterViewInit,
Component,
OnDestroy,
OnInit, Renderer2
} from '@angular/core';
import * as L from 'leaflet';
import 'leaflet.markercluster';
import { Subscription } from 'rxjs';
import { SocketService } from '../../../shared/services/socket.service';
import {MatDialog} from "@angular/material/dialog";
import {CameraDialogComponent} from "../camera-dialog/camera-dialog.component";
import {AlarmSoundService} from "../../../shared/services/alarm-sound.service";


const alarmData = [
{
title: ' Alarm System 1',
detail: {
position: 'Hanoi',
coordinates: {
lat: 21.0285,
lng: 105.8542
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 2',
detail: {
position: 'Hai Phong',
coordinates: {
lat: 20.8449,
lng: 106.6881
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 3',
detail: {
position: 'Ha Long',
coordinates: {
lat: 20.9460,
lng: 107.0740
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 4',
detail: {
position: 'Vinh',
coordinates: {
lat: 18.6796,
lng: 105.6813
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 5',
detail: {
position: 'Dong Hoi',
coordinates: {
lat: 17.4834,
lng: 106.6000
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 6',
detail: {
position: 'Hue',
coordinates: {
lat: 16.4637,
lng: 107.5909
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 7',
detail: {
position: 'Da Nang',
coordinates: {
lat: 16.0471,
lng: 108.2068
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 8',
detail: {
position: 'Quy Nhon',
coordinates: {
lat: 13.7820,
lng: 109.2198
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 9',
detail: {
position: 'Nha Trang',
coordinates: {
lat: 12.2388,
lng: 109.1967
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 10',
detail: {
position: 'Da Lat',
coordinates: {
lat: 11.9416,
lng: 108.4580
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 11',
detail: {
position: 'Ho Chi Minh City',
coordinates: {
lat: 10.8231,
lng: 106.6297
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 12',
detail: {
position: 'Can Tho',
coordinates: {
lat: 10.0452,
lng: 105.7469
},
time: '06-07-2024'
},
warning: true
},

];

const alarmDemo = {
title: ' Demo Alarm System', // Thêm mới warning
detail: {
position: 'Vinhomes Quận 9',
coordinates: {
lat: 10.7483,
lng: 106.8016
},
time: '06-07-2024'
},
}

@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit, AfterViewInit, OnDestroy {
private map!: L.Map;
private markers!: L.MarkerClusterGroup;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
data = alarmData;
alarmDemo = alarmDemo;
state1 = false;
state2 = false;
state5 = false;
state6 = false;
isReady = true;

constructor(private socketService$: SocketService,
private dialog: MatDialog,
private renderer: Renderer2,
private alarmSoundService$: AlarmSoundService) { }

ngOnInit() {
this.statusSubscription = this.socketService$.status$.subscribe(isConnected => {
if (isConnected) {
this.socketService$.sendMessage({ id: '0', type: 'get' });
this.messageSubscription = this.socketService$.messages$.subscribe(message => {
this.onMessage(message);
});
}
});
}

openDialog(): void {
this.dialog.open(CameraDialogComponent, {
width: '80vw',
data: ''
});
}

ngAfterViewInit(): void {
this.initMap();
}

ngOnDestroy(): void {
this.statusSubscription?.unsubscribe();
this.messageSubscription?.unsubscribe();
this.socketService$.close();
this.alarmSoundService$.stopAlarm()
}

initMap(): void {
const mapContainer = document.getElementById('map');
if (mapContainer) {
this.map = L.map('map', {
center: [10.7483, 106.7537],
zoom: 12
});

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 15,
minZoom: 3,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(this.map);

this.addIconsToMap();
} else {
console.error('Map container not found');
}
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state5 = message.state5 === '1'; // 1 ON, 0 OFF
this.isReady = message.ready === '1';
// this.alarmSoundService$.startAlarm(true, true, true, this.state2);
this.updateIcons();
}
}

addIconsToMap(): void {
this.markers = L.markerClusterGroup();
this.data.forEach(item => this.addMarker(item));
this.addMarker(this.alarmDemo, true);
this.map.addLayer(this.markers);
}

addMarker(item: any, isDemo: boolean = false): void {
const icon = isDemo ? this.getIcon(this.state5, this.isReady, this.state1, this.state2) : this.createIcon(item.warning);
const marker = L.marker([item.detail.coordinates.lat, item.detail.coordinates.lng], { icon })
.bindPopup(this.popupDetail(item));

marker.on('popupopen', (event) => this.bindPopupEvents(event));

this.markers.addLayer(marker);
}

bindPopupEvents(event: any): void {
const popupContainer = event.popup.getElement();
const button = popupContainer?.querySelector('.dynamic-button');
if (button) {
this.renderer.listen(button, 'click', () => this.openDialog());
}
}

updateIcons(): void {
this.markers.clearLayers();
this.addIconsToMap();
}

popupDetail(item: any): string {
return `
<div class="tooltip" style="width: 200px">
<div style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 5px">
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; max-width: 180px;">${item.title}</div>
<a class="dynamic-button">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> ${item.detail.position}</div>
<div><strong>Tọa độ:</strong> ${item.detail.coordinates.lat}, ${item.detail.coordinates.lng}</div>
<div><strong>Thời gian:</strong> ${item.detail.time}</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>`;
}

createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon {
if (text.length < 1) {
return L.icon({
iconUrl: active ? '../../../../assets/images/sensor-on.png' : '../../../../assets/images/sensor-off.png',
iconSize: [30, 30],
className: className
});
} else {
let htmlContent = '';
text.forEach((item: any) => {
htmlContent += `<span><b>${item}</b></span><br>`;
});

return L.divIcon({
html: `<div class="icon">
<div style="z-index:9999" class="sensor-on">
<img alt="icon-alarm" src="assets/images/sensor-on.png" style="width: 30px; height: 30px">
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px; margin-top: 10px; text-align: center">
${htmlContent}
</div>
</div>`,
iconSize: [200, 30],
className: className
});
}
}

getIcon(isTurnOn: boolean, isReady: boolean, fireArm: boolean, fenceArm: boolean): L.Icon<L.IconOptions> | L.DivIcon {
if (isTurnOn && isReady) {
let text = [];
if (fireArm && fenceArm) {
text.push('FIRE ALARM', 'FENCE ALARM');
} else if (fireArm) {
text.push('FIRE ALARM');
} else if (fenceArm) {
text.push('FENCE ALARM');
}
return this.createIcon(true, '', text);
}
return this.createIcon(false);
}

}

+ 0
- 56
src/app/modules/overview/overall-ground/overall-ground.component.html View File

@@ -1,56 +0,0 @@
<div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px">
<div fxFlex="50" class="map-image">
<div class="card-state">
<img src="assets/images/ground.png">
<div class="state t2" id="State2">
<div>
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state1 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && isReady) }">FIRE ALARM</div>
</div>

<div class="state t3" id="State3">
<div>
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state2 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && isReady) }">FENCE ALARM</div>
</div>
<div class="state t4" id="State4">
<div>
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
<div class="state t5 tooltip" id="State5" >
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div class="state t6 tooltip" id="State6">
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
</div>
<div fxFlex="30" fxLayout="column" fxLayoutGap="50px">
<button [disabled]="!isConnected" mat-flat-button color="{{switchArm ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ switchArm ? 'DISARM' : 'ARM'}}</button>

</div>
</div>

+ 0
- 225
src/app/modules/overview/overall-ground/overall-ground.component.scss View File

@@ -1,225 +0,0 @@
button {
padding: 30px 50px;
}
.red-bg {
background-color: red !important;
color: white !important;
}

.green-bg {
background-color: green !important;
color: white !important;
}

h1 {
font-size: 1.8rem;
color: white;
}

.topnav {
overflow: hidden;
background-color: #0A1128;
}

body {
margin: 0;
}
p {
text-align: center;
}
.content {
padding: 50px;
}

.card-grid {
max-width: 800px;
margin: 0 auto;
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

.card {
background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
}

.card-title {
font-size: 1.2rem;
font-weight: bold;
color: #034078
}

.reading {
font-size: 1.2rem;
color: #1282A2;
}

.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

/*.button:hover {background-color: #0f8b8d}*/
.button:active {
background-color: #0f8b8d;
box-shadow: 2px 2px #CDCDCD;
transform: translateY(2px);
}

.state {
font-size: 1.5rem;
color: #8c8c8c;
font-weight: bold;
text-align: center;
}

.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}

.map-image{
img{
height: 100%;
width: 100%;
}
.card-state{
width: 100%;
height: 100%;
position: relative;
}
.state{
position: absolute;
color: red;
&.t1{
top: 5%;
left: 10%;
}
&.t2{
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.t3{
top: 5%;
left: 50%;
transform: translate(-50%)
}
&.t4{
top: 47%;
right: 12%;
}
&.t5{
top: 88%;
left: 50%;
transform: translate(-50%);

}
&.t6{
top: 47%;
left: 5%;
width: 100px;
.alarm-text-off{
width: 100px !important;
}
}
}

.sensor-on {
width: 30px;
height: 30px;
position: absolute;
background: linear-gradient(#ff0000, #C70039);
display: flex !important;
justify-content: center;
align-items: center;
border-radius: 50%;
img{
z-index: 9;
}
&:before, &:after {
position: absolute;
content: '';
width: 100%;
height: 100%;
background: #ff0000;
border-radius: 50%;
z-index: 1;
}

&:before {
animation: sensor-on 2s ease-out infinite;
}

&:after {
animation: sensor-on 2s 1s ease-out infinite;
}
}
.sensor-off{
display: inline-block;
}
@keyframes sensor-on{
100%{
transform: scale(2);
opacity: 0;
}
}
}
.alarm-text{
font-size: 10px;
padding: 2px 4px;
width: 100px;
margin-top: 10px;
margin-left: -22px;
border-radius: 2px;
&-off {
background: #bfe9f4;
color: #004aad;
}
&-on{
background: #F11E1E;
color: #FFF;
}

}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
text-align: start;
font-size: 10px;
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #eee;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: -50%;
}

.tooltip:hover .tooltiptext {
visibility: visible;
cursor: pointer;
}
a{
color: blue;
}

+ 0
- 21
src/app/modules/overview/overall-ground/overall-ground.component.spec.ts View File

@@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { OverallGroundComponent } from './overall-ground.component';

describe('OverallGroundComponent', () => {
let component: OverallGroundComponent;
let fixture: ComponentFixture<OverallGroundComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [OverallGroundComponent]
});
fixture = TestBed.createComponent(OverallGroundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 0
- 95
src/app/modules/overview/overall-ground/overall-ground.component.ts View File

@@ -1,95 +0,0 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import {SocketService} from "../../../shared/services/socket.service";
import {Subscription} from "rxjs";
import {ToastrService} from "ngx-toastr";


@Component({
selector: 'app-overall-ground',
templateUrl: './overall-ground.component.html',
styleUrls: ['./overall-ground.component.scss']
})
export class OverallGroundComponent implements OnInit, OnDestroy {
isConnected = false;
state1 = false;
state2 = false;
state3 = '';
state4 = '';
state5 = false;
state6 = false;
switchArm = false;
switchWarning = false;
isReady = true;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
private intervalId: any;


constructor(
private socketService$: SocketService,
private toastr: ToastrService
) {
}

ngOnInit() {
// this.socketService$.connect();
this.statusSubscription = this.socketService$.status$.subscribe(isConnected => {
this.isConnected = isConnected;
this.getReadings();
if (this.isConnected) {
this.intervalId = setInterval(() => this.getReadings(), 5000);
this.messageSubscription = this.socketService$.messages$.subscribe(message => {
this.onMessage(message);
});
}
});
}

ngOnDestroy(): void {
if (this.statusSubscription) {
this.statusSubscription.unsubscribe();
}
if (this.messageSubscription) {
this.messageSubscription.unsubscribe();
}
if (this.intervalId) {
clearInterval(this.intervalId);
}
this.socketService$.close();
}

getReadings() {
let str = {id: '0', type: 'get'};
this.socketService$.sendMessage(str);
}

toggleState1() {
this.switchArm = !this.switchArm;
let str = {id: '0', type: 'cmd', state1: this.switchArm.toString()};
this.socketService$.sendMessage(str);
}

getImageSource(): string {
return (this.state5 && this.state6) || this.state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png';
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state3 = message.state3 === '1' ? 'ON' : 'OFF';
this.state4 = message.state4 === '1' ? 'ON' : 'OFF';
this.state5 = message.state5 === '1'; // alarm 9h && 6h // 1 ON, 0 OFF
this.state6 = message.state6 === '1'; // ? 'ON' : 'OFF';

this.switchArm = message.state5 === '1';// alarm 9h && 6h // 1 ON, 0 OFF
this.switchWarning = message.state6 === '1';// alarm 9h && 6h // 1 ON, 0 OFF

this.isReady = message.ready === '1';
if (message.ready === '0' && this.state5){ // not ready and ON arm
this.toastr.warning('System not ready', 'Warning', {timeOut: 5000});
}
}
}

}

+ 0
- 28
src/app/modules/overview/overview.module.ts View File

@@ -1,28 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import {OverallGroundComponent} from "./overall-ground/overall-ground.component";
import {RouterModule} from "@angular/router";
import {overviewRoutes} from "./overview.routing";
import {MapComponent} from "./map/map.component";
import {SharedMaterialModule} from "../../shared/shared-material.module";
import {CameraStreamComponent} from "./camera-stream/camera-stream.component";
import {CameraDialogComponent} from "./camera-dialog/camera-dialog.component";


@NgModule({
declarations: [
OverallGroundComponent,
MapComponent,
CameraDialogComponent,
CameraStreamComponent
],
imports: [
CommonModule,
RouterModule.forChild(overviewRoutes),
SharedMaterialModule,


]
})
export class OverviewModule { }

+ 0
- 21
src/app/modules/overview/overview.routing.ts View File

@@ -1,21 +0,0 @@
import {Routes} from "@angular/router";
import {MapComponent} from "./map/map.component";
import {OverallGroundComponent} from "./overall-ground/overall-ground.component";
import {CameraStreamComponent} from "./camera-stream/camera-stream.component";

export const overviewRoutes: Routes = [
{
path: '',
component: MapComponent,
},
{
path: 'overall-ground',
component: OverallGroundComponent
},
{
path: 'camera-stream',
component: CameraStreamComponent
}

];


src/app/modules/overview/camera-dialog/camera-dialog.component.html → src/app/shared/component/camera-dialog/camera-dialog.component.html View File


src/app/modules/overview/camera-dialog/camera-dialog.component.scss → src/app/shared/component/camera-dialog/camera-dialog.component.scss View File


src/app/modules/overview/camera-dialog/camera-dialog.component.spec.ts → src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts View File


src/app/modules/overview/camera-dialog/camera-dialog.component.ts → src/app/shared/component/camera-dialog/camera-dialog.component.ts View File


+ 29
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.html View File

@@ -0,0 +1,29 @@
<h2 mat-dialog-title style="border-bottom: solid 1px #eeeeee">CONFIRM TO IGNORE THE WARNING</h2>
<mat-dialog-content>
<section class="example-section">
<span class="example-list-section">
<mat-checkbox class="example-margin"
color="primary"
[checked]="allComplete"
[indeterminate]="someComplete()"
(change)="setAll($event.checked)">
{{data.name}}
</mat-checkbox>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let item of data.gateStatus">
<mat-checkbox [(ngModel)]="item.ignore"
color="primary"
(ngModelChange)="updateAllComplete()">
{{item.name}}
</mat-checkbox>
</li>
</ul>
</span>
</section>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-button mat-dialog-close style="background: #ff7723; color: #ffffff">Yes</button>
</mat-dialog-actions>

+ 4
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.scss View File

@@ -0,0 +1,4 @@
ul {
list-style-type: none;
margin-top: 4px;
}

+ 21
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts View File

@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ConfirmDialogComponent } from './confirm-dialog.component';

describe('ConfirmDialogComponent', () => {
let component: ConfirmDialogComponent;
let fixture: ComponentFixture<ConfirmDialogComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ConfirmDialogComponent]
});
fixture = TestBed.createComponent(ConfirmDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 58
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.ts View File

@@ -0,0 +1,58 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';

@Component({
selector: 'app-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss'],
})
export class ConfirmDialogComponent {
constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>) {}
data = {
name: 'All',
ignore: false,
gateStatus: [
{
name: 'GATE A',
position: { latitude: 123.456, longitude: 456.789 },
ignore: false,
},
{
name: 'GATE B',
position: { latitude: 111.222, longitude: 333.444 },
ignore: false,
},
{
name: 'GATE C',
position: { latitude: 222.333, longitude: 444.555 },
ignore: false,
},
],
};

allComplete: boolean = false;

updateAllComplete() {
this.allComplete =
this.data.gateStatus != null &&
this.data.gateStatus.every((t) => t.ignore);
}

someComplete(): boolean {
if (this.data.gateStatus == null) {
return false;
}
return (
this.data.gateStatus.filter((t) => t.ignore).length > 0 &&
!this.allComplete
);
}

setAll(completed: boolean) {
this.allComplete = completed;
if (this.data.gateStatus == null) {
return;
}
this.data.gateStatus.forEach((t) => (t.ignore = completed));
}
}

+ 0
- 1
src/app/shared/component/layout/layout.component.html View File

@@ -3,7 +3,6 @@
<img src="../../../../../../assets/images/logo.png" >
<div>
<button mat-button routerLink="/homepage" routerLinkActive="active">Home</button>
<button mat-button routerLink="/overview" routerLinkActive="active">Overview</button>
</div>
</div>
<div>

+ 14
- 21
src/app/shared/component/shared-component.module.ts View File

@@ -1,29 +1,22 @@
import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {SharedMaterialModule} from "../shared-material.module";
import {RouterModule} from "@angular/router";
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedMaterialModule } from '../shared-material.module';
import { RouterModule } from '@angular/router';
import { LayoutComponent } from './layout/layout.component';
import { SliderRangeComponent } from './slider-range/slider-range.component';
import {FormsModule} from "@angular/forms";
import { FormsModule } from '@angular/forms';
import { CameraDialogComponent } from './camera-dialog/camera-dialog.component';
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';

@NgModule({
declarations: [
LayoutComponent,
SliderRangeComponent
],
imports: [
CommonModule,
SharedMaterialModule,
RouterModule,
FormsModule,
SliderRangeComponent,
CameraDialogComponent,
ConfirmDialogComponent,
],
exports: [
LayoutComponent,
SliderRangeComponent
],
providers: []
imports: [CommonModule, SharedMaterialModule, RouterModule, FormsModule],
exports: [LayoutComponent, SliderRangeComponent, CameraDialogComponent],
providers: [],
})
export class SharedComponentModule {
}
export class SharedComponentModule {}

+ 2
- 25
src/app/shared/component/slider-range/slider-range.component.html View File

@@ -1,31 +1,8 @@
<ng-content></ng-content>
<div class="volume-group">
<div class="speaker" *ngIf="value>0; else valueMin">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons/sound</title>
<g id="Icons/sound" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="SpeakerLow">
<path
d="M19.500001,12 C19.5006623,12.9135698 19.1672238,13.7958481 18.5625,14.480625 C18.2868064,14.7835586 17.8190602,14.8092747 17.5118188,14.5383902 C17.2045774,14.2675057 17.1714919,13.8002229 17.4375,13.48875 C18.185902,12.6387624 18.185902,11.3649876 17.4375,10.515 C17.1714919,10.2035271 17.2045774,9.73624429 17.5118188,9.46535978 C17.8190602,9.19447528 18.2868064,9.22019142 18.5625,9.523125 C19.1658428,10.2072505 19.4991441,11.0878325 19.500001,12 Z M15,3 L15,21 C14.9997852,21.2863161 14.8365765,21.5475297 14.5793423,21.6732576 C14.3221081,21.7989856 14.0157342,21.7672891 13.7896875,21.5915625 L7.2421875,16.5 L3,16.5 C2.17157288,16.5 1.5,15.8284271 1.5,15 L1.5,9 C1.5,8.17157288 2.17157288,7.5 3,7.5 L7.2421875,7.5 L13.7896875,2.4084375 C14.0157342,2.23271088 14.3221081,2.20101442 14.5793423,2.32674236 C14.8365765,2.4524703 14.9997852,2.71368392 15,3 Z M6.75,9 L3,9 L3,15 L6.75,15 L6.75,9 Z"
id="Shape" fill="currentColor" fill-rule="nonzero"></path>
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
</g>
</g>
</svg>
<div class="speaker">
<mat-icon>{{icon}}</mat-icon>
</div>
<ng-template #valueMin>
<div class="speaker">
<svg width="24px" height="24px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.1657 2.14424C12.8728 2.50021 13 3.27314 13 3.7446V20.2561C13 20.7286 12.8717 21.4998 12.1656 21.8554C11.416 22.2331 10.7175 21.8081 10.3623 21.4891L4.95001 16.6248H3.00001C1.89544 16.6248 1.00001 15.7293 1.00001 14.6248L1 9.43717C1 8.3326 1.89543 7.43717 3 7.43717H4.94661L10.3623 2.51158C10.7163 2.19354 11.4151 1.76635 12.1657 2.14424Z"
fill="currentColor"/>
<path
d="M21.8232 15.6767C21.4327 16.0673 20.7995 16.0673 20.409 15.6768L18.5 13.7678L16.591 15.6768C16.2005 16.0673 15.5673 16.0673 15.1768 15.6767L14.8233 15.3232C14.4327 14.9327 14.4327 14.2995 14.8233 13.909L16.7322 12L14.8232 10.091C14.4327 9.70044 14.4327 9.06727 14.8232 8.67675L15.1767 8.3232C15.5673 7.93267 16.2004 7.93267 16.591 8.32319L18.5 10.2322L20.409 8.32319C20.7996 7.93267 21.4327 7.93267 21.8233 8.3232L22.1768 8.67675C22.5673 9.06727 22.5673 9.70044 22.1768 10.091L20.2678 12L22.1767 13.909C22.5673 14.2995 22.5673 14.9327 22.1767 15.3232L21.8232 15.6767Z"
fill="currentColor"/>
</svg>
</div>
</ng-template>
<button class="volume-control" (click)="decreaseVolume()">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">

+ 2
- 1
src/app/shared/component/slider-range/slider-range.component.scss View File

@@ -1,5 +1,6 @@
.volume-group {
margin-top: 1.5rem;
padding: 0 2rem;
display: flex;
flex-direction: row;
gap: 1.5rem;
@@ -11,7 +12,7 @@
padding: 1rem;
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
position: relative;
color: #ff7723;
&:before {
content: "";
position: absolute;

+ 48
- 10
src/app/shared/component/slider-range/slider-range.component.ts View File

@@ -1,21 +1,55 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {
Component,
EventEmitter,
forwardRef,
Input,
Output,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
selector: 'app-slider-range',
templateUrl: './slider-range.component.html',
styleUrls: ['./slider-range.component.scss']
styleUrls: ['./slider-range.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SliderRangeComponent),
multi: true,
},
],
})
export class SliderRangeComponent {
export class SliderRangeComponent implements ControlValueAccessor {
@Input() value: number = 10;
@Input() max: number = 300;

@Input() icon: string = '';
@Output() valueChange = new EventEmitter<number>();

onChange = (value: number) => {};
onTouched = () => {};

constructor() {}

writeValue(value: number): void {
this.value = value;
}

registerOnChange(fn: any): void {
this.onChange = fn;
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

setDisabledState?(isDisabled: boolean): void {
// Handle the disabled state if needed
}

onSliderChange(event: any) {
this.value = event.target.value;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}

@@ -24,19 +58,19 @@ export class SliderRangeComponent {

// Adjust valPercent based on your conditions
switch (true) {
case (valPercent > 10 && valPercent <= 30):
case valPercent > 10 && valPercent <= 30:
valPercent -= 0.5;
break;
case (valPercent > 30 && valPercent <= 50):
case valPercent > 30 && valPercent <= 50:
valPercent -= 1;
break;
case (valPercent > 50 && valPercent <= 70):
case valPercent > 50 && valPercent <= 70:
valPercent -= 1.5;
break;
case (valPercent > 70 && valPercent <= 90):
case valPercent > 70 && valPercent <= 90:
valPercent -= 2;
break;
case (valPercent > 90 && valPercent <= 100):
case valPercent > 90 && valPercent <= 100:
valPercent -= 2.3;
break;
default:
@@ -45,13 +79,15 @@ export class SliderRangeComponent {

// Return CSS object with background style
return {
'background': `linear-gradient(to right, #ff7723 ${valPercent}%, transparent ${valPercent}%)`
background: `linear-gradient(to right, #ff7723 ${valPercent}%, transparent ${valPercent}%)`,
};
}

increaseVolume() {
if (this.value < this.max) {
this.value += 10;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}
}
@@ -59,6 +95,8 @@ export class SliderRangeComponent {
decreaseVolume() {
if (this.value > 0) {
this.value -= 10;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}
}

+ 13
- 9
src/app/shared/services/alarm-sound.service.ts View File

@@ -1,19 +1,19 @@
import {ElementRef, Injectable} from '@angular/core';
import { ElementRef, Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AlarmSoundService {
private alertInterval: any;
private alertDuration: number = 30000;
private audio= new Audio();
private audio = new Audio();
constructor() {
this.audio.src = 'assets/sound/alarm2.mp3';
this.audio.src = 'assets/sound/alarm_5m.mp3';
this.audio.load();
}

playSound(): void {
this.audio.play().catch(error => {
this.audio.play().catch((error) => {
console.error('Error playing audio:', error);
});
}
@@ -25,22 +25,26 @@ export class AlarmSoundService {
}
}

startAlarm(state5: boolean, isReady: boolean, state1: boolean, state2: boolean): void {
startAlarm(
state5: boolean,
isReady: boolean,
state1: boolean,
state2: boolean,
): void {
if (state5 && isReady && (state1 || state2)) {
this.playSound();
this.alertInterval = setInterval(() => {
this.stopAlarm()
this.stopAlarm();
}, this.alertDuration);
}
}

stopAlarm(): void {
this.stopSound()
this.stopSound();
clearInterval(this.alertInterval);
}

simulateClick(element: HTMLElement) {
element.click();
}

}

+ 27
- 0
src/app/shared/services/confirm-dialog.service.ts View File

@@ -0,0 +1,27 @@
import { ElementRef, Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { take } from 'rxjs';
import { ConfirmDialogComponent } from '../component/confirm-dialog/confirm-dialog.component';

@Injectable({
providedIn: 'root',
})
export class ConfirmDialogService {
private isDialogOpen = false;

constructor(private dialog: MatDialog) {}

openDialog(): void {
if (!this.isDialogOpen) {
this.isDialogOpen = true;
const dialogRef = this.dialog.open(ConfirmDialogComponent);

dialogRef
.afterClosed()
.pipe(take(1))
.subscribe(() => {
this.isDialogOpen = false;
});
}
}
}

+ 1
- 3
src/app/shared/services/mqtt-client.service.ts View File

@@ -1,7 +1,5 @@
import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { BehaviorSubject,Subject } from "rxjs";
import { config } from "../../../assets/config/config";

import {
IMqttMessage,
IMqttServiceOptions,

+ 14
- 8
src/app/shared/shared-material.module.ts View File

@@ -1,9 +1,12 @@
import { NgModule } from "@angular/core";
import { FlexLayoutModule } from "@angular/flex-layout";
import { MatButtonModule } from "@angular/material/button";
import { MatDialogModule } from "@angular/material/dialog";
import { MatIconModule } from "@angular/material/icon";
import {MatMenuModule} from "@angular/material/menu";
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';

@NgModule({
exports: [
@@ -11,7 +14,10 @@ import {MatMenuModule} from "@angular/material/menu";
FlexLayoutModule,
MatDialogModule,
MatIconModule,
MatMenuModule
]
MatMenuModule,
MatExpansionModule,
MatCardModule,
MatCheckboxModule,
],
})
export class SharedMaterialModule {}

BIN
src/assets/sound/alarm_5m.mp3 View File


Loading…
Cancel
Save