#17 add component managment hotel

Merged
trungnd merged 1 commits from features/hotel-management into master 1 year ago
  1. +5
    -1
      package.json
  2. +8
    -4
      src/app/app.component.spec.ts
  3. +23
    -15
      src/app/app.component.ts
  4. +7
    -9
      src/app/app.module.ts
  5. +0
    -1
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html
  6. +6
    -5
      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. +37
    -11
      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. +5
    -2
      src/app/modules/homepage/homepage/home-page.component.html
  13. +1
    -1
      src/app/modules/homepage/homepage/home-page.component.spec.ts
  14. +24
    -16
      src/app/modules/homepage/homepage/home-page.component.ts
  15. +34
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.html
  16. +81
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.scss
  17. +21
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.spec.ts
  18. +15
    -0
      src/app/modules/homepage/hotel-management/hotel-management.component.ts
  19. +67
    -27
      src/app/modules/homepage/security-atm-details/security-atm-details.component.html
  20. +1
    -1
      src/app/modules/homepage/security-atm-details/security-atm-details.component.spec.ts
  21. +17
    -23
      src/app/modules/homepage/security-atm-details/security-atm-details.component.ts
  22. +60
    -16
      src/app/modules/homepage/security-system-details/security-system-details.component.html
  23. +1
    -1
      src/app/modules/homepage/security-system-details/security-system-details.component.spec.ts
  24. +32
    -19
      src/app/modules/homepage/security-system-details/security-system-details.component.ts
  25. +10
    -4
      src/app/shared/component/camera-dialog/camera-dialog.component.html
  26. +10
    -4
      src/app/shared/component/camera-dialog/camera-dialog.component.scss
  27. +1
    -1
      src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts
  28. +11
    -6
      src/app/shared/component/camera-dialog/camera-dialog.component.ts
  29. +5
    -3
      src/app/shared/component/confirm-dialog/confirm-dialog.component.html
  30. +4
    -2
      src/app/shared/component/confirm-dialog/confirm-dialog.component.scss
  31. +1
    -1
      src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts
  32. +6
    -9
      src/app/shared/component/confirm-dialog/confirm-dialog.component.ts
  33. +4
    -1
      src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.html
  34. +1
    -3
      src/app/shared/component/detail-postion-dialog/detail-position-dialog.component.scss
  35. +5
    -5
      src/app/shared/component/layout/layout.component.html
  36. +2
    -2
      src/app/shared/component/layout/layout.component.scss
  37. +1
    -1
      src/app/shared/component/layout/layout.component.spec.ts
  38. +2
    -4
      src/app/shared/component/layout/layout.component.ts
  39. +54
    -12
      src/app/shared/component/slider-range/slider-range.component.html
  40. +27
    -29
      src/app/shared/component/slider-range/slider-range.component.scss
  41. +1
    -1
      src/app/shared/component/slider-range/slider-range.component.spec.ts
  42. +1
    -2
      src/app/shared/component/slider-range/slider-range.component.ts
  43. +15
    -0
      src/app/shared/pipes/time-elapsed.pipe.ts
  44. +13
    -7
      src/app/shared/services/alarm-sound.service.ts
  45. +1
    -1
      src/app/shared/services/confirm-dialog.service.ts
  46. +34
    -18
      src/app/shared/services/mqtt-client.service.ts
  47. +11
    -10
      src/app/shared/services/socket.service.ts
  48. +6
    -11
      src/app/shared/shared.module.ts
  49. +1
    -1
      src/assets/config/config.ts
  50. +25
    -4
      src/assets/style/scss/common.scss
  51. +13
    -7
      src/index.html
  52. +3
    -3
      src/main.ts
  53. +8
    -9
      src/server/server.js
  54. +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",

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


it('should create the app', () => { it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement; const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('iot-web-ui app is running!');
expect(compiled.querySelector('.content span')?.textContent).toContain(
'iot-web-ui app is running!',
);
}); });
}); });

