| @@ -0,0 +1,308 @@ | |||
| export const ICON = { | |||
| sensorOff: `<div style="display: flex; | |||
| flex-direction: column; | |||
| align-items: center"> | |||
| <div style="display: flex; flex-direction: row; gap: 5px"> | |||
| <div class="tooltip"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/sensor-off.png"> | |||
| <div class="tooltiptext"> | |||
| <div style="display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between;"> | |||
| <div style="color: #F33152; | |||
| padding: 5px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;">Sự cố cháy</div> | |||
| <a href="/overview/camera-stream"> | |||
| <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> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> | |||
| </div> | |||
| </div> | |||
| <a href="/overview/camera-stream" target="_blank"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/camera.png"> | |||
| </a> | |||
| </div> | |||
| </div> | |||
| <style> | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #fff; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: 50%; | |||
| margin-left: -60px; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| } | |||
| </style>`, | |||
| sensorOn: `<div style="display: flex; | |||
| flex-direction: column; | |||
| align-items: center"> | |||
| <div style="display: flex; flex-direction: row; gap: 5px"> | |||
| <div class="tooltip"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/sensor-on.png"> | |||
| <div class="tooltiptext"> | |||
| <div style="display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between;"> | |||
| <div style="color: #F33152; | |||
| padding: 5px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;">Sự cố cháy</div> | |||
| <a href="/overview/camera-stream"> | |||
| <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> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> | |||
| </div> | |||
| </div> | |||
| <a href="/overview/camera-stream" target="_blank"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/camera.png"> | |||
| </a> | |||
| </div> | |||
| <!-- <div style="font-size: 11px"><b>ALARM: SMOKE ALERT</b></div>--> | |||
| </div> | |||
| <style> | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #fff; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: 50%; | |||
| margin-left: -60px; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| } | |||
| </style>`, | |||
| sensorActive: `<div> | |||
| <div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);"> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div> | |||
| <img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;"> | |||
| </div> | |||
| <img style="width: 30px; height: 30px;margin-left: 40px;" src="assets/images/camera.png"> | |||
| <style> | |||
| @keyframes sensor-on { | |||
| 100% { | |||
| transform: scale(2); | |||
| opacity: 0; | |||
| } | |||
| } | |||
| </style> | |||
| </div>`, | |||
| fireContent: `<div style="display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between;"> | |||
| <div style="color: #F33152; | |||
| padding: 5px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;">Sự cố cháy</div> | |||
| <a href="/overview/camera-stream" target="_blank"> | |||
| <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> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>`, | |||
| sensorActiveSmoke: ` | |||
| <div style="display: flex; | |||
| flex-direction: column; | |||
| align-items: center"> | |||
| <div style="display: flex; flex-direction: row; gap: 39px;width: 100%; justify-content: center;"> | |||
| <div class="tooltip"> | |||
| <div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);"> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div> | |||
| <img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;"> | |||
| </div> | |||
| <div class="tooltiptext"> | |||
| <div style="display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between;"> | |||
| <div style="color: #F33152; | |||
| padding: 5px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;">Sự cố cháy</div> | |||
| <a href="/overview/camera-stream"> | |||
| <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> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> | |||
| </div> | |||
| </div> | |||
| <a href="/overview/camera-stream" target="_blank"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/camera.png"> | |||
| </a> | |||
| </div> | |||
| <div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px"><b>ALARM: SMOKE ALERT</b></div> | |||
| </div> | |||
| <style> | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #fff; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: 50%; | |||
| margin-left: -60px; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| } | |||
| @keyframes sensor-on { | |||
| 100% { | |||
| transform: scale(2); | |||
| opacity: 0; | |||
| } | |||
| } | |||
| </style>`, | |||
| sensorActiveVib: ` | |||
| <div style="display: flex; | |||
| flex-direction: column; | |||
| align-items: center"> | |||
| <div style="display: flex; flex-direction: row; gap: 39px;width: 100%; justify-content: center;"> | |||
| <div class="tooltip"> | |||
| <div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);"> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div> | |||
| <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div> | |||
| <img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;"> | |||
| </div> | |||
| <div class="tooltiptext"> | |||
| <div style="display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between;"> | |||
| <div style="color: #F33152; | |||
| padding: 5px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;">Sự cố cháy</div> | |||
| <a href="/overview/camera-stream"> | |||
| <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> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a> | |||
| </div> | |||
| </div> | |||
| <a href="/overview/camera-stream" target="_blank"> | |||
| <img style="width: 30px; height: 30px;" src="assets/images/camera.png"> | |||
| </a> | |||
| </div> | |||
| <div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px"><b>ALARM: VIBRATION ALERT</b></div> | |||
| </div> | |||
| <style> | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #fff; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: 50%; | |||
| margin-left: -60px; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| } | |||
| @keyframes sensor-on { | |||
| 100% { | |||
| transform: scale(2); | |||
| opacity: 0; | |||
| } | |||
| } | |||
| </style>`, | |||
| } | |||
| @@ -1,10 +1,17 @@ | |||
| import { NgModule } from '@angular/core'; | |||
| import {APP_INITIALIZER, NgModule} from '@angular/core'; | |||
| import { BrowserModule } from '@angular/platform-browser'; | |||
| import { AppRoutingModule } from './app-routing.module'; | |||
| import { AppComponent } from './app.component'; | |||
| import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | |||
| import {SharedModule} from "./shared/shared.module"; | |||
| import {HttpClientModule} from "@angular/common/http"; | |||
| import {AppInitService} from "./shared/services/app-init.service"; | |||
| export function appInitializerFactory(appInitializerService: AppInitService): () => Promise<any> { | |||
| return () => appInitializerService.initializeApp(); | |||
| } | |||
| @NgModule({ | |||
| declarations: [ | |||
| @@ -15,8 +22,17 @@ import {SharedModule} from "./shared/shared.module"; | |||
| AppRoutingModule, | |||
| BrowserAnimationsModule, | |||
| SharedModule, | |||
| HttpClientModule | |||
| ], | |||
| providers: [ | |||
| { | |||
| provide: APP_INITIALIZER, | |||
| useFactory: appInitializerFactory, | |||
| deps: [AppInitService], | |||
| multi: true | |||
| } | |||
| ], | |||
| providers: [], | |||
| bootstrap: [AppComponent] | |||
| }) | |||
| export class AppModule { } | |||
| @@ -0,0 +1,6 @@ | |||
| <h3>Camera Stream</h3> | |||
| <!--<canvas #videoPlayer></canvas>--> | |||
| <video width="700" controls style="display: block; margin-left: auto; margin-right: auto"> | |||
| <source src="assets/video/video.mp4" type="video/mp4"> | |||
| </video> | |||
| @@ -0,0 +1,22 @@ | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #fff; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: 50%; | |||
| margin-left: -60px; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| 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,0 +1,30 @@ | |||
| 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>; | |||
| ngOnInit() { | |||
| } | |||
| constructor(private el: ElementRef, private renderer: Renderer2) {} | |||
| async ngAfterViewInit() { | |||
| // this.player = await loadPlayer({ | |||
| // url: 'ws://localhost:8081', | |||
| // canvas: this.videoPlayer!.nativeElement, | |||
| // | |||
| // onDisconnect: () => console.log('Connection lost!'), | |||
| // }); | |||
| // | |||
| // console.log('Connected!', this.player); | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| <div class="map-container"> | |||
| <h2>ATMs Security Monitoring and Control</h2> | |||
| <div class="map-frame"> | |||
| <div id="map"></div> <!-- Ensure no extra space after "map" --> | |||
| <div id="map"></div> | |||
| </div> | |||
| </div> | |||
| @@ -9,7 +9,7 @@ | |||
| .map-frame { | |||
| border: 2px solid black; | |||
| height: 100%; | |||
| height: 90%; | |||
| } | |||
| #map { | |||
| @@ -23,3 +23,7 @@ p { | |||
| padding: 3px 2px; | |||
| background-color: rbg(243,49,82,0.1); | |||
| } | |||
| .hidden-background{ | |||
| background: transparent; | |||
| border: none; | |||
| } | |||
| @@ -1,7 +1,8 @@ | |||
| import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; | |||
| import * as L from 'leaflet'; | |||
| import {Subscription} from "rxjs"; | |||
| import {SocketService} from "../../../shared/services/socket.service"; | |||
| import { Subscription } from 'rxjs'; | |||
| import { SocketService } from '../../../shared/services/socket.service'; | |||
| import {ICON} from "../../../app.constants"; | |||
| @Component({ | |||
| selector: 'app-map', | |||
| @@ -9,48 +10,54 @@ import {SocketService} from "../../../shared/services/socket.service"; | |||
| styleUrls: ['./map.component.scss'] | |||
| }) | |||
| export class MapComponent implements OnInit, AfterViewInit, OnDestroy { | |||
| private map: any; | |||
| houseIcon = L.icon({ | |||
| iconUrl: '../../../../assets/images/icon.png', | |||
| iconSize: [38, 48], | |||
| shadowSize: [50, 64], | |||
| private map!: L.Map; | |||
| private featureGroup!: L.FeatureGroup; | |||
| private statusSubscription?: Subscription; | |||
| private messageSubscription?: Subscription; | |||
| sensorOff = ICON.sensorOff; | |||
| sensorOn = ICON.sensorOn; | |||
| sensorSmoke = ICON.sensorActiveSmoke; | |||
| sensorVib = ICON.sensorActiveVib; | |||
| sensorSmokeIcon = L.divIcon({ | |||
| html: this.sensorSmoke, | |||
| iconSize: [170, 48], | |||
| className: 'hidden-background' | |||
| }); | |||
| fireIcon = L.icon({ | |||
| iconUrl: '../../../../assets/images/fire.gif', | |||
| iconSize: [38, 48], | |||
| }) | |||
| sensorVibIcon = L.divIcon({ | |||
| html: this.sensorVib, | |||
| iconSize: [170, 48], | |||
| className: 'hidden-background' | |||
| }); | |||
| sensorOffIcon = L.divIcon({ | |||
| html: this.sensorOff, | |||
| iconSize: [150, 38], | |||
| className: 'hidden-background' | |||
| }); | |||
| sensorOnIcon = L.divIcon({ | |||
| className: '', | |||
| html:'<div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);">'+ | |||
| ' <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div>'+ | |||
| ' <div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div>'+ | |||
| ' <img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;">'+ | |||
| '</div>'+ | |||
| '<style>'+ | |||
| '@keyframes sensor-on {'+ | |||
| ' 100% {'+ | |||
| ' transform: scale(2);'+ | |||
| ' opacity: 0;'+ | |||
| ' }'+ | |||
| '}'+ | |||
| '</style>', | |||
| iconSize: [38, 48] | |||
| html: this.sensorOn, | |||
| iconSize: [150, 38], | |||
| className: 'hidden-background' | |||
| }); | |||
| // sensorOffIcon = L.icon({ | |||
| // iconUrl: '../../../../assets/images/sensor-off.png', | |||
| // iconSize: [38, 38], | |||
| // }); | |||
| // sensorOnIcon = L.icon({ | |||
| // iconUrl: '../../../../assets/images/sensor-on.png', | |||
| // iconSize: [38, 38], | |||
| // }); | |||
| sensorOffIcon = L.icon({ | |||
| iconUrl: '../../../../assets/images/sensor-off.png', | |||
| iconSize: [38, 38], | |||
| }) | |||
| state5 = false; | |||
| state6 = false; | |||
| private statusSubscription?: Subscription; | |||
| private messageSubscription?: Subscription; | |||
| constructor(private socketService$: SocketService) { } | |||
| ngOnInit() { | |||
| this.statusSubscription = this.socketService$.status$.subscribe(isConnected => { | |||
| if (isConnected) { | |||
| @@ -60,29 +67,17 @@ export class MapComponent implements OnInit, AfterViewInit, OnDestroy { | |||
| } | |||
| }); | |||
| } | |||
| ngOnDestroy(): void { | |||
| if (this.statusSubscription) { | |||
| this.statusSubscription.unsubscribe(); | |||
| } | |||
| if (this.messageSubscription) { | |||
| this.messageSubscription.unsubscribe(); | |||
| } | |||
| this.socketService$.close(); | |||
| } | |||
| onMessage(message: any) { | |||
| if (message.id == '0' && message.type === 'get') { | |||
| this.state5 = message.state5 === '1'; | |||
| this.state6 = message.state6 === '1' ; | |||
| this.updateIcons(); | |||
| } | |||
| } | |||
| ngAfterViewInit(): void { | |||
| this.initMap(); | |||
| } | |||
| ngOnDestroy(): void { | |||
| this.statusSubscription?.unsubscribe(); | |||
| this.messageSubscription?.unsubscribe(); | |||
| this.socketService$.close(); | |||
| } | |||
| private initMap(): void { | |||
| const mapContainer = document.getElementById('map'); | |||
| if (mapContainer) { | |||
| @@ -91,44 +86,39 @@ export class MapComponent implements OnInit, AfterViewInit, OnDestroy { | |||
| zoom: 12 | |||
| }); | |||
| const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |||
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |||
| maxZoom: 18, | |||
| minZoom: 3, | |||
| attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' | |||
| }); | |||
| }).addTo(this.map); | |||
| tiles.addTo(this.map); | |||
| this.addIconToMap() | |||
| this.featureGroup = L.featureGroup().addTo(this.map); | |||
| this.addIconToMap(); | |||
| } else { | |||
| console.error('Map container not found'); | |||
| } | |||
| } | |||
| onMessage(message: any) { | |||
| if (message.id == '0' && message.type === 'get') { | |||
| this.state5 = message.state5 === '1'; | |||
| this.state6 = message.state6 === '1'; | |||
| this.updateIcons(); | |||
| } | |||
| } | |||
| addIconToMap() { | |||
| //add marker | |||
| let popupContent = `<div>Vinhome quận 9</div> | |||
| const popupContent = `<div>Vinhome quận 9</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>`; | |||
| let fireContent = `<div style="color: #F33152; | |||
| padding: 2px 13px; | |||
| background-color: rgba(243, 49, 82, 0.1); | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| width: 100px;"> | |||
| Sự cố cháy | |||
| </div> | |||
| <div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div> | |||
| <div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div> | |||
| <div><strong>Thời gian:</strong> 01:54, 16/05/2022</div> | |||
| <a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>` | |||
| L.marker([10.8356, 106.8300], {icon: this.state5 ? this.sensorOnIcon : this.sensorOffIcon}) | |||
| const fireContent = ICON.fireContent; | |||
| L.marker([10.8356, 106.8300], {icon: this.applyIcon(this.state5, this.state6, 'smoke')}) | |||
| .addTo(this.map) | |||
| .bindPopup(popupContent); | |||
| L.marker([10.8661, 106.8029], {icon: this.state6 ? this.sensorOnIcon : this.sensorOffIcon}) | |||
| L.marker([10.8661, 106.8029], {icon: this.applyIcon(this.state5, this.state6, 'vib')}) | |||
| .addTo(this.map) | |||
| .bindPopup(fireContent); | |||
| } | |||
| updateIcons(): void { | |||
| @@ -140,4 +130,14 @@ export class MapComponent implements OnInit, AfterViewInit, OnDestroy { | |||
| this.addIconToMap(); | |||
| } | |||
| applyIcon(state1: boolean, state2: boolean, type:any): L.Icon | L.DivIcon { | |||
| if (state1 && state2) { | |||
| return type==='smoke' ? this.sensorSmokeIcon : this.sensorVibIcon; | |||
| } | |||
| if (state1) { | |||
| return this.sensorOnIcon; | |||
| } | |||
| return this.sensorOffIcon; | |||
| } | |||
| } | |||
| @@ -1,31 +1,86 @@ | |||
| <div class="p-3" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px"> | |||
| <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"> | |||
| <p class="state t1" id="State1" [ngClass]="{'sensor-on': state1 === 'ON'}"> | |||
| <img [src]="getImageSource(state1)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <p class="state t2" id="State2" [ngClass]="{'sensor-on': state2 === 'ON'}"> | |||
| <img [src]="getImageSource(state2)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <p class="state t3" id="State3" [ngClass]="{'sensor-on': state3 === 'ON'}"> | |||
| <img [src]="getImageSource(state3)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <p class="state t4" id="State4" [ngClass]="{'sensor-on': state4 === 'ON'}"> | |||
| <img [src]="getImageSource(state4)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <p class="state t5" id="State5" [ngClass]="{'sensor-on': state5 === 'ON'}"> | |||
| <img [src]="getImageSource(state5)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <p class="state t6" id="State6" [ngClass]="{'sensor-on': state6 === 'ON'}"> | |||
| <img [src]="getImageSource(state6)" style="width: 30px; height: 30px"> | |||
| </p> | |||
| <div class="state t1" id="State1"> | |||
| <div> | |||
| <img src="assets/images/sensor-off.png" 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 t2" id="State2"> | |||
| <div> | |||
| <img src="assets/images/sensor-off.png" 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 t3" id="State3"> | |||
| <div> | |||
| <img src="assets/images/sensor-off.png" 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 t4" id="State4"> | |||
| <div> | |||
| <img src="assets/images/sensor-off.png" 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" id="State5" > | |||
| <div> | |||
| <div [ngClass]="{'sensor-on': Sstate1 && Sstate2, | |||
| 'sensor-off': !(Sstate1 && Sstate2)}"> | |||
| <img [src]="getImageSource()" 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> | |||
| <ng-template [ngIf]="Sstate1 && Sstate2"> | |||
| <div class="alarm-text" | |||
| [ngClass]="{'alarm-text-on': Sstate1 && Sstate2 }">ALARM: <br>VIBRATION ALERT</div> | |||
| </ng-template> | |||
| </div> | |||
| <div class="state t6 " id="State6"> | |||
| <div> | |||
| <div [ngClass]="{'sensor-on': Sstate1 && Sstate2, | |||
| 'sensor-off': !(Sstate1 && Sstate2)}"> | |||
| <img [src]="getImageSource()" 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> | |||
| <ng-template [ngIf]="Sstate1 && Sstate2"> | |||
| <div class="alarm-text" | |||
| [ngClass]="{'alarm-text-on': Sstate1 && Sstate2 }">ALARM: <br>SMOKE ALERT</div> | |||
| </ng-template> | |||
| </div> | |||
| <!-- <div *ngFor="let state of states; let i = index"--> | |||
| <!-- [ngClass]="{'sensor-on': Sstate1 && Sstate2}"--> | |||
| <!-- class="state t{{i + 1}}"--> | |||
| <!-- id="State{{i + 1}}">--> | |||
| <!-- <img [src]="getImageSource()" style="width: 30px; height: 30px">--> | |||
| <!-- </div>--> | |||
| </div> | |||
| </div> | |||
| <div fxFlex="30" fxLayout="column" fxLayoutGap="50px"> | |||
| <div>Công tắc 1 <button [disabled]="!isConnected" mat-flat-button color="{{Sstate1 ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ Sstate1 ? 'ON' : 'OFF'}}</button></div> | |||
| <div>Công tắc 2 <button [disabled]="!isConnected" mat-flat-button color="{{Sstate2 ? 'accent' : 'primary'}}" (click)="toggleState2()">{{ Sstate2 ? 'ON' : 'OFF'}}</button></div> | |||
| <button [disabled]="!isConnected" mat-flat-button color="{{Sstate1 ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ Sstate1 ? 'DISARM' : 'ARM'}}</button> | |||
| <button [disabled]="!isConnected" mat-flat-button color="{{Sstate2 ? 'accent' : 'primary'}}" (click)="toggleState2()">{{ Sstate2 ? 'TURN OFF WARNING' : 'TURN ON WARNING'}}</button> | |||
| <button mat-stroked-button color="primary" [routerLink]="'/overview/camera-stream'" target="_blank"> LIVE CAMERA </button> | |||
| </div> | |||
| </div> | |||
| @@ -107,15 +107,15 @@ p { | |||
| position: absolute; | |||
| color: red; | |||
| &.t1{ | |||
| top: 8%; | |||
| top: 5%; | |||
| left: 10%; | |||
| } | |||
| &.t2{ | |||
| top: 8%; | |||
| left: 47%; | |||
| top: 5%; | |||
| left: 42%; | |||
| } | |||
| &.t3{ | |||
| top: 8%; | |||
| top: 5%; | |||
| right: 12%; | |||
| } | |||
| &.t4{ | |||
| @@ -124,11 +124,19 @@ p { | |||
| } | |||
| &.t5{ | |||
| top: 88%; | |||
| left: 47%; | |||
| left: 42%; | |||
| width: 100px; | |||
| .alarm-text-off{ | |||
| width: 100px !important; | |||
| } | |||
| } | |||
| &.t6{ | |||
| top: 47%; | |||
| left: 10%; | |||
| left: 5%; | |||
| width: 100px; | |||
| .alarm-text-off{ | |||
| width: 100px !important; | |||
| } | |||
| } | |||
| } | |||
| @@ -137,7 +145,7 @@ p { | |||
| height: 30px; | |||
| position: absolute; | |||
| background: linear-gradient(#ff0000, #C70039); | |||
| display: flex; | |||
| display: flex !important; | |||
| justify-content: center; | |||
| align-items: center; | |||
| border-radius: 50%; | |||
| @@ -162,6 +170,9 @@ p { | |||
| animation: sensor-on 2s 1s ease-out infinite; | |||
| } | |||
| } | |||
| .sensor-off{ | |||
| display: inline-block; | |||
| } | |||
| @keyframes sensor-on{ | |||
| 100%{ | |||
| transform: scale(2); | |||
| @@ -169,6 +180,20 @@ p { | |||
| } | |||
| } | |||
| } | |||
| .button-on { | |||
| .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; | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import {ChangeDetectorRef, Component, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core'; | |||
| import {SocketService} from "../../../shared/services/socket.service"; | |||
| import {Subscription} from "rxjs"; | |||
| import * as L from "leaflet"; | |||
| @Component({ | |||
| selector: 'app-overall-ground', | |||
| @@ -10,6 +11,7 @@ import {Subscription} from "rxjs"; | |||
| export class OverallGroundComponent implements OnInit, OnDestroy { | |||
| isClicked = false; | |||
| isConnected = false; | |||
| states = Array(6).fill(0); | |||
| state1 = ''; | |||
| state2 = ''; | |||
| state3 = ''; | |||
| @@ -18,6 +20,8 @@ export class OverallGroundComponent implements OnInit, OnDestroy{ | |||
| state6 = ''; | |||
| Sstate1 = false; | |||
| Sstate2 = false; | |||
| imageIcon = 'assets/images/sensor-off.png'; | |||
| title = 'ALARM: SMOKE ALERT' | |||
| private statusSubscription?: Subscription; | |||
| private messageSubscription?: Subscription; | |||
| private intervalId: any; | |||
| @@ -27,7 +31,7 @@ export class OverallGroundComponent implements OnInit, OnDestroy{ | |||
| } | |||
| ngOnInit() { | |||
| this.socketService$.connect(); | |||
| // this.socketService$.connect(); | |||
| this.statusSubscription = this.socketService$.status$.subscribe(isConnected => { | |||
| this.isConnected = isConnected; | |||
| if (this.isConnected) { | |||
| @@ -60,6 +64,7 @@ export class OverallGroundComponent implements OnInit, OnDestroy{ | |||
| let str = {id: '0', type: 'get'}; | |||
| this.socketService$.sendMessage(str); | |||
| } | |||
| toggleState1() { | |||
| this.Sstate1 = !this.Sstate1; | |||
| let str = {id: '0', type: 'cmd', state1: this.Sstate1.toString()}; | |||
| @@ -72,9 +77,10 @@ export class OverallGroundComponent implements OnInit, OnDestroy{ | |||
| this.socketService$.sendMessage(str); | |||
| } | |||
| getImageSource(state: string): string { | |||
| return state === 'ON' ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; | |||
| getImageSource(): string { | |||
| return (this.Sstate1 && this.Sstate2) || this.Sstate1 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; | |||
| } | |||
| onMessage(message: any) { | |||
| if (message.id == '0' && message.type === 'get') { | |||
| this.state1 = message.state1 === '1' ? 'ON' : 'OFF'; | |||
| @@ -86,5 +92,9 @@ export class OverallGroundComponent implements OnInit, OnDestroy{ | |||
| } | |||
| this.Sstate1 = this.state5 === 'ON'; | |||
| this.Sstate2 = this.state6 === 'ON'; | |||
| if(this.Sstate1 && this.Sstate2) | |||
| this.title = 'ALARM: VIBRATION ALERT' | |||
| } | |||
| } | |||
| @@ -7,17 +7,20 @@ import {overviewRoutes} from "./overview.routing"; | |||
| import {MapComponent} from "./map/map.component"; | |||
| import {SharedMaterialModule} from "../../shared/shared-material.module"; | |||
| import {MatIconModule} from "@angular/material/icon"; | |||
| import { CameraStreamComponent } from './camera-stream/camera-stream.component'; | |||
| import {FormsModule} from "@angular/forms"; | |||
| @NgModule({ | |||
| declarations: [ | |||
| OverallGroundComponent, | |||
| MapComponent | |||
| MapComponent, | |||
| CameraStreamComponent | |||
| ], | |||
| imports: [ | |||
| CommonModule, | |||
| RouterModule.forChild(overviewRoutes), | |||
| SharedMaterialModule, | |||
| MatIconModule | |||
| ] | |||
| }) | |||
| export class OverviewModule { } | |||
| @@ -1,6 +1,7 @@ | |||
| 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 = [ | |||
| { | |||
| @@ -10,6 +11,10 @@ export const overviewRoutes: Routes = [ | |||
| { | |||
| path: 'overall-ground', | |||
| component: OverallGroundComponent | |||
| }, | |||
| { | |||
| path: 'camera-stream', | |||
| component: CameraStreamComponent | |||
| } | |||
| ]; | |||
| @@ -0,0 +1,18 @@ | |||
| import { Injectable } from '@angular/core'; | |||
| import {config} from "../../../assets/config/config"; | |||
| @Injectable({ | |||
| providedIn: 'root' | |||
| }) | |||
| export class AppInitService { | |||
| appConfig = config; | |||
| constructor() {} | |||
| initializeApp(): Promise<any> { | |||
| return new Promise((resolve, reject) => { | |||
| console.log('Loaded:', this.appConfig); | |||
| resolve(true); | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,13 +1,16 @@ | |||
| import { Injectable } from '@angular/core'; | |||
| import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; | |||
| import { BehaviorSubject, Subject } from "rxjs"; | |||
| import { config } from "../../../config"; | |||
| import { config } from "../../../assets/config/config"; | |||
| @Injectable({ | |||
| providedIn: 'root' | |||
| }) | |||
| export class SocketService { | |||
| gateway = config.gateway; | |||
| websocket$!: WebSocketSubject<any> ; | |||
| private isConnected: boolean = false; | |||
| private messagesSubject$ = new Subject(); | |||
| @@ -17,7 +20,6 @@ export class SocketService { | |||
| constructor() { | |||
| } | |||
| public connect(cfg: { reconnect: boolean } = {reconnect: false}): void { | |||
| if (!this.websocket$ || this.websocket$.closed) { | |||
| console.log('Trying to open a WebSocket connection…'); | |||
| @@ -0,0 +1,23 @@ | |||
| const WebSocket = require('ws'); | |||
| const http = require('http'); | |||
| const server = http.createServer(); | |||
| const wss = new WebSocket.Server({ server }); | |||
| wss.on('connection', function connection(ws) { | |||
| ws.on('message', function incoming(message) { | |||
| // Log tin nhắn nhận được | |||
| console.log('Nhận được: %s', message); | |||
| }); | |||
| // Bắt sự kiện khi kết nối đóng | |||
| ws.on('close', function close() { | |||
| console.log('Client đã ngắt kết nối'); | |||
| }); | |||
| }); | |||
| server.listen(8081, function listening() { | |||
| console.log('WebSocket server đang lắng nghe trên cổng 8081'); | |||
| }); | |||
| @@ -0,0 +1,4 @@ | |||
| ffmpeg -rtsp_transport tcp -i rtsp://admin:hd543211@@xuanphuong32.dyndns.org:8001/cam/realmonitor?channel=7&subtype=0 ^ | |||
| -f mpegts -codec:v mpeg1video -s 640x480 -b:v 800k -r 30 ^ | |||
| -codec:a mp2 -b:a 128k ^ | |||
| http://localhost:8081 | |||