Browse Source

add component managment hotel

features/hotel-management
PhamY0601 1 year ago
parent
commit
8783b7df0c
55 changed files with 1016 additions and 459 deletions
  1. +5
    -1
      package.json
  2. +9
    -5
      src/app/app.component.spec.ts
  3. +30
    -22
      src/app/app.component.ts
  4. +9
    -11
      src/app/app.module.ts
  5. +0
    -1
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html
  6. +10
    -9
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss
  7. +1
    -1
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.spec.ts
  8. +42
    -16
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts
  9. +137
    -0
      src/app/modules/homepage/data/fake-data.ts
  10. +10
    -9
      src/app/modules/homepage/home-page.module.ts
  11. +7
    -2
      src/app/modules/homepage/home-page.routing.ts
  12. +8
    -5
      src/app/modules/homepage/homepage/home-page.component.html
  13. +2
    -2
      src/app/modules/homepage/homepage/home-page.component.scss
  14. +1
    -1
      src/app/modules/homepage/homepage/home-page.component.spec.ts
  15. +32
    -24
      src/app/modules/homepage/homepage/home-page.component.ts
  16. +34
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.html
  17. +81
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.scss
  18. +21
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.spec.ts
  19. +15
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.ts
  20. +73
    -33
      src/app/modules/homepage/security-atm-details/security-atm-details.component.html
  21. +1
    -1
      src/app/modules/homepage/security-atm-details/security-atm-details.component.spec.ts
  22. +17
    -23
      src/app/modules/homepage/security-atm-details/security-atm-details.component.ts
  23. +62
    -18
      src/app/modules/homepage/security-system-details/security-system-details.component.html
  24. +1
    -1
      src/app/modules/homepage/security-system-details/security-system-details.component.spec.ts
  25. +41
    -28
      src/app/modules/homepage/security-system-details/security-system-details.component.ts
  26. +10
    -4
      src/app/shared/component/camera-dialog/camera-dialog.component.html
  27. +15
    -9
      src/app/shared/component/camera-dialog/camera-dialog.component.scss
  28. +1
    -1
      src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts
  29. +20
    -15
      src/app/shared/component/camera-dialog/camera-dialog.component.ts
  30. +8
    -6
      src/app/shared/component/confirm-dialog/confirm-dialog.component.html
  31. +4
    -2
      src/app/shared/component/confirm-dialog/confirm-dialog.component.scss
  32. +1
    -1
      src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts
  33. +13
    -16
      src/app/shared/component/confirm-dialog/confirm-dialog.component.ts
  34. +7
    -4
      src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.html
  35. +2
    -4
      src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.scss
  36. +5
    -5
      src/app/shared/component/layout/layout.component.html
  37. +4
    -4
      src/app/shared/component/layout/layout.component.scss
  38. +1
    -1
      src/app/shared/component/layout/layout.component.spec.ts
  39. +2
    -4
      src/app/shared/component/layout/layout.component.ts
  40. +56
    -14
      src/app/shared/component/slider-range/slider-range.component.html
  41. +38
    -40
      src/app/shared/component/slider-range/slider-range.component.scss
  42. +1
    -1
      src/app/shared/component/slider-range/slider-range.component.spec.ts
  43. +2
    -3
      src/app/shared/component/slider-range/slider-range.component.ts
  44. +15
    -0
      src/app/shared/pipes/time-elapsed.pipe.ts
  45. +22
    -16
      src/app/shared/services/alarm-sound.service.ts
  46. +1
    -1
      src/app/shared/services/confirm-dialog.service.ts
  47. +52
    -36
      src/app/shared/services/mqtt-client.service.ts
  48. +13
    -12
      src/app/shared/services/socket.service.ts
  49. +7
    -12
      src/app/shared/shared.module.ts
  50. +1
    -1
      src/assets/config/config.ts
  51. +28
    -7
      src/assets/style/scss/common.scss
  52. +19
    -13
      src/index.html
  53. +3
    -3
      src/main.ts
  54. +8
    -9
      src/server/server.js
  55. +8
    -2
      src/styles.scss