+ 23
- 15
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) => {
this.mqtt$.sendPublish(
{ id: '0', type: 'get', whistletime: '' },
TOPIC_GETTING,
);
this.mqtt$.sendPublish(
{ id: '0', type: 'get', arlamtime: '' },
TOPIC_GETTING,
);
this.mqtt$.setting$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message: any) => {
if (message.id == '0' && message.whistletime) { if (message.id == '0' && message.whistletime) {
localStorage.setItem('whistletime', message.whistletime); localStorage.setItem('whistletime', message.whistletime);
} }
localStorage.setItem('alarmtime', message.arlamtime); localStorage.setItem('alarmtime', message.arlamtime);
} }
}); });
},
);
});
} }
} }

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

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>


+ 6
- 5
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);
} }
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;
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;

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

+ 37
- 11
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts View File

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 => {
this.mqtt$.messages$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message) => {
console.log('map:', message); console.log('map:', message);
this.onMessage(message); this.onMessage(message);
}); });
this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h
this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass
this.state5 = message.state5 == '1'; // 1 ON, 0 OFF this.state5 = message.state5 == '1'; // 1 ON, 0 OFF
this.alarmSoundService$.startAlarm(this.state5, this.state1, this.status1, this.state2, this.status2);
this.alarmSoundService$.startAlarm(
this.state5,
this.state1,
this.status1,
this.state2,
this.status2,
);
this.updateIcons(); this.updateIcons();
} }

} }


