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

@@ -7,7 +7,9 @@
"build": "ng build",
"watch": "ng build --watch --configuration development",
"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,
"dependencies": {
@@ -29,8 +31,10 @@
"express-ws": "^5.0.2",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"moment": "^2.30.1",
"ngx-mqtt": "^17.0.0",
"ngx-toastr": "^17.0.2",
"prettier": "^3.3.3",
"rtsp-relay": "^1.8.0",
"rxjs": "~7.8.0",
"rxjs-websockets": "^9.0.0",

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

@@ -3,10 +3,12 @@ import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent]
}));
beforeEach(() =>
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
}),
);

it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
@@ -24,6 +26,8 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
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

@@ -1,11 +1,8 @@
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({
selector: 'app-root',
@@ -20,22 +17,33 @@ export class AppComponent implements OnInit {

ngOnInit() {
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.mqtt$.getSetting();
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

@@ -1,11 +1,11 @@
import { NgModule} from '@angular/core';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {SharedModule} from "./shared/shared.module";
import {HttpClientModule} from "@angular/common/http";
import { SharedModule } from './shared/shared.module';
import { HttpClientModule } from '@angular/common/http';
import { ToastrModule } from 'ngx-toastr';
import { IMqttServiceOptions, MqttModule } from 'ngx-mqtt';
export const connection: IMqttServiceOptions = {
@@ -18,12 +18,10 @@ export const connection: IMqttServiceOptions = {
clientId: 'iot-' + Date.parse(new Date().toString()),
protocol: 'ws',
connectOnCreate: false,
}
};

@NgModule({
declarations: [
AppComponent,
],
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
@@ -33,11 +31,11 @@ export const connection: IMqttServiceOptions = {
ToastrModule.forRoot({
maxOpened: 1,
preventDuplicates: true,
autoDismiss: true
autoDismiss: true,
}), // ToastrModule added
MqttModule.forRoot(connection)
MqttModule.forRoot(connection),
],
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

@@ -4,4 +4,3 @@
<div id="map"></div>
</div>
</div>


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

@@ -5,7 +5,7 @@
width: 100%;
height: calc(100% - 7rem);
h2 {
padding: .5rem 3rem 0 ;
padding: 0.5rem 3rem 0;
text-align: center;
}
}
@@ -21,26 +21,27 @@ p {
margin: 0 0 0 16px !important;
}
.box-custom {
color: #F33152;
color: #f33152;
padding: 3px 2px;
background-color: rbg(243,49,82,0.1);
background-color: rbg(243, 49, 82, 0.1);
}

::ng-deep.sensor-on {
width: 30px;
height: 30px;
position: relative;
background: linear-gradient(#ff0000, #C70039);
background: linear-gradient(#ff0000, #c70039);
display: flex !important;
justify-content: center;
align-items: center;
border-radius: 50%;
img{
img {
z-index: 9;
}
&:before, &:after {
&:before,
&:after {
position: absolute;
content: '';
content: "";
width: 100%;
height: 100%;
background: #ff0000;
@@ -56,7 +57,7 @@ p {
animation: sensor-on 2s 1s ease-out infinite;
}
}
.sensor-off{
.sensor-off {
display: inline-block;
}
@keyframes sensor-on {
@@ -69,7 +70,7 @@ p {
display: flex;
flex-direction: column;
gap: 5px;
.dynamic-button{
.dynamic-button {
cursor: pointer;
}
}

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

@@ -8,7 +8,7 @@ describe('CentralizedSecurityManagementComponent', () => {

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

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

@@ -7,21 +7,22 @@ import {
} from '@angular/core';
import * as L from 'leaflet';
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 { MatDialog } from '@angular/material/dialog';
import { AlarmSoundService } from '../../../shared/services/alarm-sound.service';
import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component';
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({
selector: 'app-centralized-security-management',
templateUrl: './centralized-security-management.component.html',
styleUrls: ['./centralized-security-management.component.scss'],
})
export class CentralizedSecurityManagementComponent implements AfterViewInit, OnDestroy
export class CentralizedSecurityManagementComponent
implements AfterViewInit, OnDestroy
{
private map!: L.Map;
private markers!: L.MarkerClusterGroup;
@@ -39,7 +40,7 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On
private dialog: MatDialog,
private renderer: Renderer2,
private alarmSoundService$: AlarmSoundService,
private mqtt$: MqttClientService
private mqtt$: MqttClientService,
) {}

openDialog(): void {
@@ -51,10 +52,15 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On

ngAfterViewInit(): void {
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 {
@@ -91,10 +97,15 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On
this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h
this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass
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();
}

}

addIconsToMap(): void {
@@ -106,7 +117,13 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On

addMarker(item: any, isDemo: boolean = false): void {
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);
const marker = L.marker(
[item.detail.coordinates.lat, item.detail.coordinates.lng],
@@ -152,7 +169,11 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On
</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) {
return L.icon({
iconUrl: active
@@ -182,7 +203,13 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On
}
}

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) {
let text = [];
if (fireStatus && fireArm) {
@@ -191,8 +218,7 @@ export class CentralizedSecurityManagementComponent implements AfterViewInit, On
if (fenceStatus && fenceArm) {
text.push('FENCE ALARM');
}
return this.createIcon(true, '', text);

return this.createIcon(true, '', text);
}
return this.createIcon(false);
}

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

@@ -219,3 +219,140 @@ export const atmWarningData = [
},
},
];
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

@@ -1,24 +1,25 @@
import { NgModule } from '@angular/core';
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 {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 { 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({
declarations: [
HomePageComponent,
CentralizedSecurityManagementComponent,
SecuritySystemDetailsComponent,
SecurityAtmDetailsComponent
SecurityAtmDetailsComponent,
HotelManagementComponent,
],
imports: [
CommonModule,

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

@@ -2,7 +2,8 @@ import { Routes } from '@angular/router';
import { HomePageComponent } from './homepage/home-page.component';
import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.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 = [
{
@@ -20,5 +21,9 @@ export const homePageRoutes: Routes = [
{
path: 'atm-monitoring',
component: SecurityAtmDetailsComponent,
}
},
{
path: 'hotel-room-management',
component: HotelManagementComponent,
},
];

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

@@ -3,16 +3,19 @@
Centralized Security Management
</button>
<button mat-stroked-button routerLink="./security-system-details">
Security System Details
Factory Security System
</button>
<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>
</div>

<mat-card class="sound-group">
<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-content>
<div class="volume-group">
@@ -28,14 +31,14 @@

<mat-card class="sound-group">
<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-content>
<div class="volume-group">
<app-slider-range
[value]="alarm.time"
[icon]="'access_alarm'"
(valueChange)="emitSetting('alarm',$event)"
(valueChange)="emitSetting('alarm', $event)"
[disable]="!isConnected"
></app-slider-range>
</div>

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

@@ -1,6 +1,6 @@
.sound-group{
.sound-group {
margin: 2rem 3rem;
}
button{
button {
color: #ff7723 !important;
}

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

@@ -8,7 +8,7 @@ describe('HomepageComponent', () => {

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

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

@@ -1,8 +1,8 @@
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({
selector: 'app-homepage',
@@ -21,35 +21,43 @@ export class HomePageComponent implements OnInit, OnDestroy {
isConnected = false;
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.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) {
if (this.isConnected) {
if (type === 'whistle') {
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') {
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

@@ -0,0 +1,34 @@
<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

@@ -0,0 +1,81 @@
.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

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,15 @@
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

@@ -1,47 +1,88 @@
<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 class="card-state">
<img src="assets/images/map-quan5.jpg">
<img src="assets/images/map-quan5.jpg" />
<div class="state t2" id="State2">
<div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)">
<img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'"
style="width: 30px; height: 30px; 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>
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div *ngIf="(state1 && state5 && status1)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div>
<div
*ngIf="state1 && state5 && status1"
class="alarm-text"
[ngClass]="{ 'alarm-text-on': state1 && state5 && status1 }"
>
FIRE ALARM
</div>
</div>

<div class="state t3" id="State3">
<div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)">
<img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'"
style="width: 30px; height: 30px; 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>
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div *ngIf="(state2 && state5 && status2)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">VIBRATION ALARM</div>
<div
*ngIf="state2 && state5 && status2"
class="alarm-text"
[ngClass]="{ 'alarm-text-on': state2 && state5 && status2 }"
>
VIBRATION ALARM
</div>
</div>
<div class="state t4" id="State4" fxLayout="row" >
<img [src]="getImageSource()" style="width: 30px; height: 30px; 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 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>
</div>
<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>

</div>
</div>
</div>
@@ -49,24 +90,23 @@
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef >
Title
</th>
<td mat-cell *matCellDef="let element"> {{element.title}} </td>
<th mat-header-cell *matHeaderCellDef>Title</th>
<td mat-cell *matCellDef="let element">{{ element.title }}</td>
</ng-container>
<ng-container matColumnDef="date">
<th mat-header-cell *matHeaderCellDef >
Date
</th>
<td mat-cell *matCellDef="let element"> {{element.date}} </td>
<th mat-header-cell *matHeaderCellDef>Date</th>
<td mat-cell *matCellDef="let element">{{ element.date }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</div>
<ng-template #camera>
<div (click)="openDialog()">
<img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png">
<img
style="width: 30px; height: 30px; cursor: pointer"
src="assets/images/camera.png"
/>
</div>
</ng-template>

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

@@ -8,7 +8,7 @@ describe('SecuritySystemDetailsComponent', () => {

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

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

@@ -1,11 +1,13 @@
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 { MatDialog } from '@angular/material/dialog';
import { AlarmSoundService } from '../../../shared/services/alarm-sound.service';
import { MatTableDataSource } from '@angular/material/table';
import { DetailPositionDialogComponent } from '../../../shared/component/detail-postion-dialog/detail-position-dialog.component';
import { atmWarningData } from '../data/fake-data';
import { MqttClientService } from '../../../shared/services/mqtt-client.service';
import { takeUntil } from 'rxjs/operators';

const ELEMENT_DATA: any[] = [
{ title: 'Vibration warning Phường 15', date: '26/08/2024 12:14' },
@@ -37,31 +39,23 @@ export class SecurityAtmDetailsComponent implements OnInit, OnDestroy {
constructor(
private dialog: MatDialog,
private alarmSoundService$: AlarmSoundService,
private mqtt$: MqttClientService,
) {}

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 {

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

@@ -1,49 +1,93 @@
<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 class="card-state">
<img src="assets/images/ground.png">
<img src="assets/images/ground.png" />
<div class="state t2" id="State2">
<div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)">
<img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
<div
class="sensor-off"
[class.sensor-on]="state1 && state5 && status1"
>
<img
[src]="
state5 && status1
? 'assets/images/sensor-on.png'
: 'assets/images/sensor-off.png'
"
style="width: 30px; height: 30px"
/>
</div>
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div *ngIf="(state1 && state5 && status1)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div>
<div
*ngIf="state1 && state5 && status1"
class="alarm-text"
[ngClass]="{ 'alarm-text-on': state1 && state5 && status1 }"
>
FIRE ALARM
</div>
</div>

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

</div>
</div>
</div>
<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>
<ng-template #camera>
<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>
</ng-template>

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

@@ -8,7 +8,7 @@ describe('SecuritySystemDetailsComponent', () => {

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

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

@@ -1,17 +1,17 @@
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 { 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({
selector: 'app-security-system-details',
templateUrl: './security-system-details.component.html',
styleUrls: ['./security-system-details.component.scss']
styleUrls: ['./security-system-details.component.scss'],
})
export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
isConnected = false;
@@ -28,21 +28,26 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
private unsubscribeAll = new Subject();

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

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 {
@@ -53,13 +58,14 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {

toggleState1() {
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);

}

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) {
@@ -73,20 +79,27 @@ export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
this.state5 = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF
this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF';

this.switchArm = message.state5 == '1';// alarm 9h && 6h // 1 ON, 0 OFF
this.switchArm = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF

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 = [];
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.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 {

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

@@ -1,14 +1,20 @@
<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()">
<mat-icon>close</mat-icon>
</button>
</div>

<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="loader"></div>
</div>

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

@@ -2,9 +2,9 @@
width: 90% !important;
//height: 90%;
display: block;
margin: 0 auto
margin: 0 auto;
}
.mat-mdc-dialog-content{
.mat-mdc-dialog-content {
max-height: unset;
}

@@ -17,20 +17,26 @@
animation: l4 1s steps(4) infinite;
}
.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 {
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
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;
}
@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

@@ -8,7 +8,7 @@ describe('CameraDialogComponent', () => {

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

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

@@ -1,27 +1,32 @@
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({
selector: 'app-camera-dialog',
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;
@ViewChild('videoPlayer') videoPlayer?: ElementRef<HTMLCanvasElement>;

constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) { }
constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) {}

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 {
@@ -29,7 +34,7 @@ export class CameraDialogComponent implements AfterViewInit, OnDestroy{
}

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

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

@@ -1,11 +1,13 @@
<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">
<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>
</div>
</mat-dialog-content>

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

@@ -1,10 +1,12 @@
.mat-mdc-dialog-title {
border-bottom: solid 1px #eeeeee;
font-size: 14px;
color: #FFFFFF;
color: #ffffff;
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;
border: orange !important;
}

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

@@ -8,7 +8,7 @@ describe('ConfirmDialogComponent', () => {

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

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

@@ -1,16 +1,15 @@
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({
selector: 'app-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss'],
})
export class ConfirmDialogComponent implements OnDestroy{

export class ConfirmDialogComponent implements OnDestroy {
private statusSubscription?: Subscription;
isConnected: boolean = false;

@@ -19,22 +18,20 @@ export class ConfirmDialogComponent implements OnDestroy{
private socketService$: SocketService,
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';
this.mqtt$.sendPublish({ id: '0', type: 'bypass', state: position})
this.mqtt$.sendPublish({ id: '0', type: 'bypass', state: position });
item.value = true;
}
}

ngOnDestroy() {
ngOnDestroy() {
if (this.statusSubscription) {
this.statusSubscription.unsubscribe();
}

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

@@ -1,11 +1,14 @@
<div class="popup-container">
<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>
</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>

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

@@ -14,16 +14,14 @@
}

.popup-title {
color: #F33152;
color: #f33152;
font-weight: 500;
}

.close-button {
cursor: pointer;
color: #eeeeee;
&:hover{
&:hover {
color: #000000;
}
}



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

@@ -1,13 +1,13 @@
<div class="layout-container">
<div class="header mat-elevation-z1">
<img src="../../../../../../assets/images/logo.png" >
<img src="../../../../../../assets/images/logo.png" />
<div>
<button mat-button routerLink="/homepage" routerLinkActive="active">Home</button>
<button mat-button routerLink="/homepage" routerLinkActive="active">
Home
</button>
</div>
</div>
<div>
<div style="flex: 1">
<router-outlet></router-outlet>
</div>
</div>



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

@@ -1,14 +1,15 @@
.layout-container{
.layout-container {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
.header {
height: 3rem;
padding: .25rem 2rem;
padding: 0.25rem 2rem;
display: flex;
flex-direction: row;
align-items: center;
img{
img {
width: 7rem;
height: 3rem;
}
@@ -19,5 +20,4 @@
color: #ff7723 !important;
}
}

}

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

@@ -8,7 +8,7 @@ describe('LayoutComponent', () => {

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

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

@@ -3,8 +3,6 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-layout',
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

@@ -3,15 +3,34 @@
<div class="speaker">
<mat-icon>{{ icon }}</mat-icon>
</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>
<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">
<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"
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>
</g>
</g>
@@ -19,21 +38,44 @@
</button>
<div class="volume-control" style="padding: 1rem 0">
<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>
<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>
<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">
<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"
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>
</g>
</g>

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

@@ -21,29 +21,29 @@
width: 100%;
height: 100%;
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;
}
}

.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;
border-left: none;
border-right: none;
@@ -53,12 +53,11 @@
width: 1.5rem;
height: 1.5rem;
}

}

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

@@ -72,29 +71,28 @@
width: 38.5rem;
appearance: none;
background-color: transparent;
margin: .25rem;
margin: 0.25rem;

&::-webkit-slider-thumb {
-webkit-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;
background-clip: content-box;
transition: transform .2s ease;
transition: transform 0.2s ease;

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

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

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

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

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

@@ -8,7 +8,7 @@ describe('SilderRangeComponent', () => {

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

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

@@ -6,7 +6,7 @@ import {
Output,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {debounceTime, Subject} from 'rxjs';
import { debounceTime, Subject } from 'rxjs';

@Component({
selector: 'app-slider-range',
@@ -31,7 +31,7 @@ export class SliderRangeComponent implements ControlValueAccessor {
private valueChangeSubject = new Subject<number>();

constructor() {
this.valueChangeSubject.pipe(debounceTime(500)).subscribe(value => {
this.valueChangeSubject.pipe(debounceTime(500)).subscribe((value) => {
this.valueChange.emit(value);
});
}
@@ -95,7 +95,6 @@ export class SliderRangeComponent implements ControlValueAccessor {
this.onChange(this.value);
this.onTouched();
this.valueChangeSubject.next(this.value);

}
}


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

@@ -0,0 +1,15 @@
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

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import {max} from "rxjs";
import { max } from 'rxjs';

@Injectable({
providedIn: 'root',
@@ -13,25 +13,31 @@ export class AlarmSoundService {
constructor() {
this.audio.src = 'assets/sound/alarm_5m.mp3';
this.audio.load();

}

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) {
this.stopAlarm();
return;
@@ -60,12 +66,12 @@ export class AlarmSoundService {
this.audio.currentTime = 0;
this.isPlay = false;
}
if (this.alertInterval){
if (this.alertInterval) {
clearInterval(this.alertInterval);
}
}

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

@@ -15,7 +15,7 @@ export class ConfirmDialogService {
if (!this.isDialogOpen) {
this.isDialogOpen = true;
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: data
data: data,
});
dialogRef
.afterClosed()

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

@@ -7,11 +7,16 @@ import {
IPublishOptions,
} from 'ngx-mqtt';
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({
providedIn: 'root'
providedIn: 'root',
})
export class MqttClientService {
client: MqttService | undefined;
@@ -23,11 +28,11 @@ export class MqttClientService {
connection = {
hostname: 'broker.hivemq.com',
clean: true,
path:'/mqtt',
path: '/mqtt',
port: 8884,
reconnectPeriod: 4000,
protocol: 'wss',
}
};
receiveNews = '';
isConnection = false;
private statusSubject = new BehaviorSubject<boolean>(this.isConnection);
@@ -40,7 +45,7 @@ export class MqttClientService {
// 创建连接
createConnection() {
try {
this.client?.connect(this.connection as IMqttServiceOptions)
this.client?.connect(this.connection as IMqttServiceOptions);
} catch (error) {
console.log('mqtt.connect error', error);
}
@@ -69,64 +74,75 @@ export class MqttClientService {
topic: TOPIC_LOG,
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() {
const { topic, qos } = {
topic: TOPIC_SETTING,
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() {
this.curSubscription?.unsubscribe()
this.curSubscription?.unsubscribe();
this.settingSubscription?.unsubscribe();
}

// 发送消息
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) {
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();
}
// 断开连接
destroyConnection() {
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) {
console.log('Disconnect failed', error.toString())
console.log('Disconnect failed', error.toString());
}
}
}

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

@@ -1,34 +1,35 @@
import { Injectable } from '@angular/core';
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({
providedIn: 'root'
providedIn: 'root',
})
export class SocketService {
gateway = config.gateway;

websocket$: WebSocketSubject<any> | undefined ;
websocket$: WebSocketSubject<any> | undefined;
private isConnected: boolean = false;
private messagesSubject$ = new Subject();
public messages$ = this.messagesSubject$.asObservable();
private statusSubject = new BehaviorSubject<boolean>(this.isConnected);
public status$ = this.statusSubject.asObservable();

constructor() {
}
constructor() {}

public connect(cfg: { reconnect: boolean } = { reconnect: false }): void {
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$.subscribe((messages) => {
this.messagesSubject$.next(messages);
});
}

}

close() {
@@ -48,16 +49,16 @@ export class SocketService {
console.log('Connection ok');
this.isConnected = true;
this.statusSubject.next(this.isConnected);
}
},
},
closeObserver: {
next: () => {
console.log('Connection closed');
this.websocket$ = undefined;
this.isConnected = false;
this.connect({reconnect: true});
this.connect({ reconnect: true });
this.statusSubject.next(this.isConnected);
}
},
},
});
}

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

@@ -1,17 +1,12 @@
import { NgModule } from '@angular/core';
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({
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

@@ -1,3 +1,3 @@
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

@@ -66,7 +66,7 @@
}
@mixin mixPosition($i, $px) {
.top-#{$i} {
top: $px * 1px !important;
top: $px * 1px !important;
}
.top--#{$i} {
top: $px * -1px !important;
@@ -84,14 +84,25 @@
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 {
@include mixPaddingMargin($i, $px);
@include mixPosition($i, $px);
}

@each $weight in 100, 200, 300, 400, 500, 600, 700, 800, 900 {
[mat-weight='#{$weight}'] {
[mat-weight="#{$weight}"] {
font-weight: $weight !important;
}
}
@@ -99,12 +110,22 @@ $pxs: 0 0, 1 4, 2 8, 3 16, 4 24, 5 32, 6 48, 7 56, 8 62, 9 70, 10 78 ;
@mixin mixFontSize($i, $rem) {
// reusable code here
.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);
}

+ 19
- 13
src/index.html View File

@@ -1,16 +1,22 @@
<!doctype html>
<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>

+ 3
- 3
src/main.ts View File

@@ -2,6 +2,6 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

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

@@ -1,14 +1,15 @@
const express = require('express');
const expressWs = require('express-ws');
const express = require("express");
const expressWs = require("express-ws");
const app = express();
expressWs(app);

const { proxy, scriptUrl } = require('rtsp-relay')(app);
const { proxy, scriptUrl } = require("rtsp-relay")(app);

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({
url: `rtsp://${req.params.cameraIP}:554/feed`,
})(ws),
@@ -17,13 +18,11 @@ app.ws('/api/stream/:cameraIP', (ws, req) =>
const handler = proxy({
url: urlCamera,
verbose: false,
transport: 'tcp'
transport: "tcp",
});


app.ws('/stream', handler);
app.ws("/stream", handler);

const server = app.listen(port, () => {
console.log(`RTSP Relay Server is listening on port ${port}`);
});


+ 8
- 2
src/styles.scss View File

@@ -1,4 +1,10 @@
/* You can add global styles to this file, and also import other style files */
@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