+ 5
- 1
package.json View File

"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",

+ 9
- 5
src/app/app.component.spec.ts View File

import { AppComponent } from './app.component'; import { AppComponent } from './app.component';


describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent]
}));
beforeEach(() =>
TestBed.configureTestingModule({
imports: [RouterTestingModule],
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!',
);
}); });
}); });

+ 30
- 22
src/app/app.component.ts View File

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) => {
if (message.id == '0' && message.whistletime) {
localStorage.setItem('whistletime', message.whistletime);
}
if (message.id == '0' && message.arlamtime) {
localStorage.setItem('alarmtime', message.arlamtime);
}
});
},
);
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) {
localStorage.setItem('whistletime', message.whistletime);
}
if (message.id == '0' && message.arlamtime) {
localStorage.setItem('alarmtime', message.arlamtime);
}
});
});
} }
} }

+ 9
- 11
src/app/app.module.ts View File

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 {}

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

<div id="map"></div> <div id="map"></div>
</div> </div>
</div> </div>


+ 10
- 9
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss View File

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;
} }
} }

+ 1
- 1
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.spec.ts View File



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

+ 42
- 16
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts View File

} 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.onMessage(message);
});
this.mqtt$.messages$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message) => {
console.log('map:', message);
this.onMessage(message);
});
} }


ngOnDestroy(): void { ngOnDestroy(): void {
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) {
if (fenceStatus && fenceArm) { if (fenceStatus && fenceArm) {
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);
} }

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

}, },
}, },
]; ];
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',
},
],
},
];

+ 10
- 9
src/app/modules/homepage/home-page.module.ts View File

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,

+ 7
- 2
src/app/modules/homepage/home-page.routing.ts View File

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,
},
]; ];

+ 8
- 5
src/app/modules/homepage/homepage/home-page.component.html View File

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>

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

.sound-group{
.sound-group {
margin: 2rem 3rem; margin: 2rem 3rem;
} }
button{
button {
color: #ff7723 !important; color: #ff7723 !important;
} }

+ 1
- 1
src/app/modules/homepage/homepage/home-page.component.spec.ts View File



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

+ 32
- 24
src/app/modules/homepage/homepage/home-page.component.ts View File

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) => {
if (message.id == '0' && message.whistletime) {
this.whistle.time = Number(message.whistletime);
}
if (message.id == '0' && message.arlamtime) {
this.alarm.time = Number(message.arlamtime);
}
});
});
this.mqtt$.setting$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message: any) => {
if (message.id == '0' && message.whistletime) {
this.whistle.time = Number(message.whistletime);
}
if (message.id == '0' && message.arlamtime) {
this.alarm.time = Number(message.arlamtime);
}
});
} }


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,
);
} }
} }
} }

+ 34
- 0
src/app/modules/homepage/hotel-management/hotel-management.component.html View File

<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>

+ 81
- 0
src/app/modules/homepage/hotel-management/hotel-management.component.scss View File

.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;
}

+ 21
- 0
src/app/modules/homepage/hotel-management/hotel-management.component.spec.ts View File

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();
});
});

+ 15
- 0
src/app/modules/homepage/hotel-management/hotel-management.component.ts View File

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() {}
}

+ 73
- 33
src/app/modules/homepage/security-atm-details/security-atm-details.component.html View File

<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'"
style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-11')">
<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"
(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'"
style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-9')">
<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"
(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')">
<ng-container [ngTemplateOutlet]="camera"></ng-container>
<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>
</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>

+ 1
- 1
src/app/modules/homepage/security-atm-details/security-atm-details.component.spec.ts View File



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

+ 17
- 23
src/app/modules/homepage/security-atm-details/security-atm-details.component.ts View File

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 {

+ 62
- 18
src/app/modules/homepage/security-system-details/security-system-details.component.html View File

<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">
<ng-container [ngTemplateOutlet]="camera"></ng-container>
<div class="state t4" id="State4" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px" />
<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>

+ 1
- 1
src/app/modules/homepage/security-system-details/security-system-details.component.spec.ts View File



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

+ 41
- 28
src/app/modules/homepage/security-system-details/security-system-details.component.ts View File

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.isConnected = isConnected;
});
this.mqtt$.messages$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe(message => {
console.log('detail:',message);
this.onMessage(message);
});
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 {


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 {

+ 10
- 4
src/app/shared/component/camera-dialog/camera-dialog.component.html View File

<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>

+ 15
- 9
src/app/shared/component/camera-dialog/camera-dialog.component.scss View File

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);
}
} }