addIconsToMap(): void { addIconsToMap(): void {


addMarker(item: any, isDemo: boolean = false): void { addMarker(item: any, isDemo: boolean = false): void {
const icon = isDemo const icon = isDemo
? this.getIcon(this.state5, this.state1, this.status1, this.state2, this.status2)
? this.getIcon(
this.state5,
this.state1,
this.status1,
this.state2,
this.status2,
)
: this.createIcon(item.warning); : this.createIcon(item.warning);
const marker = L.marker( const marker = L.marker(
[item.detail.coordinates.lat, item.detail.coordinates.lng], [item.detail.coordinates.lat, item.detail.coordinates.lng],
</div>`; </div>`;
} }


createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon {
createIcon(
active: boolean,
className: string = '',
text: any = [],
): L.Icon<L.IconOptions> | L.DivIcon {
if (text.length < 1) { if (text.length < 1) {
return L.icon({ return L.icon({
iconUrl: active iconUrl: active
} }
} }


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

} }
return this.createIcon(false); return this.createIcon(false);
} }

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

+ 5
- 2
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>



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

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

import { Component, OnDestroy, OnInit } from '@angular/core'; 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 { Subject, filter } from 'rxjs';
import { MqttClientService } from '../../../shared/services/mqtt-client.service';
import { takeUntil } from 'rxjs/operators';
import { TOPIC_GETTING, TOPIC_SETTING } from '../../../app.constants';


@Component({ @Component({
selector: 'app-homepage', selector: 'app-homepage',
isConnected = false; isConnected = false;
private unsubscribeAll = new Subject(); private unsubscribeAll = new Subject();


constructor(
private mqtt$: MqttClientService
) {
this.mqtt$.status$.pipe(takeUntil(this.unsubscribeAll)).subscribe(
(isConnected: boolean) => {
constructor(private mqtt$: MqttClientService) {
this.mqtt$.status$
.pipe(takeUntil(this.unsubscribeAll))
.subscribe((isConnected: boolean) => {
this.isConnected = isConnected; this.isConnected = isConnected;
},
);
this.mqtt$.setting$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe((message: any) => {
});
this.mqtt$.setting$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message: any) => {
if (message.id == '0' && message.whistletime) { if (message.id == '0' && message.whistletime) {
this.whistle.time = Number(message.whistletime); this.whistle.time = Number(message.whistletime);
} }
}); });
} }


ngOnInit() {
}
ngOnInit() {}


emitSetting(type: string, event: any) { emitSetting(type: string, event: any) {
if (this.isConnected) { if (this.isConnected) {
if (type === 'whistle') { if (type === 'whistle') {
this.whistle.time = event; this.whistle.time = event;
this.mqtt$.sendPublish({id: '0', type: 'set', whistletime: event.toString()}, TOPIC_GETTING);
this.mqtt$.sendPublish(
{ id: '0', type: 'set', whistletime: event.toString() },
TOPIC_GETTING,
);
} else if (type === 'alarm') { } else if (type === 'alarm') {
this.alarm.time = event; this.alarm.time = event;
this.mqtt$.sendPublish({id: '0', type: 'set', arlamtime: event.toString()}, TOPIC_GETTING);
this.mqtt$.sendPublish(
{ id: '0', type: 'set', arlamtime: event.toString() },
TOPIC_GETTING,
);
} }
} }
} }

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

+ 67
- 27
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'"
<div
class="sensor-off"
[class.sensor-on]="state1 && state5 && status1"
>
<img
[src]="
state5 && status1
? 'assets/images/sensor-on.png'
: 'assets/images/sensor-off.png'
"
style="width: 30px; height: 30px; cursor: pointer" style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-11')">
(click)="openPositionDialog($event, 'ward-11')"
/>
</div> </div>
<ng-container [ngTemplateOutlet]="camera"></ng-container> <ng-container [ngTemplateOutlet]="camera"></ng-container>
</div> </div>
<div *ngIf="(state1 && state5 && status1)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div>
<div
*ngIf="state1 && state5 && status1"
class="alarm-text"
[ngClass]="{ 'alarm-text-on': state1 && state5 && status1 }"
>
FIRE ALARM
</div>
</div> </div>


<div class="state t3" id="State3"> <div class="state t3" id="State3">
<div fxLayout="row"> <div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)">
<img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'"
<div
class="sensor-off"
[class.sensor-on]="state2 && state5 && status2"
>
<img
[src]="
state5 && status2
? 'assets/images/sensor-on.png'
: 'assets/images/sensor-off.png'
"
style="width: 30px; height: 30px; cursor: pointer" style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-9')">
(click)="openPositionDialog($event, 'ward-9')"
/>
</div> </div>
<ng-container [ngTemplateOutlet]="camera"></ng-container> <ng-container [ngTemplateOutlet]="camera"></ng-container>
</div> </div>
<div *ngIf="(state2 && state5 && status2)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">VIBRATION ALARM</div>
<div
*ngIf="state2 && state5 && status2"
class="alarm-text"
[ngClass]="{ 'alarm-text-on': state2 && state5 && status2 }"
>
VIBRATION ALARM
</div>
</div> </div>
<div class="state t4" id="State4" fxLayout="row"> <div class="state t4" id="State4" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-2')">
<img
[src]="getImageSource()"
style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-2')"
/>
<ng-container [ngTemplateOutlet]="camera"></ng-container> <ng-container [ngTemplateOutlet]="camera"></ng-container>
</div> </div>
<div class="state t5 tooltip" id="State5" fxLayout="row"> <div class="state t5 tooltip" id="State5" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-1')">
<img
[src]="getImageSource()"
style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-1')"
/>
<ng-container [ngTemplateOutlet]="camera"></ng-container> <ng-container [ngTemplateOutlet]="camera"></ng-container>
</div> </div>
<div class="state t6 tooltip" id="State6" fxLayout="row"> <div class="state t6 tooltip" id="State6" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-15')">
<img
[src]="getImageSource()"
style="width: 30px; height: 30px; cursor: pointer"
(click)="openPositionDialog($event, 'ward-15')"
/>
<ng-container [ngTemplateOutlet]="camera"></ng-container> <ng-container [ngTemplateOutlet]="camera"></ng-container>

</div> </div>
</div> </div>
</div> </div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Position Column --> <!-- Position Column -->
<ng-container matColumnDef="title"> <ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef >
Title
</th>
<th mat-header-cell *matHeaderCellDef>Title</th>
<td mat-cell *matCellDef="let element">{{ element.title }}</td> <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>
<th mat-header-cell *matHeaderCellDef>Date</th>
<td mat-cell *matCellDef="let element">{{ element.date }}</td> <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 {

+ 60
- 16
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"> <div class="state t4" id="State4" 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 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;

+ 32
- 19
src/app/modules/homepage/security-system-details/security-system-details.component.ts View File

import { Component, OnDestroy, OnInit } from '@angular/core'; 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 { debounceTime, filter, Subject, Subscription } from 'rxjs';
import { SocketService } from '../../../shared/services/socket.service';
import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service';
import { AlarmSoundService } from "../../../shared/services/alarm-sound.service";
import { MqttClientService } from "../../../shared/services/mqtt-client.service";
import {takeUntil} from "rxjs/operators";
import { AlarmSoundService } from '../../../shared/services/alarm-sound.service';
import { MqttClientService } from '../../../shared/services/mqtt-client.service';
import { takeUntil } from 'rxjs/operators';


@Component({ @Component({
selector: 'app-security-system-details', selector: 'app-security-system-details',
templateUrl: './security-system-details.component.html', templateUrl: './security-system-details.component.html',
styleUrls: ['./security-system-details.component.scss']
styleUrls: ['./security-system-details.component.scss'],
}) })
export class SecuritySystemDetailsComponent implements OnInit, OnDestroy { export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
isConnected = false; isConnected = false;
private unsubscribeAll = new Subject(); private unsubscribeAll = new Subject();


constructor( constructor(
private socketService$: SocketService,
private dialog: MatDialog, private dialog: MatDialog,
private confirm$: ConfirmDialogService, private confirm$: ConfirmDialogService,
private alarmSoundService$: AlarmSoundService, private alarmSoundService$: AlarmSoundService,
private mqtt$: MqttClientService
private mqtt$: MqttClientService,
) {} ) {}


ngOnInit() { ngOnInit() {
this.mqtt$.status$.pipe(takeUntil(this.unsubscribeAll)).subscribe((isConnected) => {
this.mqtt$.status$
.pipe(takeUntil(this.unsubscribeAll))
.subscribe((isConnected) => {
this.isConnected = isConnected; this.isConnected = isConnected;
}); });
this.mqtt$.messages$.pipe(takeUntil(this.unsubscribeAll), filter((item) => item !== null)).subscribe(message => {
console.log('detail:',message);
this.mqtt$.messages$
.pipe(
takeUntil(this.unsubscribeAll),
filter((item) => item !== null),
)
.subscribe((message) => {
this.onMessage(message); this.onMessage(message);
}); });
} }
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.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') { if (message.status1 == '0') {
data.push({key: 'status1', value: false})
data.push({ key: 'status1', value: false });
} }
if (message.status2 == '0') { if (message.status2 == '0') {
data.push({key: 'status2', value: false})
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>

+ 10
- 4
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;
animation: l13 1s infinite linear; animation: l13 1s infinite linear;
} }
@keyframes l13 { @keyframes l13 {
100%{transform: rotate(1turn)}
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;

+ 11
- 6
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>;


canvas: this.videoPlayer!.nativeElement, canvas: this.videoPlayer!.nativeElement,
onDisconnect(): void { onDisconnect(): void {
console.log('disconnect camera, please wait'); console.log('disconnect camera, please wait');
}
},
}); });
} }



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

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

+ 6
- 9
src/app/shared/component/confirm-dialog/confirm-dialog.component.ts View File

import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; 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 { 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',
styleUrls: ['./confirm-dialog.component.scss'], styleUrls: ['./confirm-dialog.component.scss'],
}) })
export class ConfirmDialogComponent implements OnDestroy { export class ConfirmDialogComponent implements OnDestroy {

private statusSubscription?: Subscription; private statusSubscription?: Subscription;
isConnected: boolean = false; isConnected: boolean = false;


private socketService$: SocketService, private socketService$: SocketService,
private mqtt$: MqttClientService, private mqtt$: MqttClientService,
) { ) {
this.statusSubscription = this.mqtt$.status$.subscribe(
(isConnected) => {
this.statusSubscription = this.mqtt$.status$.subscribe((isConnected) => {
this.isConnected = isConnected; this.isConnected = isConnected;
},
);
});
} }


