| 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 {FormsModule} from "@angular/forms"; | ||||
| import {SecurityAtmDetailsComponent} from "./security-atm-details/security-atm-details.component"; | |||||
| import {MatTableModule} from "@angular/material/table"; | |||||
| @NgModule({ | @NgModule({ | ||||
| HomePageComponent, | HomePageComponent, | ||||
| CentralizedSecurityManagementComponent, | CentralizedSecurityManagementComponent, | ||||
| SecuritySystemDetailsComponent, | SecuritySystemDetailsComponent, | ||||
| SecurityAtmDetailsComponent | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| CommonModule, | CommonModule, | ||||
| SharedMaterialModule, | SharedMaterialModule, | ||||
| SharedModule, | SharedModule, | ||||
| FormsModule, | FormsModule, | ||||
| MatTableModule, | |||||
| ], | ], | ||||
| }) | }) | ||||
| export class HomePageModule {} | export class HomePageModule {} |
| 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"; | |||||
| export const homePageRoutes: Routes = [ | export const homePageRoutes: Routes = [ | ||||
| { | { | ||||
| { | { | ||||
| path: 'security-system-details', | path: 'security-system-details', | ||||
| component: SecuritySystemDetailsComponent, | component: SecuritySystemDetailsComponent, | ||||
| }, | |||||
| { | |||||
| path: 'atm-monitoring', | |||||
| component: SecurityAtmDetailsComponent, | |||||
| } | } | ||||
| ]; | ]; |
| <button mat-stroked-button routerLink="./security-system-details"> | <button mat-stroked-button routerLink="./security-system-details"> | ||||
| Security System Details | Security System Details | ||||
| </button> | </button> | ||||
| <button mat-stroked-button routerLink="./atm-monitoring"> | |||||
| ATM monitoring | |||||
| </button> | |||||
| </div> | </div> | ||||
| <mat-card class="sound-group"> | <mat-card class="sound-group"> |
| <div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px"> | |||||
| <div fxFlex="70" class="map-image"> | |||||
| <div class="card-state"> | |||||
| <img src="assets/images/map-quan5.jpg"> | |||||
| <div class="state t2" id="State2"> | |||||
| <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> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||||
| </div> | |||||
| <div *ngIf="(state1 && state5 && status1)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div> | |||||
| </div> | |||||
| <div class="state t3" id="State3"> | |||||
| <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> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||||
| </div> | |||||
| <div *ngIf="(state2 && state5 && status2)" class="alarm-text" | |||||
| [ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">VIBRATION ALARM</div> | |||||
| </div> | |||||
| <div class="state t4" id="State4" fxLayout="row" > | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||||
| </div> | |||||
| <div class="state t5 tooltip" id="State5" fxLayout="row"> | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||||
| </div> | |||||
| <div class="state t6 tooltip" id="State6" fxLayout="row"> | |||||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div fxFlex="30" fxLayout="column" fxLayoutGap="50px"> | |||||
| <table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> | |||||
| <!-- Position Column --> | |||||
| <ng-container matColumnDef="title"> | |||||
| <th mat-header-cell *matHeaderCellDef > | |||||
| Title | |||||
| </th> | |||||
| <td mat-cell *matCellDef="let element"> {{element.title}} </td> | |||||
| </ng-container> | |||||
| <ng-container matColumnDef="date"> | |||||
| <th mat-header-cell *matHeaderCellDef > | |||||
| Date | |||||
| </th> | |||||
| <td mat-cell *matCellDef="let element"> {{element.date}} </td> | |||||
| </ng-container> | |||||
| <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | |||||
| <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> | |||||
| </table> | |||||
| </div> | |||||
| </div> | |||||
| <ng-template #camera> | |||||
| <div (click)="openDialog()"> | |||||
| <img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png"> | |||||
| </div> | |||||
| </ng-template> |
| button { | |||||
| padding: 30px 50px; | |||||
| } | |||||
| .red-bg { | |||||
| background-color: red !important; | |||||
| color: white !important; | |||||
| } | |||||
| .green-bg { | |||||
| background-color: green !important; | |||||
| color: white !important; | |||||
| } | |||||
| h1 { | |||||
| font-size: 1.8rem; | |||||
| color: white; | |||||
| } | |||||
| .topnav { | |||||
| overflow: hidden; | |||||
| background-color: #0a1128; | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| } | |||||
| p { | |||||
| text-align: center; | |||||
| } | |||||
| .content { | |||||
| padding: 50px; | |||||
| } | |||||
| .card-grid { | |||||
| max-width: 800px; | |||||
| margin: 0 auto; | |||||
| display: grid; | |||||
| grid-gap: 2rem; | |||||
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |||||
| } | |||||
| .card { | |||||
| background-color: white; | |||||
| box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, 0.5); | |||||
| } | |||||
| .card-title { | |||||
| font-size: 1.2rem; | |||||
| font-weight: bold; | |||||
| color: #034078; | |||||
| } | |||||
| .reading { | |||||
| font-size: 1.2rem; | |||||
| color: #1282a2; | |||||
| } | |||||
| .button { | |||||
| padding: 15px 50px; | |||||
| font-size: 24px; | |||||
| text-align: center; | |||||
| outline: none; | |||||
| color: #fff; | |||||
| background-color: #0f8b8d; | |||||
| border: none; | |||||
| border-radius: 5px; | |||||
| -webkit-touch-callout: none; | |||||
| -webkit-user-select: none; | |||||
| -khtml-user-select: none; | |||||
| -moz-user-select: none; | |||||
| -ms-user-select: none; | |||||
| user-select: none; | |||||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
| } | |||||
| /*.button:hover {background-color: #0f8b8d}*/ | |||||
| .button:active { | |||||
| background-color: #0f8b8d; | |||||
| box-shadow: 2px 2px #cdcdcd; | |||||
| transform: translateY(2px); | |||||
| } | |||||
| .state { | |||||
| font-size: 1.5rem; | |||||
| color: #8c8c8c; | |||||
| font-weight: bold; | |||||
| text-align: center; | |||||
| } | |||||
| .content { | |||||
| padding: 30px; | |||||
| max-width: 600px; | |||||
| margin: 0 auto; | |||||
| } | |||||
| .map-image { | |||||
| img { | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| } | |||||
| .card-state { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| position: relative; | |||||
| } | |||||
| .state { | |||||
| position: absolute; | |||||
| color: red; | |||||
| &.t1 { | |||||
| top: 5%; | |||||
| left: 10%; | |||||
| } | |||||
| &.t2 { | |||||
| top: 58%; | |||||
| left: 31%; | |||||
| transform: translate(-50%, -50%); | |||||
| } | |||||
| &.t3 { | |||||
| top: 35%; | |||||
| left: 50%; | |||||
| transform: translate(-50%); | |||||
| } | |||||
| &.t4 { | |||||
| top: 41%; | |||||
| right: 12%; | |||||
| } | |||||
| &.t5 { | |||||
| top: 54%; | |||||
| left: 86%; | |||||
| transform: translate(-50%); | |||||
| } | |||||
| &.t6 { | |||||
| top: 48%; | |||||
| left: 5%; | |||||
| width: 100px; | |||||
| .alarm-text-off { | |||||
| width: 100px !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| .sensor-on { | |||||
| width: 30px; | |||||
| height: 30px; | |||||
| position: relative; | |||||
| background: linear-gradient(#ff0000, #c70039); | |||||
| display: flex !important; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| border-radius: 50%; | |||||
| img { | |||||
| z-index: 9; | |||||
| } | |||||
| &:before, | |||||
| &:after { | |||||
| position: absolute; | |||||
| content: ""; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| background: #ff0000; | |||||
| border-radius: 50%; | |||||
| z-index: 1; | |||||
| } | |||||
| &:before { | |||||
| animation: sensor-on 2s ease-out infinite; | |||||
| } | |||||
| &:after { | |||||
| animation: sensor-on 2s 1s ease-out infinite; | |||||
| } | |||||
| } | |||||
| .sensor-off { | |||||
| display: inline-block; | |||||
| } | |||||
| @keyframes sensor-on { | |||||
| 100% { | |||||
| transform: scale(2); | |||||
| opacity: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .alarm-text { | |||||
| position: absolute; | |||||
| font-size: 10px; | |||||
| padding: 2px 4px; | |||||
| width: 100px; | |||||
| margin-top: 10px; | |||||
| margin-left: -22px; | |||||
| border-radius: 2px; | |||||
| &-off { | |||||
| background: #bfe9f4; | |||||
| color: #004aad; | |||||
| } | |||||
| &-on { | |||||
| background: #f11e1e; | |||||
| color: #fff; | |||||
| } | |||||
| } | |||||
| .tooltip { | |||||
| position: relative; | |||||
| display: inline-block; | |||||
| } | |||||
| .tooltip .tooltiptext { | |||||
| text-align: start; | |||||
| font-size: 10px; | |||||
| visibility: hidden; | |||||
| width: 300px; | |||||
| padding: 10px; | |||||
| background-color: #eee; | |||||
| color: black; | |||||
| border-radius: 6px; | |||||
| position: absolute; | |||||
| z-index: 1; | |||||
| bottom: 100%; | |||||
| left: -50%; | |||||
| } | |||||
| .tooltip:hover .tooltiptext { | |||||
| visibility: visible; | |||||
| cursor: pointer; | |||||
| } | |||||
| a { | |||||
| color: blue; | |||||
| } | |||||
| table { | |||||
| width: 100%; | |||||
| } | |||||
| th.mat-sort-header-sorted { | |||||
| color: black; | |||||
| } |
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { SecurityAtmDetailsComponent } from './security-atm-details.component'; | |||||
| describe('SecuritySystemDetailsComponent', () => { | |||||
| let component: SecurityAtmDetailsComponent; | |||||
| let fixture: ComponentFixture<SecurityAtmDetailsComponent>; | |||||
| beforeEach(() => { | |||||
| TestBed.configureTestingModule({ | |||||
| declarations: [SecurityAtmDetailsComponent] | |||||
| }); | |||||
| fixture = TestBed.createComponent(SecurityAtmDetailsComponent); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); |
| 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 { AlarmSoundService } from "../../../shared/services/alarm-sound.service"; | |||||
| import {MatTableDataSource} from "@angular/material/table"; | |||||
| const ELEMENT_DATA: any[] = [ | |||||
| {title: 'Vibration warning Phường 15', date: '26/08/2024 12:14'}, | |||||
| {title: 'Fire warning Phường 11', date: '26/08/2024 10:14'}, | |||||
| {title: 'Fire warning Phường 9', date: '25/08/2024 12:14'}, | |||||
| {title: 'Vibration warning Phường 1', date: '25/08/2024 12:14'}, | |||||
| {title: 'Vibration warning Phường 2', date: '25/08/2024 12:14'}, | |||||
| ]; | |||||
| @Component({ | |||||
| selector: 'app-security-atm-details', | |||||
| templateUrl: './security-atm-details.component.html', | |||||
| styleUrls: ['./security-atm-details.component.scss'] | |||||
| }) | |||||
| export class SecurityAtmDetailsComponent implements OnInit, OnDestroy { | |||||
| isConnected = false; | |||||
| state1 = false; | |||||
| status1 = false; | |||||
| state2 = false; | |||||
| status2 = false; | |||||
| state3 = ''; | |||||
| state4 = ''; | |||||
| state5 = false; | |||||
| state6 = false; | |||||
| switchArm = false; | |||||
| isReady = true; | |||||
| private unsubscribeAll = new Subject(); | |||||
| constructor( | |||||
| private socketService$: SocketService, | |||||
| private dialog: MatDialog, | |||||
| private confirm$: ConfirmDialogService, | |||||
| private alarmSoundService$: AlarmSoundService, | |||||
| ) {} | |||||
| 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); | |||||
| } | |||||
| ngOnDestroy(): void { | |||||
| this.unsubscribeAll.next(''); | |||||
| this.unsubscribeAll.complete(); | |||||
| this.alarmSoundService$.stopAlarm(); | |||||
| } | |||||
| getImageSource(): string { | |||||
| return (this.state5 && this.state6) || this.state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; | |||||
| } | |||||
| onMessage(message: any) { | |||||
| this.state1 = message.state1 == '0'; // 1 OFF // alarm 12h | |||||
| this.status1 = message.status1 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||||
| this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h | |||||
| this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||||
| this.state3 = message.state3 == '1' ? 'ON' : 'OFF'; | |||||
| this.state4 = message.state4 == '1' ? 'ON' : 'OFF'; | |||||
| this.state5 = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||||
| this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF'; | |||||
| this.switchArm = message.state5 == '1';// alarm 9h && 6h // 1 ON, 0 OFF | |||||
| this.isReady = message.ready == '1'; | |||||
| } | |||||
| openDialog(): void { | |||||
| this.dialog.open(CameraDialogComponent, { | |||||
| width: '80vw', | |||||
| data: '', | |||||
| }); | |||||
| } | |||||
| displayedColumns: string[] = ['title', 'date']; | |||||
| dataSource = new MatTableDataSource(ELEMENT_DATA); | |||||
| } |