+ 1
- 1
src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts View File



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

+ 20
- 15
src/app/shared/component/camera-dialog/camera-dialog.component.ts View File

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({
url: 'wss://wss.aztrace.vn/stream',
canvas: this.videoPlayer!.nativeElement,
onDisconnect(): void{
console.log('disconnect camera, please wait');
}
});
this.player = await loadPlayer({
url: 'wss://wss.aztrace.vn/stream',
canvas: this.videoPlayer!.nativeElement,
onDisconnect(): void {
console.log('disconnect camera, please wait');
},
});
} }


onClose(): void { onClose(): void {
} }


ngOnDestroy(): void { ngOnDestroy(): void {
if (this.player){
if (this.player) {
this.player?.destroy(); this.player?.destroy();
} }
} }

+ 8
- 6
src/app/shared/component/confirm-dialog/confirm-dialog.component.html View File

<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"
(ngModelChange)="submitChange(item)"
[disabled]="isConnected && item.value"
name="sensor">
{{ (item.key == 'status1' ? 'Fire Alarm' : 'Fence Alarm') }}
<mat-checkbox
[(ngModel)]="item.value"
(ngModelChange)="submitChange(item)"
[disabled]="isConnected && item.value"
name="sensor"
>
{{ item.key == "status1" ? "Fire Alarm" : "Fence Alarm" }}
</mat-checkbox> </mat-checkbox>
</div> </div>
</mat-dialog-content> </mat-dialog-content>

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

.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;
} }

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



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

+ 13
- 16
src/app/shared/component/confirm-dialog/confirm-dialog.component.ts View File

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.isConnected = isConnected;
},
);
this.statusSubscription = this.mqtt$.status$.subscribe((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();
} }

+ 7
- 4
src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.html View File

<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>

+ 2
- 4
src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.scss View File

} }


.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;
} }
} }



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

<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>



+ 4
- 4
src/app/shared/component/layout/layout.component.scss View File

.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;
} }
} }

} }

+ 1
- 1
src/app/shared/component/layout/layout.component.spec.ts View File



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

+ 2
- 4
src/app/shared/component/layout/layout.component.ts View File

@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 {}

+ 56
- 14
src/app/shared/component/slider-range/slider-range.component.html View File

<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)"
[ngStyle]="onSliderChangeBackground()"
[max]="max"
[disabled]="disable">
<input
type="range"
[(ngModel)]="value"
(input)="onSliderChange($event)"
[ngStyle]="onSliderChangeBackground()"
[max]="max"
[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>

+ 38
- 40
src/app/shared/component/slider-range/slider-range.component.scss View File

width: 100%; width: 100%;
height: 100%; height: 100%;
clip-path: polygon( clip-path: polygon(
50% 0,
100% 25%,
100% 75%,
50% 100%,
0 75%,
0 25%,
50% 0,
50% 1px,
1px calc(25% + .5px),
1px calc(75% - .5px),
50% calc(100% - 1px),
calc(100% - 1px) calc(75% - .5px),
calc(100% - 1px) calc(25% + .5px),
50% 1px
50% 0,
100% 25%,
100% 75%,
50% 100%,
0 75%,
0 25%,
50% 0,
50% 1px,
1px calc(25% + 0.5px),
1px calc(75% - 0.5px),
50% calc(100% - 1px),
calc(100% - 1px) calc(75% - 0.5px),
calc(100% - 1px) calc(25% + 0.5px),
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;
} }

+ 1
- 1
src/app/shared/component/slider-range/slider-range.component.spec.ts View File



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

+ 2
- 3
src/app/shared/component/slider-range/slider-range.component.ts View File

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);

} }
} }