submitChange(item: any): void { submitChange(item: any): void {
if (this.isConnected) { 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;
} }
} }

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

</div> </div>
<div> <div>
<p><strong>Địa điểm:</strong> {{ item.detail.position }}</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>Tọa độ:</strong> {{ item.detail.coordinates.lat }},
{{ item.detail.coordinates.lng }}
</p>
<p><strong>Thời gian:</strong> {{ item.detail.time }}</p> <p><strong>Thời gian:</strong> {{ item.detail.time }}</p>
</div> </div>
</div> </div>

+ 1
- 3
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;
} }


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>



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

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

+ 54
- 12
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)"
<input
type="range"
[(ngModel)]="value"
(input)="onSliderChange($event)"
[ngStyle]="onSliderChangeBackground()" [ngStyle]="onSliderChangeBackground()"
[max]="max" [max]="max"
[disabled]="disable">
[disabled]="disable"
/>
</div> </div>
</div> </div>
<button class="volume-control" [disabled]="disable" (click)="increaseVolume()">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<button
class="volume-control"
[disabled]="disable"
(click)="increaseVolume()"
>
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>Icons/Plus square fill</title> <title>Icons/Plus square fill</title>
<g id="Icons/Plus-square-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g
id="Icons/Plus-square-fill"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<g id="Plus"> <g id="Plus">
<path <path
d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M17.25,12.75 L12.75,12.75 L12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.75 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.25 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 Z" d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M17.25,12.75 L12.75,12.75 L12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.75 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.25 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 Z"
id="Shape" fill="currentColor" fill-rule="nonzero"></path>
id="Shape"
fill="currentColor"
fill-rule="nonzero"
></path>
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
</g> </g>
</g> </g>

