features/hotel-management into master 1 year ago
| "build": "ng build", | "build": "ng build", | ||||
| "watch": "ng build --watch --configuration development", | "watch": "ng build --watch --configuration development", | ||||
| "test": "ng test", | "test": "ng test", | ||||
| "camera-server": "node dist/lot-web-ui/server/server.js" | |||||
| "camera-server": "node dist/lot-web-ui/server/server.js", | |||||
| "prettier:write": "prettier --write src", | |||||
| "prettier:check": "prettier --check src" | |||||
| }, | }, | ||||
| "private": true, | "private": true, | ||||
| "dependencies": { | "dependencies": { | ||||
| "express-ws": "^5.0.2", | "express-ws": "^5.0.2", | ||||
| "leaflet": "^1.9.4", | "leaflet": "^1.9.4", | ||||
| "leaflet.markercluster": "^1.5.3", | "leaflet.markercluster": "^1.5.3", | ||||
| "moment": "^2.30.1", | |||||
| "ngx-mqtt": "^17.0.0", | "ngx-mqtt": "^17.0.0", | ||||
| "ngx-toastr": "^17.0.2", | "ngx-toastr": "^17.0.2", | ||||
| "prettier": "^3.3.3", | |||||
| "rtsp-relay": "^1.8.0", | "rtsp-relay": "^1.8.0", | ||||
| "rxjs": "~7.8.0", | "rxjs": "~7.8.0", | ||||
| "rxjs-websockets": "^9.0.0", | "rxjs-websockets": "^9.0.0", |
| import { AppComponent } from './app.component'; | import { AppComponent } from './app.component'; | ||||
| describe('AppComponent', () => { | describe('AppComponent', () => { | ||||
| beforeEach(() => TestBed.configureTestingModule({ | |||||
| beforeEach(() => | |||||
| TestBed.configureTestingModule({ | |||||
| imports: [RouterTestingModule], | imports: [RouterTestingModule], | ||||
| declarations: [AppComponent] | |||||
| })); | |||||
| declarations: [AppComponent], | |||||
| }), | |||||
| ); | |||||
| it('should create the app', () => { | it('should create the app', () => { | ||||
| const fixture = TestBed.createComponent(AppComponent); | const fixture = TestBed.createComponent(AppComponent); | ||||
| const fixture = TestBed.createComponent(AppComponent); | const fixture = TestBed.createComponent(AppComponent); | ||||
| fixture.detectChanges(); | fixture.detectChanges(); | ||||
| const compiled = fixture.nativeElement as HTMLElement; | const compiled = fixture.nativeElement as HTMLElement; | ||||
| expect(compiled.querySelector('.content span')?.textContent).toContain('iot-web-ui app is running!'); | |||||
| expect(compiled.querySelector('.content span')?.textContent).toContain( | |||||
| 'iot-web-ui app is running!', | |||||
| ); | |||||
| }); | }); | ||||
| }); | }); |
| import { | |||||
| Component, | |||||
| OnInit, | |||||
| } from '@angular/core'; | |||||
| import {MqttClientService} from "./shared/services/mqtt-client.service"; | |||||
| import {takeUntil} from "rxjs/operators"; | |||||
| import {filter, Subject} from "rxjs"; | |||||
| import {TOPIC_GETTING, TOPIC_SETTING} from "./app.constants"; | |||||
| import { Component, OnInit } from '@angular/core'; | |||||
| import { MqttClientService } from './shared/services/mqtt-client.service'; | |||||
| import { takeUntil } from 'rxjs/operators'; | |||||
| import { filter, Subject } from 'rxjs'; | |||||
| import { TOPIC_GETTING, TOPIC_SETTING } from './app.constants'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-root', | selector: 'app-root', | ||||
| ngOnInit() { | ngOnInit() { | ||||
| this.mqtt$.createConnection(); | this.mqtt$.createConnection(); | ||||
| this.mqtt$.status$.pipe(takeUntil(this.unsubscribeAll)).subscribe( | |||||
| (isConnected: boolean) => { | |||||
| this.mqtt$.status$ | |||||
| .pipe(takeUntil(this.unsubscribeAll)) | |||||
| .subscribe((isConnected: boolean) => { | |||||
| this.isConnected = isConnected; | this.isConnected = isConnected; | ||||
| this.mqtt$.getSetting(); | this.mqtt$.getSetting(); | ||||
| this.mqtt$.getLog(); | this.mqtt$.getLog(); | ||||
| this.mqtt$.sendPublish({ id: '0', type: 'get', whistletime: ''}, TOPIC_GETTING); | |||||
| this.mqtt$.sendPublish({ id: '0', type: 'get', arlamtime: ''}, TOPIC_GETTING); | |||||
| this.mqtt$.setting$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe((message: any) => { | |||||
| this.mqtt$.sendPublish( | |||||
| { id: '0', type: 'get', whistletime: '' }, | |||||
| TOPIC_GETTING, | |||||
| ); | |||||
| this.mqtt$.sendPublish( | |||||
| { id: '0', type: 'get', arlamtime: '' }, | |||||
| TOPIC_GETTING, | |||||
| ); | |||||
| this.mqtt$.setting$ | |||||
| .pipe( | |||||
| takeUntil(this.unsubscribeAll), | |||||
| filter((item) => item !== null), | |||||
| ) | |||||
| .subscribe((message: any) => { | |||||
| if (message.id == '0' && message.whistletime) { | if (message.id == '0' && message.whistletime) { | ||||
| localStorage.setItem('whistletime', message.whistletime); | localStorage.setItem('whistletime', message.whistletime); | ||||
| } | } | ||||
| localStorage.setItem('alarmtime', message.arlamtime); | localStorage.setItem('alarmtime', message.arlamtime); | ||||
| } | } | ||||
| }); | }); | ||||
| }, | |||||
| ); | |||||
| }); | |||||
| } | } | ||||
| } | } |
| import { NgModule} from '@angular/core'; | |||||
| import { NgModule } from '@angular/core'; | |||||
| import { BrowserModule } from '@angular/platform-browser'; | import { BrowserModule } from '@angular/platform-browser'; | ||||
| import { AppRoutingModule } from './app-routing.module'; | import { AppRoutingModule } from './app-routing.module'; | ||||
| import { AppComponent } from './app.component'; | import { AppComponent } from './app.component'; | ||||
| import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | ||||
| import {SharedModule} from "./shared/shared.module"; | |||||
| import {HttpClientModule} from "@angular/common/http"; | |||||
| import { SharedModule } from './shared/shared.module'; | |||||
| import { HttpClientModule } from '@angular/common/http'; | |||||
| import { ToastrModule } from 'ngx-toastr'; | import { ToastrModule } from 'ngx-toastr'; | ||||
| import { IMqttServiceOptions, MqttModule } from 'ngx-mqtt'; | import { IMqttServiceOptions, MqttModule } from 'ngx-mqtt'; | ||||
| export const connection: IMqttServiceOptions = { | export const connection: IMqttServiceOptions = { | ||||
| clientId: 'iot-' + Date.parse(new Date().toString()), | clientId: 'iot-' + Date.parse(new Date().toString()), | ||||
| protocol: 'ws', | protocol: 'ws', | ||||
| connectOnCreate: false, | connectOnCreate: false, | ||||
| } | |||||
| }; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | |||||
| AppComponent, | |||||
| ], | |||||
| declarations: [AppComponent], | |||||
| imports: [ | imports: [ | ||||
| BrowserModule, | BrowserModule, | ||||
| AppRoutingModule, | AppRoutingModule, | ||||
| ToastrModule.forRoot({ | ToastrModule.forRoot({ | ||||
| maxOpened: 1, | maxOpened: 1, | ||||
| preventDuplicates: true, | preventDuplicates: true, | ||||
| autoDismiss: true | |||||
| autoDismiss: true, | |||||
| }), // ToastrModule added | }), // ToastrModule added | ||||
| MqttModule.forRoot(connection) | |||||
| MqttModule.forRoot(connection), | |||||
| ], | ], | ||||
| providers: [], | providers: [], | ||||
| bootstrap: [AppComponent] | |||||
| bootstrap: [AppComponent], | |||||
| }) | }) | ||||
| export class AppModule { } | |||||
| export class AppModule {} |
| <div id="map"></div> | <div id="map"></div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| width: 100%; | width: 100%; | ||||
| height: calc(100% - 7rem); | height: calc(100% - 7rem); | ||||
| h2 { | h2 { | ||||
| padding: .5rem 3rem 0 ; | |||||
| padding: 0.5rem 3rem 0; | |||||
| text-align: center; | text-align: center; | ||||
| } | } | ||||
| } | } | ||||
| margin: 0 0 0 16px !important; | margin: 0 0 0 16px !important; | ||||
| } | } | ||||
| .box-custom { | .box-custom { | ||||
| color: #F33152; | |||||
| color: #f33152; | |||||
| padding: 3px 2px; | padding: 3px 2px; | ||||
| background-color: rbg(243,49,82,0.1); | |||||
| background-color: rbg(243, 49, 82, 0.1); | |||||
| } | } | ||||
| ::ng-deep.sensor-on { | ::ng-deep.sensor-on { | ||||
| width: 30px; | width: 30px; | ||||
| height: 30px; | height: 30px; | ||||
| position: relative; | position: relative; | ||||
| background: linear-gradient(#ff0000, #C70039); | |||||
| background: linear-gradient(#ff0000, #c70039); | |||||
| display: flex !important; | display: flex !important; | ||||
| justify-content: center; | justify-content: center; | ||||
| align-items: center; | align-items: center; | ||||
| border-radius: 50%; | border-radius: 50%; | ||||
| img{ | |||||
| img { | |||||
| z-index: 9; | z-index: 9; | ||||
| } | } | ||||
| &:before, &:after { | |||||
| &:before, | |||||
| &:after { | |||||
| position: absolute; | position: absolute; | ||||
| content: ''; | |||||
| content: ""; | |||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| background: #ff0000; | background: #ff0000; | ||||
| animation: sensor-on 2s 1s ease-out infinite; | animation: sensor-on 2s 1s ease-out infinite; | ||||
| } | } | ||||
| } | } | ||||
| .sensor-off{ | |||||
| .sensor-off { | |||||
| display: inline-block; | display: inline-block; | ||||
| } | } | ||||
| @keyframes sensor-on { | @keyframes sensor-on { | ||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| gap: 5px; | gap: 5px; | ||||
| .dynamic-button{ | |||||
| .dynamic-button { | |||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [CentralizedSecurityManagementComponent] | |||||
| declarations: [CentralizedSecurityManagementComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(CentralizedSecurityManagementComponent); | fixture = TestBed.createComponent(CentralizedSecurityManagementComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| } from '@angular/core'; | } from '@angular/core'; | ||||
| import * as L from 'leaflet'; | import * as L from 'leaflet'; | ||||
| import 'leaflet.markercluster'; | import 'leaflet.markercluster'; | ||||
| import {filter, Subject, Subscription, take} from 'rxjs'; | |||||
| import { filter, Subject, Subscription, take } from 'rxjs'; | |||||
| import { SocketService } from '../../../shared/services/socket.service'; | import { SocketService } from '../../../shared/services/socket.service'; | ||||
| import { MatDialog } from '@angular/material/dialog'; | import { MatDialog } from '@angular/material/dialog'; | ||||
| import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | ||||
| import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | ||||
| import { alarmData, alarmDemo } from '../data/fake-data'; | import { alarmData, alarmDemo } from '../data/fake-data'; | ||||
| import {takeUntil} from "rxjs/operators"; | |||||
| import {MqttClientService} from "../../../shared/services/mqtt-client.service"; | |||||
| import { takeUntil } from 'rxjs/operators'; | |||||
| import { MqttClientService } from '../../../shared/services/mqtt-client.service'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-centralized-security-management', | selector: 'app-centralized-security-management', | ||||
| templateUrl: './centralized-security-management.component.html', | templateUrl: './centralized-security-management.component.html', | ||||
| styleUrls: ['./centralized-security-management.component.scss'], | styleUrls: ['./centralized-security-management.component.scss'], | ||||
| }) | }) | ||||
| export class CentralizedSecurityManagementComponent implements AfterViewInit, OnDestroy | |||||
| export class CentralizedSecurityManagementComponent | |||||
| implements AfterViewInit, OnDestroy | |||||
| { | { | ||||
| private map!: L.Map; | private map!: L.Map; | ||||
| private markers!: L.MarkerClusterGroup; | private markers!: L.MarkerClusterGroup; | ||||
| private dialog: MatDialog, | private dialog: MatDialog, | ||||
| private renderer: Renderer2, | private renderer: Renderer2, | ||||
| private alarmSoundService$: AlarmSoundService, | private alarmSoundService$: AlarmSoundService, | ||||
| private mqtt$: MqttClientService | |||||
| private mqtt$: MqttClientService, | |||||
| ) {} | ) {} | ||||
| openDialog(): void { | openDialog(): void { | ||||
| ngAfterViewInit(): void { | ngAfterViewInit(): void { | ||||
| this.initMap(); | this.initMap(); | ||||
| this.mqtt$.messages$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe(message => { | |||||
| console.log('map:',message); | |||||
| this.mqtt$.messages$ | |||||
| .pipe( | |||||
| takeUntil(this.unsubscribeAll), | |||||
| filter((item) => item !== null), | |||||
| ) | |||||
| .subscribe((message) => { | |||||
| console.log('map:', message); | |||||
| this.onMessage(message); | this.onMessage(message); | ||||
| }); | }); | ||||
| } | } | ||||
| this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h | this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h | ||||
| this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | ||||
| this.state5 = message.state5 == '1'; // 1 ON, 0 OFF | this.state5 = message.state5 == '1'; // 1 ON, 0 OFF | ||||
| this.alarmSoundService$.startAlarm(this.state5, this.state1, this.status1, this.state2, this.status2); | |||||
| this.alarmSoundService$.startAlarm( | |||||
| this.state5, | |||||
| this.state1, | |||||
| this.status1, | |||||
| this.state2, | |||||
| this.status2, | |||||
| ); | |||||
| this.updateIcons(); | this.updateIcons(); | ||||
| } | } | ||||
| } | } | ||||
| addIconsToMap(): void { | addIconsToMap(): void { | ||||
| addMarker(item: any, isDemo: boolean = false): void { | addMarker(item: any, isDemo: boolean = false): void { | ||||
| const icon = isDemo | const icon = isDemo | ||||
| ? this.getIcon(this.state5, this.state1, this.status1, this.state2, this.status2) | |||||
| ? this.getIcon( | |||||
| this.state5, | |||||
| this.state1, | |||||
| this.status1, | |||||
| this.state2, | |||||
| this.status2, | |||||
| ) | |||||
| : this.createIcon(item.warning); | : this.createIcon(item.warning); | ||||
| const marker = L.marker( | const marker = L.marker( | ||||
| [item.detail.coordinates.lat, item.detail.coordinates.lng], | [item.detail.coordinates.lat, item.detail.coordinates.lng], | ||||
| </div>`; | </div>`; | ||||
| } | } | ||||
| createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon { | |||||
| createIcon( | |||||
| active: boolean, | |||||
| className: string = '', | |||||
| text: any = [], | |||||
| ): L.Icon<L.IconOptions> | L.DivIcon { | |||||
| if (text.length < 1) { | if (text.length < 1) { | ||||
| return L.icon({ | return L.icon({ | ||||
| iconUrl: active | iconUrl: active | ||||
| } | } | ||||
| } | } | ||||
| getIcon(isTurnOn: boolean, fireArm: boolean, fireStatus: boolean, fenceArm: boolean, fenceStatus: boolean): L.Icon<L.IconOptions> | L.DivIcon { | |||||
| getIcon( | |||||
| isTurnOn: boolean, | |||||
| fireArm: boolean, | |||||
| fireStatus: boolean, | |||||
| fenceArm: boolean, | |||||
| fenceStatus: boolean, | |||||
| ): L.Icon<L.IconOptions> | L.DivIcon { | |||||
| if (isTurnOn) { | if (isTurnOn) { | ||||
| let text = []; | let text = []; | ||||
| if (fireStatus && fireArm) { | if (fireStatus && fireArm) { | ||||
| text.push('FENCE ALARM'); | text.push('FENCE ALARM'); | ||||
| } | } | ||||
| return this.createIcon(true, '', text); | return this.createIcon(true, '', text); | ||||
| } | } | ||||
| return this.createIcon(false); | return this.createIcon(false); | ||||
| } | } |
| }, | }, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| export const hotelData = [ | |||||
| { | |||||
| floor: 1, | |||||
| rooms: [ | |||||
| { | |||||
| roomNumber: '101', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T12:00:00', | |||||
| }, | |||||
| { roomNumber: '102', status: 'available' }, | |||||
| { | |||||
| roomNumber: '103', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T13:30:00', | |||||
| }, | |||||
| { roomNumber: '104', status: 'available' }, | |||||
| { | |||||
| roomNumber: '105', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T14:00:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '106', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T15:00:00', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| floor: 2, | |||||
| rooms: [ | |||||
| { | |||||
| roomNumber: '201', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T11:00:00', | |||||
| }, | |||||
| { roomNumber: '202', status: 'available' }, | |||||
| { | |||||
| roomNumber: '203', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T14:00:00', | |||||
| }, | |||||
| { roomNumber: '204', status: 'available' }, | |||||
| { | |||||
| roomNumber: '205', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T15:30:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '206', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T16:30:00', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| floor: 3, | |||||
| rooms: [ | |||||
| { roomNumber: '301', status: 'available' }, | |||||
| { | |||||
| roomNumber: '302', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T12:45:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '303', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T14:15:00', | |||||
| }, | |||||
| { roomNumber: '304', status: 'available' }, | |||||
| { | |||||
| roomNumber: '305', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T17:00:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '306', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T18:00:00', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| floor: 4, | |||||
| rooms: [ | |||||
| { roomNumber: '401', status: 'available' }, | |||||
| { | |||||
| roomNumber: '402', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T10:00:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '403', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T11:30:00', | |||||
| }, | |||||
| { roomNumber: '404', status: 'available' }, | |||||
| { | |||||
| roomNumber: '405', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T15:45:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '406', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T16:30:00', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| floor: 5, | |||||
| rooms: [ | |||||
| { | |||||
| roomNumber: '501', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T09:00:00', | |||||
| }, | |||||
| { roomNumber: '502', status: 'available' }, | |||||
| { | |||||
| roomNumber: '503', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T10:00:00', | |||||
| }, | |||||
| { | |||||
| roomNumber: '504', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-11T13:00:00', | |||||
| }, | |||||
| { roomNumber: '505', status: 'available' }, | |||||
| { | |||||
| roomNumber: '506', | |||||
| status: 'occupied', | |||||
| checkInTime: '2024-09-12T00:30:00', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ]; |
| import { NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | import { CommonModule } from '@angular/common'; | ||||
| import {RouterModule} from "@angular/router"; | |||||
| import {SharedMaterialModule} from "../../shared/shared-material.module"; | |||||
| import { RouterModule } from '@angular/router'; | |||||
| import { SharedMaterialModule } from '../../shared/shared-material.module'; | |||||
| import { HomePageComponent } from './homepage/home-page.component'; | import { HomePageComponent } from './homepage/home-page.component'; | ||||
| import {homePageRoutes} from "./home-page.routing"; | |||||
| import {SharedModule} from "../../shared/shared.module"; | |||||
| import { homePageRoutes } from './home-page.routing'; | |||||
| import { SharedModule } from '../../shared/shared.module'; | |||||
| import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | ||||
| import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | ||||
| import {FormsModule} from "@angular/forms"; | |||||
| import {SecurityAtmDetailsComponent} from "./security-atm-details/security-atm-details.component"; | |||||
| import {MatTableModule} from "@angular/material/table"; | |||||
| import { FormsModule } from '@angular/forms'; | |||||
| import { SecurityAtmDetailsComponent } from './security-atm-details/security-atm-details.component'; | |||||
| import { MatTableModule } from '@angular/material/table'; | |||||
| import { HotelManagementComponent } from './hotel-management/hotel-management.component'; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| HomePageComponent, | HomePageComponent, | ||||
| CentralizedSecurityManagementComponent, | CentralizedSecurityManagementComponent, | ||||
| SecuritySystemDetailsComponent, | SecuritySystemDetailsComponent, | ||||
| SecurityAtmDetailsComponent | |||||
| SecurityAtmDetailsComponent, | |||||
| HotelManagementComponent, | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| CommonModule, | CommonModule, |
| import { HomePageComponent } from './homepage/home-page.component'; | import { HomePageComponent } from './homepage/home-page.component'; | ||||
| import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | ||||
| import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | ||||
| import {SecurityAtmDetailsComponent} from "./security-atm-details/security-atm-details.component"; | |||||
| import { SecurityAtmDetailsComponent } from './security-atm-details/security-atm-details.component'; | |||||
| import { HotelManagementComponent } from './hotel-management/hotel-management.component'; | |||||
| export const homePageRoutes: Routes = [ | export const homePageRoutes: Routes = [ | ||||
| { | { | ||||
| { | { | ||||
| path: 'atm-monitoring', | path: 'atm-monitoring', | ||||
| component: SecurityAtmDetailsComponent, | component: SecurityAtmDetailsComponent, | ||||
| } | |||||
| }, | |||||
| { | |||||
| path: 'hotel-room-management', | |||||
| component: HotelManagementComponent, | |||||
| }, | |||||
| ]; | ]; |
| Centralized Security Management | Centralized Security Management | ||||
| </button> | </button> | ||||
| <button mat-stroked-button routerLink="./security-system-details"> | <button mat-stroked-button routerLink="./security-system-details"> | ||||
| Security System Details | |||||
| Factory Security System | |||||
| </button> | </button> | ||||
| <button mat-stroked-button routerLink="./atm-monitoring"> | <button mat-stroked-button routerLink="./atm-monitoring"> | ||||
| ATM monitoring | |||||
| ATM Security Management | |||||
| </button> | |||||
| <button mat-stroked-button routerLink="./hotel-room-management"> | |||||
| Hotel Room Management | |||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| <mat-card class="sound-group"> | <mat-card class="sound-group"> | ||||
| <mat-card-header> | <mat-card-header> | ||||
| <mat-card-title>SIREN TIME OFF: {{whistle.time}}s</mat-card-title> | |||||
| <mat-card-title>SIREN TIME OFF: {{ whistle.time }}s</mat-card-title> | |||||
| </mat-card-header> | </mat-card-header> | ||||
| <mat-card-content> | <mat-card-content> | ||||
| <div class="volume-group"> | <div class="volume-group"> | ||||
| <mat-card class="sound-group"> | <mat-card class="sound-group"> | ||||
| <mat-card-header> | <mat-card-header> | ||||
| <mat-card-title>24-HOUR ZONE ALARM TIME: {{alarm.time}}s</mat-card-title> | |||||
| <mat-card-title>24-HOUR ZONE ALARM TIME: {{ alarm.time }}s</mat-card-title> | |||||
| </mat-card-header> | </mat-card-header> | ||||
| <mat-card-content> | <mat-card-content> | ||||
| <div class="volume-group"> | <div class="volume-group"> | ||||
| <app-slider-range | <app-slider-range | ||||
| [value]="alarm.time" | [value]="alarm.time" | ||||
| [icon]="'access_alarm'" | [icon]="'access_alarm'" | ||||
| (valueChange)="emitSetting('alarm',$event)" | |||||
| (valueChange)="emitSetting('alarm', $event)" | |||||
| [disable]="!isConnected" | [disable]="!isConnected" | ||||
| ></app-slider-range> | ></app-slider-range> | ||||
| </div> | </div> |
| .sound-group{ | |||||
| .sound-group { | |||||
| margin: 2rem 3rem; | margin: 2rem 3rem; | ||||
| } | } | ||||
| button{ | |||||
| button { | |||||
| color: #ff7723 !important; | color: #ff7723 !important; | ||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [HomePageComponent] | |||||
| declarations: [HomePageComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(HomePageComponent); | fixture = TestBed.createComponent(HomePageComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| import {Component, OnDestroy, OnInit} from '@angular/core'; | |||||
| import {Subject, filter} from "rxjs"; | |||||
| import {MqttClientService} from "../../../shared/services/mqtt-client.service"; | |||||
| import {takeUntil} from "rxjs/operators"; | |||||
| import {TOPIC_GETTING, TOPIC_SETTING} from "../../../app.constants"; | |||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | |||||
| import { Subject, filter } from 'rxjs'; | |||||
| import { MqttClientService } from '../../../shared/services/mqtt-client.service'; | |||||
| import { takeUntil } from 'rxjs/operators'; | |||||
| import { TOPIC_GETTING, TOPIC_SETTING } from '../../../app.constants'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-homepage', | selector: 'app-homepage', | ||||
| isConnected = false; | isConnected = false; | ||||
| private unsubscribeAll = new Subject(); | private unsubscribeAll = new Subject(); | ||||
| constructor( | |||||
| private mqtt$: MqttClientService | |||||
| ) { | |||||
| this.mqtt$.status$.pipe(takeUntil(this.unsubscribeAll)).subscribe( | |||||
| (isConnected: boolean) => { | |||||
| constructor(private mqtt$: MqttClientService) { | |||||
| this.mqtt$.status$ | |||||
| .pipe(takeUntil(this.unsubscribeAll)) | |||||
| .subscribe((isConnected: boolean) => { | |||||
| this.isConnected = isConnected; | this.isConnected = isConnected; | ||||
| }, | |||||
| ); | |||||
| this.mqtt$.setting$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe((message: any) => { | |||||
| }); | |||||
| this.mqtt$.setting$ | |||||
| .pipe( | |||||
| takeUntil(this.unsubscribeAll), | |||||
| filter((item) => item !== null), | |||||
| ) | |||||
| .subscribe((message: any) => { | |||||
| if (message.id == '0' && message.whistletime) { | if (message.id == '0' && message.whistletime) { | ||||
| this.whistle.time = Number(message.whistletime); | this.whistle.time = Number(message.whistletime); | ||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| ngOnInit() { | |||||
| } | |||||
| ngOnInit() {} | |||||
| emitSetting(type: string, event: any) { | emitSetting(type: string, event: any) { | ||||
| if (this.isConnected) { | if (this.isConnected) { | ||||
| if (type === 'whistle') { | if (type === 'whistle') { | ||||
| this.whistle.time = event; | this.whistle.time = event; | ||||
| this.mqtt$.sendPublish({id: '0', type: 'set', whistletime: event.toString()}, TOPIC_GETTING); | |||||
| this.mqtt$.sendPublish( | |||||
| { id: '0', type: 'set', whistletime: event.toString() }, | |||||
| TOPIC_GETTING, | |||||
| ); | |||||
| } else if (type === 'alarm') { | } else if (type === 'alarm') { | ||||
| this.alarm.time = event; | this.alarm.time = event; | ||||
| this.mqtt$.sendPublish({id: '0', type: 'set', arlamtime: event.toString()}, TOPIC_GETTING); | |||||
| this.mqtt$.sendPublish( | |||||
| { id: '0', type: 'set', arlamtime: event.toString() }, | |||||
| TOPIC_GETTING, | |||||
| ); | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| <div class="container"> | |||||
| <h2>Hotel Room Management</h2> | |||||
| <div class="description-room"> | |||||
| <div> | |||||
| <span style="background-color: lightgray"></span> | |||||
| Floor | |||||
| </div> | |||||
| <div> | |||||
| <span class="available"></span> | |||||
| Available | |||||
| </div> | |||||
| <div> | |||||
| <span class="occupied"></span> | |||||
| Booked | |||||
| </div> | |||||
| </div> | |||||
| <div class="management-container"> | |||||
| <div *ngFor="let floor of data" class="floor-row"> | |||||
| <div class="floor-number">{{ floor.floor }}</div> | |||||
| <div class="room-grid"> | |||||
| <div | |||||
| class="room" | |||||
| *ngFor="let room of floor.rooms" | |||||
| [ngClass]="room.status" | |||||
| > | |||||
| <span class="time-checkin" *ngIf="room.checkInTime"> | |||||
| {{ room.checkInTime | timeElapsed }} | |||||
| </span> | |||||
| {{ room?.roomNumber }} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> |
| .container { | |||||
| height: 100%; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| h2 { | |||||
| text-align: center; | |||||
| padding-top: 1rem; | |||||
| } | |||||
| .description-room { | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| gap: 2rem; | |||||
| padding: 1rem; | |||||
| span { | |||||
| display: inline-block; | |||||
| width: 1rem; | |||||
| height: 0.8rem; | |||||
| } | |||||
| } | |||||
| .management-container { | |||||
| height: 100%; | |||||
| display: grid; | |||||
| grid-template-rows: repeat(5, 1fr); | |||||
| gap: 1rem; | |||||
| } | |||||
| } | |||||
| .floor-row { | |||||
| display: grid; | |||||
| grid-template-columns: 5rem auto; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| gap: 1rem; | |||||
| } | |||||
| .floor-number { | |||||
| text-align: center; | |||||
| font-weight: bold; | |||||
| background-color: lightgray; | |||||
| padding: 10px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| box-sizing: border-box; | |||||
| } | |||||
| .room-grid { | |||||
| display: grid; | |||||
| grid-template-columns: repeat(6, 1fr); | |||||
| gap: 1rem; | |||||
| width: 100%; | |||||
| } | |||||
| .room { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| padding: 20px 0; | |||||
| font-size: 2rem; | |||||
| border: 1px solid black; | |||||
| box-sizing: border-box; | |||||
| transition: all 0.3s ease; | |||||
| position: relative; | |||||
| .time-checkin { | |||||
| font-size: 0.75rem; | |||||
| font-weight: 500; | |||||
| color: green; | |||||
| position: absolute; | |||||
| top: 5px; | |||||
| right: 5px; | |||||
| } | |||||
| } | |||||
| .available { | |||||
| background-color: #ffcdd2; | |||||
| } | |||||
| .occupied { | |||||
| background-color: #c8e6c9; | |||||
| } |
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { HotelManagementComponent } from './hotel-management.component'; | |||||
| describe('HotelManagementComponent', () => { | |||||
| let component: HotelManagementComponent; | |||||
| let fixture: ComponentFixture<HotelManagementComponent>; | |||||
| beforeEach(() => { | |||||
| TestBed.configureTestingModule({ | |||||
| declarations: [HotelManagementComponent], | |||||
| }); | |||||
| fixture = TestBed.createComponent(HotelManagementComponent); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); |
| import { Component, OnInit } from '@angular/core'; | |||||
| import { hotelData } from '../data/fake-data'; | |||||
| import * as moment from 'moment'; | |||||
| @Component({ | |||||
| selector: 'app-hotel-management', | |||||
| templateUrl: './hotel-management.component.html', | |||||
| styleUrls: ['./hotel-management.component.scss'], | |||||
| }) | |||||
| export class HotelManagementComponent implements OnInit { | |||||
| data = hotelData.reverse(); | |||||
| constructor() {} | |||||
| ngOnInit() {} | |||||
| } |
| <div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px"> | |||||
| <div | |||||
| class="px-3 py-5" | |||||
| fxLayout="row" | |||||
| fxLayoutAlign="space-around center" | |||||
| fxLayoutGap="20px" | |||||
| > | |||||
| <div fxFlex="70" class="map-image"> | <div fxFlex="70" class="map-image"> | ||||
| <div class="card-state"> | <div class="card-state"> | ||||
| <img src="assets/images/map-quan5.jpg"> | |||||
| <img src="assets/images/map-quan5.jpg" /> | |||||
| <div class="state t2" id="State2"> | <div class="state t2" id="State2"> | ||||
| <div fxLayout="row"> | <div fxLayout="row"> | ||||
| <div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)"> | |||||
| <img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" | |||||
| <div | |||||
| class="sensor-off" | |||||
| [class.sensor-on]="state1 && state5 && status1" | |||||
| > | |||||
| <img | |||||
| [src]=" | |||||
| state5 && status1 | |||||
| ? 'assets/images/sensor-on.png' | |||||
| : 'assets/images/sensor-off.png' | |||||
| " | |||||
| style="width: 30px; height: 30px; cursor: pointer" | style="width: 30px; height: 30px; cursor: pointer" | ||||
| (click)="openPositionDialog($event, 'ward-11')"> | |||||
| (click)="openPositionDialog($event, 'ward-11')" | |||||
| /> | |||||
| </div> | </div> | ||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div *ngIf="(state1 && state5 && status1)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div> | |||||
| <div | |||||
| *ngIf="state1 && state5 && status1" | |||||
| class="alarm-text" | |||||
| [ngClass]="{ 'alarm-text-on': state1 && state5 && status1 }" | |||||
| > | |||||
| FIRE ALARM | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="state t3" id="State3"> | <div class="state t3" id="State3"> | ||||
| <div fxLayout="row"> | <div fxLayout="row"> | ||||
| <div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)"> | |||||
| <img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" | |||||
| <div | |||||
| class="sensor-off" | |||||
| [class.sensor-on]="state2 && state5 && status2" | |||||
| > | |||||
| <img | |||||
| [src]=" | |||||
| state5 && status2 | |||||
| ? 'assets/images/sensor-on.png' | |||||
| : 'assets/images/sensor-off.png' | |||||
| " | |||||
| style="width: 30px; height: 30px; cursor: pointer" | style="width: 30px; height: 30px; cursor: pointer" | ||||
| (click)="openPositionDialog($event, 'ward-9')"> | |||||
| (click)="openPositionDialog($event, 'ward-9')" | |||||
| /> | |||||
| </div> | </div> | ||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div *ngIf="(state2 && state5 && status2)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">VIBRATION ALARM</div> | |||||
| <div | |||||
| *ngIf="state2 && state5 && status2" | |||||
| class="alarm-text" | |||||
| [ngClass]="{ 'alarm-text-on': state2 && state5 && status2 }" | |||||
| > | |||||
| VIBRATION ALARM | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="state t4" id="State4" fxLayout="row" > | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-2')"> | |||||
| <div class="state t4" id="State4" fxLayout="row"> | |||||
| <img | |||||
| [src]="getImageSource()" | |||||
| style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-2')" | |||||
| /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div class="state t5 tooltip" id="State5" fxLayout="row"> | <div class="state t5 tooltip" id="State5" fxLayout="row"> | ||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-1')"> | |||||
| <img | |||||
| [src]="getImageSource()" | |||||
| style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-1')" | |||||
| /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div class="state t6 tooltip" id="State6" fxLayout="row"> | <div class="state t6 tooltip" id="State6" fxLayout="row"> | ||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-15')"> | |||||
| <img | |||||
| [src]="getImageSource()" | |||||
| style="width: 30px; height: 30px; cursor: pointer" | |||||
| (click)="openPositionDialog($event, 'ward-15')" | |||||
| /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> | <table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> | ||||
| <!-- Position Column --> | <!-- Position Column --> | ||||
| <ng-container matColumnDef="title"> | <ng-container matColumnDef="title"> | ||||
| <th mat-header-cell *matHeaderCellDef > | |||||
| Title | |||||
| </th> | |||||
| <td mat-cell *matCellDef="let element"> {{element.title}} </td> | |||||
| <th mat-header-cell *matHeaderCellDef>Title</th> | |||||
| <td mat-cell *matCellDef="let element">{{ element.title }}</td> | |||||
| </ng-container> | </ng-container> | ||||
| <ng-container matColumnDef="date"> | <ng-container matColumnDef="date"> | ||||
| <th mat-header-cell *matHeaderCellDef > | |||||
| Date | |||||
| </th> | |||||
| <td mat-cell *matCellDef="let element"> {{element.date}} </td> | |||||
| <th mat-header-cell *matHeaderCellDef>Date</th> | |||||
| <td mat-cell *matCellDef="let element">{{ element.date }}</td> | |||||
| </ng-container> | </ng-container> | ||||
| <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | ||||
| <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> | |||||
| <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> | |||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <ng-template #camera> | <ng-template #camera> | ||||
| <div (click)="openDialog()"> | <div (click)="openDialog()"> | ||||
| <img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png"> | |||||
| <img | |||||
| style="width: 30px; height: 30px; cursor: pointer" | |||||
| src="assets/images/camera.png" | |||||
| /> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [SecurityAtmDetailsComponent] | |||||
| declarations: [SecurityAtmDetailsComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(SecurityAtmDetailsComponent); | fixture = TestBed.createComponent(SecurityAtmDetailsComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| import { Component, OnDestroy, OnInit } from '@angular/core'; | import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Subject } from 'rxjs'; | |||||
| import { filter, Subject } from 'rxjs'; | |||||
| import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | ||||
| import { MatDialog } from '@angular/material/dialog'; | import { MatDialog } from '@angular/material/dialog'; | ||||
| import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | ||||
| import { MatTableDataSource } from '@angular/material/table'; | import { MatTableDataSource } from '@angular/material/table'; | ||||
| import { DetailPositionDialogComponent } from '../../../shared/component/detail-postion-dialog/detail-position-dialog.component'; | import { DetailPositionDialogComponent } from '../../../shared/component/detail-postion-dialog/detail-position-dialog.component'; | ||||
| import { atmWarningData } from '../data/fake-data'; | import { atmWarningData } from '../data/fake-data'; | ||||
| import { MqttClientService } from '../../../shared/services/mqtt-client.service'; | |||||
| import { takeUntil } from 'rxjs/operators'; | |||||
| const ELEMENT_DATA: any[] = [ | const ELEMENT_DATA: any[] = [ | ||||
| { title: 'Vibration warning Phường 15', date: '26/08/2024 12:14' }, | { title: 'Vibration warning Phường 15', date: '26/08/2024 12:14' }, | ||||
| constructor( | constructor( | ||||
| private dialog: MatDialog, | private dialog: MatDialog, | ||||
| private alarmSoundService$: AlarmSoundService, | private alarmSoundService$: AlarmSoundService, | ||||
| private mqtt$: MqttClientService, | |||||
| ) {} | ) {} | ||||
| ngOnInit() { | ngOnInit() { | ||||
| const fakeData = { | |||||
| state5: '0', | |||||
| state6: '1', | |||||
| status1: '1', | |||||
| state1: '0', | |||||
| status2: '1', | |||||
| state2: '0', | |||||
| }; | |||||
| this.onMessage(fakeData); | |||||
| setTimeout(() => { | |||||
| { | |||||
| const fakeData = { | |||||
| state5: '1', | |||||
| state6: '1', | |||||
| status1: '1', | |||||
| state1: '0', | |||||
| status2: '1', | |||||
| state2: '0', | |||||
| }; | |||||
| this.onMessage(fakeData); | |||||
| } | |||||
| }, 3000); | |||||
| this.mqtt$.status$ | |||||
| .pipe(takeUntil(this.unsubscribeAll)) | |||||
| .subscribe((isConnected) => { | |||||
| this.isConnected = isConnected; | |||||
| }); | |||||
| this.mqtt$.messages$ | |||||
| .pipe( | |||||
| takeUntil(this.unsubscribeAll), | |||||
| filter((item) => item !== null), | |||||
| ) | |||||
| .subscribe((message) => { | |||||
| this.onMessage(message); | |||||
| }); | |||||
| } | } | ||||
| ngOnDestroy(): void { | ngOnDestroy(): void { |
| <div class="px-3 py-5" 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 fxFlex="50" class="map-image"> | ||||
| <div class="card-state"> | <div class="card-state"> | ||||
| <img src="assets/images/ground.png"> | |||||
| <img src="assets/images/ground.png" /> | |||||
| <div class="state t2" id="State2"> | <div class="state t2" id="State2"> | ||||
| <div fxLayout="row"> | <div fxLayout="row"> | ||||
| <div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)"> | |||||
| <img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px"> | |||||
| <div | |||||
| class="sensor-off" | |||||
| [class.sensor-on]="state1 && state5 && status1" | |||||
| > | |||||
| <img | |||||
| [src]=" | |||||
| state5 && status1 | |||||
| ? 'assets/images/sensor-on.png' | |||||
| : 'assets/images/sensor-off.png' | |||||
| " | |||||
| style="width: 30px; height: 30px" | |||||
| /> | |||||
| </div> | </div> | ||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div *ngIf="(state1 && state5 && status1)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div> | |||||
| <div | |||||
| *ngIf="state1 && state5 && status1" | |||||
| class="alarm-text" | |||||
| [ngClass]="{ 'alarm-text-on': state1 && state5 && status1 }" | |||||
| > | |||||
| FIRE ALARM | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="state t3" id="State3"> | <div class="state t3" id="State3"> | ||||
| <div fxLayout="row"> | <div fxLayout="row"> | ||||
| <div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)"> | |||||
| <img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px"> | |||||
| <div | |||||
| class="sensor-off" | |||||
| [class.sensor-on]="state2 && state5 && status2" | |||||
| > | |||||
| <img | |||||
| [src]=" | |||||
| state5 && status2 | |||||
| ? 'assets/images/sensor-on.png' | |||||
| : 'assets/images/sensor-off.png' | |||||
| " | |||||
| style="width: 30px; height: 30px" | |||||
| /> | |||||
| </div> | </div> | ||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div *ngIf="(state2 && state5 && status2)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">FENCE ALARM</div> | |||||
| <div | |||||
| *ngIf="state2 && state5 && status2" | |||||
| class="alarm-text" | |||||
| [ngClass]="{ 'alarm-text-on': state2 && state5 && status2 }" | |||||
| > | |||||
| FENCE ALARM | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="state t4" id="State4" fxLayout="row" > | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <div class="state t4" id="State4" fxLayout="row"> | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px" /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div class="state t5 tooltip" id="State5" fxLayout="row"> | <div class="state t5 tooltip" id="State5" fxLayout="row"> | ||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px" /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| <div class="state t6 tooltip" id="State6" fxLayout="row"> | <div class="state t6 tooltip" id="State6" fxLayout="row"> | ||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px" /> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | <ng-container [ngTemplateOutlet]="camera"></ng-container> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div fxFlex="30" fxLayout="column" fxLayoutGap="50px"> | <div fxFlex="30" fxLayout="column" fxLayoutGap="50px"> | ||||
| <button [disabled]="!isConnected" mat-flat-button color="{{switchArm ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ switchArm ? 'DISARM' : 'ARM'}}</button> | |||||
| <button | |||||
| [disabled]="!isConnected" | |||||
| mat-flat-button | |||||
| color="{{ switchArm ? 'accent' : 'primary' }}" | |||||
| (click)="toggleState1()" | |||||
| > | |||||
| {{ switchArm ? "DISARM" : "ARM" }} | |||||
| </button> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <ng-template #camera> | <ng-template #camera> | ||||
| <div (click)="openDialog()"> | <div (click)="openDialog()"> | ||||
| <img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png"> | |||||
| <img | |||||
| style="width: 30px; height: 30px; cursor: pointer" | |||||
| src="assets/images/camera.png" | |||||
| /> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [SecuritySystemDetailsComponent] | |||||
| declarations: [SecuritySystemDetailsComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(SecuritySystemDetailsComponent); | fixture = TestBed.createComponent(SecuritySystemDetailsComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| import {Component, OnDestroy, OnInit} from '@angular/core'; | |||||
| import {debounceTime, filter, Subject, Subscription} from "rxjs"; | |||||
| import {SocketService} from "../../../shared/services/socket.service"; | |||||
| import {CameraDialogComponent} from "../../../shared/component/camera-dialog/camera-dialog.component"; | |||||
| import {MatDialog} from "@angular/material/dialog"; | |||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | |||||
| import { debounceTime, filter, Subject, Subscription } from 'rxjs'; | |||||
| import { SocketService } from '../../../shared/services/socket.service'; | |||||
| import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component'; | |||||
| import { MatDialog } from '@angular/material/dialog'; | |||||
| import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; | import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; | ||||
| import { AlarmSoundService } from "../../../shared/services/alarm-sound.service"; | |||||
| import { MqttClientService } from "../../../shared/services/mqtt-client.service"; | |||||
| import {takeUntil} from "rxjs/operators"; | |||||
| import { AlarmSoundService } from '../../../shared/services/alarm-sound.service'; | |||||
| import { MqttClientService } from '../../../shared/services/mqtt-client.service'; | |||||
| import { takeUntil } from 'rxjs/operators'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-security-system-details', | selector: 'app-security-system-details', | ||||
| templateUrl: './security-system-details.component.html', | templateUrl: './security-system-details.component.html', | ||||
| styleUrls: ['./security-system-details.component.scss'] | |||||
| styleUrls: ['./security-system-details.component.scss'], | |||||
| }) | }) | ||||
| export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { | export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { | ||||
| isConnected = false; | isConnected = false; | ||||
| private unsubscribeAll = new Subject(); | private unsubscribeAll = new Subject(); | ||||
| constructor( | constructor( | ||||
| private socketService$: SocketService, | |||||
| private dialog: MatDialog, | private dialog: MatDialog, | ||||
| private confirm$: ConfirmDialogService, | private confirm$: ConfirmDialogService, | ||||
| private alarmSoundService$: AlarmSoundService, | private alarmSoundService$: AlarmSoundService, | ||||
| private mqtt$: MqttClientService | |||||
| private mqtt$: MqttClientService, | |||||
| ) {} | ) {} | ||||
| ngOnInit() { | ngOnInit() { | ||||
| this.mqtt$.status$.pipe(takeUntil(this.unsubscribeAll)).subscribe((isConnected) => { | |||||
| this.mqtt$.status$ | |||||
| .pipe(takeUntil(this.unsubscribeAll)) | |||||
| .subscribe((isConnected) => { | |||||
| this.isConnected = isConnected; | this.isConnected = isConnected; | ||||
| }); | }); | ||||
| this.mqtt$.messages$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe(message => { | |||||
| console.log('detail:',message); | |||||
| this.mqtt$.messages$ | |||||
| .pipe( | |||||
| takeUntil(this.unsubscribeAll), | |||||
| filter((item) => item !== null), | |||||
| ) | |||||
| .subscribe((message) => { | |||||
| this.onMessage(message); | this.onMessage(message); | ||||
| }); | }); | ||||
| } | } | ||||
| toggleState1() { | toggleState1() { | ||||
| this.switchArm = !this.switchArm; | this.switchArm = !this.switchArm; | ||||
| let str = {id: '0', type: 'cmd', state1: this.switchArm.toString()}; | |||||
| let str = { id: '0', type: 'cmd', state1: this.switchArm.toString() }; | |||||
| this.mqtt$.sendPublish(str); | this.mqtt$.sendPublish(str); | ||||
| } | } | ||||
| getImageSource(): string { | getImageSource(): string { | ||||
| return (this.state5 && this.state6) || this.state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; | |||||
| return (this.state5 && this.state6) || this.state5 | |||||
| ? 'assets/images/sensor-on.png' | |||||
| : 'assets/images/sensor-off.png'; | |||||
| } | } | ||||
| onMessage(message: any) { | onMessage(message: any) { | ||||
| this.state5 = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | this.state5 = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | ||||
| this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF'; | this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF'; | ||||
| this.switchArm = message.state5 == '1';// alarm 9h && 6h // 1 ON, 0 OFF | |||||
| this.switchArm = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||||
| this.isReady = message.ready == '1'; | this.isReady = message.ready == '1'; | ||||
| if ((message.status1 == '0' || message.status2 == '0') && this.state5) { // not ready and ON arm | |||||
| if ((message.status1 == '0' || message.status2 == '0') && this.state5) { | |||||
| // not ready and ON arm | |||||
| const data = []; | const data = []; | ||||
| if (message.status1 == '0'){ | |||||
| data.push({key: 'status1', value: false}) | |||||
| if (message.status1 == '0') { | |||||
| data.push({ key: 'status1', value: false }); | |||||
| } | } | ||||
| if (message.status2 == '0'){ | |||||
| data.push({key: 'status2', value: false}) | |||||
| if (message.status2 == '0') { | |||||
| data.push({ key: 'status2', value: false }); | |||||
| } | } | ||||
| this.confirm$.openDialog(data); | this.confirm$.openDialog(data); | ||||
| } | } | ||||
| this.alarmSoundService$.startAlarm(this.state5, this.state1,this.status1, this.state2, this.status2); | |||||
| this.alarmSoundService$.startAlarm( | |||||
| this.state5, | |||||
| this.state1, | |||||
| this.status1, | |||||
| this.state2, | |||||
| this.status2, | |||||
| ); | |||||
| } | } | ||||
| } | } | ||||
| openDialog(): void { | openDialog(): void { |
| <div style="padding: 10px 20px" fxLayout="row" fxLayoutAlign="center"> | <div style="padding: 10px 20px" fxLayout="row" fxLayoutAlign="center"> | ||||
| <div style="text-align: center; font-size: 24px; width: 95%">Camera Stream</div> | |||||
| <div style="text-align: center; font-size: 24px; width: 95%"> | |||||
| Camera Stream | |||||
| </div> | |||||
| <button mat-icon-button color="warn" (click)="onClose()"> | <button mat-icon-button color="warn" (click)="onClose()"> | ||||
| <mat-icon>close</mat-icon> | <mat-icon>close</mat-icon> | ||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| <div mat-dialog-content> | <div mat-dialog-content> | ||||
| <div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="10px" | |||||
| style="height: 50%;" | |||||
| *ngIf="!player"> | |||||
| <div | |||||
| fxLayout="row" | |||||
| fxLayoutAlign="center center" | |||||
| fxLayoutGap="10px" | |||||
| style="height: 50%" | |||||
| *ngIf="!player" | |||||
| > | |||||
| <div class="circle-loader"></div> | <div class="circle-loader"></div> | ||||
| <div class="loader"></div> | <div class="loader"></div> | ||||
| </div> | </div> |
| width: 90% !important; | width: 90% !important; | ||||
| //height: 90%; | //height: 90%; | ||||
| display: block; | display: block; | ||||
| margin: 0 auto | |||||
| margin: 0 auto; | |||||
| } | } | ||||
| .mat-mdc-dialog-content{ | |||||
| .mat-mdc-dialog-content { | |||||
| max-height: unset; | max-height: unset; | ||||
| } | } | ||||
| animation: l4 1s steps(4) infinite; | animation: l4 1s steps(4) infinite; | ||||
| } | } | ||||
| .loader:before { | .loader:before { | ||||
| content:"Loading..." | |||||
| content: "Loading..."; | |||||
| } | |||||
| @keyframes l4 { | |||||
| to { | |||||
| clip-path: inset(0 -1ch 0 0); | |||||
| } | |||||
| } | } | ||||
| @keyframes l4 {to{clip-path: inset(0 -1ch 0 0)}} | |||||
| .circle-loader { | .circle-loader { | ||||
| width: 50px; | width: 50px; | ||||
| aspect-ratio: 1; | aspect-ratio: 1; | ||||
| border-radius: 50%; | border-radius: 50%; | ||||
| background: | background: | ||||
| radial-gradient(farthest-side,#ffa516 94%,#0000) top/8px 8px no-repeat, | |||||
| conic-gradient(#0000 30%,#ffa516); | |||||
| -webkit-mask: radial-gradient(farthest-side,#0000 calc(100% - 8px),#000 0); | |||||
| radial-gradient(farthest-side, #ffa516 94%, #0000) top/8px 8px no-repeat, | |||||
| conic-gradient(#0000 30%, #ffa516); | |||||
| -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0); | |||||
| animation: l13 1s infinite linear; | animation: l13 1s infinite linear; | ||||
| } | } | ||||
| @keyframes l13{ | |||||
| 100%{transform: rotate(1turn)} | |||||
| @keyframes l13 { | |||||
| 100% { | |||||
| transform: rotate(1turn); | |||||
| } | |||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [CameraDialogComponent] | |||||
| declarations: [CameraDialogComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(CameraDialogComponent); | fixture = TestBed.createComponent(CameraDialogComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core'; | |||||
| import {MatDialogRef} from "@angular/material/dialog"; | |||||
| import {loadPlayer, Player} from "rtsp-relay/browser"; | |||||
| import { | |||||
| AfterViewInit, | |||||
| Component, | |||||
| ElementRef, | |||||
| OnDestroy, | |||||
| ViewChild, | |||||
| } from '@angular/core'; | |||||
| import { MatDialogRef } from '@angular/material/dialog'; | |||||
| import { loadPlayer, Player } from 'rtsp-relay/browser'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-camera-dialog', | selector: 'app-camera-dialog', | ||||
| templateUrl: './camera-dialog.component.html', | templateUrl: './camera-dialog.component.html', | ||||
| styleUrls: ['./camera-dialog.component.scss'] | |||||
| styleUrls: ['./camera-dialog.component.scss'], | |||||
| }) | }) | ||||
| export class CameraDialogComponent implements AfterViewInit, OnDestroy{ | |||||
| export class CameraDialogComponent implements AfterViewInit, OnDestroy { | |||||
| player?: Player; | player?: Player; | ||||
| @ViewChild('videoPlayer') videoPlayer?: ElementRef<HTMLCanvasElement>; | @ViewChild('videoPlayer') videoPlayer?: ElementRef<HTMLCanvasElement>; | ||||
| constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) { } | |||||
| constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) {} | |||||
| async ngAfterViewInit() { | async ngAfterViewInit() { | ||||
| this.player = await loadPlayer({ | this.player = await loadPlayer({ | ||||
| url: 'wss://wss.aztrace.vn/stream', | url: 'wss://wss.aztrace.vn/stream', | ||||
| canvas: this.videoPlayer!.nativeElement, | canvas: this.videoPlayer!.nativeElement, | ||||
| onDisconnect(): void{ | |||||
| onDisconnect(): void { | |||||
| console.log('disconnect camera, please wait'); | console.log('disconnect camera, please wait'); | ||||
| } | |||||
| }, | |||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| ngOnDestroy(): void { | ngOnDestroy(): void { | ||||
| if (this.player){ | |||||
| if (this.player) { | |||||
| this.player?.destroy(); | this.player?.destroy(); | ||||
| } | } | ||||
| } | } |
| <h5 mat-dialog-title >CONFIRM TO IGNORE THE WARNING</h5> | |||||
| <h5 mat-dialog-title>CONFIRM TO IGNORE THE WARNING</h5> | |||||
| <mat-dialog-content style="padding-bottom: 4px" *ngIf="data.length"> | <mat-dialog-content style="padding-bottom: 4px" *ngIf="data.length"> | ||||
| <div *ngFor="let item of data"> | <div *ngFor="let item of data"> | ||||
| <mat-checkbox [(ngModel)]="item.value" | |||||
| <mat-checkbox | |||||
| [(ngModel)]="item.value" | |||||
| (ngModelChange)="submitChange(item)" | (ngModelChange)="submitChange(item)" | ||||
| [disabled]="isConnected && item.value" | [disabled]="isConnected && item.value" | ||||
| name="sensor"> | |||||
| {{ (item.key == 'status1' ? 'Fire Alarm' : 'Fence Alarm') }} | |||||
| name="sensor" | |||||
| > | |||||
| {{ item.key == "status1" ? "Fire Alarm" : "Fence Alarm" }} | |||||
| </mat-checkbox> | </mat-checkbox> | ||||
| </div> | </div> | ||||
| </mat-dialog-content> | </mat-dialog-content> |
| .mat-mdc-dialog-title { | .mat-mdc-dialog-title { | ||||
| border-bottom: solid 1px #eeeeee; | border-bottom: solid 1px #eeeeee; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| color: #FFFFFF; | |||||
| color: #ffffff; | |||||
| background: orange; | background: orange; | ||||
| } | } | ||||
| ::ng-deep.mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background{ | |||||
| ::ng-deep.mdc-checkbox | |||||
| .mdc-checkbox__native-control:enabled:checked | |||||
| ~ .mdc-checkbox__background { | |||||
| background: orange !important; | background: orange !important; | ||||
| border: orange !important; | border: orange !important; | ||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [ConfirmDialogComponent] | |||||
| declarations: [ConfirmDialogComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(ConfirmDialogComponent); | fixture = TestBed.createComponent(ConfirmDialogComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| import {Component, Inject, OnDestroy, OnInit} from '@angular/core'; | |||||
| import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; | |||||
| import {SocketService} from "../../services/socket.service"; | |||||
| import {Subscription} from "rxjs"; | |||||
| import {MqttClientService} from "../../services/mqtt-client.service"; | |||||
| import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; | |||||
| import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | |||||
| import { SocketService } from '../../services/socket.service'; | |||||
| import { Subscription } from 'rxjs'; | |||||
| import { MqttClientService } from '../../services/mqtt-client.service'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-confirm-dialog', | selector: 'app-confirm-dialog', | ||||
| templateUrl: './confirm-dialog.component.html', | templateUrl: './confirm-dialog.component.html', | ||||
| styleUrls: ['./confirm-dialog.component.scss'], | styleUrls: ['./confirm-dialog.component.scss'], | ||||
| }) | }) | ||||
| export class ConfirmDialogComponent implements OnDestroy{ | |||||
| export class ConfirmDialogComponent implements OnDestroy { | |||||
| private statusSubscription?: Subscription; | private statusSubscription?: Subscription; | ||||
| isConnected: boolean = false; | isConnected: boolean = false; | ||||
| private socketService$: SocketService, | private socketService$: SocketService, | ||||
| private mqtt$: MqttClientService, | private mqtt$: MqttClientService, | ||||
| ) { | ) { | ||||
| this.statusSubscription = this.mqtt$.status$.subscribe( | |||||
| (isConnected) => { | |||||
| this.statusSubscription = this.mqtt$.status$.subscribe((isConnected) => { | |||||
| this.isConnected = isConnected; | this.isConnected = isConnected; | ||||
| }, | |||||
| ); | |||||
| }); | |||||
| } | } | ||||
| submitChange(item: any): void{ | |||||
| if(this.isConnected) { | |||||
| submitChange(item: any): void { | |||||
| if (this.isConnected) { | |||||
| const position = item.key === 'status1' ? '1' : '2'; | const position = item.key === 'status1' ? '1' : '2'; | ||||
| this.mqtt$.sendPublish({ id: '0', type: 'bypass', state: position}) | |||||
| this.mqtt$.sendPublish({ id: '0', type: 'bypass', state: position }); | |||||
| item.value = true; | item.value = true; | ||||
| } | } | ||||
| } | } | ||||
| ngOnDestroy() { | |||||
| ngOnDestroy() { | |||||
| if (this.statusSubscription) { | if (this.statusSubscription) { | ||||
| this.statusSubscription.unsubscribe(); | this.statusSubscription.unsubscribe(); | ||||
| } | } |
| <div class="popup-container"> | <div class="popup-container"> | ||||
| <div class="popup-header"> | <div class="popup-header"> | ||||
| <span class="popup-title">{{item.title}}</span> | |||||
| <span class="popup-title">{{ item.title }}</span> | |||||
| <mat-icon class="close-button" (click)="onClose()">close</mat-icon> | <mat-icon class="close-button" (click)="onClose()">close</mat-icon> | ||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <p><strong>Địa điểm:</strong> {{item.detail.position}}</p> | |||||
| <p><strong>Tọa độ:</strong> {{item.detail.coordinates.lat}}, {{item.detail.coordinates.lng}}</p> | |||||
| <p><strong>Thời gian:</strong> {{item.detail.time}}</p> | |||||
| <p><strong>Địa điểm:</strong> {{ item.detail.position }}</p> | |||||
| <p> | |||||
| <strong>Tọa độ:</strong> {{ item.detail.coordinates.lat }}, | |||||
| {{ item.detail.coordinates.lng }} | |||||
| </p> | |||||
| <p><strong>Thời gian:</strong> {{ item.detail.time }}</p> | |||||
| </div> | </div> | ||||
| </div> | </div> |
| } | } | ||||
| .popup-title { | .popup-title { | ||||
| color: #F33152; | |||||
| color: #f33152; | |||||
| font-weight: 500; | font-weight: 500; | ||||
| } | } | ||||
| .close-button { | .close-button { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| color: #eeeeee; | color: #eeeeee; | ||||
| &:hover{ | |||||
| &:hover { | |||||
| color: #000000; | color: #000000; | ||||
| } | } | ||||
| } | } | ||||
| <div class="layout-container"> | <div class="layout-container"> | ||||
| <div class="header mat-elevation-z1"> | <div class="header mat-elevation-z1"> | ||||
| <img src="../../../../../../assets/images/logo.png" > | |||||
| <img src="../../../../../../assets/images/logo.png" /> | |||||
| <div> | <div> | ||||
| <button mat-button routerLink="/homepage" routerLinkActive="active">Home</button> | |||||
| <button mat-button routerLink="/homepage" routerLinkActive="active"> | |||||
| Home | |||||
| </button> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div> | |||||
| <div style="flex: 1"> | |||||
| <router-outlet></router-outlet> | <router-outlet></router-outlet> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| .layout-container{ | |||||
| .layout-container { | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| height: 100%; | |||||
| .header { | .header { | ||||
| height: 3rem; | height: 3rem; | ||||
| padding: .25rem 2rem; | |||||
| padding: 0.25rem 2rem; | |||||
| display: flex; | display: flex; | ||||
| flex-direction: row; | flex-direction: row; | ||||
| align-items: center; | align-items: center; | ||||
| img{ | |||||
| img { | |||||
| width: 7rem; | width: 7rem; | ||||
| height: 3rem; | height: 3rem; | ||||
| } | } | ||||
| color: #ff7723 !important; | color: #ff7723 !important; | ||||
| } | } | ||||
| } | } | ||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [LayoutComponent] | |||||
| declarations: [LayoutComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(LayoutComponent); | fixture = TestBed.createComponent(LayoutComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| @Component({ | @Component({ | ||||
| selector: 'app-layout', | selector: 'app-layout', | ||||
| templateUrl: './layout.component.html', | templateUrl: './layout.component.html', | ||||
| styleUrls: ['./layout.component.scss'] | |||||
| styleUrls: ['./layout.component.scss'], | |||||
| }) | }) | ||||
| export class LayoutComponent { | |||||
| } | |||||
| export class LayoutComponent {} |
| <div class="speaker"> | <div class="speaker"> | ||||
| <mat-icon>{{ icon }}</mat-icon> | <mat-icon>{{ icon }}</mat-icon> | ||||
| </div> | </div> | ||||
| <button class="volume-control" [disabled]="disable" (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"> | |||||
| <button | |||||
| class="volume-control" | |||||
| [disabled]="disable" | |||||
| (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" | |||||
| > | |||||
| <title>Icons/Minus square fill</title> | <title>Icons/Minus square fill</title> | ||||
| <g id="Icons/Minus-square-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |||||
| <g | |||||
| id="Icons/Minus-square-fill" | |||||
| stroke="none" | |||||
| stroke-width="1" | |||||
| fill="none" | |||||
| fill-rule="evenodd" | |||||
| > | |||||
| <g id="MinusSquare"> | <g id="MinusSquare"> | ||||
| <path | <path | ||||
| d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M15.75,12.75 L8.25,12.75 C7.83578644,12.75 7.5,12.4142136 7.5,12 C7.5,11.5857864 7.83578644,11.25 8.25,11.25 L15.75,11.25 C16.1642136,11.25 16.5,11.5857864 16.5,12 C16.5,12.4142136 16.1642136,12.75 15.75,12.75 Z" | d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M15.75,12.75 L8.25,12.75 C7.83578644,12.75 7.5,12.4142136 7.5,12 C7.5,11.5857864 7.83578644,11.25 8.25,11.25 L15.75,11.25 C16.1642136,11.25 16.5,11.5857864 16.5,12 C16.5,12.4142136 16.1642136,12.75 15.75,12.75 Z" | ||||
| id="Shape" fill="currentColor" fill-rule="nonzero"></path> | |||||
| id="Shape" | |||||
| fill="currentColor" | |||||
| fill-rule="nonzero" | |||||
| ></path> | |||||
| <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> | <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> | ||||
| </g> | </g> | ||||
| </g> | </g> | ||||
| </button> | </button> | ||||
| <div class="volume-control" style="padding: 1rem 0"> | <div class="volume-control" style="padding: 1rem 0"> | ||||
| <div class="volume-slider"> | <div class="volume-slider"> | ||||
| <input type="range" [(ngModel)]="value" (input)="onSliderChange($event)" | |||||
| <input | |||||
| type="range" | |||||
| [(ngModel)]="value" | |||||
| (input)="onSliderChange($event)" | |||||
| [ngStyle]="onSliderChangeBackground()" | [ngStyle]="onSliderChangeBackground()" | ||||
| [max]="max" | [max]="max" | ||||
| [disabled]="disable"> | |||||
| [disabled]="disable" | |||||
| /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <button class="volume-control" [disabled]="disable" (click)="increaseVolume()"> | |||||
| <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"> | |||||
| <button | |||||
| class="volume-control" | |||||
| [disabled]="disable" | |||||
| (click)="increaseVolume()" | |||||
| > | |||||
| <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/Plus square fill</title> | <title>Icons/Plus square fill</title> | ||||
| <g id="Icons/Plus-square-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |||||
| <g | |||||
| id="Icons/Plus-square-fill" | |||||
| stroke="none" | |||||
| stroke-width="1" | |||||
| fill="none" | |||||
| fill-rule="evenodd" | |||||
| > | |||||
| <g id="Plus"> | <g id="Plus"> | ||||
| <path | <path | ||||
| d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M17.25,12.75 L12.75,12.75 L12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.75 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.25 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 Z" | d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M17.25,12.75 L12.75,12.75 L12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.75 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.25 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 Z" | ||||
| id="Shape" fill="currentColor" fill-rule="nonzero"></path> | |||||
| id="Shape" | |||||
| fill="currentColor" | |||||
| fill-rule="nonzero" | |||||
| ></path> | |||||
| <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> | <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> | ||||
| </g> | </g> | ||||
| </g> | </g> |
| 0 25%, | 0 25%, | ||||
| 50% 0, | 50% 0, | ||||
| 50% 1px, | 50% 1px, | ||||
| 1px calc(25% + .5px), | |||||
| 1px calc(75% - .5px), | |||||
| 1px calc(25% + 0.5px), | |||||
| 1px calc(75% - 0.5px), | |||||
| 50% calc(100% - 1px), | 50% calc(100% - 1px), | ||||
| calc(100% - 1px) calc(75% - .5px), | |||||
| calc(100% - 1px) calc(25% + .5px), | |||||
| calc(100% - 1px) calc(75% - 0.5px), | |||||
| calc(100% - 1px) calc(25% + 0.5px), | |||||
| 50% 1px | 50% 1px | ||||
| ); | ); | ||||
| background-color: #ff7723; | background-color: #ff7723; | ||||
| } | } | ||||
| .volume-control { | .volume-control { | ||||
| padding: .63rem; | |||||
| border-top: solid .06rem #ff7723; | |||||
| border-bottom: solid .06rem #ff7723; | |||||
| padding: 0.63rem; | |||||
| border-top: solid 0.06rem #ff7723; | |||||
| border-bottom: solid 0.06rem #ff7723; | |||||
| background-color: transparent; | background-color: transparent; | ||||
| border-left: none; | border-left: none; | ||||
| border-right: none; | border-right: none; | ||||
| width: 1.5rem; | width: 1.5rem; | ||||
| height: 1.5rem; | height: 1.5rem; | ||||
| } | } | ||||
| } | } | ||||
| .volume-slider { | .volume-slider { | ||||
| border: .06rem solid #eed3c2; | |||||
| height: .75rem; | |||||
| border: 0.06rem solid #eed3c2; | |||||
| height: 0.75rem; | |||||
| width: 39rem; | width: 39rem; | ||||
| position: relative; | position: relative; | ||||
| width: 38.5rem; | width: 38.5rem; | ||||
| appearance: none; | appearance: none; | ||||
| background-color: transparent; | background-color: transparent; | ||||
| margin: .25rem; | |||||
| margin: 0.25rem; | |||||
| &::-webkit-slider-thumb { | &::-webkit-slider-thumb { | ||||
| -webkit-appearance: none; | -webkit-appearance: none; | ||||
| appearance: none; | appearance: none; | ||||
| background: #ff7723 | |||||
| ; | |||||
| border: .06rem solid #ff7723; | |||||
| height: .88rem; | |||||
| width: .88rem; | |||||
| background: #ff7723; | |||||
| border: 0.06rem solid #ff7723; | |||||
| height: 0.88rem; | |||||
| width: 0.88rem; | |||||
| padding: 2px; | padding: 2px; | ||||
| background-clip: content-box; | background-clip: content-box; | ||||
| transition: transform .2s ease; | |||||
| transition: transform 0.2s ease; | |||||
| &:focus { | &:focus { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .12rem solid #ff7723; | |||||
| border: 0.12rem solid #ff7723; | |||||
| transform: scale(1.2); | transform: scale(1.2); | ||||
| } | } | ||||
| &:hover { | &:hover { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .12rem solid #ff7723; | |||||
| border: 0.12rem solid #ff7723; | |||||
| transform: scale(1.2); | transform: scale(1.2); | ||||
| } | } | ||||
| } | } | ||||
| &::-moz-range-thumb { | &::-moz-range-thumb { | ||||
| border-radius: 0; | border-radius: 0; | ||||
| background: #ff7723; | background: #ff7723; | ||||
| border: .06rem solid #ff7723; | |||||
| height: .5rem; | |||||
| width: .5rem ; | |||||
| border: 0.06rem solid #ff7723; | |||||
| height: 0.5rem; | |||||
| width: 0.5rem; | |||||
| padding: 3px; | padding: 3px; | ||||
| background-clip: content-box; | background-clip: content-box; | ||||
| &:focus { | &:focus { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .14rem solid #ff7723; | |||||
| border: 0.14rem solid #ff7723; | |||||
| transform: scale(1); | transform: scale(1); | ||||
| } | } | ||||
| &:hover { | &:hover { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .14rem solid #ff7723; | |||||
| border: 0.14rem solid #ff7723; | |||||
| transform: scale(1); | transform: scale(1); | ||||
| } | } | ||||
| } | } | ||||
| -webkit-appearance: none; | -webkit-appearance: none; | ||||
| appearance: none; | appearance: none; | ||||
| background: #ff7723; | background: #ff7723; | ||||
| border: .06rem solid #ff7723; | |||||
| height: .88rem; | |||||
| width: .88rem; | |||||
| border: 0.06rem solid #ff7723; | |||||
| height: 0.88rem; | |||||
| width: 0.88rem; | |||||
| padding: 3px; | padding: 3px; | ||||
| background-clip: content-box; | background-clip: content-box; | ||||
| &:focus { | &:focus { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .12rem solid #ff7723; | |||||
| border: 0.12rem solid #ff7723; | |||||
| transform: scale(1.2); | transform: scale(1.2); | ||||
| } | } | ||||
| &:hover { | &:hover { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border: .12rem solid #ff7723; | |||||
| border: 0.12rem solid #ff7723; | |||||
| transform: scale(1.2); | transform: scale(1.2); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| svg{ | |||||
| svg { | |||||
| color: #ff7723; | color: #ff7723; | ||||
| } | } |
| beforeEach(() => { | beforeEach(() => { | ||||
| TestBed.configureTestingModule({ | TestBed.configureTestingModule({ | ||||
| declarations: [SliderRangeComponent] | |||||
| declarations: [SliderRangeComponent], | |||||
| }); | }); | ||||
| fixture = TestBed.createComponent(SliderRangeComponent); | fixture = TestBed.createComponent(SliderRangeComponent); | ||||
| component = fixture.componentInstance; | component = fixture.componentInstance; |
| Output, | Output, | ||||
| } from '@angular/core'; | } from '@angular/core'; | ||||
| import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import {debounceTime, Subject} from 'rxjs'; | |||||
| import { debounceTime, Subject } from 'rxjs'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-slider-range', | selector: 'app-slider-range', | ||||
| private valueChangeSubject = new Subject<number>(); | private valueChangeSubject = new Subject<number>(); | ||||
| constructor() { | constructor() { | ||||
| this.valueChangeSubject.pipe(debounceTime(500)).subscribe(value => { | |||||
| this.valueChangeSubject.pipe(debounceTime(500)).subscribe((value) => { | |||||
| this.valueChange.emit(value); | this.valueChange.emit(value); | ||||
| }); | }); | ||||
| } | } | ||||
| this.onChange(this.value); | this.onChange(this.value); | ||||
| this.onTouched(); | this.onTouched(); | ||||
| this.valueChangeSubject.next(this.value); | this.valueChangeSubject.next(this.value); | ||||
| } | } | ||||
| } | } | ||||
| import { Pipe, PipeTransform } from '@angular/core'; | |||||
| import * as moment from 'moment'; | |||||
| @Pipe({ | |||||
| name: 'timeElapsed', | |||||
| }) | |||||
| export class TimeElapsedPipe implements PipeTransform { | |||||
| transform(checkInTime: string | null): any { | |||||
| if (!checkInTime) return; | |||||
| const checkInMoment = moment(checkInTime); | |||||
| const now = moment(); | |||||
| return moment.duration(now.diff(checkInMoment)).humanize(); | |||||
| } | |||||
| } |
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||
| import {max} from "rxjs"; | |||||
| import { max } from 'rxjs'; | |||||
| @Injectable({ | @Injectable({ | ||||
| providedIn: 'root', | providedIn: 'root', | ||||
| constructor() { | constructor() { | ||||
| this.audio.src = 'assets/sound/alarm_5m.mp3'; | this.audio.src = 'assets/sound/alarm_5m.mp3'; | ||||
| this.audio.load(); | this.audio.load(); | ||||
| } | } | ||||
| playSound(): void { | playSound(): void { | ||||
| if(!this.isPlay){ | |||||
| this.audio.play().then(() => { | |||||
| if (!this.isPlay) { | |||||
| this.audio | |||||
| .play() | |||||
| .then(() => { | |||||
| this.isPlay = true; | this.isPlay = true; | ||||
| this.alertInterval = setInterval(() => { | this.alertInterval = setInterval(() => { | ||||
| this.stopAlarm(); | this.stopAlarm(); | ||||
| }, this.alertDuration); | }, this.alertDuration); | ||||
| }).catch((error) => { | |||||
| }) | |||||
| .catch((error) => { | |||||
| console.error('Error playing audio:', error); | console.error('Error playing audio:', error); | ||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| startAlarm(isTurnOn: boolean, fireArm: boolean, fireStatus: boolean, fenceArm: boolean, fenceStatus: boolean): void { | |||||
| startAlarm( | |||||
| isTurnOn: boolean, | |||||
| fireArm: boolean, | |||||
| fireStatus: boolean, | |||||
| fenceArm: boolean, | |||||
| fenceStatus: boolean, | |||||
| ): void { | |||||
| if (!isTurnOn) { | if (!isTurnOn) { | ||||
| this.stopAlarm(); | this.stopAlarm(); | ||||
| return; | return; | ||||
| this.audio.currentTime = 0; | this.audio.currentTime = 0; | ||||
| this.isPlay = false; | this.isPlay = false; | ||||
| } | } | ||||
| if (this.alertInterval){ | |||||
| if (this.alertInterval) { | |||||
| clearInterval(this.alertInterval); | clearInterval(this.alertInterval); | ||||
| } | } | ||||
| } | } |
| if (!this.isDialogOpen) { | if (!this.isDialogOpen) { | ||||
| this.isDialogOpen = true; | this.isDialogOpen = true; | ||||
| const dialogRef = this.dialog.open(ConfirmDialogComponent, { | const dialogRef = this.dialog.open(ConfirmDialogComponent, { | ||||
| data: data | |||||
| data: data, | |||||
| }); | }); | ||||
| dialogRef | dialogRef | ||||
| .afterClosed() | .afterClosed() |
| IPublishOptions, | IPublishOptions, | ||||
| } from 'ngx-mqtt'; | } from 'ngx-mqtt'; | ||||
| import { IClientSubscribeOptions } from 'mqtt-browser'; | import { IClientSubscribeOptions } from 'mqtt-browser'; | ||||
| import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs'; | |||||
| import {TOPIC_GETTING, TOPIC_INFO, TOPIC_LOG, TOPIC_SETTING} from "../../app.constants"; | |||||
| import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; | |||||
| import { | |||||
| TOPIC_GETTING, | |||||
| TOPIC_INFO, | |||||
| TOPIC_LOG, | |||||
| TOPIC_SETTING, | |||||
| } from '../../app.constants'; | |||||
| @Injectable({ | @Injectable({ | ||||
| providedIn: 'root' | |||||
| providedIn: 'root', | |||||
| }) | }) | ||||
| export class MqttClientService { | export class MqttClientService { | ||||
| client: MqttService | undefined; | client: MqttService | undefined; | ||||
| connection = { | connection = { | ||||
| hostname: 'broker.hivemq.com', | hostname: 'broker.hivemq.com', | ||||
| clean: true, | clean: true, | ||||
| path:'/mqtt', | |||||
| path: '/mqtt', | |||||
| port: 8884, | port: 8884, | ||||
| reconnectPeriod: 4000, | reconnectPeriod: 4000, | ||||
| protocol: 'wss', | protocol: 'wss', | ||||
| } | |||||
| }; | |||||
| receiveNews = ''; | receiveNews = ''; | ||||
| isConnection = false; | isConnection = false; | ||||
| private statusSubject = new BehaviorSubject<boolean>(this.isConnection); | private statusSubject = new BehaviorSubject<boolean>(this.isConnection); | ||||
| // 创建连接 | // 创建连接 | ||||
| createConnection() { | createConnection() { | ||||
| try { | try { | ||||
| this.client?.connect(this.connection as IMqttServiceOptions) | |||||
| this.client?.connect(this.connection as IMqttServiceOptions); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.log('mqtt.connect error', error); | console.log('mqtt.connect error', error); | ||||
| } | } | ||||
| topic: TOPIC_LOG, | topic: TOPIC_LOG, | ||||
| qos: 0, | qos: 0, | ||||
| }; | }; | ||||
| this.curSubscription = this.client?.observe(topic, { qos, dup: false } as IClientSubscribeOptions).subscribe((message: IMqttMessage) => { | |||||
| this.curSubscription = this.client | |||||
| ?.observe(topic, { qos, dup: false } as IClientSubscribeOptions) | |||||
| .subscribe((message: IMqttMessage) => { | |||||
| try { | try { | ||||
| let plainMessage = ""; | |||||
| let plainMessage = ''; | |||||
| for (var i = 0; i < message.payload.length; i++) { | for (var i = 0; i < message.payload.length; i++) { | ||||
| plainMessage += String.fromCharCode(parseInt(String(message.payload[i]))); | |||||
| plainMessage += String.fromCharCode( | |||||
| parseInt(String(message.payload[i])), | |||||
| ); | |||||
| } | } | ||||
| console.log(JSON.parse(plainMessage)); | console.log(JSON.parse(plainMessage)); | ||||
| this.messagesSubject$.next(JSON.parse(plainMessage)); | this.messagesSubject$.next(JSON.parse(plainMessage)); | ||||
| }catch (e) { | |||||
| } catch (e) { | |||||
| console.log(e); | console.log(e); | ||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| // 订阅主题 | // 订阅主题 | ||||
| getSetting() { | getSetting() { | ||||
| const { topic, qos } = { | const { topic, qos } = { | ||||
| topic: TOPIC_SETTING, | topic: TOPIC_SETTING, | ||||
| qos: 0, | qos: 0, | ||||
| }; | }; | ||||
| this.settingSubscription = this.client?.observe(topic, { qos, dup: false } as IClientSubscribeOptions).subscribe((message: IMqttMessage) => { | |||||
| this.settingSubscription = this.client | |||||
| ?.observe(topic, { qos, dup: false } as IClientSubscribeOptions) | |||||
| .subscribe((message: IMqttMessage) => { | |||||
| try { | try { | ||||
| let plainMessage = ""; | |||||
| let plainMessage = ''; | |||||
| for (var i = 0; i < message.payload.length; i++) { | for (var i = 0; i < message.payload.length; i++) { | ||||
| plainMessage += String.fromCharCode(parseInt(String(message.payload[i]))); | |||||
| plainMessage += String.fromCharCode( | |||||
| parseInt(String(message.payload[i])), | |||||
| ); | |||||
| } | } | ||||
| console.log(JSON.parse(plainMessage)); | console.log(JSON.parse(plainMessage)); | ||||
| this.settingSubject$.next(JSON.parse(plainMessage)); | this.settingSubject$.next(JSON.parse(plainMessage)); | ||||
| }catch (e) { | |||||
| } catch (e) { | |||||
| console.log(e); | console.log(e); | ||||
| } | } | ||||
| }); | }); | ||||
| // 取消订阅 | // 取消订阅 | ||||
| clearLog() { | clearLog() { | ||||
| this.curSubscription?.unsubscribe() | |||||
| this.curSubscription?.unsubscribe(); | |||||
| this.settingSubscription?.unsubscribe(); | this.settingSubscription?.unsubscribe(); | ||||
| } | } | ||||
| // 发送消息 | // 发送消息 | ||||
| doPublish(data: any) { | doPublish(data: any) { | ||||
| this.client?.unsafePublish(TOPIC_INFO, JSON.stringify(data), {qos: 0} as IPublishOptions); | |||||
| this.client?.unsafePublish(TOPIC_INFO, JSON.stringify(data), { | |||||
| qos: 0, | |||||
| } as IPublishOptions); | |||||
| } | } | ||||
| // 发送消息 | // 发送消息 | ||||
| sendPublish(data: any, topic = TOPIC_INFO) { | sendPublish(data: any, topic = TOPIC_INFO) { | ||||
| this.client?.unsafePublish(topic, JSON.stringify(data), {qos: 0} as IPublishOptions); | |||||
| this.client?.unsafePublish(topic, JSON.stringify(data), { | |||||
| qos: 0, | |||||
| } as IPublishOptions); | |||||
| // this.client?.publish("isoft/node 4/in4", JSON.stringify(data), {qos: 0} as IPublishOptions).subscribe(); | // this.client?.publish("isoft/node 4/in4", JSON.stringify(data), {qos: 0} as IPublishOptions).subscribe(); | ||||
| } | } | ||||
| // 断开连接 | // 断开连接 | ||||
| destroyConnection() { | destroyConnection() { | ||||
| try { | try { | ||||
| this.client?.disconnect(true) | |||||
| this.isConnection = false | |||||
| console.log('Successfully disconnected!') | |||||
| this.client?.disconnect(true); | |||||
| this.isConnection = false; | |||||
| console.log('Successfully disconnected!'); | |||||
| } catch (error: any) { | } catch (error: any) { | ||||
| console.log('Disconnect failed', error.toString()) | |||||
| console.log('Disconnect failed', error.toString()); | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||
| import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; | import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; | ||||
| import { BehaviorSubject,Subject } from "rxjs"; | |||||
| import { config } from "../../../assets/config/config"; | |||||
| import { BehaviorSubject, Subject } from 'rxjs'; | |||||
| import { config } from '../../../assets/config/config'; | |||||
| @Injectable({ | @Injectable({ | ||||
| providedIn: 'root' | |||||
| providedIn: 'root', | |||||
| }) | }) | ||||
| export class SocketService { | export class SocketService { | ||||
| gateway = config.gateway; | gateway = config.gateway; | ||||
| websocket$: WebSocketSubject<any> | undefined ; | |||||
| websocket$: WebSocketSubject<any> | undefined; | |||||
| private isConnected: boolean = false; | private isConnected: boolean = false; | ||||
| private messagesSubject$ = new Subject(); | private messagesSubject$ = new Subject(); | ||||
| public messages$ = this.messagesSubject$.asObservable(); | public messages$ = this.messagesSubject$.asObservable(); | ||||
| private statusSubject = new BehaviorSubject<boolean>(this.isConnected); | private statusSubject = new BehaviorSubject<boolean>(this.isConnected); | ||||
| public status$ = this.statusSubject.asObservable(); | public status$ = this.statusSubject.asObservable(); | ||||
| constructor() { | |||||
| } | |||||
| constructor() {} | |||||
| public connect(cfg: { reconnect: boolean } = { reconnect: false }): void { | public connect(cfg: { reconnect: boolean } = { reconnect: false }): void { | ||||
| if (cfg.reconnect || !this.websocket$ || this.websocket$.closed) { | if (cfg.reconnect || !this.websocket$ || this.websocket$.closed) { | ||||
| console.log(cfg.reconnect ? 'Reconnecting WebSocket…' : 'Trying to open a WebSocket connection…'); | |||||
| console.log( | |||||
| cfg.reconnect | |||||
| ? 'Reconnecting WebSocket…' | |||||
| : 'Trying to open a WebSocket connection…', | |||||
| ); | |||||
| this.websocket$ = this.getNewWebSocket(); | this.websocket$ = this.getNewWebSocket(); | ||||
| this.websocket$.subscribe((messages) => { | this.websocket$.subscribe((messages) => { | ||||
| this.messagesSubject$.next(messages); | this.messagesSubject$.next(messages); | ||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| close() { | close() { | ||||
| console.log('Connection ok'); | console.log('Connection ok'); | ||||
| this.isConnected = true; | this.isConnected = true; | ||||
| this.statusSubject.next(this.isConnected); | this.statusSubject.next(this.isConnected); | ||||
| } | |||||
| }, | |||||
| }, | }, | ||||
| closeObserver: { | closeObserver: { | ||||
| next: () => { | next: () => { | ||||
| console.log('Connection closed'); | console.log('Connection closed'); | ||||
| this.websocket$ = undefined; | this.websocket$ = undefined; | ||||
| this.isConnected = false; | this.isConnected = false; | ||||
| this.connect({reconnect: true}); | |||||
| this.connect({ reconnect: true }); | |||||
| this.statusSubject.next(this.isConnected); | this.statusSubject.next(this.isConnected); | ||||
| } | |||||
| }, | |||||
| }, | }, | ||||
| }); | }); | ||||
| } | } |
| import { NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | import { CommonModule } from '@angular/common'; | ||||
| import {SharedComponentModule} from "./component/shared-component.module"; | |||||
| import {FormsModule} from "@angular/forms"; | |||||
| import { SharedComponentModule } from './component/shared-component.module'; | |||||
| import { FormsModule } from '@angular/forms'; | |||||
| import { TimeElapsedPipe } from './pipes/time-elapsed.pipe'; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [], | |||||
| imports: [ | |||||
| CommonModule, | |||||
| SharedComponentModule, | |||||
| FormsModule | |||||
| ], | |||||
| exports: [ | |||||
| SharedComponentModule, | |||||
| ] | |||||
| declarations: [TimeElapsedPipe], | |||||
| imports: [CommonModule, SharedComponentModule, FormsModule], | |||||
| exports: [SharedComponentModule, TimeElapsedPipe], | |||||
| }) | }) | ||||
| export class SharedModule { } | |||||
| export class SharedModule {} |
| export const config = { | export const config = { | ||||
| gateway: 'wss:/socket.aztrace.vn/ws' | |||||
| gateway: 'wss:/socket.aztrace.vn/ws', | |||||
| }; | }; |
| left: $px * 1px !important; | left: $px * 1px !important; | ||||
| } | } | ||||
| } | } | ||||
| $pxs: 0 0, 1 4, 2 8, 3 16, 4 24, 5 32, 6 48, 7 56, 8 62, 9 70, 10 78 ; | |||||
| $pxs: | |||||
| 0 0, | |||||
| 1 4, | |||||
| 2 8, | |||||
| 3 16, | |||||
| 4 24, | |||||
| 5 32, | |||||
| 6 48, | |||||
| 7 56, | |||||
| 8 62, | |||||
| 9 70, | |||||
| 10 78; | |||||
| @each $i, $px in $pxs { | @each $i, $px in $pxs { | ||||
| @include mixPaddingMargin($i, $px); | @include mixPaddingMargin($i, $px); | ||||
| @include mixPosition($i, $px); | @include mixPosition($i, $px); | ||||
| } | } | ||||
| @each $weight in 100, 200, 300, 400, 500, 600, 700, 800, 900 { | @each $weight in 100, 200, 300, 400, 500, 600, 700, 800, 900 { | ||||
| [mat-weight='#{$weight}'] { | |||||
| [mat-weight="#{$weight}"] { | |||||
| font-weight: $weight !important; | font-weight: $weight !important; | ||||
| } | } | ||||
| } | } | ||||
| .fz-#{$i} { | .fz-#{$i} { | ||||
| font-size: $rem * 1rem !important; | font-size: $rem * 1rem !important; | ||||
| } | } | ||||
| } | } | ||||
| $rems: 0 0.5, 1 0.75, 2 1, 3 1.25, 4 1.5, 5 1.75, 6 2, 7 2.25, 8 2.5, 9 2.75, 10 3; | |||||
| $rems: | |||||
| 0 0.5, | |||||
| 1 0.75, | |||||
| 2 1, | |||||
| 3 1.25, | |||||
| 4 1.5, | |||||
| 5 1.75, | |||||
| 6 2, | |||||
| 7 2.25, | |||||
| 8 2.5, | |||||
| 9 2.75, | |||||
| 10 3; | |||||
| @each $i, $rem in $rems { | @each $i, $rem in $rems { | ||||
| @include mixFontSize($i, $rem); | @include mixFontSize($i, $rem); | ||||
| } | } |
| <!doctype html> | <!doctype html> | ||||
| <html lang="en"> | <html lang="en"> | ||||
| <head> | |||||
| <meta charset="utf-8"> | |||||
| <head> | |||||
| <meta charset="utf-8" /> | |||||
| <title>IotWebUi</title> | <title>IotWebUi</title> | ||||
| <base href="/"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <link rel="icon" type="image/x-icon" href="favicon.ico"> | |||||
| <link rel="preconnect" href="https://fonts.gstatic.com"> | |||||
| <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> | |||||
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |||||
| </head> | |||||
| <body class="mat-typography"> | |||||
| <base href="/" /> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||||
| <link rel="icon" type="image/x-icon" href="favicon.ico" /> | |||||
| <link rel="preconnect" href="https://fonts.gstatic.com" /> | |||||
| <link | |||||
| href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" | |||||
| rel="stylesheet" | |||||
| /> | |||||
| <link | |||||
| href="https://fonts.googleapis.com/icon?family=Material+Icons" | |||||
| rel="stylesheet" | |||||
| /> | |||||
| </head> | |||||
| <body class="mat-typography"> | |||||
| <app-root></app-root> | <app-root></app-root> | ||||
| </body> | |||||
| </body> | |||||
| </html> | </html> |
| import { AppModule } from './app/app.module'; | import { AppModule } from './app/app.module'; | ||||
| platformBrowserDynamic().bootstrapModule(AppModule) | |||||
| .catch(err => console.error(err)); | |||||
| platformBrowserDynamic() | |||||
| .bootstrapModule(AppModule) | |||||
| .catch((err) => console.error(err)); |
| const express = require('express'); | |||||
| const expressWs = require('express-ws'); | |||||
| const express = require("express"); | |||||
| const expressWs = require("express-ws"); | |||||
| const app = express(); | const app = express(); | ||||
| expressWs(app); | expressWs(app); | ||||
| const { proxy, scriptUrl } = require('rtsp-relay')(app); | |||||
| const { proxy, scriptUrl } = require("rtsp-relay")(app); | |||||
| const port = 8080; | const port = 8080; | ||||
| const urlCamera = 'rtsp://admin:[email protected]:88/cam/realmonitor?channel=1&subtype=0'; | |||||
| const urlCamera = | |||||
| "rtsp://admin:[email protected]:88/cam/realmonitor?channel=1&subtype=0"; | |||||
| app.ws('/api/stream/:cameraIP', (ws, req) => | |||||
| app.ws("/api/stream/:cameraIP", (ws, req) => | |||||
| proxy({ | proxy({ | ||||
| url: `rtsp://${req.params.cameraIP}:554/feed`, | url: `rtsp://${req.params.cameraIP}:554/feed`, | ||||
| })(ws), | })(ws), | ||||
| const handler = proxy({ | const handler = proxy({ | ||||
| url: urlCamera, | url: urlCamera, | ||||
| verbose: false, | verbose: false, | ||||
| transport: 'tcp' | |||||
| transport: "tcp", | |||||
| }); | }); | ||||
| app.ws('/stream', handler); | |||||
| app.ws("/stream", handler); | |||||
| const server = app.listen(port, () => { | const server = app.listen(port, () => { | ||||
| console.log(`RTSP Relay Server is listening on port ${port}`); | console.log(`RTSP Relay Server is listening on port ${port}`); | ||||
| }); | }); | ||||
| /* You can add global styles to this file, and also import other style files */ | /* You can add global styles to this file, and also import other style files */ | ||||
| @import "assets/style/scss/common"; | @import "assets/style/scss/common"; | ||||
| html, body { height: 100%; } | |||||
| body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } | |||||
| html, | |||||
| body { | |||||
| height: 100%; | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| font-family: Roboto, "Helvetica Neue", sans-serif; | |||||
| } |