+ 15
- 0
src/app/shared/pipes/time-elapsed.pipe.ts View File

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();
}
}

+ 22
- 16
src/app/shared/services/alarm-sound.service.ts View File

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(() => {
this.isPlay = true;
this.alertInterval = setInterval(() => {
this.stopAlarm();
}, this.alertDuration);
}).catch((error) => {
console.error('Error playing audio:', error);
});
if (!this.isPlay) {
this.audio
.play()
.then(() => {
this.isPlay = true;
this.alertInterval = setInterval(() => {
this.stopAlarm();
}, this.alertDuration);
})
.catch((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);
} }
} }


getTimeDuration(key: string) { getTimeDuration(key: string) {
return parseInt(localStorage.getItem(key) ?? '0') * 100;
return parseInt(localStorage.getItem(key) ?? '0') * 100;
} }
} }

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

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()

+ 52
- 36
src/app/shared/services/mqtt-client.service.ts View File

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) => {
try {
let plainMessage = "";
for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(parseInt(String(message.payload[i])));
this.curSubscription = this.client
?.observe(topic, { qos, dup: false } as IClientSubscribeOptions)
.subscribe((message: IMqttMessage) => {
try {
let plainMessage = '';
for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(
parseInt(String(message.payload[i])),
);
}
console.log(JSON.parse(plainMessage));
this.messagesSubject$.next(JSON.parse(plainMessage));
} catch (e) {
console.log(e);
} }
console.log(JSON.parse(plainMessage));
this.messagesSubject$.next(JSON.parse(plainMessage));
}catch (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) => {
try {
let plainMessage = "";
for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(parseInt(String(message.payload[i])));
this.settingSubscription = this.client
?.observe(topic, { qos, dup: false } as IClientSubscribeOptions)
.subscribe((message: IMqttMessage) => {
try {
let plainMessage = '';
for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(
parseInt(String(message.payload[i])),
);
}
console.log(JSON.parse(plainMessage));
this.settingSubject$.next(JSON.parse(plainMessage));
} catch (e) {
console.log(e);
} }
console.log(JSON.parse(plainMessage));
this.settingSubject$.next(JSON.parse(plainMessage));
}catch (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());
} }
} }
} }

+ 13
- 12
src/app/shared/services/socket.service.ts View File

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);
}
},
}, },
}); });
} }

+ 7
- 12
src/app/shared/shared.module.ts View File

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 {}

+ 1
- 1
src/assets/config/config.ts View File

export const config = { export const config = {
gateway: 'wss:/socket.aztrace.vn/ws'
gateway: 'wss:/socket.aztrace.vn/ws',
}; };

+ 28
- 7
src/assets/style/scss/common.scss View File

} }
@mixin mixPosition($i, $px) { @mixin mixPosition($i, $px) {
.top-#{$i} { .top-#{$i} {
top: $px * 1px !important;
top: $px * 1px !important;
} }
.top--#{$i} { .top--#{$i} {
top: $px * -1px !important; top: $px * -1px !important;
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;
} }
} }
@mixin mixFontSize($i, $rem) { @mixin mixFontSize($i, $rem) {
// reusable code here // reusable code here
.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;
@each $i, $rem in $rems {
$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 {
@include mixFontSize($i, $rem); @include mixFontSize($i, $rem);
} }

+ 19
- 13
src/index.html View File

<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<meta charset="utf-8">
<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">
<app-root></app-root>
</body>
<head>
<meta charset="utf-8" />
<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">
<app-root></app-root>
</body>
</html> </html>

+ 3
- 3
src/main.ts View File



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));

+ 8
- 9
src/server/server.js View File

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}`);
}); });


+ 8
- 2
src/styles.scss View File

/* 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;
}

Loading…
Cancel
Save