+ 27
- 29
src/app/shared/component/slider-range/slider-range.component.scss View File

0 25%, 0 25%,
50% 0, 50% 0,
50% 1px, 50% 1px,
1px calc(25% + .5px),
1px calc(75% - .5px),
1px calc(25% + 0.5px),
1px calc(75% - 0.5px),
50% calc(100% - 1px), 50% calc(100% - 1px),
calc(100% - 1px) calc(75% - .5px),
calc(100% - 1px) calc(25% + .5px),
calc(100% - 1px) calc(75% - 0.5px),
calc(100% - 1px) calc(25% + 0.5px),
50% 1px 50% 1px
); );
background-color: #ff7723; background-color: #ff7723;
} }


.volume-control { .volume-control {
padding: .63rem;
border-top: solid .06rem #ff7723;
border-bottom: solid .06rem #ff7723;
padding: 0.63rem;
border-top: solid 0.06rem #ff7723;
border-bottom: solid 0.06rem #ff7723;
background-color: transparent; background-color: transparent;
border-left: none; border-left: none;
border-right: none; border-right: none;
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
} }

} }


.volume-slider { .volume-slider {
border: .06rem solid #eed3c2;
height: .75rem;
border: 0.06rem solid #eed3c2;
height: 0.75rem;
width: 39rem; width: 39rem;
position: relative; position: relative;


width: 38.5rem; width: 38.5rem;
appearance: none; appearance: none;
background-color: transparent; background-color: transparent;
margin: .25rem;
margin: 0.25rem;


&::-webkit-slider-thumb { &::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
background: #ff7723
;
border: .06rem solid #ff7723;
height: .88rem;
width: .88rem;
background: #ff7723;
border: 0.06rem solid #ff7723;
height: 0.88rem;
width: 0.88rem;
padding: 2px; padding: 2px;
background-clip: content-box; background-clip: content-box;
transition: transform .2s ease;
transition: transform 0.2s ease;


&:focus { &:focus {
cursor: pointer; cursor: pointer;
border: .12rem solid #ff7723;
border: 0.12rem solid #ff7723;
transform: scale(1.2); transform: scale(1.2);
} }


&:hover { &:hover {
cursor: pointer; cursor: pointer;
border: .12rem solid #ff7723;
border: 0.12rem solid #ff7723;
transform: scale(1.2); transform: scale(1.2);
} }
} }
&::-moz-range-thumb { &::-moz-range-thumb {
border-radius: 0; border-radius: 0;
background: #ff7723; background: #ff7723;
border: .06rem solid #ff7723;
height: .5rem;
width: .5rem ;
border: 0.06rem solid #ff7723;
height: 0.5rem;
width: 0.5rem;
padding: 3px; padding: 3px;
background-clip: content-box; background-clip: content-box;
&:focus { &:focus {
cursor: pointer; cursor: pointer;
border: .14rem solid #ff7723;
border: 0.14rem solid #ff7723;
transform: scale(1); transform: scale(1);
} }


&:hover { &:hover {
cursor: pointer; cursor: pointer;
border: .14rem solid #ff7723;
border: 0.14rem solid #ff7723;
transform: scale(1); transform: scale(1);
} }
} }
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
background: #ff7723; background: #ff7723;
border: .06rem solid #ff7723;
height: .88rem;
width: .88rem;
border: 0.06rem solid #ff7723;
height: 0.88rem;
width: 0.88rem;
padding: 3px; padding: 3px;
background-clip: content-box; background-clip: content-box;
&:focus { &:focus {
cursor: pointer; cursor: pointer;
border: .12rem solid #ff7723;
border: 0.12rem solid #ff7723;
transform: scale(1.2); transform: scale(1.2);
} }


&:hover { &:hover {
cursor: pointer; cursor: pointer;
border: .12rem solid #ff7723;
border: 0.12rem solid #ff7723;
transform: scale(1.2); transform: scale(1.2);
} }
} }

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

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

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

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

} }



startAlarm(isTurnOn: boolean, fireArm: boolean, fireStatus: boolean, fenceArm: boolean, fenceStatus: boolean): void {
startAlarm(
isTurnOn: boolean,
fireArm: boolean,
fireStatus: boolean,
fenceArm: boolean,
fenceStatus: boolean,
): void {
if (!isTurnOn) { if (!isTurnOn) {
this.stopAlarm(); this.stopAlarm();
return; return;

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

+ 34
- 18
src/app/shared/services/mqtt-client.service.ts View File

} from 'ngx-mqtt'; } from 'ngx-mqtt';
import { IClientSubscribeOptions } from 'mqtt-browser'; import { IClientSubscribeOptions } from 'mqtt-browser';
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
import {TOPIC_GETTING, TOPIC_INFO, TOPIC_LOG, TOPIC_SETTING} from "../../app.constants";
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;
port: 8884, port: 8884,
reconnectPeriod: 4000, reconnectPeriod: 4000,
protocol: 'wss', protocol: 'wss',
}
};
receiveNews = ''; receiveNews = '';
isConnection = false; isConnection = false;
private statusSubject = new BehaviorSubject<boolean>(this.isConnection); private statusSubject = new BehaviorSubject<boolean>(this.isConnection);
// 创建连接 // 创建连接
createConnection() { createConnection() {
try { try {
this.client?.connect(this.connection as IMqttServiceOptions)
this.client?.connect(this.connection as IMqttServiceOptions);
} catch (error) { } catch (error) {
console.log('mqtt.connect error', error); console.log('mqtt.connect error', error);
} }
topic: TOPIC_LOG, topic: TOPIC_LOG,
qos: 0, qos: 0,
}; };
this.curSubscription = this.client?.observe(topic, { qos, dup: false } as IClientSubscribeOptions).subscribe((message: IMqttMessage) => {
this.curSubscription = this.client
?.observe(topic, { qos, dup: false } as IClientSubscribeOptions)
.subscribe((message: IMqttMessage) => {
try { try {
let plainMessage = "";
let plainMessage = '';
for (var i = 0; i < message.payload.length; i++) { for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(parseInt(String(message.payload[i])));
plainMessage += String.fromCharCode(
parseInt(String(message.payload[i])),
);
} }
console.log(JSON.parse(plainMessage)); console.log(JSON.parse(plainMessage));
this.messagesSubject$.next(JSON.parse(plainMessage)); this.messagesSubject$.next(JSON.parse(plainMessage));
}); });
} }



// 订阅主题 // 订阅主题
getSetting() { getSetting() {
const { topic, qos } = { const { topic, qos } = {
topic: TOPIC_SETTING, topic: TOPIC_SETTING,
qos: 0, qos: 0,
}; };
this.settingSubscription = this.client?.observe(topic, { qos, dup: false } as IClientSubscribeOptions).subscribe((message: IMqttMessage) => {
this.settingSubscription = this.client
?.observe(topic, { qos, dup: false } as IClientSubscribeOptions)
.subscribe((message: IMqttMessage) => {
try { try {
let plainMessage = "";
let plainMessage = '';
for (var i = 0; i < message.payload.length; i++) { for (var i = 0; i < message.payload.length; i++) {
plainMessage += String.fromCharCode(parseInt(String(message.payload[i])));
plainMessage += String.fromCharCode(
parseInt(String(message.payload[i])),
);
} }
console.log(JSON.parse(plainMessage)); console.log(JSON.parse(plainMessage));
this.settingSubject$.next(JSON.parse(plainMessage)); this.settingSubject$.next(JSON.parse(plainMessage));


// 取消订阅 // 取消订阅
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());
} }
} }
} }

+ 11
- 10
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;
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: () => {
this.isConnected = false; this.isConnected = false;
this.connect({ reconnect: true }); this.connect({ reconnect: true });
this.statusSubject.next(this.isConnected); this.statusSubject.next(this.isConnected);
}
},
}, },
}); });
} }

+ 6
- 11
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',
}; };

+ 25
- 4
src/assets/style/scss/common.scss View File

left: $px * 1px !important; left: $px * 1px !important;
} }
} }
$pxs: 0 0, 1 4, 2 8, 3 16, 4 24, 5 32, 6 48, 7 56, 8 62, 9 70, 10 78 ;
$pxs:
0 0,
1 4,
2 8,
3 16,
4 24,
5 32,
6 48,
7 56,
8 62,
9 70,
10 78;
@each $i, $px in $pxs { @each $i, $px in $pxs {
@include mixPaddingMargin($i, $px); @include mixPaddingMargin($i, $px);
@include mixPosition($i, $px); @include mixPosition($i, $px);
} }


@each $weight in 100, 200, 300, 400, 500, 600, 700, 800, 900 { @each $weight in 100, 200, 300, 400, 500, 600, 700, 800, 900 {
[mat-weight='#{$weight}'] {
[mat-weight="#{$weight}"] {
font-weight: $weight !important; font-weight: $weight !important;
} }
} }
.fz-#{$i} { .fz-#{$i} {
font-size: $rem * 1rem !important; font-size: $rem * 1rem !important;
} }

} }


$rems: 0 0.5, 1 0.75, 2 1, 3 1.25, 4 1.5, 5 1.75, 6 2, 7 2.25, 8 2.5, 9 2.75, 10 3;
$rems:
0 0.5,
1 0.75,
2 1,
3 1.25,
4 1.5,
5 1.75,
6 2,
7 2.25,
8 2.5,
9 2.75,
10 3;
@each $i, $rem in $rems { @each $i, $rem in $rems {
@include mixFontSize($i, $rem); @include mixFontSize($i, $rem);
} }

+ 13
- 7
src/index.html View File

<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>IotWebUi</title> <title>IotWebUi</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<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> </head>
<body class="mat-typography"> <body class="mat-typography">
<app-root></app-root> <app-root></app-root>

+ 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