김대철
Showing 173 changed files with 10557 additions and 0 deletions
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
# compiled output
/dist
/node_modules
/output
# config env
.env
.envrc
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
builds/*
nohup.out
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
ormconfig.json
\ No newline at end of file
cache:
untracked: true
key: '$CI_BUILD_REF_NAME'
paths:
- node_modules/
build:
stage: build
script:
- npm install
- npm run build
only:
- master
tags:
- build
deploy:
stage: deploy
script:
- source ./env/production.sh
- pm2 start --exp-backoff-restart-delay=200
only:
- master
tags:
- deploy
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
## Description
드.론.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
File mode changed
module.exports = {
apps: [
{
name: 'backend',
script: 'npm',
args: 'start',
},
],
};
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
This diff could not be displayed because it is too large.
{
"name": "web-api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
},
"dependencies": {
"@nestjs/common": "^7.6.13",
"@nestjs/config": "^0.6.3",
"@nestjs/core": "^7.6.13",
"@nestjs/platform-express": "^7.6.13",
"@nestjs/platform-socket.io": "^7.6.15",
"@nestjs/platform-ws": "^7.6.15",
"@nestjs/typeorm": "^7.1.5",
"@nestjs/websockets": "^7.6.15",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.6",
"typeorm": "^0.2.32"
},
"devDependencies": {
"@nestjs/cli": "^7.5.6",
"@nestjs/schematics": "^7.2.7",
"@nestjs/testing": "^7.6.13",
"@types/express": "^4.17.11",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.31",
"@types/socket.io": "^2.1.13",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.15.2",
"@typescript-eslint/parser": "^4.15.2",
"eslint": "^7.20.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"supertest": "^6.1.3",
"ts-jest": "^26.5.2",
"ts-loader": "^8.0.17",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.1.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import configuration from './config/configuration';
import { DroneModule } from './drone/drone.module';
import { DroneEntity } from './entities/drone.entity';
import { DroneLogEntity } from './entities/drone.log.entity';
import { ScheduleEntity } from './entities/schedule.entity';
import { MemberEntity } from './entities/member.entity';
import { CodeEntity } from './entities/code.entity';
import { DroneScheduleMappingEntity } from './entities/drone.schedule.mapping.entity';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('database.host'),
username: configService.get('database.user'),
password: configService.get('database.password'),
database: configService.get('database.name'),
port: configService.get('database.port'),
entities: [`${__dirname}/**/*.entity.{ts,js}`],
synchronize: false,
}),
}),
DroneModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
import 'dotenv/config';
export default () => ({
database: {
host: process.env.DATABASE_HOST || '',
user: process.env.DATABASE_USER || '',
name: process.env.DATABASE_NAME || '',
password: process.env.DATABASE_PASSWORD || '',
port: process.env.DATABASE_PORT || '',
},
});
\ No newline at end of file
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { DroneService } from './drone.service';
import { DroneEntity } from 'src/entities/drone.entity';
import { MemberEntity } from 'src/entities/member.entity';
import { DroneLogEntity } from 'src/entities/drone.log.entity';
import { ScheduleEntity } from 'src/entities/schedule.entity';
import { CodeEntity } from 'src/entities/code.entity';
import { DroneApiDto } from 'src/drone/dto/drone.api.dto';
@Controller()
export class DroneController {
constructor(private droneService: DroneService) {
this.droneService = droneService;
}
@Get('/member/list')
async findMemberAll(): Promise<MemberEntity[]> {
const memberList = await this.droneService.findMemberAll();
return Object.assign({
data: {
members: memberList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/member/page')
async findMemberPagination(@Query() queries): Promise<MemberEntity[]> {
const pageNo = queries.pageNo;
const pageSize = queries.pageSize;
const begin = (pageNo - 1) * pageSize;
const end = pageNo * pageSize;
const memberList = await (await this.droneService.findMemberAll()).slice(
begin,
end,
);
const totalElement = (await this.droneService.findMemberAll()).length;
return Object.assign({
data: {
pageNo: pageNo,
pageSize: pageSize,
totalElement: totalElement,
members: memberList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/member/:id')
async findMemberOne(@Param('id') id: number): Promise<MemberEntity> {
const foundMember = await this.droneService.findMemberOne(id);
return Object.assign({
data: foundMember,
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/drone/list')
async findDroneAll(@Query() queries): Promise<DroneEntity[]> {
// Params 변수
const modelName = queries.modelName;
const maker = queries.maker;
const usageName = queries.usageName;
const minWeight = queries.minWeight;
const maxWeight = queries.maxWeight;
const droneList = await this.droneService.findDroneAll({
modelName,
maker,
usageName,
minWeight,
maxWeight,
});
return Object.assign({
data: {
drones: droneList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/drone/page')
async findDronePagination(@Query() queries): Promise<DroneEntity[]> {
const modelName = queries.modelName;
const maker = queries.maker;
const usageName = queries.usageName;
const minWeight = queries.minWeight;
const maxWeight = queries.maxWeight;
const pageNo = queries.pageNo;
const pageSize = queries.pageSize;
const droneList = await this.droneService.findDroneAll({
modelName,
maker,
usageName,
minWeight,
maxWeight,
pageNo,
pageSize,
});
const totalElement = await this.droneService.findDroneTotalElement({
modelName,
maker,
usageName,
minWeight,
maxWeight,
});
return Object.assign({
data: {
pageNo: pageNo,
pageSize: pageSize,
totalElement: totalElement,
drones: droneList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/drone/:id')
async findDroneOne(@Param('id') id: number): Promise<DroneEntity> {
const foundDrone = await this.droneService.findDroneOne(id);
return Object.assign({
data: foundDrone,
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/map/drone/:id')
async findMapDroneOne(@Param('id') id: number) {
const foundDrone = await this.droneService.findDroneOne(id);
const foundSchedule = await this.droneService.findScheduleByDroneId(id);
if (!foundSchedule) {
return Object.assign({
data: {
foundDrone,
foundSchedule: null,
droneLogs: [],
},
statusCode: 200,
statusMsg: '완료',
});
}
const logList = await this.droneService.findLogListByScheduleId(
foundSchedule.scheduleId,
);
return Object.assign({
data: {
foundDrone,
foundSchedule,
droneLogs: logList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/log/list')
async findLogAll(@Query() queries): Promise<DroneLogEntity[]> {
const scheduleId = queries.scheduleId;
const latitude = queries.latitude;
const longitude = queries.longitude;
const minVerticalSpeed = queries.minVerticalSpeed;
const maxVerticalSpeed = queries.maxVerticalSpeed;
const minHorizontalSpeed = queries.minHorizontalSpeed;
const maxHorizontalSpeed = queries.maxHorizontalSpeed;
const minAboveSeaLevel = queries.minAboveSeaLevel;
const maxAboveSeaLevel = queries.maxAboveSeaLevel;
const minAboveGroundLevel = queries.minAboveGroundLevel;
const maxAboveGroundLevel = queries.maxAboveGroundLevel;
const logList = await this.droneService.findLogAll({
scheduleId,
latitude,
longitude,
minVerticalSpeed,
maxVerticalSpeed,
minHorizontalSpeed,
maxHorizontalSpeed,
minAboveSeaLevel,
maxAboveSeaLevel,
minAboveGroundLevel,
maxAboveGroundLevel,
});
return Object.assign({
data: {
droneLogs: logList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/log/page')
async findLogPagination(@Query() queries): Promise<DroneLogEntity[]> {
const scheduleId = queries.scheduleId;
const latitude = queries.latitude;
const longitude = queries.longitude;
const minVerticalSpeed = queries.minVerticalSpeed;
const maxVerticalSpeed = queries.maxVerticalSpeed;
const minHorizontalSpeed = queries.minHorizontalSpeed;
const maxHorizontalSpeed = queries.maxHorizontalSpeed;
const minAboveSeaLevel = queries.minAboveSeaLevel;
const maxAboveSeaLevel = queries.maxAboveSeaLevel;
const minAboveGroundLevel = queries.minAboveGroundLevel;
const maxAboveGroundLevel = queries.maxAboveGroundLevel;
const pageNo = queries.pageNo;
const pageSize = queries.pageSize;
const logList = await this.droneService.findLogAll({
scheduleId,
latitude,
longitude,
minVerticalSpeed,
maxVerticalSpeed,
minHorizontalSpeed,
maxHorizontalSpeed,
minAboveSeaLevel,
maxAboveSeaLevel,
minAboveGroundLevel,
maxAboveGroundLevel,
pageNo,
pageSize,
});
const totalElement = await this.droneService.findLogTotalElement({
scheduleId,
latitude,
longitude,
minVerticalSpeed,
maxVerticalSpeed,
minHorizontalSpeed,
maxHorizontalSpeed,
minAboveSeaLevel,
maxAboveSeaLevel,
minAboveGroundLevel,
maxAboveGroundLevel,
});
return Object.assign({
data: {
pageNo: pageNo,
pageSize: pageSize,
totalElement: totalElement,
droneLogs: logList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/log/:id')
async findLogOne(@Param('id') id: number): Promise<DroneLogEntity> {
const foundLog = await this.droneService.findLogOne(id);
return Object.assign({
data: foundLog,
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/schedule/list')
async findScheduleAll(@Query() queries): Promise<ScheduleEntity[]> {
const droneId = queries.droneId;
const startTime = queries.startTime;
const terminateTime = queries.terminateTime;
const scheduleList = await this.droneService.findScheduleAll({
droneId,
startTime,
terminateTime,
});
return Object.assign({
data: {
schedules: scheduleList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/schedule/page')
async findSchedulePagination(@Query() queries): Promise<ScheduleEntity[]> {
const droneId = queries.droneId;
const startTime = queries.startTime;
const terminateTime = queries.terminateTime;
const pageNo = queries.pageNo;
const pageSize = queries.pageSize;
const scheduleList = await this.droneService.findScheduleAll({
droneId,
startTime,
terminateTime,
pageNo,
pageSize,
});
const totalElement = await this.droneService.findScheduleTotalElement({
droneId,
startTime,
terminateTime,
});
return Object.assign({
data: {
pageNo: pageNo,
pageSize: pageSize,
totalElement: totalElement,
schedules: scheduleList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/schedule/:id')
async findScheduleOne(@Param('id') id: number): Promise<ScheduleEntity> {
const foundSchedule = await this.droneService.findScheduleOne(id);
return Object.assign({
data: foundSchedule,
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/code/list')
async findCodeAll(): Promise<CodeEntity[]> {
const codeList = await this.droneService.findCodeAll();
return Object.assign({
data: {
codes: codeList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/code/page')
async findCodePagination(@Query() queries): Promise<CodeEntity[]> {
const pageNo = queries.pageNo;
const pageSize = queries.pageSize;
const begin = (pageNo - 1) * pageSize;
const end = pageNo * pageSize;
const codeList = await (await this.droneService.findCodeAll()).slice(
begin,
end,
);
const totalElement = (await this.droneService.findCodeAll()).length;
return Object.assign({
data: {
pageNo: pageNo,
pageSize: pageSize,
totalElement: totalElement,
codes: codeList,
},
statusCode: 200,
statusMsg: '완료',
});
}
@Get('/code/:id')
async findCodeOne(@Param('id') id: number): Promise<CodeEntity> {
const foundCode = await this.droneService.findCodeOne(id);
return Object.assign({
data: foundCode,
statusCode: 200,
statusMsg: '완료',
});
}
@Post()
async saveMember(@Body() memberEntity: MemberEntity): Promise<string> {
await this.droneService.saveMember(memberEntity);
return Object.assign({
statusCode: 201,
data: { ...memberEntity },
statusMsg: '완료',
});
}
@Post()
async saveDrone(@Body() droneEntity: DroneEntity): Promise<string> {
await this.droneService.saveDrone(droneEntity);
return Object.assign({
statusCode: 201,
data: { ...droneEntity },
statusMsg: '완료',
});
}
@Post('drone/list')
@UsePipes(new ValidationPipe({ transform: true }))
async saveDroneList(@Body() saveDroneListDto: DroneApiDto.SaveDroneListDto) {
const affectedRows = await this.droneService.saveDroneList(
saveDroneListDto.droneList,
);
return {
statusCode: 201,
data: { affectedRows },
statusMessage: '완료',
};
}
@Put('drone/list')
@UsePipes(new ValidationPipe({ transform: true }))
async updateDroneList(
@Body() updateDroneListDto: DroneApiDto.UpdateDroneListDto,
) {
const affectedRows = await this.droneService.updateDroneList(
updateDroneListDto.droneList,
);
return {
statusCode: 201,
data: { affectedRows },
statusMessage: '완료',
};
}
@Post()
async saveLog(@Body() dronelogEntity: DroneLogEntity): Promise<string> {
await this.droneService.saveLog(dronelogEntity);
return Object.assign({
statusCode: 201,
data: { ...dronelogEntity },
statusMsg: '완료',
});
}
@Post()
async saveSchedule(@Body() ScheduleEntity: ScheduleEntity): Promise<string> {
await this.droneService.saveSchedule(ScheduleEntity);
return Object.assign({
statusCode: 201,
data: { ...ScheduleEntity },
statusMsg: '완료',
});
}
@Post('schdule/list')
@UsePipes(new ValidationPipe({ transform: true }))
async saveSchduleList(
@Body() saveSchduleListDto: DroneApiDto.SaveSchduleListDto,
) {
const affectedRows = await this.droneService.saveScheduleList(
saveSchduleListDto.schduleList,
);
return {
statusCode: 200,
data: { affectedRows },
statusMessage: '완료',
};
}
@Put('schdule/list')
@UsePipes(new ValidationPipe({ transform: true }))
async updateSchduleList(
@Body() updateSchduleListDto: DroneApiDto.UpdateSchduleListDto,
) {
const affectedRows = await this.droneService.updateSchduleList(
updateSchduleListDto.schduleList,
);
return {
statusCode: 201,
data: { affectedRows },
statusMessage: '완료',
};
}
@Post()
async saveCode(@Body() CodeEntity: CodeEntity): Promise<string> {
await this.droneService.saveCode(CodeEntity);
return Object.assign({
statusCode: 201,
data: { ...CodeEntity },
statusMsg: '완료',
});
}
@Delete('/member/:id')
async deleteMember(@Param('id') id: number): Promise<string> {
await this.droneService.deleteMember(id);
return Object.assign({
statusCode: 201,
data: {
id: id,
},
statusMsg: '완료',
});
}
@Delete('/drone/:id')
async deleteDrone(@Param('id') id: number): Promise<string> {
await this.droneService.deleteDrone(id);
return Object.assign({
statusCode: 201,
data: {
id: id,
},
statusMsg: '완료',
});
}
@Delete('/log/:id')
async deleteLog(@Param('id') id: number): Promise<string> {
await this.droneService.deleteLog(id);
return Object.assign({
statusCode: 201,
data: {
id: id,
},
statusMsg: '완료',
});
}
@Delete('/schedule/:id')
async deleteSchedule(@Param('id') id: number): Promise<string> {
await this.droneService.deleteSchedule(id);
return Object.assign({
statusCode: 201,
data: {
id: id,
},
statusMsg: '완료',
});
}
@Delete('/code/:id')
async deleteCode(@Param('id') id: number): Promise<string> {
await this.droneService.deleteCode(id);
return Object.assign({
statusCode: 201,
data: {
id: id,
},
statusMsg: '완료',
});
}
}
import {
Body,
Controller,
Post,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { DroneDataService } from 'src/drone/drone.data.service';
import { DroneApiDto } from 'src/drone/dto/drone.api.dto';
@Controller()
export class DroneDataController {
constructor(private droneDataService: DroneDataService) {
this.droneDataService = droneDataService;
}
@Post('/droneLog/list')
@UsePipes(new ValidationPipe({ transform: true }))
async saveDroneLogList(
@Body() saveDroneLogListDto: DroneApiDto.SaveDroneLogListDto,
) {
await this.droneDataService.saveDroneLogList(
saveDroneLogListDto.droneLogList,
);
return {
statusCode: 200,
statusMsg: '완료',
};
}
}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DroneLogEntity } from 'src/entities/drone.log.entity';
import { DroneGateway } from 'src/drone/drone.gateway';
import { Repository } from 'typeorm/index';
import { DroneApiDto } from 'src/drone/dto/drone.api.dto';
@Injectable()
export class DroneDataService {
constructor(
@InjectRepository(DroneLogEntity)
private dronelogRepository: Repository<DroneLogEntity>,
private droneGateway: DroneGateway,
) {
this.dronelogRepository = dronelogRepository;
this.droneGateway = droneGateway;
}
async saveDroneLogList(droneLogList: DroneApiDto.SaveDroneLogDto[]) {
// 드론 데이터 전송
this.sendDroneLogList(droneLogList.map((log) => new DroneLogEntity(log)));
// 드론로그 생성
for (const droneLog of droneLogList) {
this.dronelogRepository.save({
droneId: droneLog.droneId,
scheduleId: droneLog.scheduleId,
latitude: droneLog.latitude,
longitude: droneLog.longitude,
verticalSpeed: droneLog.verticalSpeed,
horizontalSpeed: droneLog.horizontalSpeed,
aboveSeaLevel: droneLog.aboveSeaLevel,
aboveGroundLevel: droneLog.aboveGroundLevel,
});
}
}
sendDroneLogList(droneLogList) {
// 드론데이터 전송
this.droneGateway.sendToClientsDroneLogList(droneLogList);
for (const droneLog of droneLogList) {
let droneLogEntity = new DroneLogEntity(droneLog);
this.dronelogRepository.save(droneLogEntity);
}
}
async saveLog(droneLogEntity: DroneLogEntity): Promise<void> {
await this.dronelogRepository.save(droneLogEntity);
}
}
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'ws';
import { DroneService } from './drone.service';
import { DroneLogEntity } from 'src/entities/drone.log.entity';
async function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
@WebSocketGateway(20206, { path: '/drone' })
export class DroneGateway {
constructor(private readonly droneService: DroneService) {}
@WebSocketServer()
server: Server;
private wsClients = [];
async afterInit() {
let globalCircleStep = 0;
while (1) {
if (globalCircleStep === 3600) {
globalCircleStep = 0;
} else {
globalCircleStep += 1;
}
this.wsClients.forEach((client) => {
const droneTestData = this.droneService.getDroneTestData({
globalCircleStep,
});
client.send(
JSON.stringify(
Object.assign({
data: { droneLog: droneTestData },
statusCode: 200,
}),
),
);
});
await sleep(1000);
}
}
async handleConnection(client: Socket) {
let clientId = this.wsClients.push(client);
}
handleDisconnect(client: Socket) {
let clientIndex = this.wsClients.find((wsClient) => wsClient === client);
if (clientIndex !== -1) {
this.wsClients.splice(clientIndex, 1);
}
}
@SubscribeMessage('')
handleMessages(client: Socket, payload: { name: string; text: string }) {
client.send(
JSON.stringify({
number: 10,
member: 'tony',
}),
);
}
sendToClientsDroneLogList(droneLogList: Array<DroneLogEntity>) {
this.wsClients.forEach((client) => {
client.send(
JSON.stringify(
Object.assign({
data: { droneLog: droneLogList },
statusCode: 200,
}),
),
);
});
}
}
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DroneController } from './drone.controller';
import { DroneService } from './drone.service';
import { DroneDataController } from 'src/drone/drone.data.controller';
import { DroneDataService } from './drone.data.service';
import { DroneGateway } from './drone.gateway';
import { DroneEntity } from 'src/entities/drone.entity';
import { ScheduleEntity } from 'src/entities/schedule.entity';
import { DroneLogEntity } from 'src/entities/drone.log.entity';
import { MemberEntity } from 'src/entities/member.entity';
import { CodeEntity } from 'src/entities/code.entity';
import { DroneScheduleMappingEntity } from 'src/entities/drone.schedule.mapping.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
MemberEntity,
DroneEntity,
DroneLogEntity,
ScheduleEntity,
CodeEntity,
DroneScheduleMappingEntity,
]),
],
controllers: [DroneController, DroneDataController],
providers: [DroneGateway, DroneService, DroneDataService],
})
export class DroneModule {}
import { Injectable, HttpException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DroneEntity } from 'src/entities/drone.entity';
import { DroneLogEntity } from 'src/entities/drone.log.entity';
import { ScheduleEntity } from 'src/entities/schedule.entity';
import { MemberEntity } from 'src/entities/member.entity';
import { DroneApiDto } from 'src/drone/dto/drone.api.dto';
import { DroneLogDto } from 'src/drone/dto/droneLog.dto';
import { getRepository, Repository } from 'typeorm/index';
import { DroneScheduleMappingEntity } from 'src/entities/drone.schedule.mapping.entity';
import { CodeEntity } from 'src/entities/code.entity';
@Injectable()
export class DroneService {
constructor(
@InjectRepository(MemberEntity)
private memberRepository: Repository<MemberEntity>,
@InjectRepository(DroneEntity)
private droneRepository: Repository<DroneEntity>,
@InjectRepository(DroneLogEntity)
private dronelogRepository: Repository<DroneLogEntity>,
@InjectRepository(ScheduleEntity)
private scheduleRepository: Repository<ScheduleEntity>,
@InjectRepository(CodeEntity)
private codeRepository: Repository<CodeEntity>,
@InjectRepository(DroneScheduleMappingEntity)
private droneschedulemappingRepository: Repository<DroneScheduleMappingEntity>,
) {
this.memberRepository = memberRepository;
this.droneRepository = droneRepository;
this.dronelogRepository = dronelogRepository;
this.scheduleRepository = scheduleRepository;
this.codeRepository = codeRepository;
this.droneschedulemappingRepository = droneschedulemappingRepository;
}
droneMap = {};
// test Drone Data
droneCount: number = 1200;
getRandomArbitrary(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
circleMove(
x: number,
y: number,
radius: number,
max: number,
circleStep: number,
) {
return {
x: x + radius * Math.cos((2 * Math.PI * circleStep) / max),
y: y + radius * Math.sin((2 * Math.PI * circleStep) / max),
};
}
logData: Array<{ x: number; y: number; id: number }> = Array.from(
{ length: this.droneCount },
(v, i) => {
return {
x:
this.getRandomArbitrary(37200000000000, 37300000000000) /
1000000000000,
y:
this.getRandomArbitrary(126900000000000, 127100000000000) /
1000000000000,
id: i,
};
},
);
getDroneTestData({ globalCircleStep }) {
const cordData = [];
for (let i = 0; i < this.droneCount; i += 1) {
const circleCord: { x: number; y: number } = this.circleMove(
this.logData[i].x,
this.logData[i].y,
0.05,
3600,
globalCircleStep,
);
cordData.push({
droneId: this.logData[i].id,
scheduleId: this.logData[i].id,
latitude: circleCord.x,
longitude: circleCord.y,
verticalSpeed: Math.round(Math.random() * 100),
horizontalSpeed: Math.round(Math.random() * 100),
aboveSeaLevel: 22,
aboveGroundLevel: 33,
});
}
return cordData;
}
// 드론ID로 스케줄 찾기
async findScheduleByDroneId(droneId: number) {
const scheduleList = getRepository(DroneScheduleMappingEntity)
.createQueryBuilder('droneSchedule')
.select('droneSchedule.scheduleId', 'scheduleId')
.addSelect('schedule.startTime', 'startTime')
.addSelect('schedule.terminateTime', 'terminateTime')
.addSelect('schedule.startLatitude', 'startLatitude')
.addSelect('schedule.startLongitude', 'startLongitude')
.addSelect('schedule.terminateLatitude', 'terminateLatitude')
.addSelect('schedule.terminateLongitude', 'terminateLongitude')
.innerJoin(
ScheduleEntity,
'schedule',
'droneSchedule.scheduleId = schedule.id',
)
.where('droneSchedule.droneId = :droneId', {
droneId: droneId,
})
.andWhere('schedule.startTime <= NOW()')
.andWhere('schedule.terminateTime >= NOW()')
.orderBy('droneSchedule.scheduleId', 'DESC')
.getRawOne();
return scheduleList;
}
// 스케줄ID로 로그 찾기
async findLogListByScheduleId(scheduleId: number) {
const logList = getRepository(DroneLogEntity)
.createQueryBuilder('droneLog')
.where('droneLog.scheduleId = :scheduleId', {
scheduleId: scheduleId,
})
.orderBy('droneLog.id', 'DESC')
.getMany();
return logList;
}
findMemberAll(): Promise<MemberEntity[]> {
return this.memberRepository.find();
}
async findDroneAll({
modelName,
maker,
usageName,
minWeight,
maxWeight,
pageNo,
pageSize,
}: {
modelName?: string;
maker?: string;
usageName?: string;
minWeight?: number;
maxWeight?: number;
pageNo?: number;
pageSize?: number;
}): Promise<DroneEntity[]> {
const droneList = getRepository(DroneEntity).createQueryBuilder('drone');
if (modelName) {
droneList.where('drone.modelName like :modelName', {
modelName: `%${modelName}%`,
});
}
if (maker) {
droneList.andWhere('drone.maker like :maker', { maker: `%${maker}%` });
}
if (usageName) {
droneList.andWhere('drone.usageName = :usageName', {
usageName: usageName,
});
}
if (minWeight) {
droneList.andWhere('drone.weight >= :minWeight', {
minWeight: minWeight,
});
}
if (maxWeight) {
droneList.andWhere('drone.weight <= :maxWeight', {
maxWeight: maxWeight,
});
}
droneList.orderBy('drone.id', 'DESC');
if (pageNo && pageSize) {
droneList.offset((pageNo - 1) * pageSize).limit(pageSize);
}
return droneList.getMany();
}
async findDroneTotalElement({
modelName,
maker,
usageName,
minWeight,
maxWeight,
}: {
modelName?: string;
maker?: string;
usageName?: string;
minWeight?: number;
maxWeight?: number;
}) {
const droneList = getRepository(DroneEntity).createQueryBuilder('drone');
if (modelName) {
droneList.where('drone.modelName like :modelName', {
modelName: `%${modelName}%`,
});
}
if (maker) {
droneList.andWhere('drone.maker like :maker', { maker: `%${maker}%` });
}
if (usageName) {
droneList.andWhere('drone.usageName = :usageName', {
usageName: usageName,
});
}
if (minWeight) {
droneList.andWhere('drone.weight >= :minWeight', {
minWeight: minWeight,
});
}
if (maxWeight) {
droneList.andWhere('drone.weight <= :maxWeight', {
maxWeight: maxWeight,
});
}
return droneList.getCount();
}
async findLogAll({
scheduleId,
latitude,
longitude,
minVerticalSpeed,
maxVerticalSpeed,
minHorizontalSpeed,
maxHorizontalSpeed,
minAboveSeaLevel,
maxAboveSeaLevel,
minAboveGroundLevel,
maxAboveGroundLevel,
pageNo,
pageSize,
}: {
scheduleId?: number;
latitude?: number;
longitude?: string;
minVerticalSpeed?: number;
maxVerticalSpeed?: number;
minHorizontalSpeed?: number;
maxHorizontalSpeed?: number;
minAboveSeaLevel?: number;
maxAboveSeaLevel?: number;
minAboveGroundLevel?: number;
maxAboveGroundLevel?: number;
pageNo?: number;
pageSize?: number;
}): Promise<any[]> {
const logList = getRepository(DroneLogEntity)
.createQueryBuilder('droneLog')
.select('droneLog.id', 'id')
.addSelect('drone.modelName', 'modelName')
.addSelect('droneLog.droneId', 'droneId')
.addSelect('droneLog.scheduleId', 'scheduleId')
.addSelect('droneLog.latitude', 'latitude')
.addSelect('droneLog.longitude', 'longitude')
.addSelect('droneLog.verticalSpeed', 'verticalSpeed')
.addSelect('droneLog.horizontalSpeed', 'horizontalSpeed')
.addSelect('droneLog.aboveSeaLevel', 'aboveSeaLevel')
.addSelect('droneLog.aboveGroundLevel', 'aboveGroundLevel')
.addSelect('droneLog.createdAt', 'createdAt')
.innerJoin(DroneEntity, 'drone', 'drone.id = droneLog.droneId');
if (scheduleId) {
logList.where('droneLog.scheduleId = :scheduleId', {
scheduleId: scheduleId,
});
}
if (latitude) {
logList.andWhere('cast(droneLog.latitude as varchar) like :latitude', {
latitude: `%${latitude}%`,
});
}
if (longitude) {
logList.andWhere('cast(droneLog.longitude as varchar) like :longitude', {
longitude: `%${longitude}%`,
});
}
if (minVerticalSpeed) {
logList.andWhere('droneLog.verticalSpeed >= :minVerticalSpeed', {
minVerticalSpeed: minVerticalSpeed,
});
}
if (maxVerticalSpeed) {
logList.andWhere('droneLog.verticalSpeed <= :maxVerticalSpeed', {
maxVerticalSpeed: maxVerticalSpeed,
});
}
if (minHorizontalSpeed) {
logList.andWhere('droneLog.horizontalSpeed >= :minHorizontalSpeed', {
minHorizontalSpeed: minHorizontalSpeed,
});
}
if (maxHorizontalSpeed) {
logList.andWhere('droneLog.horizontalSpeed <= :maxHorizontalSpeed', {
maxHorizontalSpeed: maxHorizontalSpeed,
});
}
if (minAboveSeaLevel) {
logList.andWhere('droneLog.aboveSeaLevel >= :minAboveSeaLevel', {
minAboveSeaLevel: minAboveSeaLevel,
});
}
if (maxAboveSeaLevel) {
logList.andWhere('droneLog.aboveSeaLevel <= :maxAboveSeaLevel', {
maxAboveSeaLevel: maxAboveSeaLevel,
});
}
if (minAboveGroundLevel) {
logList.andWhere('droneLog.aboveGroundLevel >= :minAboveGroundLevel', {
minAboveGroundLevel: minAboveGroundLevel,
});
}
if (maxAboveGroundLevel) {
logList.andWhere('droneLog.aboveGroundLevel <= :maxAboveGroundLevel', {
maxAboveGroundLevel: maxAboveGroundLevel,
});
}
logList.orderBy('id', 'DESC');
if (pageNo && pageSize) {
logList.offset((pageNo - 1) * pageSize).limit(pageSize);
}
return logList.getRawMany();
}
async findLogTotalElement({
scheduleId,
latitude,
longitude,
minVerticalSpeed,
maxVerticalSpeed,
minHorizontalSpeed,
maxHorizontalSpeed,
minAboveSeaLevel,
maxAboveSeaLevel,
minAboveGroundLevel,
maxAboveGroundLevel,
}: {
scheduleId?: number;
latitude?: string;
longitude?: string;
minVerticalSpeed?: number;
maxVerticalSpeed?: number;
minHorizontalSpeed?: number;
maxHorizontalSpeed?: number;
minAboveSeaLevel?: number;
maxAboveSeaLevel?: number;
minAboveGroundLevel?: number;
maxAboveGroundLevel?: number;
}) {
const logList = getRepository(DroneLogEntity)
.createQueryBuilder('droneLog')
.select('droneLog.id', 'id')
.addSelect('drone.modelName', 'modelName')
.addSelect('droneLog.droneId', 'droneId')
.addSelect('droneLog.scheduleId', 'scheduleId')
.addSelect('droneLog.latitude', 'latitude')
.addSelect('droneLog.longitude', 'longitude')
.addSelect('droneLog.verticalSpeed', 'verticalSpeed')
.addSelect('droneLog.horizontalSpeed', 'horizontalSpeed')
.addSelect('droneLog.aboveSeaLevel', 'aboveSeaLevel')
.addSelect('droneLog.aboveGroundLevel', 'aboveGroundLevel')
.addSelect('droneLog.createdAt', 'createdAt')
.innerJoin(DroneEntity, 'drone', 'drone.id = droneLog.droneId');
if (scheduleId) {
logList.where('droneLog.scheduleId = :scheduleId', {
scheduleId: scheduleId,
});
}
if (latitude) {
logList.andWhere('cast(droneLog.latitude as varchar) like :latitude', {
latitude: `%${latitude}%`,
});
}
if (longitude) {
logList.andWhere('cast(droneLog.longitude as varchar) like :longitude', {
longitude: `%${longitude}%`,
});
}
if (minVerticalSpeed) {
logList.andWhere('droneLog.verticalSpeed >= :minVerticalSpeed', {
minVerticalSpeed: minVerticalSpeed,
});
}
if (maxVerticalSpeed) {
logList.andWhere('droneLog.verticalSpeed <= :maxVerticalSpeed', {
maxVerticalSpeed: maxVerticalSpeed,
});
}
if (minHorizontalSpeed) {
logList.andWhere('droneLog.horizontalSpeed >= :minHorizontalSpeed', {
minHorizontalSpeed: minHorizontalSpeed,
});
}
if (maxHorizontalSpeed) {
logList.andWhere('droneLog.horizontalSpeed <= :maxHorizontalSpeed', {
maxHorizontalSpeed: maxHorizontalSpeed,
});
}
if (minAboveSeaLevel) {
logList.andWhere('droneLog.aboveSeaLevel >= :minAboveSeaLevel', {
minAboveSeaLevel: minAboveSeaLevel,
});
}
if (maxAboveSeaLevel) {
logList.andWhere('droneLog.aboveSeaLevel <= :maxAboveSeaLevel', {
maxAboveSeaLevel: maxAboveSeaLevel,
});
}
if (minAboveGroundLevel) {
logList.andWhere('droneLog.aboveGroundLevel >= :minAboveGroundLevel', {
minAboveGroundLevel: minAboveGroundLevel,
});
}
if (maxAboveGroundLevel) {
logList.andWhere('droneLog.aboveGroundLevel <= :maxAboveGroundLevel', {
maxAboveGroundLevel: maxAboveGroundLevel,
});
}
return logList.getCount();
}
async findScheduleAll({
droneId,
startTime,
terminateTime,
pageNo,
pageSize,
}: {
droneId?: number;
startTime?: Date;
terminateTime?: Date;
pageNo?: number;
pageSize?: number;
}): Promise<ScheduleEntity[]> {
const scheduleList = getRepository(ScheduleEntity)
.createQueryBuilder('schedule')
.select('schedule.id', 'id')
.addSelect('drone.id', 'droneId')
.addSelect('drone.modelName', 'modelName')
.addSelect('schedule.startTime', 'startTime')
.addSelect('schedule.terminateTime', 'terminateTime')
.addSelect('schedule.startLatitude', 'startLatitude')
.addSelect('schedule.terminateLatitude', 'terminateLatitude')
.addSelect('schedule.startLongitude', 'startLongitude')
.addSelect('schedule.terminateLongitude', 'terminateLongitude')
.innerJoin(
DroneScheduleMappingEntity,
'mapping',
'schedule.id = mapping.scheduleId',
)
.innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId');
if (droneId) {
scheduleList.where('mapping.droneId = :droneId', { droneId: droneId });
}
if (startTime) {
scheduleList.andWhere('schedule.startTime >= :startTime', {
startTime: startTime,
});
}
if (terminateTime) {
scheduleList.andWhere('schedule.terminateTime <= :terminateTime', {
terminateTime: terminateTime,
});
}
scheduleList.orderBy('id', 'DESC');
if (pageNo && pageSize) {
scheduleList.offset((pageNo - 1) * pageSize).limit(pageSize);
}
return scheduleList.getRawMany();
}
async findScheduleTotalElement({
droneId,
startTime,
terminateTime,
}: {
droneId?: number;
startTime?: string;
terminateTime?: string;
}) {
const scheduleList = getRepository(ScheduleEntity)
.createQueryBuilder('schedule')
.select('schedule.id', 'id')
.addSelect('drone.id', 'droneId')
.addSelect('drone.modelName', 'modelName')
.addSelect('schedule.startTime', 'startTime')
.addSelect('schedule.terminateTime', 'terminateTime')
.addSelect('schedule.startLatitude', 'startLatitude')
.addSelect('schedule.terminateLatitude', 'terminateLatitude')
.addSelect('schedule.startLongitude', 'startLongitude')
.addSelect('schedule.terminateLongitude', 'terminateLongitude')
.innerJoin(
DroneScheduleMappingEntity,
'mapping',
'schedule.id = mapping.scheduleId',
)
.innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId');
if (droneId) {
scheduleList.where('mapping.droneId = :droneId', { droneId: droneId });
}
if (startTime) {
scheduleList.andWhere('schedule.startTime >= :startTime', {
startTime: startTime,
});
}
if (terminateTime) {
scheduleList.andWhere('schedule.terminateTime <= :terminateTime', {
terminateTime: terminateTime,
});
}
return scheduleList.getCount();
}
findCodeAll(): Promise<CodeEntity[]> {
return this.codeRepository.find();
}
// 각 테이블별 detail 처리
findMemberOne(id: number): Promise<MemberEntity> {
return this.memberRepository.findOne({ id: id });
}
findDroneOne(id: number): Promise<DroneEntity> {
return this.droneRepository.findOne({ id: id });
}
findLogOne(id: number): Promise<DroneLogEntity> {
return this.dronelogRepository.findOne({ id: id });
}
findScheduleOne(id: number): Promise<ScheduleEntity> {
const foundschedule = getRepository(ScheduleEntity)
.createQueryBuilder('schedule')
.select('schedule.id', 'id')
.addSelect('mapping.droneId', 'droneId')
.addSelect('schedule.startTime', 'startTime')
.addSelect('schedule.terminateTime', 'terminateTime')
.addSelect('schedule.startLatitude', 'startLatitude')
.addSelect('schedule.terminateLatitude', 'terminateLatitude')
.addSelect('schedule.startLongitude', 'startLongitude')
.addSelect('schedule.terminateLongitude', 'terminateLongitude')
.innerJoin(
DroneScheduleMappingEntity,
'mapping',
'schedule.id = mapping.scheduleId',
)
.where('schedule.id = :id', { id: id });
return foundschedule.getRawOne();
}
findCodeOne(id: number): Promise<CodeEntity> {
return this.codeRepository.findOne({ id: id });
}
// 각 테이블별 insert
async saveMember(memberEntity: MemberEntity): Promise<void> {
await this.memberRepository.save(memberEntity);
}
async saveDrone(droneEntity: DroneEntity): Promise<void> {
await this.droneRepository.save(droneEntity);
}
async saveDroneList(droneEntityList: DroneEntity[]): Promise<number> {
const ret = await this.droneRepository.save(droneEntityList);
return ret.length;
}
async updateDroneList(
droneList: DroneApiDto.UpdateDroneDto[],
): Promise<number> {
let affectedRows = 0;
for (const drone of droneList) {
const ret = await this.droneRepository
.createQueryBuilder()
.update('drone')
.set({
maker: drone.maker,
usage: drone.usage,
specification: drone.specification,
weight: drone.weight,
})
.where('id = :id', { id: drone.id })
.execute();
affectedRows += ret.affected || 0;
}
return affectedRows;
}
async saveLog(droneLogEntity: DroneLogEntity): Promise<void> {
await this.dronelogRepository.save(droneLogEntity);
}
async saveSchedule(ScheduleEntity: ScheduleEntity): Promise<void> {
await this.scheduleRepository.save(ScheduleEntity);
}
async saveScheduleList(
scheduleList: DroneApiDto.SaveSchduleDto[],
): Promise<number> {
// 스케쥴 중복여부 확인
for (const schedule of scheduleList) {
const ret = await this.droneRepository
.createQueryBuilder('drone')
.select('drone.id', 'droneId')
.addSelect('schedule.id', 'scheduleId')
.innerJoin(
DroneScheduleMappingEntity,
'mapping',
'drone.id = mapping.droneId',
)
.innerJoin(
ScheduleEntity,
'schedule',
'schedule.id = mapping.scheduleId',
)
.where('drone.id = :id', { id: schedule.droneId })
.andWhere('schedule.start_time <= :startTime', {
startTime: schedule.startTime,
})
.andWhere('schedule.terminate_time >= :terminateTime', {
terminateTime: schedule.terminateTime,
})
.getRawOne();
if (ret) {
// 시간이 중복된 스케쥴이 존제함.
throw new HttpException(
`해당 기간에 설정된 스케쥴이 이미 존제합니다. droneId: ${ret.droneId}, scheduleId: ${ret.scheduleId}`,
400,
);
}
}
let affectedRows = 0;
// 스케쥴 생성
for (const schedule of scheduleList) {
const ret = await this.scheduleRepository.save({
startTime: schedule.startTime,
terminateTime: schedule.terminateTime,
startLatitude: schedule.startLatitude,
startLongitude: schedule.startLongitude,
terminateLatitude: schedule.terminateLatitude,
terminateLongitude: schedule.terminateLongitude,
});
const scheduleId = ret.id;
await this.droneschedulemappingRepository.save({
droneId: schedule.droneId,
scheduleId,
});
affectedRows += 1;
}
return affectedRows;
}
async updateSchduleList(
scheduleList: DroneApiDto.UpdateSchduleDto[],
): Promise<number> {
// 스케쥴 중복여부 확인
for (const schedule of scheduleList) {
const ret = await this.droneschedulemappingRepository
.createQueryBuilder('mapping')
.select('drone.id', 'droneId')
.addSelect('schedule.id', 'scheduleId')
.innerJoin(DroneEntity, 'drone', 'drone.id = mapping.droneId')
.innerJoin(
DroneScheduleMappingEntity,
'mapping2',
'drone.id = mapping2.droneId',
)
.innerJoin(
ScheduleEntity,
'schedule',
'schedule.id = mapping2.scheduleId',
)
.where('mapping.schedule_id = :id', {
id: schedule.id,
})
.andWhere('mapping2.schedule_id != :id', {
id: schedule.id,
})
.andWhere('schedule.start_time <= :startTime', {
startTime: schedule.startTime,
})
.andWhere('schedule.terminate_time >= :terminateTime', {
terminateTime: schedule.terminateTime,
})
.getRawOne();
if (ret) {
// 시간이 중복된 스케쥴이 존제함.
throw new HttpException(
`해당 기간에 설정된 스케쥴이 이미 존제합니다. droneId: ${ret.droneId}, scheduleId: ${ret.scheduleId}`,
400,
);
}
}
let affectedRows = 0;
for (const schedule of scheduleList) {
const ret = await this.scheduleRepository
.createQueryBuilder()
.update('schedule')
.set({
startTime: schedule.startTime,
terminateTime: schedule.terminateTime,
startLatitude: schedule.startLatitude,
startLongitude: schedule.startLongitude,
terminateLatitude: schedule.terminateLatitude,
terminateLongitude: schedule.terminateLongitude,
})
.where('id = :id', { id: schedule.id })
.execute();
affectedRows += ret.affected || 0;
}
return affectedRows;
}
updateDroneMap(droneLogList: DroneLogDto[]) {
droneLogList.forEach((droneLog) => {
this.droneMap[`${droneLog.droneId}`] = droneLog;
});
}
async saveCode(CodeEntity: CodeEntity): Promise<void> {
await this.codeRepository.save(CodeEntity);
}
// 각 테이블별 Delete
async deleteMember(id: number): Promise<void> {
await this.memberRepository.delete({ id: id });
}
async deleteDrone(id: number): Promise<void> {
await this.droneRepository.delete({ id: id });
}
async deleteLog(id: number): Promise<void> {
await this.dronelogRepository.delete({ id: id });
}
async deleteSchedule(id: number): Promise<void> {
await this.scheduleRepository.delete({ id: id });
}
async deleteCode(id: number): Promise<void> {
await this.codeRepository.delete({ id: id });
}
async findFirstDroneLogList() {
const droneList = await getRepository(DroneEntity)
.createQueryBuilder('drone')
.select('drone.id', 'droneId')
.addSelect('schedule.id', 'scheduleId')
.addSelect('log.latitude')
.addSelect('log.longitude')
.addSelect('log.vertical_speed', 'verticalSpeed')
.addSelect('log.horizontal_speed', 'horizontalSpeed')
.addSelect('log.above_sea_level', 'aboveSeaLevel')
.addSelect('log.above_sea_level', 'aboveSeaLevel')
.addSelect('log.above_ground_level', 'above_GroundLevel')
.leftJoin(
DroneScheduleMappingEntity,
'mapping',
'drone.id = mapping.droneId',
)
.leftJoin(
ScheduleEntity,
'schedule',
'mapping.scheduleId = schedule.id AND NOW() between schedule.start_time AND schedule.terminate_time',
)
.leftJoin(DroneLogEntity, 'log', 'log.scheduleId = schedule.id')
.where('log.id IS NULL')
.orWhere(
`log.id IN (
SELECT MAX(log2.id) FROM drone_log log2 WHERE log2.schedule_id = schedule.id GROUP BY log2.schedule_id
)`,
)
.orderBy('drone.id', 'DESC')
.getRawMany();
return droneList;
}
}
import {
IsArray,
IsNotEmpty,
IsOptional,
IsNumber,
IsString,
ValidateNested,
IsDateString,
} from 'class-validator';
import { Type } from 'class-transformer';
import { DroneEntity } from 'src/entities/drone.entity';
export namespace DroneApiDto {
export class SaveDroneListDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => DroneEntity)
droneList: DroneEntity[];
}
export class UpdateDroneListDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => UpdateDroneDto)
droneList: UpdateDroneDto[];
}
export class UpdateDroneDto {
@IsNotEmpty()
@IsNumber()
id: number;
@IsString()
@IsOptional()
maker: string;
@IsString()
@IsOptional()
usage: string;
@IsNumber()
@IsOptional()
specification: number;
@IsNumber()
@IsOptional()
weight: number;
}
export class SaveSchduleListDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => SaveSchduleDto)
schduleList: SaveSchduleDto[];
}
export class SaveSchduleDto {
@IsNotEmpty()
@IsNumber()
droneId: number;
@IsDateString()
@IsNotEmpty()
startTime: string;
@IsDateString()
@IsNotEmpty()
terminateTime: string;
@IsNumber()
@IsNotEmpty()
startLatitude: number;
@IsNumber()
@IsNotEmpty()
startLongitude: number;
@IsNumber()
@IsNotEmpty()
terminateLatitude: number;
@IsNumber()
@IsNotEmpty()
terminateLongitude: number;
}
export class UpdateSchduleListDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => UpdateSchduleDto)
schduleList: UpdateSchduleDto[];
}
export class UpdateSchduleDto {
@IsNotEmpty()
@IsNumber()
id: number;
@IsDateString()
@IsNotEmpty()
startTime: string;
@IsDateString()
@IsNotEmpty()
terminateTime: string;
@IsNumber()
@IsNotEmpty()
startLatitude: number;
@IsNumber()
@IsNotEmpty()
startLongitude: number;
@IsNumber()
@IsNotEmpty()
terminateLatitude: number;
@IsNumber()
@IsNotEmpty()
terminateLongitude: number;
}
export class SaveDroneLogListDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => SaveDroneLogDto)
droneLogList: SaveDroneLogDto[];
}
export class SaveDroneLogDto {
@IsNotEmpty()
@IsNumber()
droneId: number;
@IsNotEmpty()
@IsNumber()
scheduleId: number;
@IsNotEmpty()
@IsNumber()
latitude: number;
@IsNotEmpty()
@IsNumber()
longitude: number;
@IsNotEmpty()
@IsNumber()
verticalSpeed: number;
@IsNotEmpty()
@IsNumber()
horizontalSpeed: number;
@IsNotEmpty()
@IsNumber()
aboveSeaLevel: number;
@IsNotEmpty()
@IsNumber()
aboveGroundLevel: number;
}
}
export class DroneDto {
private _id: number;
private _model_name: string;
private _maker: string;
private _usage: string;
private _picture: string;
private _specification: number;
private _weight: number;
constructor(
id: number,
model_name: string,
maker: string,
usage: string,
picture: string,
specification: number,
weight: number,
) {
this._id = id;
this._model_name = model_name;
this._maker = maker;
this._usage = usage;
this._picture = picture;
this._specification = specification;
this._weight = weight;
}
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get model_name(): string {
return this._model_name;
}
set model_name(value: string) {
this._model_name = value;
}
get maker(): string {
return this._maker;
}
set maker(value: string) {
this._maker = value;
}
get usage(): string {
return this._usage;
}
set usage(value: string) {
this._usage = value;
}
get picture(): string {
return this._picture;
}
set picture(value: string) {
this._picture = value;
}
get specification(): number {
return this._specification;
}
set specification(value: number) {
this._specification = value;
}
get weight(): number {
return this._weight;
}
set weight(value: number) {
this._weight = value;
}
}
export class DroneLogDto {
constructor(props) {
// props.id = props.id ? parseInt(props.id) : props.id;
props.droneId = props.droneId ? parseInt(props.droneId) : props.droneId;
props.scheduleId = props.scheduleId
? parseInt(props.scheduleId)
: props.scheduleId;
Object.assign(this, props);
}
id: number;
droneId: number;
scheduleId: number;
latitude: number;
longitude: number;
verticalSpeed: number;
horizontalSpeed: number;
aboveSeaLevel: number;
aboveGroundLevel: number;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'code', schema: 'public' })
export class CodeEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ length: 20, name: 'code_group' })
code_Group: string;
@Column({ length: 20, name: 'code_group_name' })
codeGroupName: string;
@Column({ length: 20, name: 'code_text' })
codeText: string;
@Column({ length: 20, name: 'code_value' })
codeValue: string;
@Column({ length: 20, name: 'code_value_name' })
codeValueName: string;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'drone', schema: 'public' })
export class DroneEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ length: 20, name: 'model_name' })
modelName: string;
@Column({ length: 20 })
maker: string;
@Column({ length: 20 })
usage: string;
@Column({ length: 20, name: 'usagename' })
usageName: string;
@Column({ length: 20 })
picture: string;
@Column()
specification: number;
@Column()
weight: number;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'drone_log', schema: 'public' })
export class DroneLogEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ name: 'drone_id' })
droneId: number;
@Column({ name: 'schedule_id' })
scheduleId: number;
@Column()
latitude: number;
@Column()
longitude: number;
@Column({ name: 'vertical_speed' })
verticalSpeed: number;
@Column({ name: 'horizontal_speed' })
horizontalSpeed: number;
@Column({ name: 'above_sea_level' })
aboveSeaLevel: number;
@Column({ name: 'above_ground_level' })
aboveGroundLevel: number;
@Column({ type: 'timestamp', name: 'created_at' })
createdAt: Date;
constructor(props) {
Object.assign(this, props);
}
}
import { Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'drone_schedule_mapping', schema: 'public' })
export class DroneScheduleMappingEntity {
@PrimaryGeneratedColumn({ name: 'drone_id' })
droneId: number;
@PrimaryGeneratedColumn({ name: 'schedule_id' })
scheduleId: number;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'member', schema: 'public' })
export class MemberEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ length: 15 })
name: string;
@Column({ length: 20, name:'tel_number' })
telNumber: string;
@Column({ length: 20 })
affiliation: string;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm/index';
@Entity({ name: 'schedule', schema: 'public' })
export class ScheduleEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;
@Column({ type: 'timestamp', name: 'terminate_time' })
terminateTime: Date;
@Column({ name: 'start_latitude' })
startLatitude: number;
@Column({ name: 'start_longitude' })
startLongitude: number;
@Column({ name: 'terminate_latitude' })
terminateLatitude: number;
@Column({ name: 'terminate_longitude' })
terminateLongitude: number;
}
import { NestFactory } from '@nestjs/core';
import { WsAdapter } from '@nestjs/platform-ws';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(20205);
}
bootstrap();
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true
}
}
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
node_modules
lib/api-client
**/*.ts
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
parserOptions: {
parser: 'babel-eslint',
},
extends: [
'plugin:vue/essential',
'@vue/airbnb',
],
plugins: [
],
// add your custom rules here
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-unused-vars': 'warn',
'comma-dangle': ['error', 'always-multiline'],
'linebreak-style': 0,
'import/no-extraneous-dependencies': 0,
'no-shadow': 0,
'import/prefer-default-export': 0,
'max-len': ['warn', { code: 200 }],
'import/extensions': ['error', 'always', {
js: 'never',
jsx: 'never',
vue: 'never',
}],
indent: [2, 2],
},
settings: {
'import/extensions': ['.js', '.jsx', '.vue'],
'import/resolver': {
alias: {
map: [
['@', './src'],
],
extensions: ['.js', '.vue', '.jsx'],
},
node: {
extensions: ['.js', '.vue', '.jsx'],
},
},
},
};
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
.DS_Store
stages:
- others
- build
- deploy
.build:
cache:
paths:
- node_modules/
.deploy:
variables:
GIT_STRATEGY: none
dev_build:
stage: build
extends: .build
script:
- yarn install
- yarn build
only:
- develop
tags:
- front
dev_deploy:
stage: deploy
extends: .deploy
script:
- pm2 start --exp-backoff-restart-delay=100
only:
- develop
tags:
- front
#(DEV) mockserver:
# stage: deploy
# extends: .deploy
# image: node:15
# script: yarn serve
# only:
# - develop
# artifacts:
# paths:
# - ./
# tags:
# - ws-server
#
#(DEV) websocket:
# stage: deploy
# extends: .deploy
# image: docker:latest
# script: yarn ws
# only:
# - develop
# artifacts:
# paths:
# - ./
# tags:
# - api-server
{
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": false,
"arrowParens": "always"
}
# drone-web-nuxt
## Build Setup
```bash
# install dependencies
$ yarn install
# serve with hot reload at localhost:3000
$ yarn dev
# build for production and launch server
$ yarn build
$ yarn start
# generate static project
$ yarn generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
#!/bin/bash
echo "Running server in the background"
sudo systemctl restart hello-react
module.exports = {
apps: [
{
name: 'drone-front',
// package.json에 정의된 npm run start를 실행하게 해서 PM2로 관리하게 한다.
script: 'yarn',
args: 'run start',
instances: '1',
max_memory_restart: '1G',
error_file: 'err.log',
out_file: 'out.log',
log_file: 'combined.log',
},
],
};
[Unit]
Description=Drone front service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=gitlab-runner
ExecStart=/home/gitlab-runner/builds/4hhEfxWU/0/khu-oz-wizard/drone-monitoring-web-ui yarn start
[Install]
WantedBy=multi-user.target
This diff could not be displayed because it is too large.
map $sent_http_content_type $expires {
"text/html" epoch;
"text/html; charset=utf-8" epoch;
default off;
}
server {
listen 20205;
server_name localhost;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
gzip_min_length 1000;
location / {
expires $expires;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
proxy_pass http://127.0.0.1:3000;
}
}
/* eslint-disable no-unused-vars */
import api from './nuxtConfig/api';
import build from './nuxtConfig/build';
import theme from './nuxtConfig/theme';
import nuxtConfigModule from './nuxtConfig/module';
import io from './nuxtConfig/ioConfig';
import extendRouter from './nuxtConfig/extendRouter';
// 설정 내용이 짧은 것 및 구조화 하기 애매한 것은 별도 파일로 관리하지 않음.
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'drone-web-nuxt',
htmlAttrs: {
lang: 'en',
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
// Auto import components: https://go.nuxtjs.dev/config-components
components: false,
// source Directory
srcDir: 'src/',
/* middleware */
serverMiddleware: [
'./serverMiddleWare/index',
],
router: {
// router middleware
middleware: 'router',
// router extend
// extendRoutes: extendRouter,
},
// module, plugin, alias, robots
...nuxtConfigModule,
// axios, proxy, auth
...api,
// env, runtimeConfig, build
...build,
// loading, transition, css
...theme,
// vue Global Config
vue: {
config: {
productionTip: true,
devtools: process.env.NODE_ENV === 'development',
// silent: process.env.NODE_ENV !== 'development',
// performance: process.env.NODE_ENV === 'development',
},
},
// robots Setting
robots: {
UserAgent: '*',
Disallow: '/',
},
// socket io Setting
io,
};
import { version } from '../package.json';
/**
* api 관련된 nuxt 옵션을 정리합니다.
* 해당 옵션은 아래와 같습니다.
* auth, axios, proxy,
*/
export default {
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
proxy: true,
retry: { retries: 3 },
// baseUrl: 'http://localhost:5555',
headers: {
common: {
Accept: 'application/json, text/plain, */*',
AppVersion: version,
},
delete: {},
get: {},
head: {},
post: {},
put: {},
patch: {},
},
},
proxy: {
'/api': {
target: process.env.BASE_API_URL || 'http://14.33.35.148:20205',
pathRewrite: {
'^/api': '',
},
changeOrigin: true,
},
},
auth: {
// Options
},
};
/**
* 빌드에 관련된 nuxt 옵션을 정리합니다.
* 해당 옵션은 아래와 같습니다.
* env, build,
*/
export default {
// modern property https://ko.nuxtjs.org/docs/2.x/configuration-glossary/configuration-modern
modern: false,
/* env Setting */
env: {
BASE_API_URL: process.env.BASE_API_URL,
BASE_APP_URL: process.env.BASE_APP_URL,
BASE_I18N_LOCALE: process.env.BASE_I18N_LOCALE,
BASE_I18N_FALLBACK_LOCALE: process.env.BASE_I18N_FALLBACK_LOCALE,
},
// public nuxt.context config variables
publicRuntimeConfig: {
BASE_API_URL: process.env.BASE_API_URL,
BASE_APP_URL: process.env.BASE_APP_URL,
BASE_I18N_LOCALE: process.env.BASE_I18N_LOCALE,
BASE_I18N_FALLBACK_LOCALE: process.env.BASE_I18N_FALLBACK_LOCALE,
},
// private nuxt.context config variables
privateRuntimeConfig: {
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
loaders: {
vue: {
transformAssetUrls: {
'vl-style-icon': 'src',
},
},
// for Antdv CustomTheme Setting
less: {
lessOptions: {
javascriptEnabled: true,
math: 'always',
},
},
},
devtool: true,
analyze: true,
},
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
// https://go.nuxtjs.dev/eslint
'@nuxt/image',
],
image: {
staticFilename: '[publicPath]/images/[name]-[hash][ext]',
presets: {
avatar: {
modifiers: {
format: 'jpg',
width: 50,
height: 50,
},
},
},
},
};
export default function extendRoutes(routes, resolve) {
routes.push({
name: '404Page',
path: '*',
redirect: '/auth/404',
component: resolve(__dirname, '../src/pages/auth/404.vue'),
});
}
export default {
sockets: [
{
name: 'main',
url: 'http://localhost:8888',
default: true,
},
],
};
/**
* 모듈에 관련된 nuxt 옵션을 정리합니다.
* 해당 옵션은 아래와 같습니다.
* module, plugin, alias
*/
import { resolve } from 'path';
export default {
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
resolve(__dirname, '../src/plugins/ApiClient/index'),
resolve(__dirname, '../src/plugins/antDesign'),
resolve(__dirname, '../src/plugins/Dayjs/index'),
{ src: '@/plugins/client-only.client.js' },
{ src: '@/plugins/globalMixins' },
{ src: '@/plugins/vuelayers.js', ssr: false },
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
'@nuxtjs/style-resources',
'@nuxtjs/auth-next',
'@nuxtjs/sitemap',
'@nuxtjs/robots',
'nuxt-socket-io',
'nuxt-leaflet',
resolve(__dirname, '../src/modules/vuelayers.js'),
],
// alias
alias: {
'@': resolve(__dirname, '../src/'),
images: resolve(__dirname, '../src/assets/images'),
styles: resolve(__dirname, '../src/assets/styles'),
},
};
/**
* 테마에 관련된 nuxt 옵션을 정리합니다.
* 해당 옵션은 아래와 같습니다.
* trainsition, css, loading
*/
import { resolve } from 'path';
export default {
// Theme Animation
loading: {
color: '#1890ff',
height: '4px',
},
layoutTransition: {
name: 'default-layout',
mode: 'out-in',
},
pageTransition: {
name: 'default-page',
mode: 'out-in',
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
resolve(__dirname, '../src/assets/styles/less/index'),
resolve(__dirname, '../src/assets/styles/scss/index'),
],
};
{
"name": "drone-web-nuxt",
"version": "1.0.3",
"private": true,
"scripts": {
"dev": "nuxt",
"analyze-dev": "nuxt --analyze",
"build": "nuxt build",
"start": "nuxt start",
"build-mo": "nuxt build --modern=server",
"analyze-build": "nuxt build --analyze",
"start-mo": "nuxt start --modern=server",
"generate": "nuxt generate",
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
"lint": "yarn lint:js",
"serve": "json-server --watch lib/db.json --port 5555",
"socket": "node ./serverMiddleWare/socket.js",
"ws": "node ./serverMiddleWare/ws.js",
"wsm": "node ./serverMiddleWare/multWs.js",
"wsmp": "node ./serverMiddleWare/multPWs.js",
"k6": "k6 run ./serverMiddleWare/k6.js",
"k6m": "k6 run ./serverMiddleWare/k6mult.js"
},
"dependencies": {
"@nuxtjs/auth-next": "5.0.0-1613647907.37b1156",
"@nuxtjs/axios": "^5.13.1",
"@nuxtjs/robots": "^2.5.0",
"@nuxtjs/sitemap": "^2.4.0",
"ant-design-vue": "^1.7.2",
"core-js": "^3.8.3",
"cors": "^2.8.5",
"dayjs": "^1.10.4",
"express": "^4.17.1",
"highcharts": "^9.0.1",
"highcharts-vue": "^1.3.5",
"nuxt": "^2.14.12",
"nuxt-leaflet": "^0.0.25",
"nuxt-socket-io": "^1.1.14",
"socket.io": "^4.0.0",
"store2": "^2.12.0",
"vuelayers": "^0.11.35",
"vuex": "^3.6.2",
"ws": "^7.4.4"
},
"devDependencies": {
"@nuxt/image": "^0.4.13",
"@nuxtjs/eslint-config": "^5.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/style-resources": "^1.0.0",
"@vue/cli-service": "^4.5.11",
"@vue/eslint-config-airbnb": "^5.3.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.18.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-nuxt": "^2.0.0",
"eslint-plugin-vue": "^7.5.0",
"fibers": "^5.0.0",
"json-server": "^0.16.3",
"less": "^4.1.1",
"less-loader": "7",
"sass": "^1.32.8",
"sass-loader": "10"
}
}
// <project root>/api/index.js
// const express = require('express');
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
// 실제로는 /api 라우트를 처리하는 메소드가 된다.
app.get('/', (req, res) => {
console.log('hi');
io.emit('connection');
res.send('API root');
});
io.of('/analytics').on('connect', (socket) => {
console.log('클라이언트 접속');
socket.on('disconnect', () => {
console.log('클라이언트 접속 종료');
});
setInterval(() => {
socket.emit('message', '메세지');
}, 3000);
});
// 모듈로 사용할 수 있도록 export
// 앱의 /api/* 라우트로 접근하는 모든 요청은 모두 app 인스턴스에게 전달된다.
module.exports = {
path: '/socket',
handler: app,
};
import ws from 'k6/ws';
import { check } from 'k6';
export const options = {
// vus: 100,
// duration: '30s',
stages: [
{ duration: '60s', target: 10 },
],
};
export default function () {
const url = 'ws://localhost:8080';
const params = { tags: { my_tag: 'hello' } };
const res = ws.connect(url, params, (socket) => {
socket.on('open', () => console.log('connected'));
socket.on('message', (data) => console.log('Message received: '));
socket.on('close', () => console.log('disconnected'));
socket.setTimeout(() => {
console.log('60 seconds passed, closing the socket');
socket.close();
}, 30000);
});
check(res, { 'status is 101': (r) => r && r.status === 101 });
}
import ws from 'k6/ws';
import { check } from 'k6';
export const options = {
// vus: 100,
// duration: '30s',
stages: [
{ duration: '60s', target: 10 },
// { duration: '10s', target: 30 },
// { duration: '20s', target: 50 },
],
};
export default function () {
const url = 'ws://localhost:8080';
const params = { tags: { my_tag: 'hello' } };
const res = {};
for (let i = 0; i < 10; i += 1) {
res[i] = ws.connect(`${url}/${i + 1}`, params, (socket) => {
socket.on('open', () => console.log('connected', i));
socket.on('message', (data) => console.log('Message received: ', i));
socket.on('close', () => console.log('disconnected'));
socket.setTimeout(() => {
console.log('60 seconds passed, closing the socket');
socket.close();
}, 60000);
});
}
console.log(res);
check(res, {
'status1 is 101': (r) => r && r[0].status === 101,
'status2 is 101': (r) => r && r[1].status === 101,
'status3 is 101': (r) => r && r[2].status === 101,
'status4 is 101': (r) => r && r[3].status === 101,
});
}
//
// const res1 = ws.connect(`${url}/${1}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 1'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res2 = ws.connect(`${url}/${2}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 2'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res3 = ws.connect(`${url}/${3}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 3'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res4 = ws.connect(`${url}/${4}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 4'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res5 = ws.connect(`${url}/${5}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 5'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res6 = ws.connect(`${url}/${6}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 6'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res7 = ws.connect(`${url}/${7}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 7'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res8 = ws.connect(`${url}/${8}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 8'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res9 = ws.connect(`${url}/${9}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 9'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
//
// const res10 = ws.connect(`${url}/${10}`, params, (socket) => {
// socket.on('open', () => console.log('connected'));
// socket.on('message', (data) => console.log('Message received: 10'));
// socket.on('close', () => console.log('disconnected'));
// socket.setTimeout(() => {
// console.log('60 seconds passed, closing the socket');
// socket.close();
// }, 60000);
// });
// check([res1, res2, res3, res10], { 'status is 101': (r) => r && r.status === 101 });
/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */
const http = require('http');
const WebSocket = require('ws');
const url = require('url');
const server = http.createServer();
const wss = {};
const dataNum = process.argv[2];
const wsServerCnt = process.argv[3];
const port = process.argv[4];
const dataPerWsServer = dataNum / wsServerCnt;
console.log(process.argv);
let pingInterval = null;
let sendInterval = null;
const logData = [];
for (let i = 0; i < dataPerWsServer; i += 1) {
logData.push({
latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000,
longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000,
id: i,
});
}
function getRandomArbitrary(min, max) {
return parseInt((Math.random() * (max - min) + min), 10);
}
function circleMove(x, y, radius, max, circleStep) {
return {
latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max),
longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max),
};
}
function makeCoordData(log, circleStep) {
if (circleStep == null) {
circleStep = 0;
}
if (circleStep === 3600) {
circleStep = 0;
} else circleStep += 1;
// console.log('step', circleStep);
return Array.from(
{ length: dataPerWsServer },
(v, i) => ({
id: i,
...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep),
time: new Date(),
}),
);
}
function heartbeat() {
this.isAlive = true;
console.log('client Heartbeat');
}
function noop() {}
for (let i = 0; i < wsServerCnt; i += 1) {
wss[i + 1] = new WebSocket.Server({ noServer: true });
}
Object.entries(wss).forEach(([key, wss]) => {
wss.on('connection', (ws) => {
console.log('connected', key);
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
let circleStep = 0;
sendInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
const coordData = makeCoordData(logData, circleStep);
ws.send(JSON.stringify(coordData));
}
circleStep += 1;
}, 1000);
ws.on('close', function close() {
console.log('websocket Closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
// sendInterval = null;
});
/* ping check */
pingInterval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on('close', function close() {
console.log('server closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
});
});
});
server.on('upgrade', (request, socket, head) => {
const { pathname } = url.parse(request.url);
Object.entries(wss).forEach(([key, wss]) => {
if (`/${key}` === pathname) {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
}
});
});
server.listen(port);
/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */
const http = require('http');
const WebSocket = require('ws');
const url = require('url');
const server = http.createServer();
const wss = {};
const dataNum = 20000;
const wsServerCnt = process.argv[2];
console.log(wsServerCnt);
const dataPerWsServer = dataNum / wsServerCnt;
let pingInterval = null;
let sendInterval = null;
const logData = [];
for (let i = 0; i < dataPerWsServer; i += 1) {
logData.push({
latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000,
longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000,
id: i,
});
}
function getRandomArbitrary(min, max) {
return parseInt((Math.random() * (max - min) + min), 10);
}
function circleMove(x, y, radius, max, circleStep) {
return {
latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max),
longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max),
};
}
function makeCoordData(log, circleStep) {
if (circleStep == null) {
circleStep = 0;
}
if (circleStep === 3600) {
circleStep = 0;
} else circleStep += 1;
// console.log('step', circleStep);
return Array.from(
{ length: dataPerWsServer },
(v, i) => ({
id: i,
...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep),
time: new Date(),
}),
);
}
function heartbeat() {
this.isAlive = true;
console.log('client Heartbeat');
}
function noop() {}
for (let i = 0; i < wsServerCnt; i += 1) {
wss[i + 1] = new WebSocket.Server({ noServer: true });
}
Object.entries(wss).forEach(([key, wss]) => {
wss.on('connection', (ws) => {
console.log('connected', key);
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
let circleStep = 0;
sendInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
const coordData = makeCoordData(logData, circleStep);
ws.send(JSON.stringify(coordData));
}
circleStep += 1;
}, 1000);
ws.on('close', function close() {
console.log('websocket Closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
// sendInterval = null;
});
/* ping check */
pingInterval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on('close', function close() {
console.log('server closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
});
});
});
server.on('upgrade', (request, socket, head) => {
const { pathname } = url.parse(request.url);
Object.entries(wss).forEach(([key, wss]) => {
if (`/${key}` === pathname) {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
}
});
});
server.listen(20203);
/* eslint-disable prefer-arrow-callback */
// <project root>/api/index.js
// const express = require('express');
function getRandomArbitrary(min, max) {
return parseInt((Math.random() * (max - min) + min), 10);
}
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http, {
cors: true,
origins: ['http://127.0.0.1:3000', 'http://127.0.0.1:8888', 'http://localhost:3000'],
});
const cors = require('cors');
app.use(cors());
// 실제로는 /api 라우트를 처리하는 메소드가 된다.
app.get('/', (req, res) => {
console.log('hi');
io.of('/testSoc').emit('connection', { data: '1234' });
res.send('API root');
});
io.of('/testSoc').on('connect', (socket) => {
console.log('클라이언트 접속');
socket.on('getMessage', (data) => {
console.log('fromClient', data);
});
socket.on('disconnect', () => {
console.log('클라이언트 접속 종료');
});
setInterval(() => {
socket.emit('receiveLog', { num: getRandomArbitrary(10, 100), time: new Date() });
}, 1000);
});
http.listen(8888, () => {
console.log('Socket IO server listening on port 8888');
});
import ws from 'k6/ws';
import { check } from 'k6';
export default function () {
const url = 'ws://echo.websocket.org';
const params = { tags: { my_tag: 'hello' } };
const res = ws.connect(url, params, (socket) => {
socket.on('open', () => {
console.log('connected');
socket.setInterval(() => {
socket.ping();
console.log('Pinging every 1sec (setInterval test)');
}, 1000);
});
socket.on('ping', () => {
console.log('PING!');
});
socket.on('pong', () => {
console.log('PONG!');
});
socket.on('close', () => {
console.log('disconnected');
});
socket.setTimeout(() => {
console.log('2 seconds passed, closing the socket');
socket.close();
}, 2000);
});
check(res, {
'status is 101': (r) => r && r.status === 101,
'Homepage body size is 11026 bytes': (r) => r.body && r.body.length === 11026,
test: (r) => r,
});
}
/* eslint-disable prefer-arrow-callback,consistent-return,no-param-reassign,no-mixed-operators,no-use-before-define */
let pingInterval = null;
let sendInterval = null;
const logData = [];
const dataNum = 20000;
for (let i = 0; i < dataNum; i += 1) {
logData.push({
latitude: getRandomArbitrary(37200000000000, 37300000000000) / 1000000000000,
longitude: getRandomArbitrary(126900000000000, 127100000000000) / 1000000000000,
id: i,
});
}
function getRandomArbitrary(min, max) {
return parseInt((Math.random() * (max - min) + min), 10);
}
function circleMove(x, y, radius, max, circleStep) {
return {
latitude: x + radius * Math.cos(2 * Math.PI * circleStep / max),
longitude: y + radius * Math.sin(2 * Math.PI * circleStep / max),
};
}
function makeCoordData(log, circleStep) {
if (circleStep == null) {
circleStep = 0;
}
if (circleStep === 3600) {
circleStep = 0;
} else circleStep += 1;
console.log('step', circleStep);
return Array.from(
{ length: dataNum },
(v, i) => ({
id: i,
...circleMove(log[i].latitude, log[i].longitude, 0.05, 3600, circleStep),
time: new Date(),
}),
);
}
function heartbeat() {
this.isAlive = true;
console.log('client Heartbeat');
}
function noop() {}
const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 20202,
perMessageDeflate: {
zlibDeflateOptions: {
// See zlib defaults.
chunkSize: 1024,
memLevel: 7,
level: 3,
},
zlibInflateOptions: {
chunkSize: 10 * 1024,
},
// Other options settable:
clientNoContextTakeover: true, // Defaults to negotiated value.
serverNoContextTakeover: true, // Defaults to negotiated value.
serverMaxWindowBits: 10, // Defaults to negotiated value.
// Below options specified as default values.
concurrencyLimit: 10, // Limits zlib concurrency for perf.
threshold: 1024, // Size (in bytes) below which messages
// should not be compressed.
},
});
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
let circleStep = 0;
sendInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
const coordData = makeCoordData(logData, circleStep);
ws.send(JSON.stringify(coordData));
}
circleStep += 1;
}, 1000);
ws.on('close', function close() {
console.log('websocket Closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
// sendInterval = null;
});
});
/* ping check */
pingInterval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on('close', function close() {
console.log('server closed');
clearInterval(pingInterval);
clearInterval(sendInterval);
});
# ASSETS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).
@import 'node_modules/ant-design-vue/dist/antd.less';
@import '@/assets/styles/less/partials/antdesignCustom.less';
/* default Variable */
@primary-color: #1890ff; // primary color for all components
@link-color: #1890ff; // link color
@success-color: #52c41a; // success state color
@warning-color: #faad14; // warning state color
@error-color: #f5222d; // error state color
@font-size-base: 14px; // major text font size
@heading-color: rgba(0, 0, 0, 0.85); // heading text color
@text-color: rgba(0, 0, 0, 0.65); // major text color
@text-color-secondary: rgba(0, 0, 0, 0.45); // secondary text color
@disabled-color: rgba(0, 0, 0, 0.25); // disable state color
@border-radius-base: 4px; // major border radius
@border-color-base: #d9d9d9; // major border color
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // major shadow for layers
// Colors
$white: #fff;
$black: #001529;
$blue: var(--kit-color-primary);
$blue-light: #3d6ee7;
$blue-dark: #103daf;
$gray-1: #f2f4f8;
$gray-2: #e4e9f0;
$gray-3: #dde2ec;
$gray-4: #c3bedc;
$gray-5: #aca6cc;
$gray-6: #786fa4;
$yellow: #ff0;
$orange: #f2a654;
$red: #b52427;
$pink: #fd3995;
$purple: #652eff;
$green: #41b883;
$kdis-color: #0c7037;
$antblue: #1890ff;
$text: $gray-6;
$border: $gray-2;
// Accent colors
$default: $gray-4;
$primary: $kdis-color;
$secondary: $gray-5;
$success: $green;
$info: $blue-light;
$warning: $orange;
$danger: $red;
$light: $gray-1;
$dark: $black;
// dark theme
$dark-gray-1: #aeaee0;
$dark-gray-2: #7575a3;
$dark-gray-3: #4f4f7a;
$dark-gray-4: #2a274d;
$dark-gray-5: #161537;
$dark-gray-6: #100f28;
// Font Family
$base-font-family: 'Noto Sans KR', sans-serif;
// Font Size
$base-font-size: 15 !default;
@import '@/assets/styles/scss/partials/transition.scss';
@import '@/assets/styles/scss/partials/page.scss';
@import '@/assets/styles/scss/partials/layouts.scss';
@import '@/assets/styles/scss/partials/description.scss';
@import '@/assets/styles/scss/partials/box.scss';
@import '@/assets/styles/scss/partials/test.scss';
@import '@/assets/styles/scss/partials/pagination.scss';
@import '@/assets/styles/scss/partials/alert.scss';
.filter-alert{
padding-top: 5px;
padding-bottom: 5px;
margin-top: -10px;
.ant-alert-icon{
top: 9px;
left: 14px;
}
}
.mapBox {
::-webkit-scrollbar {
width: 7px;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: #47749E;
border: 2px solid transparent;
width: 5px;
border-radius: 2px;
}
::-webkit-scrollbar-track {
background-color: rgb(236, 236, 236);
border-radius: 0px 2px 2px 0px;
}
.search-box {
position: absolute;
right: 10px;
top: 10px;
.searchBtn{
width: 50px;
height: 50px;
padding: 7px;
background: white;
border: 2px solid rgba(0,0,0,0.2);
border-radius: 4px;
cursor: pointer;
}
.ant-modal-content {
position: absolute;
right: 60px;
top: 0px;
width: 400px;
max-height: calc(100vh - 120px);
.ant-modal-header {
background: #47749e;
.ant-modal-title {
color: white;
text-align: center;
}
}
.ant-modal-body {
padding: 20px;
}
.ant-input:hover {
border-color: #47749e;
border-right-width: 1px !important;
}
.ant-input:focus {
border-color: #47749e;
}
.ant-btn-primary {
background-color: #47749e;
border-color: #47749e;
}
.ant-list-header {
color: white;
background: #47749e;
}
.ant-input-search {
width: 100%;
margin-bottom: 10px;
}
.ant-list-items {
max-height: calc(100vh - 300px);
overflow-y: scroll;
}
.ant-list-item {
padding: 10px;
background: white;
width: 100%;
}
.ant-list-item:hover {
color: #47749e;
font-weight: 600;
}
}
}
}
.filter-feature-box {
.ant-modal-content {
position: absolute;
right: 70px;
top: 10px;
width: 300px;
.ant-modal-header {
background: #47749e;
.ant-modal-title {
color: white;
text-align: center;
}
}
}
.label {
font-size: 13px;
font-weight: 700;
margin-bottom: 10px;
margin-top: 20px;
span {
display: inline-block;
border: 2px solid #47749e;
border-radius: 3px;
padding: 2px 5px;
}
}
}
.boxBtn {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
padding: 7px;
background: white;
border: 2px solid rgba(0,0,0,0.2);
border-radius: 4px;
cursor: pointer;
font-size: 30px;
}
.bottom-tool-box {
display: flex;
gap: 10px;
position: absolute;
bottom: 10px;
right: calc(50% - 25px);
.filterBox {
.filterBtn {
width: 50px;
height: 50px;
padding: 7px;
background: white;
border: 2px solid rgba(0,0,0,0.2);
border-radius: 4px;
cursor: pointer;
}
}
}
\ No newline at end of file
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&family=Roboto:wght@500&display=swap');
.ant-descriptions-bordered .ant-descriptions-item-label {
background-color: #47749e;
color: white;
}
.ant-descriptions-item {
display: inline-flex;
margin-right: 10px;
}
.ant-descriptions-row {
font-family: 'Noto Sans KR', sans-serif;
.ant-descriptions-item-content {
min-width: 150px;
font-size: 13px;
background: white;
}
}
.ant-descriptions-item > span {
align-self: center;
}
.ant-descriptions-bordered .ant-descriptions-item-label,
.ant-descriptions-bordered .ant-descriptions-item-content {
padding: 10px 10px;
}
.description-box .ant-descriptions-item {
display: table-cell;
}
.drone-detail {
.ant-descriptions-view {
max-height: calc(100vh - 120px);
overflow: auto;
}
.ant-descriptions-item-label.ant-descriptions-item-colon {
font-size: 16px;
}
}
.ant-page-header-content {
.search-box .ant-descriptions-row .ant-descriptions-item-content {
min-width: 0;
}
.search-box .ant-descriptions-row {
font-family: none;
}
}
.mt-size-default{
margin-top: 8px;
}
.mt-size-sm {
margin-top: 4px;
}
.mt-size-md {
margin-top: 12px;
}
.mt-size-lg {
margin-top: 20px;
}
.ml-size-default{
margin-left: 8px;
}
.ml-size-sm {
margin-left: 4px;
}
.ml-size-md {
margin-left: 12px;
}
.ml-size-lg {
margin-left: 20px;
}
.mr-size-default{
margin-right: 8px;
}
.mr-size-sm {
margin-right: 4px;
}
.mr-size-md {
margin-right: 12px;
}
.mr-size-lg {
margin-right: 20px;
}
.mb-size-default{
margin-bottom: 8px;
}
.mb-size-sm {
margin-bottom: 4px;
}
.mb-size-md {
margin-bottom: 12px;
}
.mb-size-lg {
margin-bottom: 20px;
}
.margin-size-default{
margin: 8px;
}
.margin-size-sm {
margin: 4px;
}
.margin-size-md {
margin: 12px;
}
.margin-size-lg {
margin: 20px;
}
.pt-size-default{
padding-top: 8px;
}
.pt-size-sm {
padding-top: 4px;
}
.pt-size-md {
padding-top: 12px;
}
.pt-size-lg {
padding-top: 20px;
}
.pl-size-default{
padding-left: 8px;
}
.pl-size-sm {
padding-left: 4px;
}
.pl-size-md {
padding-left: 12px;
}
.pl-size-lg {
padding-left: 20px;
}
.pr-size-default{
padding-right: 8px;
}
.pr-size-sm {
padding-right: 4px;
}
.pr-size-md {
padding-right: 12px;
}
.pr-size-lg {
padding-right: 20px;
}
.pb-size-default{
padding-bottom: 8px;
}
.pb-size-sm {
padding-bottom: 4px;
}
.pb-size-md {
padding-bottom: 12px;
}
.pb-size-lg {
padding-bottom: 20px;
}
.padding-size-default{
padding: 8px;
}
.padding-size-sm {
padding: 4px;
}
.padding-size-md {
padding: 12px;
}
.padding-size-lg {
padding: 20px;
}
.r-flex{
display: flex;
&.center{
justify-content: center;
align-items: center;
}
&.space-between{
justify-content: space-between;
align-items: center;
}
&.space-around{
justify-content: space-around;
align-items: center;
}
&.space-evenly{
justify-content: space-evenly;
align-items: center;
}
&.start{
justify-content: start;
align-items: center;
}
&.end{
justify-content: flex-end;
align-items: center;
}
&.gap-1 {
gap: 4px
}
&.gap-2 {
gap: 8px
}
&.gap-3 {
gap: 12px
}
&.gap-4 {
gap: 16px
}
&.gap-5 {
gap: 20px
}
&.gap-6 {
gap: 24px
}
&.gap-default{
gap: 8px
}
&.gap-sm {
gap: 6px
}
&.gap-md {
gap: 12px
}
&.gap-lg {
gap: 20px
}
}
@import '@/assets/styles/mixins.scss';
.page-header {
background-color: white;
border: $gray-3 1px solid;
border-radius: 6px;
}
.search-input {
width: 50%;
min-width: 200px;
}
.page-main {
margin-top: 20px;
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
.page-main-without-header {
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
.ant-table-pagination.ant-pagination {
float: none;
text-align: center;
}
.ant-pagination-prev .ant-pagination-item-link,
.ant-pagination-next .ant-pagination-item-link,
.ant-pagination-item {
border: none;
outline: none;
}
.ant-pagination-item {
font-size: 1rem;
}
.ant-pagination-item-active a {
font-weight: bolder;
}
@import '@/assets/styles/mixins.scss';
.default-layout-enter-active,
.default-layout-leave-active {
transition: opacity 0.5s;
}
.default-layout-enter,
.default-layout-leave-active {
opacity: 0;
}
//
.default-page-enter-active,
.default-page-leave-active {
transition: opacity 0.3s;
}
.default-page-enter,
.default-page-leave-active {
opacity: 0;
}
<template>
<pie-chart :chart-data="chartData"
:chart-settings="chartSettings"
/>
</template>
<script>
import PieChart from '@/components/_Common/Chart/pieChart';
export default {
name: 'droneCategoryPieChart',
components: {
PieChart,
},
props: {
chartData: {
type: Array,
default: null,
},
},
data() {
return {
chartSettings: {
chart: {
height: 400,
},
title: {
text: '카테고리별 드론 기체 수',
style: {
fontSize: '20px',
},
},
width: '50%',
plotOptions: {
pie: {
shadow: true,
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
useHTML: true,
distance: -50,
formatter() {
if (this.percentage.toFixed(0) < 6) return '';
return `<div style="padding: 6px 4px 4px 6px;
background-color: rgba(0, 0, 0, 0.5);
border: 2px solid #f2f4f8;
border-radius: 6px;
">${this.percentage.toFixed(1)}%</div>`;
},
style: {
fontSize: '16px',
color: 'white',
},
},
showInLegend: true,
},
},
tooltip: {
formatter() {
return `
<span style="font-size:16px; color:${this.color}">${this.key}</span>
<table>
<tr>
<td style="padding:0">${this.series.name}</td>
<td style="padding:0">: <b>${this.y}</b></td>
</tr>
<tr>
<td style="padding:0">점유율</td>
<td style="color:{series.color};padding:0">: <b>${this.percentage.toFixed(1)}%</b></td>
</tr>
</table>
`;
},
},
},
};
},
};
</script>
<style scoped>
</style>
<template>
<live-line-chart :chart-data="chartData"
:chart-settings="chartSettings"
/>
</template>
<script>
import LiveLineChart from '@/components/_Common/Chart/liveLineChart';
import dayjs from 'dayjs';
export default {
components: {
LiveLineChart,
},
props: {
chartData: {
type: Array,
default: null,
},
},
data() {
return {
chartSettings: {
chart: {
height: 400,
},
title: {
text: '시간별 드론 기체 수',
style: {
fontSize: '20px',
},
},
width: '100%',
xAxis: {
type: 'datetime',
},
yAxis: {
title: {
text: null,
},
min: 0,
},
tooltip: {
formatter() {
const trList = this.points.map((elem) => `
<tr>
<td style="font-size: 16px; padding:0; color:${elem.color}">${elem.series.name}</td>
<td style="font-size: 16px; padding:0; color:${elem.color}">: <b>${elem.y}</b></td>
</tr>
`);
return `
<span style="font-size:12px; color:${this.color}">${dayjs(this.x).format('YYYY-MM-DD HH:mm:ss')}</span>
<table>
${trList}
</table>
`;
},
},
data: {
enablePolling: true,
dataRefreshRate: 2,
},
},
};
},
};
</script>
<style scoped lang="scss">
</style>
<template>
<pie-chart :chart-data="chartData"
:chart-settings="chartSettings"
/>
</template>
<script>
import PieChart from '@/components/_Common/Chart/pieChart';
export default {
name: 'makerPieChart',
components: {
PieChart,
},
props: {
chartData: {
type: Array,
default: null,
},
},
data() {
return {
chartSettings: {
chart: {
height: 400,
},
title: {
text: '제조사별 드론 기체 수',
style: {
fontSize: '20px',
},
},
width: '50%',
plotOptions: {
pie: {
shadow: true,
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
useHTML: true,
distance: -50,
formatter() {
if (this.percentage.toFixed(0) < 6) return '';
return `<div style="padding: 6px 4px 4px 6px;
background-color: rgba(0, 0, 0, 0.5);
border: 2px solid #f2f4f8;
border-radius: 6px;
">${this.percentage.toFixed(1)}%</div>`;
},
style: {
fontSize: '16px',
color: 'white',
},
},
showInLegend: true,
},
},
tooltip: {
formatter() {
return `
<span style="font-size:16px; color:${this.color}">${this.key}</span>
<table>
<tr>
<td style="padding:0">${this.series.name}</td>
<td style="padding:0">: <b>${this.y}</b></td>
</tr>
<tr>
<td style="padding:0">점유율</td>
<td style="color:{series.color};padding:0">: <b>${this.percentage.toFixed(1)}%</b></td>
</tr>
</table>
`;
},
},
},
};
},
};
</script>
<style scoped>
</style>
<template>
<column-chart :chart-data="chartData"
:chart-settings="chartSettings"/>
</template>
<script>
import ColumnChart from '@/components/_Common/Chart/columnChart';
export default {
name: 'timeCategoryColumnChart',
components: {
ColumnChart,
},
props: {
chartData: {
type: Array,
default: () => [],
},
},
data() {
return {
chartSettings: {
chart: {
height: 400,
},
title: {
text: '시간-드론 타입별 드론 기체 수',
style: {
fontSize: '20px',
},
},
width: '100%',
xAxis: {
categories: [
'00:00 ~ 04:00',
'04:00 ~ 08:00',
'08:00 ~ 12:00',
'12:00 ~ 16:00',
'16:00 ~ 20:00',
'20:00 ~ 24:00',
],
},
yAxis: {
title: {
text: null,
},
min: 0,
},
},
};
},
};
</script>
<style scoped lang="scss">
</style>
<template>
<a-page-header
class="page-header"
title="Analytics"
sub-title="Chart for Realtime Drone Data"
>
<template slot="extra">
<a-button key="1" type="primary">
action
</a-button>
</template>
</a-page-header>
</template>
<script>
export default {
name: 'AnalyticsHeader',
components: {},
data() {
return {
};
},
};
</script>
<style scoped lang="scss">
</style>
<template>
<a-page-header
class="page-header"
title="드론 정보"
sub-title="Drone-Schedule"
@back="$router.go(-1)"
>
<div :style="{display: 'flex'}">
<div :style="{width: '30%'}">
<img :src="droneInfo.picture" :style="{width: '90%'}" />
</div>
<div :style="{width: '70%', marginLeft: '20px'}">
<div class="label-modelName">{{ droneInfo.modelName }}</div>
<div :style="{display: 'flex', marginBottom: '15px'}">
<div class="label-info">info</div>
<a-descriptions
:column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}"
class="description-box"
>
<a-descriptions-item label="모델명">
{{ droneInfo.modelName }}
</a-descriptions-item>
<a-descriptions-item label="제조사">
{{ droneInfo.maker == null ? 'None' : droneInfo.maker }}
</a-descriptions-item>
<a-descriptions-item label="종류">
{{ droneInfo.usage == null ? 'None' : droneInfo.usage }}
</a-descriptions-item>
<a-descriptions-item label="제원">
{{
droneInfo.specification == null
? 'None'
: droneInfo.specification
}}
</a-descriptions-item>
<a-descriptions-item label="무게">
{{ droneInfo.weight == null ? '?' : droneInfo.weight }} kg
</a-descriptions-item>
<a-descriptions-item label="No">
{{ droneInfo.id }}
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</div>
<a-divider />
</a-page-header>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'DatabaseDetailHeader',
components: {},
data() {
return {
droneInfo: {},
};
},
computed: {
...mapGetters('Drone/detail', {
getDetailData: 'getDetailData',
}),
...mapGetters('Code', {
getCodes: 'getCodes',
}),
droneCategory() {
return (data) => {
switch (parseInt(data, 10)) {
case 1:
return '촬영용';
case 2:
return '레이싱용';
case 3:
return '완구용';
default:
return null;
}
};
},
},
created() {
console.log(this.getDetailData);
this.droneInfo = this.getDetailData;
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.label-modelName {
font-size: 30px;
color: $antblue;
}
.label-info {
padding: 0 10px;
border-right: 1px solid #777777;
min-width: 20%;
}
.description-box {
padding-left: 10px;
}
</style>
<template>
<a-page-header
class="page-header"
title="Database"
sub-title="Table - Drone list"
>
<template slot="extra">
<a-button key="1" type="primary" form="form" html-type="submit">
검색
</a-button>
</template>
<a-form id="form" @submit.prevent="searchData" class="form-wrapper">
<a-form-item label="모델명" class="form-item">
<a-input v-model="searchParams.modelName" class="search-input" />
</a-form-item>
<a-form-item label="제조사" class="form-item">
<a-input v-model="searchParams.maker" class="search-input" />
</a-form-item>
<a-form-item label="무게" class="form-item">
<a-button
v-if="searchOpenFlag"
class="search-input"
@click="searchOpenFlag = !searchOpenFlag"
>{{ sliderBegin }} - {{ sliderEnd == 50 ? '50+' : sliderEnd }} kg
<a-icon type="up" />
</a-button>
<a-button
v-else
class="search-input"
@click="searchOpenFlag = !searchOpenFlag"
>{{ sliderBegin }} - {{ sliderEnd == 50 ? '50+' : sliderEnd }} kg
<a-icon type="down" />
</a-button>
<div v-if="searchOpenFlag" class="slider-box">
<a-slider
range
:marks="marks"
:max="50"
:step="1"
:default-value="[sliderBegin, sliderEnd]"
class="search-input"
@change="onSliderChange"
@afterChange="onSliderAfterChange"
/>
</div>
</a-form-item>
<a-form-item label="종류" has-feedback class="form-item">
<a-select
class="search-input"
default-value=""
@change="handleSelectChange"
>
<a-select-option value="">
선택 안 함
</a-select-option>
<a-select-option value="촬영용">
촬영용
</a-select-option>
<a-select-option value="산업용">
산업용
</a-select-option>
<a-select-option value="군사용">
군사용
</a-select-option>
<a-select-option value="레이싱용">
레이싱용
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-page-header>
</template>
<script>
import {droneCategory} from '@/utils/CommonData/selectOptions';
import {mapActions, mapGetters} from 'vuex';
export default {
name: 'DatabaseSearchFilter',
components: {},
data() {
return {
searchOpenFlag: false,
categoryOptions: droneCategory,
searchParams: {},
marks: {
0: '0kg',
50: '50+kg',
},
sliderBegin: 0,
sliderEnd: 50,
};
},
computed: {
...mapGetters('Drone/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.searchParams = JSON.parse(JSON.stringify(this.getPageParams));
},
methods: {
...mapActions('Drone/page', {
setPageParams: 'setPageParams',
}),
searchData() {
console.log(this.searchParams);
this.setPageParams(this.searchParams);
this.$emit('loadData');
},
handleSelectChange(value) {
this.searchParams.usageName = value;
this.searchData();
},
onSliderChange(value) {
this.sliderBegin = value[0];
this.sliderEnd = value[1];
},
onSliderAfterChange(value) {
this.searchParams.minWeight = value[0];
this.searchParams.maxWeight = value[1];
if (value[1] == 50) {
this.searchParams.maxWeight = null;
}
this.searchOpenFlag = !this.searchOpenFlag;
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.form-wrapper {
height: 40px;
}
.form-wrapper,
.form-item {
display: flex;
margin-left: 10px;
margin-bottom: 0;
}
.slider-box {
background: white;
padding: 10px 20px;
border-radius: 20px;
border: solid 1px $antblue;
position: fixed;
z-index: 10;
}
</style>
<template>
<a-table
rowKey="id"
bordered
:loading="childLoading"
:columns="columns"
:data-source="getPageData"
:scroll="{x: 1000}"
:pagination="pagination"
@change="changePage"
>
<a slot="modelName" slot-scope="data, row" @click="goDetail(row)">
{{ data }}
</a>
<div slot="usageName" slot-scope="data">
{{ data == null ? '?' : data }}
</div>
<div slot="weight" slot-scope="data">
{{ data == null ? '?' : data + 'kg' }}
</div>
<div slot="specification" slot-scope="data">
{{ data == null ? '?' : data }}
</div>
<div slot="droneCategory" slot-scope="data">
{{ data }}
</div>
</a-table>
</template>
<script>
import databaseColumn from '@/utils/ColumnData/databaseColumn';
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'DatabaseTable',
props: {
childLoading: {
type: Boolean,
default: false,
},
},
data() {
return {
columns: databaseColumn,
};
},
computed: {
...mapGetters('Drone/page', {
getPageData: 'getPageData',
getPagination: 'getPagination',
getPageParams: 'getPageParams',
}),
pagination: {
get() {
return this.getPagination;
},
set(e) {
this.setPagination({
size: e.size,
page: e.page,
});
},
},
},
methods: {
...mapActions('Drone/page', {
setPagination: 'setPagination',
fetchPageData: 'fetchPageData',
}),
changePage(e) {
this.pagination = {
size: e.pageSize,
page: e.current,
};
this.fetchPageData(this.getPageParams);
},
goDetail(row) {
this.$router.push({
path: `/database/drone/${row.id}`,
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<a-page-header
class="page-header"
title="Database"
sub-title="Table - Drone Log"
>
<template slot="extra">
<a-button
key="1"
type="primary"
form="form"
html-type="submit"
:style="{padding: '0 15px'}"
>
검색
</a-button>
</template>
<a-form id="form" @submit.prevent="searchData" class="form-wrapper">
<a-descriptions
:column="{xs: 1, sm: 3, md: 3, lg: 6, xl: 7, xxl: 7}"
class="search-box"
>
<a-descriptions-item label="Schedule ID" class="form-item">
<a-input v-model="searchParams.scheduleId" class="search-input-log" />
</a-descriptions-item>
<a-descriptions-item label="수평 속도" class="form-item">
<a-button v-if="hsOpenFlag" @click="hsOpenFlag = !hsOpenFlag"
>{{ minHorizontalSpeed }} -
{{ maxHorizontalSpeed == 100 ? '100+' : maxHorizontalSpeed }} km/s
<a-icon type="up" />
</a-button>
<a-button v-else @click="hsOpenFlag = !hsOpenFlag"
>{{ minHorizontalSpeed }} -
{{ maxHorizontalSpeed == 100 ? '100+' : maxHorizontalSpeed }} km/s
<a-icon type="down" />
</a-button>
<div v-if="hsOpenFlag" class="slider-box">
<a-slider
range
:marks="speedMarks"
:max="100"
:step="1"
:default-value="[minHorizontalSpeed, maxHorizontalSpeed]"
class="search-input"
@change="onHorizontalSpeedChange"
@afterChange="onHorizontalSpeedAfterChange"
/>
</div>
</a-descriptions-item>
<a-descriptions-item label="수직 속도" class="form-item">
<a-button v-if="vsOpenFlag" @click="vsOpenFlag = !vsOpenFlag"
>{{ minVerticalSpeed }} -
{{ maxVerticalSpeed == 100 ? '100+' : maxVerticalSpeed }} km/s
<a-icon type="up" />
</a-button>
<a-button v-else @click="vsOpenFlag = !vsOpenFlag"
>{{ minVerticalSpeed }} -
{{ maxVerticalSpeed == 100 ? '100+' : maxVerticalSpeed }} km/s
<a-icon type="down" />
</a-button>
<div v-if="vsOpenFlag" class="slider-box">
<a-slider
range
:marks="speedMarks"
:max="100"
:step="1"
:default-value="[minVerticalSpeed, maxVerticalSpeed]"
class="search-input"
@change="onVerticalSpeedChange"
@afterChange="onVerticalSpeedAfterChange"
/>
</div>
</a-descriptions-item>
<a-descriptions-item label="지면 고도" class="form-item">
<a-button v-if="aglOpenFlag" @click="aglOpenFlag = !aglOpenFlag"
>{{ minAboveGroundLevel }} -
{{ maxAboveGroundLevel == 200 ? '200+' : maxAboveGroundLevel }} m
<a-icon type="up" />
</a-button>
<a-button v-else @click="aglOpenFlag = !aglOpenFlag"
>{{ minAboveGroundLevel }} -
{{ maxAboveGroundLevel == 200 ? '200+' : maxAboveGroundLevel }} m
<a-icon type="down" />
</a-button>
<div v-if="aglOpenFlag" class="slider-box">
<a-slider
range
:marks="levelMarks"
:max="200"
:step="1"
:default-value="[minAboveGroundLevel, maxAboveGroundLevel]"
class="search-input"
@change="onAboveGroundLevelChange"
@afterChange="onAboveGroundLevelAfterChange"
/>
</div>
</a-descriptions-item>
<a-descriptions-item label="해발 고도" class="form-item">
<a-button v-if="aslOpenFlag" @click="aslOpenFlag = !aslOpenFlag"
>{{ minAboveSeaLevel }} -
{{ maxAboveSeaLevel == 200 ? '200+' : maxAboveSeaLevel }} m
<a-icon type="up" />
</a-button>
<a-button v-else @click="aslOpenFlag = !aslOpenFlag"
>{{ minAboveSeaLevel }} -
{{ maxAboveSeaLevel == 200 ? '200+' : maxAboveSeaLevel }} m
<a-icon type="down" />
</a-button>
<div v-if="aslOpenFlag" class="slider-box">
<a-slider
range
:marks="levelMarks"
:max="200"
:step="1"
:default-value="[minAboveSeaLevel, maxAboveSeaLevel]"
class="search-input"
@change="onAboveSeaLevelChange"
@afterChange="onAboveSeaLevelAfterChange"
/>
</div>
</a-descriptions-item>
<a-descriptions-item label="위도" class="form-item">
<a-input v-model="searchParams.latitude" class="search-input-log" />
</a-descriptions-item>
<a-descriptions-item label="경도" class="form-item">
<a-input v-model="searchParams.longitude" class="search-input-log" />
</a-descriptions-item>
</a-descriptions>
</a-form>
</a-page-header>
</template>
<script>
/* eslint-disable prefer-destructuring */
import {mapActions, mapGetters} from 'vuex';
export default {
name: 'LogSearchFilter',
components: {},
data() {
return {
hsOpenFlag: false,
vsOpenFlag: false,
aslOpenFlag: false,
aglOpenFlag: false,
searchParams: {},
speedMarks: {
0: '0km/h',
100: '100+km/h',
},
levelMarks: {
0: '0m',
200: '200+m',
},
minVerticalSpeed: 0,
maxVerticalSpeed: 100,
minHorizontalSpeed: 0,
maxHorizontalSpeed: 100,
minAboveSeaLevel: 0,
maxAboveSeaLevel: 200,
minAboveGroundLevel: 0,
maxAboveGroundLevel: 200,
};
},
computed: {
...mapGetters('Log/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.searchParams = JSON.parse(JSON.stringify(this.getPageParams));
},
methods: {
...mapActions('Log/page', {
setPageParams: 'setPageParams',
}),
searchData() {
console.log(this.searchParams);
this.setPageParams(this.searchParams);
this.$emit('loadData');
},
onHorizontalSpeedChange(value) {
this.minHorizontalSpeed = value[0];
this.maxHorizontalSpeed = value[1];
},
onHorizontalSpeedAfterChange(value) {
this.hsOpenFlag = !this.hsOpenFlag;
this.searchParams.minHorizontalSpeed = this.minHorizontalSpeed;
this.searchParams.maxHorizontalSpeed = this.maxHorizontalSpeed;
if (this.maxHorizontalSpeed == 100) {
this.searchParams.maxHorizontalSpeed = null;
}
},
onVerticalSpeedChange(value) {
this.minVerticalSpeed = value[0];
this.maxVerticalSpeed = value[1];
},
onVerticalSpeedAfterChange(value) {
this.vsOpenFlag = !this.vsOpenFlag;
this.searchParams.minVerticalSpeed = this.minVerticalSpeed;
this.searchParams.maxVerticalSpeed = this.maxVerticalSpeed;
if (this.maxVerticalSpeed == 100) {
this.searchParams.maxVerticalSpeed = null;
}
},
onAboveSeaLevelChange(value) {
this.minAboveSeaLevel = value[0];
this.maxAboveSeaLevel = value[1];
},
onAboveSeaLevelAfterChange(value) {
this.aslOpenFlag = !this.aslOpenFlag;
this.searchParams.minAboveSeaLevel = this.minAboveSeaLevel;
this.searchParams.maxAboveSeaLevel = this.maxAboveSeaLevel;
if (this.maxAboveSeaLevel == 200) {
this.searchParams.maxAboveSeaLevel = null;
}
},
onAboveGroundLevelChange(value) {
this.minAboveGroundLevel = value[0];
this.maxAboveGroundLevel = value[1];
},
onAboveGroundLevelAfterChange(value) {
this.aglOpenFlag = !this.aglOpenFlag;
this.searchParams.minAboveGroundLevel = this.minAboveGroundLevel;
this.searchParams.maxAboveGroundLevel = this.maxAboveGroundLevel;
if (this.maxAboveGroundLevel == 200) {
this.searchParams.maxAboveGroundLevel = null;
}
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.form-wrapper {
//height: 40px;
justify-content: space-between;
}
.form-wrapper,
.form-item {
display: flex;
margin-right: 10px;
margin-bottom: 0;
}
.slider-box {
background: white;
padding: 10px 23px;
border-radius: 20px;
border: solid 1px $antblue;
position: fixed;
z-index: 9;
}
.search-input-log {
width: 100px;
}
.ant-btn {
padding: 0 10px;
}
</style>
<template>
<a-table
rowKey="id"
bordered
:loading="childLoading"
:columns="columns"
:data-source="getPageData"
:scroll="{x: 1000}"
:pagination="pagination"
@change="changePage"
>
<a slot="modelName" slot-scope="data, row" @click="goDetail(row)">
{{ data }}
</a>
<div slot="horizontalSpeed" slot-scope="data">{{ data }} km/h</div>
<div slot="verticalSpeed" slot-scope="data">{{ data }} km/h</div>
<div slot="aboveGroundLevel" slot-scope="data">{{ data }} m</div>
<div slot="aboveSeaLevel" slot-scope="data">{{ data }} m</div>
</a-table>
</template>
<script>
import databaseLogColumn from '@/utils/ColumnData/databaseLogColumn';
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'LogTable',
props: {
childLoading: {
type: Boolean,
default: false,
},
},
data() {
return {
columns: databaseLogColumn,
};
},
computed: {
...mapGetters('Log/page', {
getPageData: 'getPageData',
getPagination: 'getPagination',
getPageParams: 'getPageParams',
}),
pagination: {
get() {
return this.getPagination;
},
set(e) {
this.setPagination({
size: e.size,
page: e.page,
});
},
},
},
methods: {
...mapActions('Log/page', {
setPagination: 'setPagination',
fetchPageData: 'fetchPageData',
}),
...mapActions('Drone/detail', {
fetchDroneDetailData: 'fetchDetailData',
}),
...mapActions('Schedule/detail', {
fetchScheduleDetailData: 'fetchDetailData',
}),
changePage(e) {
this.pagination = {
size: e.pageSize,
page: e.current,
};
this.fetchPageData(this.getPageParams);
},
goDetail(row) {
this.fetchDroneDetailData(row.droneId);
this.fetchScheduleDetailData(row.scheduleId);
this.$router.push({
path: `/database/schedule/${row.scheduleId}`,
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<a-page-header
class="page-header"
title="드론 정보"
sub-title="Drone-Schedule-Log"
@back="$router.go(-1)"
>
<div :style="{display: 'flex'}">
<div :style="{width: '30%'}">
<img :src="droneInfo.picture" :style="{width: '90%'}" />
</div>
<div :style="{width: '70%', marginLeft: '20px'}">
<div class="label-modelName">{{ droneInfo.modelName }}</div>
<div :style="{display: 'flex', marginBottom: '15px'}">
<div class="label-info">info</div>
<a-descriptions
:column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}"
class="description-box"
>
<a-descriptions-item label="모델명">
{{ droneInfo.modelName }}
</a-descriptions-item>
<a-descriptions-item label="제조사">
{{ droneInfo.maker == null ? 'None' : droneInfo.maker }}
</a-descriptions-item>
<a-descriptions-item label="종류">
{{ droneInfo.usage == null ? 'None' : droneInfo.usageName }}
</a-descriptions-item>
<a-descriptions-item label="제원">
{{
droneInfo.specification == null
? 'None'
: droneInfo.specification
}}
</a-descriptions-item>
<a-descriptions-item label="무게">
{{ droneInfo.weight == null ? '?' : droneInfo.weight }} kg
</a-descriptions-item>
<a-descriptions-item label="No">
{{ droneInfo.id }}
</a-descriptions-item>
</a-descriptions>
</div>
<div :style="{display: 'flex'}">
<div class="label-info">Schedule</div>
<a-descriptions
:column="{xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1}"
class="description-box"
>
<a-descriptions-item label="시작 시간">
{{ mc_dateTime(ScheduleInfo.startTime) }}
</a-descriptions-item>
<a-descriptions-item label="시작 위도">
{{ ScheduleInfo.startLatitude }}
</a-descriptions-item>
<a-descriptions-item label="시작 경도">
{{ ScheduleInfo.startLongitude }}
</a-descriptions-item>
<a-descriptions-item label="종료 시간">
{{ mc_dateTime(ScheduleInfo.terminateTime) }}
</a-descriptions-item>
<a-descriptions-item label="종료 위도">
{{ ScheduleInfo.terminateLatitude }}
</a-descriptions-item>
<a-descriptions-item label="종료 경도">
{{ ScheduleInfo.terminateLongitude }}
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</div>
<a-divider />
</a-page-header>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'DatabaseDetailHeader',
components: {},
data() {
return {
droneInfo: {},
ScheduleInfo: {},
};
},
created() {
this.droneInfo = this.getDetailData;
this.ScheduleInfo = this.getScheduleDetailData;
},
computed: {
...mapGetters('Drone/detail', {
getDetailData: 'getDetailData',
}),
...mapGetters('Schedule/detail', {
getScheduleDetailData: 'getDetailData',
}),
...mapGetters('Code', {
getCodes: 'getCodes',
}),
droneCategory() {
return (data) => {
switch (parseInt(data, 10)) {
case 1:
return '촬영용';
case 2:
return '레이싱용';
case 3:
return '완구용';
default:
return null;
}
};
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.label-modelName {
font-size: 30px;
color: $antblue;
}
.label-info {
padding: 0 10px;
border-right: 1px solid #777777;
min-width: 20%;
}
.description-box {
padding-left: 10px;
}
</style>
<template>
<a-page-header
class="page-header"
title="Database"
sub-title="Table - DroneSchedule"
>
<template slot="extra">
<a-button key="1" type="primary" form="form" html-type="submit">
검색
</a-button>
</template>
<a-form id="form" @submit.prevent="searchData" class="form-wrapper">
<a-form-item label="검색 범위" class="form-item">
<div>
<a-date-picker
v-model="startValue"
:disabled-date="disabledStartDate"
show-time
format="YYYY-MM-DD HH:mm:ss"
placeholder="Start Date"
@openChange="handleStartOpenChange"
/>
<span :style="{padding: '0 10px'}">-</span>
<a-date-picker
v-model="endValue"
:disabled-date="disabledEndDate"
show-time
format="YYYY-MM-DD HH:mm:ss"
placeholder="End Date"
:open="endOpen"
@openChange="handleEndOpenChange"
/>
</div>
</a-form-item>
</a-form>
</a-page-header>
</template>
<script>
import {mapActions, mapGetters} from 'vuex';
export default {
name: 'DatabaseSearchFilter',
components: {},
data() {
return {
searchParams: {},
startValue: null,
endValue: null,
endOpen: false,
};
},
watch: {
startValue(val) {
if (val != null) {
this.searchParams.startTime = val.format('YYYY-MM-DD HH:mm:ss');
} else {
this.searchParams.startTime = null;
}
},
endValue(val) {
if (val !== null) {
this.searchParams.terminateTime = val.format('YYYY-MM-DD HH:mm:ss');
} else {
this.searchParams.terminateTime = null;
}
},
},
computed: {
...mapGetters('Schedule/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.searchParams = JSON.parse(JSON.stringify(this.getPageParams));
},
methods: {
...mapActions('Schedule/page', {
setPageParams: 'setPageParams',
}),
searchData() {
console.log(this.searchParams);
this.setPageParams(this.searchParams);
this.$emit('loadData');
},
disabledStartDate(startValue) {
const endValue = this.endValue;
if (!startValue || !endValue) {
return false;
}
return startValue.valueOf() > endValue.valueOf();
},
disabledEndDate(endValue) {
const startValue = this.startValue;
if (!endValue || !startValue) {
return false;
}
return startValue.valueOf() >= endValue.valueOf();
},
handleStartOpenChange(open) {
if (!open) {
this.endOpen = true;
}
},
handleEndOpenChange(open) {
this.endOpen = open;
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.form-wrapper {
height: 40px;
}
.form-wrapper,
.form-item {
display: flex;
margin-left: 10px;
margin-bottom: 0;
}
.slider-box {
background: white;
padding: 10px 15px;
border-radius: 20px;
border: solid 1px $antblue;
position: fixed;
z-index: 9;
}
</style>
<template>
<a-table
rowKey="id"
bordered
:loading="childLoading"
:columns="columns"
:data-source="getPageData"
:scroll="{x: 1000}"
:pagination="pagination"
@change="changePage"
>
<a slot="modelName" slot-scope="data, row" @click="goDetail(row)">
{{ data }}
</a>
<div slot="startTime" slot-scope="data">
{{ mc_dateTime(data) }}
</div>
<div slot="terminateTime" slot-scope="data">
{{ mc_dateTime(data) }}
</div>
</a-table>
</template>
<script>
import databaseScheduleColumn from '@/utils/ColumnData/databaseScheduleColumn';
import {mapActions, mapGetters} from 'vuex';
export default {
name: 'DatabaseTable',
props: {
childLoading: {
type: Boolean,
default: false,
},
},
data() {
return {
columns: databaseScheduleColumn,
};
},
computed: {
...mapGetters('Schedule/page', {
getPageData: 'getPageData',
getPagination: 'getPagination',
getPageParams: 'getPageParams',
}),
pagination: {
get() {
return this.getPagination;
},
set(e) {
this.setPagination({
size: e.size,
page: e.page,
});
},
},
},
methods: {
...mapActions('Schedule/page', {
setPagination: 'setPagination',
fetchPageData: 'fetchPageData',
}),
...mapActions('Drone/detail', {
fetchDetailData: 'fetchDetailData',
}),
changePage(e) {
this.pagination = {
size: e.pageSize,
page: e.current,
};
this.fetchPageData(this.getPageParams);
},
goDetail(row) {
this.fetchDetailData(row.droneId);
this.$router.push({
path: `/database/schedule/${row.id}`,
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<div>
<h4 class="footer-font">
Drone Simulation Map
</h4>
<h5 class="footer-font">
@2021 TwentyOz & KHU
</h5>
</div>
</template>
<script>
export default {
name: 'LayoutFooter',
data() {
return {
};
},
computed: {
},
watch: {
},
created() {
},
method: {
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.footer-font {
color: $gray-3
}
</style>
<template>
<div>
<div class="bottom-tool-box">
<div class="boxBtn" @click="clickWeatherBtn">
<a-icon type="cloud"/>
</div>
<FilterBtnBox
class="filterBox"
@clickClose="clickFilterBoxClose"
@changeFilterMode="e => this.$emit('changeFilterMode', e)"
@changeSearchMode="e => this.$emit('changeSearchMode', e)"
/>
<div class="boxBtn" @click="clickBookMarkBtn">
<a-icon type="star"/>
</div>
</div>
</div>
</template>
<script>
import FilterBtnBox from '../FilterBox/filterBtnBox';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
components: {
FilterBtnBox
},
props: {
},
data() {
return {
searchMode: false,
filterMode: false,
};
},
computed: {
},
created() {
},
methods: {
clickWeatherBtn() {
console.log('click')
this.$notification['warning']({
message: '날씨 기능은 추후 추가될 예정입니다.',
duration: 3,
})
},
clickBookMarkBtn() {
this.$notification['warning']({
message: '즐겨찾기 기능은 추후 추가될 예정입니다.',
duration: 3,
})
},
toggleFilterMode() {
this.$emit('toggleFilterMode')
},
toggleSearchMode() {
this.$emit('toggleSearchMode')
},
clickSearchBoxClose() {
this.searchMode = false;
},
clickFilterBoxClose() {
this.filterMode = false;
},
clickSearchBtn() {
this.filterMode = false;
this.searchMode = true;
},
clickFilterBtn() {
this.searchMode = false;
this.filterMode = true;
},
},
};
</script>
<style lang="scss">
</style>
<template>
<div>
<a-descriptions layout="vertical" bordered>
<a-descriptions-item :span="4">
<template v-slot:label>
<div>{{ foundDrone.modelName }}</div>
</template>
<img
:src="foundDrone.picture || require('@/assets/images/drone-image.jpg')"
:style="{width: '300px'}"
/>
</a-descriptions-item>
<a-descriptions-item label="실시간 정보" :span="4">
<div class="des-sub-title">현재 위치</div>
<div class="des-sub-cont-grid-4" style="margin-bottom: 10px;">
<div>
<div>경도</div>
<div>{{ selectedLastLog.longitude ? selectedLastLog.longitude.toFixed(3) : '?' }}</div>
</div>
<div>
<div>위도</div>
<div>{{ selectedLastLog.latitude ? selectedLastLog.latitude.toFixed(3) : '?' }}</div>
</div>
<div>
<div>이동거리</div>
<div>{{ Math.floor(getDetailData.distance + selectedLastLog.distance) }}m</div>
</div>
<div>
<div>운용시간</div>
<div>{{ getTimeDiff(foundSchedule.startTime) }}</div>
</div>
</div>
<div class="des-sub-cont-grid-2" style="margin-bottom: 10px;">
<div>
<div class="des-sub-title">현재 속도</div>
<div class="des-sub-cont-grid-2">
<div>
<div>수평 속도</div>
<div>{{ selectedLastLog.horizontalSpeed }}km/h</div>
</div>
<div>
<div>수직 속도</div>
<div>{{ selectedLastLog.verticalSpeed }}km/h</div>
</div>
</div>
</div>
<div>
<div class="des-sub-title">현재 고도</div>
<div class="des-sub-cont-grid-2">
<div>
<div>지면고도</div>
<div>{{ selectedLastLog.aboveGroundLevel }}m</div>
</div>
<div>
<div>해발고도</div>
<div>{{ selectedLastLog.aboveSeaLevel }}m</div>
</div>
</div>
</div>
</div>
</a-descriptions-item>
<a-descriptions-item label="스케쥴" :span="4" :style="{padding: '0px'}">
<div class="des-sub-cont-grid-2" style="margin-bottom: 10px;">
<div>
<div class="des-sub-title">시작 스케쥴</div>
<div>
<div>날짜/시간</div>
<div style="margin-bottom: 5px;">{{ mc_dateTime(foundSchedule.startTime) || '?' }}</div>
<div>경도</div>
<div style="margin-bottom: 5px;">{{ foundSchedule.startLongitude || '?' }}</div>
<div>위도</div>
<div style="margin-bottom: 5px;">{{ foundSchedule.startLatitude || '?' }}</div>
</div>
</div>
<div>
<div class="des-sub-title">실제 시작</div>
<div>
<div>날짜/시간</div>
<div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? mc_dateTime(droneLogs[0].createdAt) : "?" }}</div>
<div>경도</div>
<div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? droneLogs[0].longitude : "?" }}</div>
<div>위도</div>
<div style="margin-bottom: 5px;">{{ droneLogs.length !== 0 ? droneLogs[0].latitude : "?" }}</div>
</div>
</div>
</div>
<div class="des-sub-title">종료 스케쥴</div>
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr">
<div>
<div>날짜/시간</div>
<div>{{ mc_dateTime(foundSchedule.terminateTime) || '?' }}</div>
</div>
<div>
<div>경도</div>
<div>{{ foundSchedule.terminateLongitude || '?' }}</div>
</div>
<div>
<div>위도</div>
<div>{{ foundSchedule.terminateLatitude || '?' }}</div>
</div>
</div>
</a-descriptions-item>
<a-descriptions-item label="드론 정보" :span="6">
<div class="des-sub-cont-grid-4">
<div>
<div class="des-sub-title">제조사</div>
<div>{{ foundDrone.maker || '?' }}</div>
</div>
<div>
<div class="des-sub-title">종류</div>
<div>{{ foundDrone.usageName || '?' }}</div>
</div>
<div>
<div class="des-sub-title">제원</div>
<div>{{ foundDrone.specification || '?' }}</div>
</div>
<div>
<div class="des-sub-title">무게</div>
<div>{{ foundDrone.weight }}g</div>
</div>
</div>
</a-descriptions-item>
</a-descriptions>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
watch: {
getSelectedLastLog: {
deep: true,
handler(newVal) {
this.selectedLastLog = newVal;
},
},
},
data() {
return {
selectedLastLog: {},
};
},
computed: {
...mapGetters('Drone/drone', {
getDetailData: 'getDetailData',
getSelectedLastLog: 'getSelectedLastLog',
}),
foundSchedule() {
return this.getDetailData.foundSchedule || {};
},
foundDrone() {
return this.getDetailData.foundDrone || {};
},
droneLogs() {
return this.getDetailData.droneLogs || [];
},
selectedLastLog() {
return this.getSelectedLastLog || {};
},
},
methods: {
getTimeDiff(startTime) {
const totalSeconds = this.$dayjs().diff(this.$dayjs(startTime), 's');
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60) % 60;
const hours = Math.floor(totalSeconds / 3600);
return `${hours}:${minutes}:${seconds}`;
},
},
};
</script>
<style lang="scss">
.des-sub-title {
font-size: 16px;
font-weight: 900;
}
.des-sub-cont-grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
}
.des-sub-cont-grid-4 {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.des-sub-cont {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
</style>
<template>
<div>
<img
@click="clickFilterBtn"
class="filterBtn"
:src="require('@/static/img/filter.png')"/>
</div>
</template>
<script>
import CloseBox from '@/components/_Common/CloseBox/closeBox';
import { mapActions, mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
components: {
CloseBox,
},
props: {},
data() {
return {
filterMode: false,
filteredDroneList: [],
manufacturerValue: [],
weightValue: [0, 50],
weightMarks: {
0: '0kg',
50: '50kg+',
},
altitudeValue: [0, 200],
altitudeMarks: {
0: '0m',
200: '200m+',
},
speedValue: [0, 100],
speedMarks: {
0: '0km/h',
100: '100km/h+',
},
filmingOptions: ['True', 'False'],
filmingValue: 'True',
};
},
computed: {
...mapGetters('Etc', {
getMakers: 'getMakers',
}),
...mapGetters('Drone', {
getLogFilter: 'drone/getLogFilter',
getFixedDroneList: 'list/getFixedDroneList',
}),
},
created() {
},
methods: {
...mapActions('Drone/drone', {
setLogFilter: 'setLogFilter',
clearLogFilter: 'clearLogFilter',
}),
clickFilterBtn() {
this.$emit('changeSearchMode', false)
this.$emit('changeFilterMode', true)
},
clickClose() {
this.$emit('clickClose');
},
changeManufacturer(value) {
this.manufacturerValue = value;
this.filteredDroneList = this.getFixedDroneList.filter((v) => !!this.manufacturerValue.find((e) => v.maker === e));
},
changeWeight(weight) {
this.weightValue = weight;
},
changeAltitude(altitude) {
this.altitudeValue = altitude;
},
changeSpeed(speed) {
this.speedValue = speed;
},
applyFilter() {
this.setLogFilter({
checkFilter: true,
maker: this.manufacturerValue,
filteredDroneList: this.filteredDroneList,
weight: this.weightValue,
altitude: this.altitudeValue,
speed: this.speedValue,
filming: this.filmingValue,
});
},
clickReset() {
this.clearLogFilter();
this.filteredDroneList = [];
this.manufacturerValue = [];
this.weightValue = [0, 50];
this.altitudeValue = [0, 200];
this.speedValue = [0, 100];
this.filmingValue = [];
},
},
};
</script>
<style lang="scss">
</style>
<template>
<div>
<CloseBox
@clickClose="clickClose"
>
<template v-slot:header>
드론 필터
</template>
<template v-slot:body>
<a-alert
v-if="getLogFilter.checkFilter"
message="필터가 적용된 상태입니다."
type="info" show-icon
class="filter-alert"
/>
<div :style="{padding: '0 10px 0 5px', marginTop: getLogFilter.checkFilter ? '-5px' : '-20px'}">
<div class="label">
<span>제조사</span>
</div>
<a-select
:value="manufacturerValue"
mode="tags"
style="width: 100%;"
placeholder="제조사"
@change="changeManufacturer">
<a-select-option v-for="(maker,index) in getMakers"
:key="index"
:value="maker"
>
{{ maker }}
</a-select-option>
</a-select>
<div class="label"><span>무게</span></div>
<a-slider
:value="weightValue"
range
:marks="weightMarks"
:max="50"
:min="0"
@change="changeWeight"
/>
<div class="label"><span>고도</span></div>
<a-slider
:value="altitudeValue"
range
:marks="altitudeMarks"
:max="200"
:min="0"
@change="changeAltitude"/>
<div class="label"><span>속력</span></div>
<a-slider
:value="speedValue"
range
:marks="speedMarks"
:max="100"
:min="0"
@change="changeSpeed"/>
<div style="display: flex; justify-content: space-between; text-align: right; margin-top: 40px">
<a-button @click="applyFilter">
<a-icon type="check"/>
필터 적용
</a-button>
<a-button @click="clickReset">
<a-icon type="reload"/>
필터 리셋
</a-button>
</div>
</div>
</template>
</CloseBox>
</div>
</template>
<script>
import CloseBox from '@/components/_Common/CloseBox/closeBox';
import { mapActions, mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
components: {
CloseBox,
},
props: {},
data() {
return {
filteredDroneList: [],
manufacturerValue: [],
weightValue: [0, 50],
weightMarks: {
0: '0kg',
50: '50kg+',
},
altitudeValue: [0, 200],
altitudeMarks: {
0: '0m',
200: '200m+',
},
speedValue: [0, 100],
speedMarks: {
0: '0km/h',
100: '100km/h+',
},
filmingOptions: ['True', 'False'],
filmingValue: 'True',
};
},
computed: {
...mapGetters('Etc', {
getMakers: 'getMakers',
}),
...mapGetters('Drone', {
getLogFilter: 'drone/getLogFilter',
getFixedDroneList: 'list/getFixedDroneList',
}),
},
created() {
},
methods: {
...mapActions('Drone/drone', {
setLogFilter: 'setLogFilter',
clearLogFilter: 'clearLogFilter',
}),
clickClose() {
this.$emit('clickClose');
},
changeManufacturer(value) {
this.manufacturerValue = value;
this.filteredDroneList = this.getFixedDroneList.filter((v) => !!this.manufacturerValue.find((e) => v.maker === e));
},
changeWeight(weight) {
this.weightValue = weight;
},
changeAltitude(altitude) {
this.altitudeValue = altitude;
},
changeSpeed(speed) {
this.speedValue = speed;
},
applyFilter() {
this.setLogFilter({
checkFilter: true,
maker: this.manufacturerValue,
filteredDroneList: this.filteredDroneList,
weight: this.weightValue,
altitude: this.altitudeValue,
speed: this.speedValue,
filming: this.filmingValue,
});
},
clickReset() {
this.clearLogFilter();
this.filteredDroneList = [];
this.manufacturerValue = [];
this.weightValue = [0, 50];
this.altitudeValue = [0, 200];
this.speedValue = [0, 100];
this.filmingValue = [];
},
},
};
</script>
<style lang="scss">
</style>
<template>
<div>
<img
class="searchBtn"
@click="clickSearchBtn"
:src="require('@/static/img/search.png')"/>
</div>
</template>
<script>
import CloseBox from '@/components/_Common/CloseBox/closeBox';
import { mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
components: {
CloseBox,
},
props: {},
data() {
return {
searchValue: null,
searchType: 'modelName',
searchData: [],
dataSource: [],
searchMode: false,
};
},
computed: {
...mapGetters('Etc', {
getMakerDroneList: 'getMakerDroneList',
}),
...mapGetters('Drone', {
getLogFilter: 'drone/getLogFilter',
getDroneLogs: 'drone/getDroneLogs',
}),
},
created() {
},
methods: {
async onSearch(value) {
const params = {};
params[this.searchType] = value;
const result = await this.$store.dispatch('Drone/list/fetchListContents', params);
const objectDrone = {};
this.getDroneLogs.forEach((e) => {
objectDrone[Number(e.droneId)] = true;
});
this.searchData = [...result.drones.filter((v) => objectDrone[Number(v.id)])];
},
clickSearchBtn() {
this.$emit('changeFilterMode', false)
this.$emit('changeSearchMode', true)
},
clickClose() {
this.$emit('clickClose');
this.searchMode = false;
},
clickListItem(item) {
console.log('click', item);
},
},
};
</script>
<style lang="scss">
</style>
<template>
<div>
<CloseBox
@clickClose="clickClose"
>
<template v-slot:header>
드론 기체 검색
</template>
<template v-slot:body>
<a-alert
v-if="getLogFilter.checkFilter"
message="필터가 적용된 상태입니다."
type="info" show-icon
class="filter-alert"
/>
<div class="r-flex gap-default"
:style="{marginTop: getLogFilter.checkFilter ? '10px' : '0px'}"
>
<a-select
v-model="searchType"
class="search-input"
style="min-width: 80px"
>
<a-select-option value="modelName">
모델명
</a-select-option>
<a-select-option value="maker">
제조사
</a-select-option>
<a-select-option value="usageName">
용도
</a-select-option>
</a-select>
<a-input-search
placeholder="검색어"
enter-button
@search="onSearch"
/>
</div>
<a-list
v-if="searchData.length"
bordered
:data-source="searchData">
<a-list-item
slot="renderItem"
slot-scope="item"
@click="() => clickListItem(item)"
>
{{ `${item.modelName} / ${item.maker} / ${item.usage || '*'}` }}
</a-list-item>
</a-list>
</template>
</CloseBox>
</div>
</template>
<script>
import CloseBox from '@/components/_Common/CloseBox/closeBox';
import { mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
components: {
CloseBox,
},
props: {},
data() {
return {
searchValue: null,
searchType: 'modelName',
searchData: [],
dataSource: [],
};
},
computed: {
...mapGetters('Etc', {
getMakerDroneList: 'getMakerDroneList',
}),
...mapGetters('Drone', {
getLogFilter: 'drone/getLogFilter',
getWholeDroneLog: 'drone/getWholeDroneLog',
}),
},
created() {
},
methods: {
onSearch(value) {
const params = {};
params[this.searchType] = value;
this.$store.dispatch('Drone/list/fetchListContents', params).then((r) => {
this.searchData = r.drones;
});
},
handleSearch(value) {
},
clickClose() {
this.$emit('clickClose');
},
clickListItem(item) {
let checkValid = false;
this.getWholeDroneLog.forEach((log) => {
if (Number(log.droneId) === Number(item.id)) {
this.$emit('focusChange', log.latitude, log.longitude);
this.$emit('clickDrone', Number(item.id));
checkValid = true;
}
});
if (!checkValid) {
this.$warning({
title: '선택하신 드론은 현재 가동 중이지 않은 드론입니다.',
});
}
},
},
};
</script>
<style lang="scss">
</style>
<template>
<div>
<l-marker
ref="markersRef"
:lat-lng="[latitude, longitude]"
@click="clickDrone"
>
<l-icon
:icon-size="iconSize"
:icon-anchor="[20, 20]"
:icon-url="icon"
></l-icon>
</l-marker>
<l-polyline :lat-lngs="drone.polyline.latlngs" :color="drone.polyline.color"></l-polyline>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
props: {
latitude: {
type: Number,
default: 0,
},
longitude: {
type: Number,
default: 0,
},
icon: {
default: null,
},
},
data() {
return {
drone: {
latitude: 37.2430125,
longitude: 127.0811054,
polyline: {
latlngs: [],
color: 'red',
},
},
};
},
mounted() {
this.$nextTick(() => {
// console.log('hi', this.$refs.markersRef)
// this.markerObjects = this.$refs.markersRef.map(ref => ref.mapObject);
// console.log(this.markerObjects)
});
},
computed: {
...mapGetters('Drone/Map', {
getZoomLevel: 'getZoomLevel',
}),
iconSize() {
if (this.getZoomLevel == null) return [40, 40];
return [this.getZoomLevel * 2.2, this.getZoomLevel * 2.2];
},
},
created() {
// this.makeDronePath();
},
methods: {
clickDrone() {
this.$emit('clickDrone');
},
makeDronePath() {
// setTimeout(() => {
// this.drone.latitude += 0.00005;
// // this.drone.longitude += 0.00005;
// this.drone.polyline.latlngs.push([this.drone.latitude, this.drone.longitude]);
// this.makeDronePath();
// }, 500);
},
},
};
</script>
<template>
<a-page-header
class="page-header"
title="RT-Map"
sub-title="Realtime Drone Simulation Map"
>
<template slot="extra">
<a-tooltip placement="bottom">
<template slot="title">
<div>맵이 중앙에 위치하도록 </div>
<div>스크롤을 이동합니다.</div>
</template>
<a-button type="primary" icon="select"
@click="moveScroll"
>
Move Scroll
</a-button>
</a-tooltip>
</template>
</a-page-header>
</template>
<script>
export default {
name: 'MainHeader',
components: {},
data() {
return {
};
},
methods: {
moveScroll() {
window.scrollTo(0, 105);
},
},
};
</script>
<style scoped lang="scss">
</style>
# COMPONENTS
**This directory is not required, you can delete it if you don't want to use it.**
The components directory contains your Vue.js Components.
_Nuxt.js doesn't supercharge these components._
<template>
<div v-if="chartData != null">
<highcharts :options="options"
:callback="onChartLoaded"
:class="chartClass"
:style="borderStyle"
/>
</div>
</template>
<script>
import { Chart } from 'highcharts-vue';
export default {
name: 'barChart',
components: {
highcharts: Chart,
},
props: {
chartData: {
type: Array,
default: null,
},
chartSettings: {
type: Object,
default: null,
},
chartClass: {
type: String,
default: null,
},
bordered: {
type: Boolean,
default: null,
},
},
data() {
return {
chart: null,
options: null,
borderStyle: {},
};
},
watch: {
chartData: {
deep: true,
handler(val) {
if (val == null) return;
this.options.series = val;
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
chartSettings: {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
},
created() {
this.options.series = this.chartData;
this.setCustomChartSetting(this.chartSettings);
if (this.bordered) {
this.borderStyle = {
border: '2px solid #dde2ec',
};
}
},
methods: {
onChartLoaded(chart) {
if (this.chart == null) {
this.chart = chart;
}
this.chart.zoomOut(false);
this.chart.redraw();
this.chart.setSize(null);
},
/**
* Label Formatter로 Html로 레이블 설정 가능
* 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions
* @param settings
*/
setCustomChartSetting(settings) {
this.options.chart = {
type: 'bar',
};
if (settings == null) return;
this.options = { ...settings, series: this.chartData };
this.options.chart.type = 'bar';
/*
* dataLabel 설정
*/
// if (settings.dataLabelSuffix) {
// this.options.plotOptions.column.dataLabels.formatter = function () {
// return `${this.key}: ${this.y.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')}${settings.dataLabelSuffix}`;
// };
// }
},
},
};
</script>
<style scoped>
</style>
<template>
<div v-if="chartData != null">
<highcharts :options="options"
:callback="onChartLoaded"
:class="chartClass"
:style="borderStyle"
/>
</div>
</template>
<script>
import { Chart } from 'highcharts-vue';
export default {
name: 'columnChart',
components: {
highcharts: Chart,
},
props: {
chartData: {
type: Array,
default: null,
},
chartSettings: {
type: Object,
default: null,
},
chartClass: {
type: String,
default: null,
},
bordered: {
type: Boolean,
default: null,
},
},
data() {
return {
chart: null,
options: {},
borderStyle: {},
};
},
watch: {
chartData: {
deep: true,
handler(val) {
if (val == null) return;
this.options.series = val;
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
chartSettings: {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
},
created() {
this.setCustomChartSetting(this.chartSettings);
if (this.bordered) {
this.borderStyle = {
border: '2px solid #dde2ec',
};
}
},
methods: {
onChartLoaded(chart) {
if (this.chart == null) { this.chart = chart; }
this.chart.zoomOut(false);
this.chart.redraw();
this.chart.setSize(null);
},
/**
* Label Formatter로 Html로 레이블 설정 가능
* 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions
* @param settings
*/
setCustomChartSetting(settings) {
this.options.chart = {
type: 'column',
};
if (settings == null) return;
this.options = { ...settings, series: this.chartData };
this.options.chart.type = 'column';
},
},
};
</script>
<style scoped>
</style>
<template>
<div v-if="chartData != null">
<highcharts :options="options"
:callback="onChartLoaded"
:class="chartClass"
:style="borderStyle"
/>
</div>
</template>
<script>
import { Chart } from 'highcharts-vue';
export default {
name: 'lineChart',
components: {
highcharts: Chart,
},
props: {
chartData: {
type: Array,
default: null,
},
chartSettings: {
type: Object,
default: null,
},
chartClass: {
type: String,
default: null,
},
bordered: {
type: Boolean,
default: null,
},
},
data() {
return {
chart: null,
options: {},
borderStyle: {},
};
},
watch: {
chartData: {
deep: true,
handler(val) {
if (val == null) return;
this.options.series = val;
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
chartSettings: {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
},
created() {
this.options.series = this.chartData;
this.setCustomChartSetting(this.chartSettings);
if (this.bordered) {
this.borderStyle = {
border: '2px solid #dde2ec',
};
}
},
methods: {
onChartLoaded(chart) {
if (this.chart == null) { this.chart = chart; }
this.chart.zoomOut(false);
this.chart.redraw();
this.chart.setSize(null);
},
/**
* Label Formatter로 Html로 레이블 설정 가능
* 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions
* @param settings
*/
setCustomChartSetting(settings) {
this.options.chart = {
type: 'line',
};
if (settings == null) return;
this.options = { ...settings, series: this.chartData };
this.options.chart.type = 'line';
},
},
};
</script>
<style scoped>
</style>
<template>
<div v-if="chartData != null">
<highcharts :options="options"
:callback="onChartLoaded"
:class="chartClass"
:style="borderStyle"
/>
</div>
</template>
<script>
import { Chart } from 'highcharts-vue';
export default {
name: 'liveLineChart',
components: {
highcharts: Chart,
},
props: {
chartData: {
type: Array,
default: null,
},
chartSettings: {
type: Object,
default: null,
},
chartClass: {
type: String,
default: null,
},
bordered: {
type: Boolean,
default: null,
},
},
data() {
return {
chart: null,
options: {},
borderStyle: {},
};
},
watch: {
chartSettings: {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
},
created() {
this.options.series = this.chartData;
this.setCustomChartSetting(this.chartSettings);
if (this.bordered) {
this.borderStyle = {
border: '2px solid #dde2ec',
};
}
},
methods: {
onChartLoaded(chart) {
if (this.chart == null) { this.chart = chart; }
this.chart.zoomOut(false);
this.chart.redraw();
this.chart.setSize(null);
},
/**
* Label Formatter로 Html로 레이블 설정 가능
* 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions
* @param settings
*/
setCustomChartSetting(settings) {
this.options.chart = {
type: 'line',
};
if (settings == null) return;
this.options = { ...settings, series: this.chartData };
this.options.chart.type = 'line';
},
},
};
</script>
<style scoped>
</style>
<template>
<div v-if="chartData != null">
<highcharts :options="options"
:callback="onChartLoaded"
:class="chartClass"
:style="borderStyle"
/>
</div>
</template>
<script>
import { Chart } from 'highcharts-vue';
export default {
name: 'pieChart',
components: {
highcharts: Chart,
},
props: {
chartData: {
type: Array,
default: null,
},
chartSettings: {
type: Object,
default: null,
},
chartClass: {
type: String,
default: null,
},
bordered: {
type: Boolean,
default: false,
},
donut: {
type: Boolean,
default: false,
},
},
data() {
return {
chart: null,
options: {},
isDataLabelAdded: false,
borderStyle: {},
};
},
watch: {
chartData: {
deep: true,
handler(val) {
if (val == null) return;
this.options.series = val;
if (this.donut && val.length > 0) {
this.options.series[0].innerSize = '50%';
}
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
chartSettings: {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
'chartSettings.subtitle': {
deep: true,
handler(val) {
if (val == null) return;
this.setCustomChartSetting(val);
if (this.chart == null) return;
this.chart.redraw();
this.chart.setSize(null);
},
},
},
created() {
this.setCustomChartSetting(this.chartSettings);
if (this.bordered) {
this.borderStyle = {
border: '2px solid #dde2ec',
};
}
},
methods: {
onChartLoaded(chart) {
if (this.chart == null) {
this.chart = chart;
}
this.chart.redraw();
this.chart.setSize(null);
},
/**
* Label Formatter로 Html로 레이블 설정 가능
* 자세한 설정은 https://api.highcharts.com/highcharts/plotOptions
* @param settings
*/
setCustomChartSetting(settings) {
this.options.chart = {
type: 'pie',
};
if (settings == null) return;
this.options = { ...settings, series: this.chartData };
this.options.chart.type = 'pie';
},
},
};
</script>
<style scoped>
</style>
<template>
<div class="ant-modal-content">
<button
type="button"
aria-label="Close"
class="ant-modal-close"
@click="clickClose"
>
<span class="ant-modal-close-x">
<a-icon style="color: white" type="close" />
</span>
</button>
<div class="ant-modal-header">
<div class="ant-modal-title">
<slot name="header"></slot>
</div>
</div>
<div class="ant-modal-body">
<slot name="body"></slot>
</div>
</div>
</template>
<script>
export default {
head() {
return {
title: 'Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
props: {
},
data() {
return {
};
},
computed: {
},
created() {
},
methods: {
clickClose() {
this.$emit('clickClose')
},
},
};
</script>
<style lang="scss">
</style>
# _COMMON
- 두개 이상의 페이지에서 사용되는 공통 컴포넌트를 저장하는 폴더입니다.
- 하나의 페이지에서만 사용하는 컴포넌트는 src/component 하위에 폴더로 작성해주시기 바랍니다.
<template>
<svg class="NuxtLogo" width="245" height="180" viewBox="0 0 452 342" xmlns="http://www.w3.org/2000/svg">
<path
d="M139 330l-1-2c-2-4-2-8-1-13H29L189 31l67 121 22-16-67-121c-1-2-9-14-22-14-6 0-15 2-22 15L5 303c-1 3-8 16-2 27 4 6 10 12 24 12h136c-14 0-21-6-24-12z"
fill="#00C58E"
/>
<path
d="M447 304L317 70c-2-2-9-15-22-15-6 0-15 3-22 15l-17 28v54l39-67 129 230h-49a23 23 0 0 1-2 14l-1 1c-6 11-21 12-23 12h76c3 0 17-1 24-12 3-5 5-14-2-26z"
fill="#108775"
/>
<path
d="M376 330v-1l1-2c1-4 2-8 1-12l-4-12-102-178-15-27h-1l-15 27-102 178-4 12a24 24 0 0 0 2 15c4 6 10 12 24 12h190c3 0 18-1 25-12zM256 152l93 163H163l93-163z"
fill="#2F495E"
/>
</svg>
</template>
<script>
export default {
name: 'Logo',
};
</script>
<style>
.NuxtLogo {
animation: 1s appear;
margin: auto;
}
@keyframes appear {
0% {
opacity: 0;
}
}
</style>
# LAYOUTS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Application Layouts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).
<template>
<div>
<span>error</span>
<Nuxt />
</div>
</template>
<template>
<a-config-provider>
<a-layout :style="{height: '100%'}">
<a-layout-header class="header-style">
<div class="r-flex gap-2 space-between">
<div class="r-flex start">
<div style="margin-right: 20px">
<a-icon class="logo-title" type="compass" />
<span class="logo-title">Drone Web</span>
</div>
<a-menu
theme="dark"
mode="horizontal"
v-model="tabKey"
:style="{lineHeight: '40px'}"
>
<a-menu-item :key="1" class="router-menu-item">
<nuxt-link to="/">
<a-icon type="environment" />
RT-Map
</nuxt-link>
</a-menu-item>
<a-menu-item :key="2" class="router-menu-item router-menu-margin">
<a-dropdown>
<a
class="ant-dropdown-link"
@click="(e) => e.preventDefault()"
>
<a-icon type="database" />
DataBase
</a>
<a-menu
v-model="dbMenu"
slot="overlay"
@click="hoverClick"
:style="{textAlign: 'center'}"
class="router-database"
>
<a-menu-item :key="1">
Drone
</a-menu-item>
<a-menu-item :key="2">
Schedule
</a-menu-item>
<a-menu-item :key="3">
Log
</a-menu-item>
</a-menu>
</a-dropdown>
</a-menu-item>
<a-menu-item :key="3" class="router-menu-item" disabled>
<!-- <nuxt-link to="/analytics">
<a-icon type="dashboard" />
Analytics
</nuxt-link> -->
<div @click="info()">
<a-icon type="dashboard" />
Analytics
</div>
</a-menu-item>
<!-- <a-menu-item :key="4" class="router-menu-item">
<nuxt-link to="/websocket">
websocket
</nuxt-link>
</a-menu-item> -->
</a-menu>
</div>
<div class="nav-etc">
<a-dropdown>
<a-button
type="dash"
icon="setting"
style="background-color: rgba(245, 245, 245, 0.9); border: 2px solid #e4e9f0; color: #001529"
/>
<a-menu
slot="overlay"
@click="clickEtc"
class="router-database"
>
<a-menu-item-group style="text-align: center">
WebSocket
</a-menu-item-group>
<a-menu-divider style="margin-top: 8px" />
<a-menu-item key="connect">
<a-icon type="link"/>Connect
</a-menu-item>
<a-menu-item key="disconnect">
<a-icon type="disconnect"/>Disconnect
</a-menu-item>
</a-menu>
</a-dropdown>
</div>
</div>
</a-layout-header>
<a-layout-content :style="{padding: '0 20px', marginTop: '84px'}">
<div :style="{minHeight: 'calc(100vh - 84px - 89px)', height: '100%'}">
<nuxt keep-alive />
</div>
</a-layout-content>
<a-layout-footer class="footer-style">
<layout-footer />
</a-layout-footer>
</a-layout>
</a-config-provider>
</template>
<script>
import LayoutFooter from '@/components/Layout/footer';
import { mapActions, mapGetters } from 'vuex';
export default {
components: { LayoutFooter },
/* vue-meta -> 각 페이지의 meta */
head() {
return {
titleTemplate: 'Drone Web - %s',
meta: [
{
hid: 'defaultLayout',
name: 'Descriptions',
content: 'Content',
},
],
};
},
data() {
return {
tabKey: [1],
dbMenu: [0],
etcAction: 'connect',
};
},
watch: {
'$route.fullPath': {
handler() {
this.changeMenuRoute();
},
},
},
created() {
this.changeMenuRoute();
this.$store.dispatch('Drone/list/fetchListContents').then((r) => {
const makerList = {};
const { drones } = r;
for (let i = 0; i < drones.length; i += 1) {
if (makerList[drones[i].maker]) makerList[drones[i].maker].push(drones[i]);
else makerList[drones[i].maker] = [drones[i]];
}
this.$store.dispatch('Drone/list/setFixedDroneList', drones);
this.$store.dispatch('Etc/setMakers', makerList);
});
},
computed: {
...mapGetters('Schedule/page', {
getSchdulePageParams: 'getPageParams',
}),
...mapGetters('Log/page', {
getLogPageParams: 'getPageParams',
}),
},
methods: {
changeMenuRoute() {
const pageRoute = this.$route.fullPath.split('/')[1];
switch (pageRoute) {
case '':
this.tabKey = [1];
this.dbMenu = [0];
break;
case 'database':
this.tabKey = [2];
break;
case 'analytics':
this.tabKey = [3];
this.dbMenu = [0];
break;
default:
this.tabKey = [999];
this.dbMenu = [0];
break;
}
},
hoverClick(data) {
const { key } = data;
this.tabKey = [2];
switch (key) {
case 1:
this.dbMenu = [1];
this.$router.push('/database/drone');
break;
case 2:
this.clearSchedulePageParams();
this.fetchSchedulePageData(this.getSchdulePageParams);
this.dbMenu = [2];
this.$router.push('/database/schedule');
break;
case 3:
this.clearLogPageParams();
this.fetchLogPageData(this.getLogPageParams);
this.dbMenu = [3];
this.$router.push('/database/log');
break;
default:
break;
}
},
...mapActions('Schedule/page', {
fetchSchedulePageData: 'fetchPageData',
clearSchedulePageParams: 'clearPageParams',
}),
...mapActions('Log/page', {
fetchLogPageData: 'fetchPageData',
clearLogPageParams: 'clearPageParams',
}),
info() {
const h = this.$createElement;
this.$info({
title: 'Notification',
content: h('div', {}, [h('p', '개발 예정인 기능입니다.')]),
onOk() {},
});
},
clickEtc(e) {
if (e === 'connect') this.connect();
else this.disconnect();
},
},
};
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
.logo-title {
color: white;
font-size: 20px;
margin-left: 20px;
}
.router-menu-item {
border-radius: 8px;
}
.router-menu-margin {
margin: 0 10px;
}
.header-style {
position: fixed;
z-index: 100;
width: 100%;
padding: 0px;
}
.nav-etc {
margin-right: 20px;
line-height: 30px;
}
.footer-style {
background-color: #001529;
text-align: center;
margin: 20px 15px 0px 15px;
border-radius: 6px 6px 0px 0px;
padding: 12px 0px 6px 0px;
}
.ant-dropdown-menu-item-selected,
.ant-dropdown-menu-submenu-title-selected,
.ant-dropdown-menu-item-selected > a,
.ant-dropdown-menu-submenu-title-selected > a {
background: none;
}
</style>
<template>
<div class="error-main">
<a-result
style="margin-top: -50px"
:status="this.error.statusCode.toString()"
:title="this.error.statusCode.toString()"
:sub-title="this.error.message"
>
<template #extra>
<a-button type="primary" @click="$router.push('/')">
Go to Main
</a-button>
</template>
</a-result>
</div>
</template>
<script>
export default {
props: {
error: {
type: Object,
default: () => ({}),
},
},
};
</script>
<style lang="scss" scoped>
.error-main {
background-color: white;
height: calc(100vh - 84px - 69px);
display: flex;
align-items: center;
justify-content: center;
}
</style>
# MIDDLEWARE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
/* eslint-disable func-names */
export default function ({
store, route, redirect,
}) {
// console.log('in middleware', store, route, redirect);
}
export default function (moduleOptions) {
this.options.css.push('vuelayers/lib/style.css');
}
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
<template>
<div>
<analytics-header/>
<div class="chart-div">
<live-log-chart :chart-data="liveDroneData"/>
</div>
<div class="r-flex">
<div class="pie-chart-div" style="margin-right: 20px">
<maker-pie-chart :chart-data="makerData"/>
</div>
<div class="pie-chart-div">
<drone-category-pie-chart :chart-data="categoryData" />
</div>
</div>
<div class="chart-div">
<time-category-column-chart :chart-data="timeDroneData"/>
</div>
<div class="page-main">
<div>
소켓 테스트
</div>
<a-button @click="getMessage">emit</a-button>
<a-button @click="onM">on</a-button>
</div>
</div>
</template>
<script>
import AnalyticsHeader from '@/components/Analytics/header';
import MakerPieChart from '@/components/Analytics/Chart/makerPieChart';
import DroneCategoryPieChart from '@/components/Analytics/Chart/droneCategoryPieChart';
import TimeCategoryColumnChart from '@/components/Analytics/Chart/timeCategoryColumnChart';
import LiveLogChart from '@/components/Analytics/Chart/liveLogChart';
export default {
components: {
LiveLogChart,
TimeCategoryColumnChart,
AnalyticsHeader,
DroneCategoryPieChart,
MakerPieChart,
},
data() {
return {
socket: null,
makerData: [],
categoryData: [],
timeDroneData: [],
liveDroneData: [{
name: '드론 기체 수',
data: [],
}],
};
},
computed: {
ranNum() {
return (min, max) => parseInt((Math.random() * (max - min) + min), 10);
},
},
created() {
this.makeChartData();
},
mounted() {
this.socket = this.$nuxtSocket({
// nuxt-socket-io opts:
name: 'main', // Use socket "home"
channel: '/testSoc',
// socket.io-client opts:
reconnection: false,
});
this.socket
.on('receiveLog', (msg, cb) => {
/* Handle event */
console.log(msg);
this.liveDroneData[0].data.push({
x: this.$dayjs(msg.time).valueOf(), y: msg.num,
});
});
this.socket
.on('connection', (msg, cb) => {
/* Handle event */
console.log(msg, cb);
});
},
methods: {
getMessage() {
this.socket.emit('getMessage', { id: 'abc123' }, (resp) => {
this.messageRxd = resp;
});
},
onM() {
this.$axios.get('http://localhost:8888')
.then((r) => {
console.log('apiCall');
});
},
makeChartData() {
this.makerData = [{
name: '기체수',
colorByPoint: true,
data: [
{
name: 'DJI', y: 6234,
},
{
name: '3DRobotics', y: 1248,
},
{
name: 'SYMA', y: 248,
},
{
name: 'Parrot', y: 783,
},
{
name: 'Cheerson', y: 563,
},
],
}];
this.categoryData = [{
name: '기체수',
colorByPoint: true,
data: [
{
name: 'TriCopter', y: 3452,
},
{
name: 'QuadCopter', y: 4032,
},
{
name: 'HexaCopter', y: 1395,
},
{
name: 'OctaCopter', y: 474,
},
],
}];
this.timeDroneData = [
{
name: 'Toy',
data: [
this.ranNum(1, 100),
this.ranNum(1, 100),
this.ranNum(100, 300),
this.ranNum(100, 300),
this.ranNum(50, 200),
this.ranNum(1, 100),
],
},
{
name: 'Racing',
data: [
this.ranNum(1, 100),
this.ranNum(1, 100),
this.ranNum(100, 300),
this.ranNum(100, 300),
this.ranNum(50, 200),
this.ranNum(1, 100),
],
},
{
name: 'Mission',
data: [
this.ranNum(100, 1000),
this.ranNum(100, 1000),
this.ranNum(1, 200),
this.ranNum(1, 200),
this.ranNum(1, 200),
this.ranNum(100, 1000),
],
},
];
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.pie-chart-div {
margin-top: 20px;
height: 440px;
width: calc(50% - 10px);
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
.chart-div {
margin-top: 20px;
height: 440px;
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
</style>
<template>
<div>
403 Error Page
</div>
</template>
<script>
export default {
layout: 'auth',
data() {
return {
};
},
computed: {
},
watch: {
},
created() {
},
method: {
},
};
</script>
<template>
<div>
404 Error Page
</div>
</template>
<script>
export default {
layout: 'auth',
data() {
return {
};
},
computed: {
},
watch: {
},
created() {
},
method: {
},
};
</script>
<template>
<div>
500 Error Page
</div>
</template>
<script>
export default {
layout: 'auth',
data() {
return {
};
},
computed: {
},
watch: {
},
created() {
},
method: {
},
};
</script>
<template>
<div>
<database-detail-header />
<div class="page-main-without-header ">
<div class="table-title">Schedule</div>
<database-table :childLoading="parentLoading" @loadData="loadData" />
</div>
</div>
</template>
<script>
import DatabaseDetailHeader from '@/components/Database/Drone/Detail/header';
import DatabaseTable from '@/components/Database/Schedule/table';
import {mapActions, mapGetters} from 'vuex';
export default {
components: {
DatabaseDetailHeader,
DatabaseTable,
},
async asyncData({params, store}) {
const {id} = params;
await store.dispatch('Drone/detail/fetchDetailData', id);
return {id};
},
data() {
return {
parentLoading: false,
searchParams: {},
};
},
computed: {
...mapGetters('Drone/detail', {
getDetailData: 'getDetailData',
}),
...mapGetters('Schedule/page', {
getPageParams: 'getPageParams',
}),
},
watch: {},
created() {
this.searchParams = JSON.parse(JSON.stringify(this.getPageParams));
this.searchParams.droneId = this.getDetailData.id;
this.setPageParams(this.searchParams);
this.loadData();
},
beforeDestroy() {
this.clearPageParams();
},
methods: {
...mapActions('Schedule/page', {
fetchPageData: 'fetchPageData',
clearPageParams: 'clearPageParams',
setPageParams: 'setPageParams',
}),
loadData() {
this.parentLoading = true;
this.fetchPageData(this.getPageParams).finally(() => {
this.parentLoading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.table-title {
font-size: 30px;
}
</style>
<template>
<div>
<database-search-filter @loadData="loadData" />
<div class="page-main">
<database-table :childLoading="parentLoading" @loadData="loadData" />
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import DatabaseSearchFilter from '@/components/Database/Drone/searchFilter';
import DatabaseTable from '@/components/Database/Drone/table';
export default {
head() {
return {
title: 'Database-Drone',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'db-Content',
},
],
};
},
components: {
DatabaseTable,
DatabaseSearchFilter,
},
data() {
return {
parentLoading: false,
};
},
computed: {
...mapGetters('Drone/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.loadData();
},
beforeDestroy() {
this.clearPageParams();
},
methods: {
...mapActions('Drone/page', {
fetchPageData: 'fetchPageData',
clearPageParams: 'clearPageParams',
}),
loadData() {
this.parentLoading = true;
this.fetchPageData(this.getPageParams).finally(() => {
this.parentLoading = false;
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<div>
<log-search-filter @loadData="loadData"/>
<div class="page-main">
<log-table :childLoading="parentLoading" @loadData="loadData"/>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import LogSearchFilter from '@/components/Database/Log/SearchFilter';
import LogTable from '@/components/Database/Log/table';
export default {
head() {
return {
title: 'Database-Log',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'db-Content',
},
],
};
},
components: {
LogTable,
LogSearchFilter,
},
data() {
return {
parentLoading: false,
};
},
computed: {
...mapGetters('Log/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.loadData();
},
beforeDestroy() {
this.clearPageParams();
},
methods: {
...mapActions('Log/page', {
fetchPageData: 'fetchPageData',
clearPageParams: 'clearPageParams',
}),
loadData() {
this.parentLoading = true;
this.fetchPageData(this.getPageParams).finally(() => {
this.parentLoading = false;
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<div>
<database-detail-header />
<div class="page-main-without-header ">
<div class="table-title">Log</div>
<database-table :childLoading="parentLoading" @loadData="loadData" />
</div>
</div>
</template>
<script>
import DatabaseDetailHeader from '@/components/Database/Schedule/Detail/header';
import DatabaseTable from '@/components/Database/Log/table';
import {mapActions, mapGetters} from 'vuex';
export default {
components: {
DatabaseDetailHeader,
DatabaseTable,
},
async asyncData({params, store}) {
const {id} = params;
await store.dispatch('Schedule/detail/fetchDetailData', id);
return {id};
},
data() {
return {
parentLoading: false,
searchParams: {},
};
},
computed: {
...mapGetters('Schedule/detail', {
getScheduleDetailData: 'getDetailData',
}),
...mapGetters('Log/page', {
getPageParams: 'getPageParams',
}),
},
watch: {},
created() {
this.searchParams = JSON.parse(JSON.stringify(this.getPageParams));
this.searchParams.scheduleId = this.getScheduleDetailData.id;
this.setPageParams(this.searchParams);
this.loadData();
},
beforeDestroy() {
this.clearPageParams();
},
methods: {
...mapActions('Log/page', {
fetchPageData: 'fetchPageData',
clearPageParams: 'clearPageParams',
setPageParams: 'setPageParams',
}),
loadData() {
this.parentLoading = true;
this.fetchPageData(this.getPageParams).finally(() => {
this.parentLoading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.table-title {
font-size: 30px;
}
</style>
<template>
<div>
<database-search-filter @loadData="loadData" />
<div class="page-main">
<database-table :childLoading="parentLoading" @loadData="loadData" />
</div>
</div>
</template>
<script>
import {mapActions, mapGetters} from 'vuex';
import DatabaseSearchFilter from '@/components/Database/Schedule/searchFilter';
import DatabaseTable from '@/components/Database/Schedule/table';
export default {
head() {
return {
title: 'Database',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'db-Content',
},
],
};
},
components: {
DatabaseTable,
DatabaseSearchFilter,
},
data() {
return {
parentLoading: false,
};
},
computed: {
...mapGetters('Schedule/page', {
getPageParams: 'getPageParams',
}),
},
created() {
this.loadData();
},
beforeDestroy() {
this.clearPageParams();
},
methods: {
...mapActions('Schedule/page', {
fetchPageData: 'fetchPageData',
clearPageParams: 'clearPageParams',
}),
loadData() {
this.parentLoading = true;
this.fetchPageData(this.getPageParams).finally(() => {
this.parentLoading = false;
});
},
},
};
</script>
<style scoped lang="scss"></style>
<template>
<div>
<main-header/>
<div class="page-main">
<div id="map-wrap">
<client-only>
<div class="mapBox">
<l-map
ref="map"
class="map"
:center="[currentLatitude, currentLongitude]"
:options="{zoomControl: false}"
:zoom="zoom"
:max-zoom="18"
:min-zoom="8"
@update:zoom="zoomUpdate"
@update:bounds="boundsUpdate"
@click="clickMap"
>
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer>
<l-control position="bottomright">
<img
@click="clickGpsBtn"
class="gpsBtn"
:src="require('@/static/img/gps.png')"
/>
</l-control>
<l-control-zoom position="bottomright"></l-control-zoom>
<template v-for="(drone) in getDroneLogs">
<drone
:key="drone.id"
:latitude="drone.latitude"
:longitude="drone.longitude"
:icon="icon"
@clickDrone=clickDrone(drone.droneId)
/>
</template>
<l-polyline :lat-lngs="selectedDroneRoute" :color="'green'"></l-polyline>
</l-map>
<DroneDetail
class="drone-detail"
v-if="showDroneDetail"
/>
<SearchBtnBox
class="search-box"
@changeFilterMode="e => filterMode = e"
@changeSearchMode="e => searchMode = e"
/>
<SearchFeatureBox
v-if="searchMode"
@clickClose="searchMode = false"
@focusChange="(lat, lng) => focusChange(lat, lng)"
@clickDrone="(id) => clickDrone(id)"
class="search-box"
/>
<BottomToolBox
class="bottom-tool-box"
@changeFilterMode="e => filterMode = e"
@changeSearchMode="e => searchMode = e"
/>
<FilterFeatureBox
v-if="filterMode"
@clickClose="filterMode = false"
class="filter-feature-box"
/>
</div>
</client-only>
</div>
</div>
</div>
</template>
<script>
/* eslint-disable no-underscore-dangle */
import { mapActions, mapGetters } from 'vuex';
import MainHeader from '@/components/Main/header';
import Drone from '@/components/Main/drone';
import DroneDetail from '@/components/Main/Box/DetailBox/detailBox';
import SearchBtnBox from '@/components/Main/Box/SearchBox/searchBtnBox';
import SearchFeatureBox from '@/components/Main/Box/SearchBox/searchFeatureBox';
import BottomToolBox from '@/components/Main/Box/BottomToolBox/bottomToolBox';
import FilterFeatureBox from '@/components/Main/Box/FilterBox/filterFeatureBox';
import droneImg from '@/static/img/drone.svg';
export default {
components: {
MainHeader, Drone, DroneDetail, SearchBtnBox, SearchFeatureBox, BottomToolBox, FilterFeatureBox,
},
head() {
return {
title: 'DroneWeb',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
data() {
return {
center: [0, 0],
rotation: 0,
geolocPosition: undefined,
map: null,
selectedDroneRoute: [],
selectedDroneData: {},
selectedDroneId: null,
currentLatitude: 37.2430125,
currentLongitude: 127.0811054,
showDroneDetail: false,
filterMode: false,
searchMode: false,
icon: droneImg,
zoom: 15,
socketCheckInterval: null,
};
},
computed: {
...mapGetters('Drone/drone', {
getDetailData: 'getDetailData',
getDroneLogs: 'getDroneLogs',
getSelectedLogList: 'getSelectedLogList',
getSelectedLastLog: 'getSelectedLastLog',
getSelectedDroneId: 'getSelectedDroneId',
}),
},
watch: {
getDroneLogs: {
deep: true,
handler(newVal) {
// console.log('드론 소켓 로그 getDroneLogs', newVal);
},
},
getSelectedLastLog: {
deep: true,
handler(newVal) {
this.selectedDroneRoute.push([newVal.latitude, newVal.longitude]);
// console.log('page 속도 등 계산한 정보 selectedDroneLog', newVal);
},
},
},
created() {
if (process.client) {
// Websocket 연결
this.connect();
this.socketCheckInterval = setInterval(() => {
console.log('socketState', window.clientSocket.readyState);
this.connect();
}, 30000);
setTimeout(() => {
this.zoom = 16;
}, 2000);
navigator.geolocation.getCurrentPosition((position) => {
this.currentLatitude = position.coords.latitude;
this.currentLongitude = position.coords.longitude;
}, (e) => {
console.log('err', e);
},
{ enableHighAccuracy: true, maximumAge: 0, timeout: 2000 });
}
},
methods: {
...mapActions('Drone/drone', {
fetchDetailInfo: 'fetchDetailInfo',
setSelectedDroneId: 'setSelectedDroneId',
}),
...mapActions('Drone/Map', {
setBoundary: 'setBoundary',
setZoomLevel: 'setZoomLevel',
}),
zoomUpdate(zoom) {
this.setZoomLevel(zoom);
},
boundsUpdate(bounds) {
this.setBoundary(bounds);
},
focusChange(lat, lng) {
this.map = this.$refs.map.mapObject.panTo([lat, lng]);
},
clickGpsBtn() {
if (process.client) {
navigator.geolocation.getCurrentPosition((position) => {
this.currentLatitude = position.coords.latitude;
this.currentLongitude = position.coords.longitude;
this.focusChange(this.currentLatitude, this.currentLongitude);
}, (e) => {
console.log('err', e);
}, { enableHighAccuracy: true, maximumAge: 0, timeout: 2000 });
}
},
clickDrone(droneId) {
if (this.getSelectedDroneId === droneId) return;
this.selectedDroneRoute = [];
this.setSelectedDroneId(droneId);
this.accumulatedDistance = 0; // 미터
this.fetchDetailInfo(this.getSelectedDroneId)
.then((r) => {
console.log('선택한 드론', r);
r.droneLogs.forEach((droneLog) => {
this.selectedDroneRoute.push([droneLog.latitude, droneLog.longitude]);
});
this.selectedDroneData = this.getDetailData;
this.showDroneDetail = true;
});
},
clickMap() {
this.showDroneDetail = false;
this.selectedDroneRoute = [];
this.setSelectedDroneId(null);
},
clickSearchBtn() {
console.log('click btn');
},
clickFilterBtn() {
console.log('click btn');
},
},
};
</script>
<style lang="scss" scoped>
.mapBox {
height: calc(100vh - 100px);
position: relative;
}
.map {
z-index: 0;
}
.drone-detail {
position: absolute;
z-index: 10;
top: 10px;
left: 10px;
}
.tool-box {
position: absolute;
z-index: 10;
top: 10px;
right: 10px;
}
.gpsBtn {
width: 35px;
height: 35px;
padding: 5px;
border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
background: white;
cursor: pointer;
}
.gpsBtn:hover {
background-color: #f4f4f4;
}
</style>
<template>
<div>
<client-only>
<vl-map :load-tiles-while-animating="true" :load-tiles-while-interacting="true"
data-projection="EPSG:4326" style="height: 400px">
<vl-view :min-zoom="2" :zoom.sync="zoom" :center.sync="center" :rotation.sync="rotation"></vl-view>
<vl-geoloc @update:position="geolocPosition = $event">
<template slot-scope="geoloc">
<!-- <vl-feature v-if="geoloc.position" id="position-feature">
{{ geoloc.position }}
<vl-geom-point :coordinates="geoloc.position"></vl-geom-point>
<vl-style-box>
<vl-style-icon src="@/assets/images/drone.png" :scale="0.1" :anchor="[0.5, 1]"></vl-style-icon>
</vl-style-box>
</vl-feature> -->
<vl-feature>
<vl-geom-point :coordinates="[126.986606,37.310334]"></vl-geom-point>
<vl-style-box>
<vl-style-icon src="@/assets/images/drone.png" :scale="0.2" :anchor="[0.5, 1]"></vl-style-icon>
</vl-style-box>
</vl-feature>
<vl-feature v-for="(drone) in droneLogs" :key="drone.id">
<vl-geom-point :coordinates="[drone.longitude,drone.latitude]"></vl-geom-point>
<vl-style-box>
<vl-style-icon src="@/assets/images/drone.png" :scale="0.1" :anchor="[0.5, 1]"></vl-style-icon>
</vl-style-box>
</vl-feature>
</template>
</vl-geoloc>
<vl-layer-tile id="osm">
<vl-source-osm></vl-source-osm>
</vl-layer-tile>
</vl-map>
<div style="padding: 20px">
Zoom: {{ zoom }}<br>
Center: {{ center }}<br>
Rotation: {{ rotation }}<br>
My geolocation: {{ geolocPosition }}
</div>
<a-button @click="connect">connect</a-button>
<!-- <a-button @click="connectMultiClient">connectMultiClient</a-button>-->
<a-button @click="disconnect">disconnect</a-button>
</client-only>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import MainHeader from '@/components/Main/header';
import Drone from '@/components/Main/drone';
import DroneDetail from '@/components/Main/Box/DetailBox/detailBox';
export default {
components: {
MainHeader, Drone, DroneDetail, ToolBox,
},
head() {
return {
title: 'DroneWeb',
meta: [
{
hid: 'database',
name: 'Descriptions',
content: 'DroneWeb-Content',
},
],
};
},
data() {
return {
zoom: 2,
center: [0, 0],
rotation: 0,
geolocPosition: undefined,
map: null,
drones: [],
testDrone: {
latitude: 37.2430125,
longitude: 127.0811054,
polyline: {
latlngs: [],
color: 'green',
},
},
currentLatitude: 37.2430125,
currentLongitude: 127.0811054,
showDroneDetail: false,
};
},
computed: {
...mapGetters('Drone/drone', {
getDetailData: 'getDetailData',
}),
},
watch: {
droneLogs: {
deep: true,
handler(newVal) {
console.log('드론 소켓 로그 droneLogs', newVal);
},
},
},
created() {
/**
* getDetailData와 fetchDetailInfo로 상세 정보 조회
* 속도, 이동거리, 방향은 모두 계산해서 출력함.
*/
if (process.browser) {
navigator.geolocation.getCurrentPosition((position) => {
this.currentLatitude = position.coords.latitude;
this.currentLongitude = position.coords.longitude;
}, () => {
}, { enableHighAccuracy: true, maximumAge: 0 });
}
},
methods: {
...mapActions('Drone/drone', {
fetchDetailInfo: 'fetchDetailInfo',
clearAccumulatedDistance: 'clearAccumulatedDistance',
setSelectedDroneId: 'setSelectedDroneId',
}),
clickGpsBtn() {
if (process.browser) {
navigator.geolocation.getCurrentPosition((position) => {
this.currentLatitude = position.coords.latitude;
this.currentLongitude = position.coords.longitude;
this.map = this.$refs.map.mapObject.panTo([this.currentLatitude, this.currentLongitude]);
}, () => {
}, { enableHighAccuracy: true, maximumAge: 0 });
}
},
clickDrone() {
/**
* id가 1일때 가정하고 만듦. 추후에 수정 필.
* distance 거리 단위는 미터
*/
this.setSelectedDroneId(1);
this.clearAccumulatedDistance();
this.fetchDetailInfo(1)
.then((r) => {
this.showDroneDetail = true;
console.log('선택한 드론', r);
});
console.log('drone click');
},
clickMap() {
console.log('click Map');
this.showDroneDetail = false;
},
clickSearchBtn() {
console.log('click btn');
},
clickFilterBtn() {
console.log('click btn');
},
},
};
</script>
<style lang="scss" scoped>
.map {
z-index: 0;
}
.gpsBtn {
width: 35px;
height: 35px;
padding: 5px;
border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
background: white;
cursor: pointer;
}
.gpsBtn:hover {
background-color: #f4f4f4;
}
</style>
<template>
<div>
<analytics-header/>
<div class="chart-div">
<live-log-chart :chart-data="liveDroneData"/>
</div>
<div class="page-main">
<div>
웹 소켓 테스트
</div>
<a-input v-model="socketClientCnt"/>
<a-button @click="multConnect">multConnect</a-button>
<a-button @click="connect">connect</a-button>
<a-button @click="sendMessage">sendMessage</a-button>
<a-button @click="disconnectMult">disconnectMult</a-button>
<a-button @click="disconnectSol">disconnectSol</a-button>
<div>
<a-button @click="multConnectDynamic">multConnectDynamic</a-button>
</div>
</div>
</div>
</template>
<script>
import AnalyticsHeader from '@/components/Analytics/header';
import LiveLogChart from '@/components/Analytics/Chart/liveLogChart';
import { mapActions, mapGetters } from 'vuex';
import calcGeoLocation from '@/utils/Mixins/Component/calcGeolocation';
export default {
components: {
LiveLogChart,
AnalyticsHeader,
},
mixins: [calcGeoLocation],
data() {
return {
socketClientCnt: 10,
liveDroneData: [
{
name: '드론 기체 수',
data: [],
},
{
name: '활성화된 드론 수',
data: [],
},
],
angle: {},
velocity: {},
};
},
computed: {
...mapGetters('GeoLocation', {
getCoord: 'getCoord',
getAngle: 'getAngle',
getVelocity: 'getVelocity',
}),
ranNum() {
return (min, max) => parseInt((Math.random() * (max - min) + min), 10);
},
},
created() {
},
mounted() {
},
methods: {
...mapActions('GeoLocation', {
saveCoord: 'saveCoord',
}),
connect() {
// window.webSocketSol = new WebSocket('ws://localhost:20202');
window.webSocketSol = new WebSocket('ws://14.33.35.148:20202');
window.webSocketSol.onopen = () => {
console.log('socket on open');
};
window.webSocketSol.onerror = () => {
console.log('socket on open');
};
window.webSocketSol.onmessage = (data) => {
// console.log(data);
const logData = JSON.parse(data.data);
// this.saveCoord(logData);
console.log(`message ${this.$dayjs().format('HH:mm:ss')} : `, logData);
};
window.webSocketSol.onclose = () => {
console.log('socket on open');
};
},
multConnect() {
for (let i = 0; i < 10; i += 1) {
if (window[`wsSocket${i}`] == null || window[`wsSocket${i}`].readyState === 3) {
// window[`wsSocket${i}`] = new WebSocket(`ws://localhost:20203/${i + 1}`);
window[`wsSocket${i}`] = new WebSocket(`ws://14.33.35.148:20203/${i + 1}`);
window[`wsSocket${i}`].onopen = () => {
console.log(`socket${i} on open`);
};
window[`wsSocket${i}`].onerror = () => {
console.log(`socket${i} on error`);
};
window[`wsSocket${i}`].onmessage = (data) => {
// console.log(data);
const logData = JSON.parse(data.data);
// this.saveCoord(logData);
console.log(`message${i} ${this.$dayjs().format('HH:mm:ss')} : `, logData);
};
window[`wsSocket${i}`].onclose = () => {
console.log(`socket${i} on close`);
};
}
}
},
sendMessage() {
window.wsSocket.send('client sent message');
},
multConnectDynamic() {
for (let i = 0; i < this.socketClientCnt; i += 1) {
if (window[`wsSocket${i}`] == null || window[`wsSocket${i}`].readyState === 3) {
// window[`wsSocket${i}`] = new WebSocket(`ws://localhost:20203/${i + 1}`);
window[`wsSocket${i}`] = new WebSocket(`ws://14.33.35.148:20204/${i + 1}`);
window[`wsSocket${i}`].onopen = () => {
console.log(`socket${i} on open`);
};
window[`wsSocket${i}`].onerror = () => {
console.log(`socket${i} on error`);
};
window[`wsSocket${i}`].onmessage = (data) => {
// console.log(data);
const logData = JSON.parse(data.data);
// this.saveCoord(logData);
console.log(`message${i} ${this.$dayjs().format('HH:mm:ss')} : `, logData);
};
window[`wsSocket${i}`].onclose = () => {
console.log(`socket${i} on close`);
};
}
}
},
disconnectMult() {
for (let i = 0; i < this.socketClientCnt; i += 1) {
if (window[`wsSocket${i}`].readyState === 1) {
window[`wsSocket${i}`].close();
window[`wsSocket${i}`] = null;
}
}
},
disconnectMult2() {
for (let i = 0; i < this.socketClientCnt; i += 1) {
if (window[`wsSocket${i}`].readyState === 1) {
window[`wsSocket${i}`].close();
window[`wsSocket${i}`] = null;
}
}
},
disconnectSol() {
if (window.webSocketSol.readyState === 1) {
window.webSocketSol.close();
window.webSocketSol = null;
}
},
},
};
</script>
<style scoped lang="scss">
@import '@/assets/styles/mixins.scss';
.pie-chart-div {
margin-top: 20px;
height: 440px;
width: calc(50% - 10px);
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
.chart-div {
margin-top: 20px;
height: 440px;
padding: 20px 20px 20px 20px;
background-color: white;
border-radius: 6px;
border: $gray-2 1px solid;
}
</style>
/* eslint-disable func-names,no-param-reassign */
import store from 'store2';
import { Modal } from 'ant-design-vue';
export default function ({ $axios, redirect }) {
$axios.onRequest((config) => {
/* header AppVersion 체크 */
if (!store.get('appVersion')) {
store.set('appVersion', config.headers.common.AppVersion);
} else if (config.headers.common.AppVersion !== store.get('appVersion')) {
store.set('appVersion', config.headers.common.AppVersion);
Modal.info({
title: '새로운 콘텐츠가 감지되었습니다.',
content: '확인을 눌러 새로고침 해주세요.',
onOk: () => {
window.location.reload(true);
},
});
}
return config;
});
$axios.onResponse((response) => response.data);
$axios.onError((error) => {
if (error.response.status === 404) {
redirect('/auth/404');
} else {
Modal.error({
title: '서버와의 통신에 에러가 발생했습니다.',
onOk: () => {
redirect('/auth/500');
},
});
}
});
}
import Vue from 'vue';
import dayjs from './partials/implement';
const options = {
// locale: process.env.VUE_APP_I18N_LOCALE || 'ko',
locale: 'ko',
localeObject: {
name: 'ko',
weekdays: ['월', '화', '수', '목', '금', '토', '일'],
months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
formats: {
L: 'YYYY-MM-DD',
LL: 'YYYY-MM-DD(ddd)',
LLL: 'YYYY-MM-DD HH:mm',
LLLL: 'YYYY-MM-DD HH:mm:sss',
},
},
};
Vue.use(dayjs, options);
Vue.filter('dayjs', (value, format) => {
if (value == null || format == null || value === 'Invalid Date') {
return '';
}
return dayjs.day(value).format(format);
});
/* eslint-disable no-param-reassign */
import dayjs from 'dayjs';
import 'dayjs/locale/ko';
export default {
install(Vue, options) {
dayjs.locale(options.locale, options.localeObject);
Vue.prototype.$dayjs = dayjs;
},
};
export const day = dayjs;
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).
import Vue from 'vue';
import {
Breadcrumb, Icon, PageHeader, Alert, Result,
Button, Layout, Table, Radio, Dropdown, Menu,
Input, Calendar, Form, Tooltip, Select, Switch,
Spin, Checkbox, Tabs, Pagination, notification,
DatePicker, TimePicker, Divider, Card, Avatar,
Row, Col, Modal, ConfigProvider, Descriptions, Badge,
List, Progress, Slider, AutoComplete,
} from 'ant-design-vue';
Vue.use(notification);
Vue.use(Badge);
Vue.use(AutoComplete);
Vue.use(List);
Vue.use(PageHeader);
Vue.use(Result);
Vue.use(Alert);
Vue.use(Modal);
Vue.use(Icon);
Vue.use(Divider);
Vue.use(Row);
Vue.use(Col);
Vue.use(Card);
Vue.use(Button);
Vue.use(Breadcrumb);
Vue.use(Layout);
Vue.use(Table);
Vue.use(Radio);
Vue.use(Dropdown);
Vue.use(Menu);
Vue.use(Input);
Vue.use(Calendar);
Vue.use(Form);
Vue.use(Tooltip);
Vue.use(Select);
Vue.use(Spin);
Vue.use(Checkbox);
Vue.use(Tabs);
Vue.use(Pagination);
Vue.use(Switch);
Vue.use(DatePicker);
Vue.use(TimePicker);
Vue.use(ConfigProvider);
Vue.use(Descriptions);
Vue.use(Avatar);
Vue.use(Progress);
Vue.use(Slider);
Vue.prototype.$notification = notification;
Vue.prototype.$confirm = Modal.confirm;
Vue.prototype.$info = Modal.info;
Vue.prototype.$success = Modal.success;
Vue.prototype.$error = Modal.error;
Vue.prototype.$warning = Modal.warning;
import Vue from 'vue';
import mixins from '@/utils/Mixins/index';
Vue.mixin(mixins);
import Vue from 'vue';
import Highcharts from 'highcharts';
import HighchartsVue from 'highcharts-vue';
import HighchartsBoost from 'highcharts/modules/boost';
import HighchartsBoostCanvas from 'highcharts/modules/boost-canvas';
// import HighchartsExporting from 'highcharts/modules/exporting';
// import HighchartsOfflineExporting from 'highcharts/modules/offline-exporting';
// import HighchartsExportData from 'highcharts/modules/export-data';
import HighchartsMore from 'highcharts/highcharts-more';
HighchartsBoost(Highcharts);
HighchartsBoostCanvas(Highcharts);
// HighchartsExporting(Highcharts);
// HighchartsOfflineExporting(Highcharts);
// HighchartsExportData(Highcharts);
HighchartsMore(Highcharts);
const options = {
time: {
timezoneOffset: -540,
},
colors: [
'#C08B9C', '#F5D1B7', '#E99C65', '#7A9A82',
'#28B3BE', '#A6A6A6', '#F4C96B', '#C04176',
'#8885A4', '#454C88', '#94A4BE', '#E9DF84',
'#7EAFB2',
],
credits: {
enabled: false,
style: {
color: '#FFFFFF',
},
},
chart: {
style: {
fontFamily: 'Noto Sans Kr, sans-serif',
fontWeight: 500,
},
animation: true,
},
title: {
style: {
color: '#2a274d',
},
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0,
},
},
xAxis: {
labels: {
style: {
color: '#4f4f7a',
fontSize: '14px',
},
shared: true,
useHTML: true,
},
},
yAxis: {
labels: {
style: {
color: '#4f4f7a',
fontSize: '14px',
},
shared: true,
useHTML: true,
},
},
labels: {
style: {
color: '#4f4f7a',
},
shared: true,
useHTML: true,
},
tooltip: {
headerFormat: '<span style="font-size:16px;">{point.key}</span><table>',
pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}</td>'
+ '<td style="padding:0"><b>: {point.y}</b></td></tr>',
footerFormat: '</table>',
shared: true,
useHTML: true,
style: {
fontSize: '14px',
},
borderWidth: 2,
borderRadius: 6,
},
legend: {
useHTML: true,
enabled: true,
itemStyle: {
color: '#4f4f7a',
fontSize: '14px',
},
itemHoverStyle: {
color: '#100f28',
fontSize: '14px',
},
itemHiddenStyle: {
color: '#dde2ec',
fontSize: '14px',
},
},
loading: {
labelStyle: {
color: '#013477',
},
style: {
backgroundColor: 'gray',
},
},
exporting: {
sourceWidth: 1000,
sourceHeight: 500,
buttons: {
contextButton: {
theme: {
fill: 'transparent',
},
x: -2,
y: 0,
menuItems: [
'viewFullscreen',
'printChart',
'separator',
'downloadPDF',
'downloadPNG',
'downloadXLS',
],
},
},
fallbackToExportServer: false,
},
mapNavigation: {
enableMouseWheelZoom: true,
},
};
Highcharts.setOptions({
lang: {
thousandsSep: ',',
},
...options,
});
Vue.use(HighchartsVue, {
highcharts: Highcharts,
});
import Vue from 'vue'
import VueLayers from 'vuelayers'
import 'vuelayers/lib/style.css' // needs css-loader
Vue.use(VueLayers)
\ No newline at end of file
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).
No preview for this file type
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="340.000000pt" height="340.000000pt" viewBox="0 0 340.000000 340.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,340.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M610 3386 c-162 -36 -269 -95 -386 -211 -77 -76 -98 -105 -137 -185
-58 -118 -77 -189 -84 -310 -4 -76 -1 -113 16 -185 57 -237 200 -414 419 -517
116 -55 201 -71 342 -65 119 5 182 20 284 65 l59 26 39 -44 c53 -62 68 -119
68 -260 0 -141 -15 -198 -68 -260 l-39 -44 -61 27 c-116 53 -212 70 -347 64
-133 -6 -192 -20 -303 -75 -297 -147 -470 -505 -393 -812 52 -206 144 -347
298 -456 120 -86 293 -144 431 -144 74 0 216 34 304 73 192 85 343 261 409
477 45 145 25 377 -45 522 l-26 53 33 28 c17 16 57 40 87 55 51 25 64 27 190
27 127 0 138 -2 190 -28 30 -15 69 -39 87 -55 l32 -27 -29 -66 c-50 -111 -64
-182 -64 -309 0 -140 16 -210 80 -338 77 -154 230 -295 381 -352 172 -64 294
-74 438 -36 115 31 172 56 256 112 159 106 267 274 314 489 54 249 -62 542
-280 710 -216 166 -494 201 -749 95 l-77 -32 -23 21 c-77 72 -111 247 -81 411
8 44 50 122 81 151 l23 22 58 -26 c117 -51 169 -62 313 -62 154 0 234 19 350
82 202 110 324 273 381 507 26 110 24 203 -5 319 -38 148 -93 245 -201 353
-76 76 -105 97 -185 137 -227 111 -446 114 -665 7 -196 -95 -356 -298 -400
-508 -31 -146 -12 -331 48 -465 l31 -70 -44 -39 c-63 -54 -131 -73 -260 -73
-131 0 -200 20 -261 73 l-43 39 26 59 c44 100 59 165 65 280 7 140 -12 234
-72 359 -146 302 -495 480 -805 411z m268 -161 c62 -15 162 -60 215 -97 l28
-20 -43 -28 c-24 -15 -99 -84 -167 -153 -118 -119 -127 -126 -173 -132 -83
-12 -138 -78 -138 -166 0 -23 -5 -28 -37 -34 -21 -4 -49 -13 -63 -20 -60 -32
-146 -154 -177 -252 -9 -29 -19 -53 -22 -53 -8 0 -53 73 -85 137 -82 165 -71
377 29 549 69 119 220 230 365 268 72 19 189 19 268 1z m1963 -17 c98 -36 157
-74 229 -147 139 -142 197 -338 156 -532 -15 -68 -67 -184 -104 -229 l-20 -25
-10 25 c-5 14 -73 91 -149 170 -131 136 -141 148 -147 193 -5 35 -17 59 -40
84 -27 30 -40 36 -91 42 l-59 6 -18 62 c-15 50 -27 69 -65 102 -57 51 -135 96
-204 120 l-52 17 34 26 c50 39 150 86 216 103 86 22 240 14 324 -17z m-1573
-287 c48 -90 65 -170 60 -291 -4 -97 -32 -217 -57 -243 -4 -4 -46 22 -93 57
-48 36 -128 93 -180 126 -51 34 -96 69 -100 78 -15 39 -9 49 40 62 26 6 61 20
78 31 59 36 163 210 164 274 l1 30 29 -34 c16 -18 42 -59 58 -90z m948 74 c5
-13 72 -89 149 -167 173 -176 172 -170 38 -257 -50 -32 -131 -89 -179 -125
-48 -37 -91 -63 -95 -59 -3 5 -17 35 -29 68 -43 115 -38 308 11 423 23 54 78
142 88 142 4 0 11 -11 17 -25z m-1462 -495 c7 0 34 -34 61 -76 26 -42 83 -124
126 -182 43 -58 76 -108 74 -112 -3 -4 -37 -18 -77 -31 -61 -20 -94 -24 -193
-24 -138 0 -199 15 -298 75 -69 42 -79 57 -45 65 13 3 89 71 168 150 84 84
151 143 158 140 8 -3 19 -5 26 -5z m1952 -27 c3 -21 17 -56 29 -79 38 -66 210
-173 280 -174 l30 -1 -33 -28 c-47 -41 -146 -88 -222 -107 -88 -22 -251 -15
-335 15 -33 12 -63 25 -67 29 -5 5 21 47 56 94 36 48 93 128 127 180 66 100
73 108 106 108 18 0 23 -7 29 -37z m-1818 -1158 c35 -8 78 -23 97 -32 l34 -18
-45 -60 c-25 -33 -82 -114 -126 -180 -44 -66 -88 -123 -97 -127 -38 -16 -49
-8 -61 40 -6 26 -20 61 -31 78 -36 59 -210 163 -274 164 l-30 1 35 30 c43 37
140 86 206 104 66 18 215 18 292 0z m1964 -17 c66 -25 168 -84 168 -97 0 -4
-11 -11 -24 -17 -14 -5 -90 -72 -169 -149 -166 -162 -177 -166 -223 -87 -25
43 -141 210 -200 287 l-24 32 49 22 c81 34 120 40 241 37 93 -3 128 -8 182
-28z m319 -283 c100 -203 88 -413 -36 -595 -186 -274 -556 -340 -829 -147
l-28 20 28 15 c16 8 92 76 169 152 132 131 143 139 189 146 82 11 136 77 136
165 0 23 5 28 37 34 21 4 49 13 63 20 61 32 148 157 176 253 15 52 16 53 35
36 11 -10 38 -54 60 -99z m-2707 -95 c113 -112 136 -140 136 -164 0 -44 35
-109 70 -129 16 -10 52 -21 79 -24 47 -5 50 -7 56 -41 10 -54 29 -85 80 -130
51 -45 163 -106 213 -117 32 -7 33 -7 16 -26 -52 -58 -256 -129 -369 -129
-349 0 -628 325 -577 672 12 82 56 194 103 260 l22 31 18 -34 c10 -19 79 -95
153 -169z m852 -39 c23 -102 15 -242 -18 -333 -24 -65 -84 -168 -97 -168 -3 0
-13 14 -21 30 -8 16 -76 92 -151 168 -164 167 -164 168 -55 236 39 24 120 81
181 125 l110 81 17 -33 c9 -17 25 -65 34 -106z m956 36 c64 -47 142 -99 172
-117 36 -20 56 -38 56 -50 0 -10 2 -25 5 -32 4 -11 -9 -18 -52 -27 -48 -11
-65 -21 -103 -61 -56 -59 -130 -195 -130 -241 l-1 -34 -30 35 c-121 141 -155
398 -77 583 14 32 24 44 32 38 6 -5 64 -47 128 -94z"/>
</g>
</svg>
export const state = () => ({
codes: [],
});
export const getters = {
getCodes: (state) => state.codes,
};
export const actions = {
fetchCodes(context) {
return new Promise((resolve, reject) => {
context.commit('DEL_CODES');
this.$axios.get('/api/code/list')
.then((r) => {
const result = r.data;
context.commit('SET_CODES', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
};
export const mutations = {
SET_CODES(state, payload) {
state.pageData = payload;
},
DEL_CODES(state) {
state.pageData = null;
},
};
/* eslint-disable no-underscore-dangle */
export const state = () => ({
zoomLevel: 16,
boundary: {
northEast: {},
southWest: {},
},
stateChange: false,
});
export const getters = {
getZoomLevel: (state) => state.zoomLevel,
getBoundary: (state) => state.boundary,
getStateChange: (state) => state.stateChange,
isBoundary: (state) => (drone) => {
const checkLatBound = drone.latitude >= state.boundary.southWest.lat && drone.latitude <= state.boundary.northEast.lat;
const checkLngBound = drone.longitude >= state.boundary.southWest.lng && drone.longitude <= state.boundary.northEast.lng;
return checkLatBound && checkLngBound;
},
zoomToActivateTime: (state) => {
switch (state.zoomLevel) {
case 8:
return 30;
case 9:
return 15;
case 10:
return 10;
case 11:
return 6;
case 12:
return 4;
case 13:
return 3;
case 14:
return 2;
default:
return 1;
}
},
};
export const actions = {
setZoomLevel(context, data) {
context.commit('SET_ZOOM_LEVEL', data);
},
setBoundary(context, data) {
context.commit('SET_BOUNDARY', data);
},
clearZoomLevel(context) {
context.commit('DEL_ZOOM_LEVEL');
},
clearBoundary(context) {
context.commit('DEL_BOUNDARY');
},
clearStateChange(context) {
context.commit('RESET_STATE_CHANGE');
},
};
export const mutations = {
SET_ZOOM_LEVEL(state, payload) {
state.zoomLevel = payload;
state.stateChange = true;
},
SET_BOUNDARY(state, payload) {
state.boundary = { northEast: payload?._northEast, southWest: payload?._southWest };
state.stateChange = true;
},
DEL_ZOOM_LEVEL(state) {
state.zoomLevel = 16;
},
DEL_BOUNDARY(state) {
state.boundary = {
northEast: {},
southWest: {},
};
},
RESET_STATE_CHANGE(state) {
state.stateChange = false;
},
};
export const state = () => ({
detailData: null,
});
export const getters = {
getDetailData: (state) => state.detailData,
};
export const actions = {
fetchDetailData(context, id) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_DETAIL_DATA');
this.$axios.get(`/api/drone/${id}`)
.then((r) => {
context.commit('SET_DETAIL_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
reject(e);
});
});
},
};
export const mutations = {
SET_DETAIL_DATA(state, payload) {
state.detailData = payload;
},
CLEAR_DETAIL_DATA(state) {
state.detailData = null;
},
};
import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord';
export const state = () => ({
detailData: {},
droneLogs: [],
wholeDroneLogs: [],
selectedLogList: [],
selectedDroneId: null,
accumulatedDistance: 0,
logFilter: {
checkFilter: false,
maker: [],
filteredDroneList: [],
weight: [0, 50],
altitude: [0, 200],
speed: [0, 100],
},
});
export const getters = {
getDetailData: (state) => state.detailData,
getDroneLogs: (state) => state.droneLogs,
getWholeDroneLog: (state) => state.wholeDroneLogs,
getSelectedLogList: (state) => state.selectedLogList,
getSelectedLastLog: (state) => {
if (state.selectedLogList.length === 0) return null;
return state.selectedLogList[state.selectedLogList.length - 1];
},
getAccumulatedDistance: (state) => state.accumulatedDistance,
getSelectedDroneId: (state) => state.selectedDroneId,
getLogFilter: (state) => state.logFilter,
};
export const actions = {
fetchDetailInfo(context, id) {
return new Promise((resolve, reject) => {
// context.commit('DEL_DETAIL_DATA');
this.$axios.get(`/api/map/drone/${id}`)
.then((r) => {
const result = r.data;
let tempLog = null;
let distance = 0;
for (let i = 0; i < result.droneLogs.length; i += 1) {
const currentLog = result.droneLogs[i];
distance += calcDistanceFromCoord(tempLog, currentLog) || 0;
tempLog = currentLog;
}
result.distance = distance;
context.commit('SET_DETAIL_DATA', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
setDroneLogs(context, data) {
const filter = context.getters.getLogFilter;
const filteredLogs = data.logs.filter((v) => {
let isMaker = true;
if (filter.maker.length !== 0) {
isMaker = !!filter.filteredDroneList.find((e) => Number(e.id) === Number(v.droneId));
}
const isSpeed = (v?.horizontalSpeed >= filter.speed[0] && (filter.speed[1] === 100 ? true : v?.horizontalSpeed <= filter.speed[1]))
|| (v?.verticalSpeed >= filter.speed[0] && (filter.speed[1] === 100 ? true : v?.verticalSpeed <= filter.speed[1]));
const isAltitude = (v?.aboveGroundLevel >= filter.altitude[0] && (filter.altitude[1] === 200 ? true : v?.aboveGroundLevel <= filter.altitude[1]))
|| (v?.aboveSeaLevel >= filter.altitude[0] && (filter.altitude[1] === 200 ? true : v?.aboveSeaLevel <= filter.altitude[1]));
const isWeight = v?.weight >= filter.weight[0] && (filter.weight[1] === 50 ? true : v?.weight <= filter.weight[1]);
return isAltitude && isSpeed && isMaker;
});
context.commit(data.mutation, filteredLogs);
},
clearDroneLogs(context) {
context.commit('CLEAR_DRONE_LOGS');
},
setSelectedLogList(context, data) {
context.commit('SET_SELECTED_LOG_LIST', data);
},
clearSelectedLogList(context) {
context.commit('CLEAR_SELECTED_LOG_LIST');
},
setSelectedDroneId(context, data) {
context.commit('SET_SELECTED_DRONE_ID', data);
},
clearSelectedDroneId(context) {
context.commit('CLEAR_SELECTED_DRONE_ID');
},
setAccumulatedDistance(context, data) {
context.commit('SET_ACCUMULATED_DISTANCE', data);
},
clearAccumulatedDistance(context) {
context.commit('CLEAR_ACCUMULATED_DISTANCE');
},
setLogFilter(context, data) {
context.commit('SET_LOG_FILTER', data);
},
clearLogFilter(context) {
context.commit('DEL_LOG_FILTER');
},
};
export const mutations = {
SET_LOG_FILTER(state, payload) {
state.logFilter = payload;
},
DEL_LOG_FILTER(state) {
state.logFilter = {
checkFilter: false,
filteredDroneList: [],
maker: [],
weight: [0, 50],
altitude: [0, 200],
speed: [0, 100],
};
},
//
SET_DETAIL_DATA(state, payload) {
state.detailData = payload;
},
DEL_DETAIL_DATA(state) {
state.detailData = null;
},
//
SET_DRONE_LOGS(state, payload) {
state.droneLogs = payload;
},
CLEAR_DRONE_LOGS(state) {
state.droneLogs = [];
},
//
SET_WHOLE_DRONE_LOGS(state, payload) {
state.wholeDroneLogs = payload;
},
CLEAR_WHOLE_DRONE_LOGS(state) {
state.wholeDroneLogs = [];
},
//
SET_SELECTED_LOG_LIST(state, payload) {
state.selectedLogList.push(payload);
},
CLEAR_SELECTED_LOG_LIST(state) {
state.selectedLogList = [];
},
//
SET_SELECTED_DRONE_ID(state, payload) {
state.selectedDroneId = payload;
},
CLEAR_SELECTED_DRONE_ID(state) {
state.selectedDroneId = null;
},
//
SET_ACCUMULATED_DISTANCE(state, payload) {
state.accumulatedDistance += payload;
},
CLEAR_ACCUMULATED_DISTANCE(state) {
state.accumulatedDistance = 0;
},
};
export const state = () => ({
listContents: [],
fixedDroneList: [],
});
export const getters = {
getListContents: (state) => state.listContents,
getFixedDroneList: (state) => state.fixedDroneList,
};
export const actions = {
/* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */
fetchListContents(context, params) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_LIST_DATA');
this.$axios.get('/api/drone/list', {
params,
})
.then((r) => {
context.commit('SET_LIST_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
console.log(e.response);
reject(e);
});
});
},
setFixedDroneList(context, data) {
context.commit('SET_FIXED_DRONE_LIST', data);
},
};
export const mutations = {
SET_FIXED_DRONE_LIST(state, payload) {
state.fixedDroneList = payload;
},
SET_LIST_DATA(state, payload) {
state.listContents = payload.drones;
},
CLEAR_LIST_DATA(state) {
state.listContents = [];
},
};
/* eslint-disable no-param-reassign */
export const state = () => ({
pageData: [],
pageParams: {
modelName: null,
maker: null,
minWeight: null,
maxWeight: null,
usageName: null,
pageNo: 1,
pageSize: 10,
total: 0,
},
});
export const getters = {
getPageData: (state) => state.pageData,
getPageParams: (state) => state.pageParams,
getPagination: (state) => {
const { total, pageSize } = state.pageParams;
const page = state.pageParams.pageNo;
return { total, pageSize, page };
},
};
export const actions = {
fetchPageData(context, params) {
return new Promise((resolve, reject) => {
context.commit('DEL_PAGE_DATA');
this.$axios
.get('/api/drone/page', {
params,
})
.then((r) => {
const result = r.data;
result.drones.forEach((elem, idx) => {
elem.no = result.totalElement - (result.pageNo * result.pageSize + idx);
});
context.commit('SET_PAGE_DATA', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
setPageParams(context, data) {
context.commit('SET_PAGE_PARAMS', data);
},
clearPageParams(context) {
context.commit('DEL_PAGE_PARAMS');
},
setPagination(context, params) {
context.commit('SET_PAGINATION', params);
},
};
export const mutations = {
SET_PAGE_DATA(state, payload) {
state.pageData = payload.drones;
state.pageParams.total = Number(payload.totalElement);
state.pageParams.pageNo = Number(payload.pageNo);
state.pageParams.pageSize = Number(payload.pageSize);
},
DEL_PAGE_DATA(state) {
state.pageData = [];
},
SET_PAGE_PARAMS(state, params) {
state.pageParams = JSON.parse(JSON.stringify(params));
},
SET_PAGINATION: (state, data) => {
state.pageParams.pageNo = data.page;
state.pageParams.pageSize = data.size;
},
DEL_PAGE_PARAMS(state) {
state.pageParams = {
modelName: null,
maker: null,
usage: null,
pageNo: 1,
pageSize: 20,
total: 0,
};
},
};
export const state = () => ({
makers: [],
makerDroneList: [],
// modelNameList: [],
});
export const getters = {
getMakers: (state) => state.makers,
getMakerDroneList: (state) => state.makerDroneList,
// getModelNameList: (state) => state.modelNameList,
};
export const actions = {
setMakers(context, data) {
context.commit('DEL_MAKERS');
context.commit('SET_MAKERS', data);
},
};
export const mutations = {
SET_MAKERS(state, payload) {
state.makers = Object.keys(payload);
Object.entries(payload).forEach(([key, value]) => {
state.makerDroneList.push({ maker: key, children: value });
// state.modelNameList = [...state.modelNameList, ...value];
});
},
DEL_MAKERS(state) {
state.makers = [];
state.makerDroneList = [];
// state.modelNameList = [];
},
};
export const state = () => ({
detailData: null,
});
export const getters = {
getDetailData: (state) => state.detailData,
};
export const actions = {
fetchDetailData(context, id) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_DETAIL_DATA');
this.$axios.get(`/api/log/${id}`)
.then((r) => {
context.commit('SET_DETAIL_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
reject(e);
});
});
},
};
export const mutations = {
SET_DETAIL_DATA(state, payload) {
state.detailData = payload;
},
CLEAR_DETAIL_DATA(state) {
state.detailData = null;
},
};
export const state = () => ({
listContents: [],
});
export const getters = {
getListContents: (state) => state.listContents,
};
export const actions = {
/* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */
fetchListContents(context, params) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_LIST_DATA');
this.$axios.get('/api/log/list', {
params,
})
.then((r) => {
context.commit('SET_LIST_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
console.log(e.response);
reject(e);
});
});
},
};
export const mutations = {
SET_LIST_DATA(state, payload) {
state.listContents = payload;
},
CLEAR_LIST_DATA(state) {
state.listContents = [];
},
N_MGR_LIST(state) {
},
};
/* eslint-disable no-param-reassign */
export const state = () => ({
pageData: [],
pageParams: {
scheduleId: null,
latitude: null,
longitude: null,
minVerticalSpeed: null,
maxVerticalSpeed: null,
minHorizontalSpeed: null,
maxHorizontalSpeed: null,
minAboveSeaLevel: null,
maxAboveSeaLevel: null,
minAboveGroundLevel: null,
maxAboveGroundLevel: null,
pageNo: 1,
pageSize: 10,
total: 0,
},
});
export const getters = {
getPageData: (state) => state.pageData,
getPageParams: (state) => state.pageParams,
getPagination: (state) => {
const {total, pageSize} = state.pageParams;
const page = state.pageParams.pageNo;
return {total, pageSize, page};
},
};
export const actions = {
fetchPageData(context, params) {
return new Promise((resolve, reject) => {
context.commit('DEL_PAGE_DATA');
this.$axios
.get('/api/log/page', {
params,
})
.then((r) => {
const result = r.data;
result.droneLogs.forEach((elem, idx) => {
elem.no =
result.totalElement - (result.pageNo * result.pageSize + idx);
});
context.commit('SET_PAGE_DATA', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
setPageParams(context, data) {
context.commit('SET_PAGE_PARAMS', data);
},
clearPageParams(context) {
context.commit('DEL_PAGE_PARAMS');
},
setPagination(context, params) {
context.commit('SET_PAGINATION', params);
},
};
export const mutations = {
SET_PAGE_DATA(state, payload) {
state.pageData = payload.droneLogs;
state.pageParams.total = Number(payload.totalElement);
state.pageParams.pageNo = Number(payload.pageNo);
state.pageParams.pageSize = Number(payload.pageSize);
},
DEL_PAGE_DATA(state) {
state.pageData = [];
},
SET_PAGE_PARAMS(state, params) {
state.pageParams = JSON.parse(JSON.stringify(params));
},
SET_PAGINATION: (state, data) => {
state.pageParams.pageNo = data.page;
state.pageParams.pageSize = data.size;
},
DEL_PAGE_PARAMS(state) {
state.pageParams = {
scheduleId: null,
minSpeed: null,
maxSpeed: null,
minAltitude: null,
maxAltitude: null,
latitude: null,
longitude: null,
pageNo: 1,
pageSize: 10,
total: 0,
};
},
};
export const state = () => ({
detailData: null,
});
export const getters = {
getDetailData: (state) => state.detailData,
};
export const actions = {
fetchDetailData(context, id) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_DETAIL_DATA');
this.$axios.get(`/api/member/${id}`)
.then((r) => {
context.commit('SET_DETAIL_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
reject(e);
});
});
},
};
export const mutations = {
SET_DETAIL_DATA(state, payload) {
state.detailData = payload;
},
CLEAR_DETAIL_DATA(state) {
state.detailData = null;
},
};
export const state = () => ({
listContents: [],
});
export const getters = {
getListContents: (state) => state.listContents,
};
export const actions = {
/* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */
fetchListContents(context, params) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_LIST_DATA');
this.$axios.get('/api/member/list', {
params,
})
.then((r) => {
context.commit('SET_LIST_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
console.log(e.response);
reject(e);
});
});
},
};
export const mutations = {
SET_LIST_DATA(state, payload) {
state.listContents = payload;
},
CLEAR_LIST_DATA(state) {
state.listContents = [];
},
N_MGR_LIST(state) {
},
};
/* eslint-disable no-param-reassign */
export const state = () => ({
pageData: [],
pageParams: {
pageNo: 1,
pageSize: 20,
total: 0,
},
});
export const getters = {
getPageData: (state) => state.pageData,
getPageParams: (state) => state.pageParams,
getPagination: (state) => {
const { total, pageSize } = state.pageParams;
const page = state.pageParams.pageNo;
return { total, pageSize, page };
},
};
export const actions = {
fetchPageData(context, params) {
return new Promise((resolve, reject) => {
context.commit('DEL_PAGE_DATA');
this.$axios.get('/api/member/page', {
params,
})
.then((r) => {
const result = r.data;
result.members.forEach((elem, idx) => {
elem.no = result.totalElement - (result.pageNo * result.pageSize + idx);
});
context.commit('SET_PAGE_DATA', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
setPageParams(context, data) {
context.commit('SET_PAGE_PARAMS', data);
},
clearPageParams(context) {
context.commit('DEL_PAGE_PARAMS');
},
setPagination(context, params) {
context.commit('SET_PAGINATION', params);
},
};
export const mutations = {
SET_PAGE_DATA(state, payload) {
state.pageData = payload.members;
state.pageParams.total = Number(payload.totalElement);
state.pageParams.pageNo = Number(payload.pageNo);
state.pageParams.pageSize = Number(payload.pageSize);
},
DEL_PAGE_DATA(state) {
state.pageData = [];
},
SET_PAGE_PARAMS(state, params) {
state.pageParams = JSON.parse(JSON.stringify(params));
},
SET_PAGINATION: (state, data) => {
state.pageParams.pageNo = data.page;
state.pageParams.pageSize = data.size;
},
DEL_PAGE_PARAMS(state) {
state.pageParams = {
pageNo: 1,
pageSize: 20,
total: 0,
};
},
};
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
export const state = () => ({
detailData: null,
});
export const getters = {
getDetailData: (state) => state.detailData,
};
export const actions = {
fetchDetailData(context, id) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_DETAIL_DATA');
this.$axios.get(`/api/schedule/${id}`)
.then((r) => {
context.commit('SET_DETAIL_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
reject(e);
});
});
},
};
export const mutations = {
SET_DETAIL_DATA(state, payload) {
state.detailData = payload;
},
CLEAR_DETAIL_DATA(state) {
state.detailData = null;
},
};
export const state = () => ({
listContents: [],
});
export const getters = {
getListContents: (state) => state.listContents,
};
export const actions = {
/* params는 page.js의 pageParams 프로퍼티 중 size, no, total을 제외한 것 */
fetchListContents(context, params) {
return new Promise((resolve, reject) => {
context.commit('CLEAR_LIST_DATA');
this.$axios.get('/api/schedule/list', {
params,
})
.then((r) => {
context.commit('SET_LIST_DATA', r.data);
resolve(r.data);
})
.catch((e) => {
console.log(e.response);
reject(e);
});
});
},
};
export const mutations = {
SET_LIST_DATA(state, payload) {
state.listContents = payload;
},
CLEAR_LIST_DATA(state) {
state.listContents = [];
},
N_MGR_LIST(state) {
},
};
/* eslint-disable no-param-reassign */
export const state = () => ({
pageData: [],
pageParams: {
droneId: null,
startTime: null,
terminateTime: null,
pageNo: 1,
pageSize: 10,
total: 0,
},
});
export const getters = {
getPageData: (state) => state.pageData,
getPageParams: (state) => state.pageParams,
getPagination: (state) => {
const {total, pageSize} = state.pageParams;
const page = state.pageParams.pageNo;
return {total, pageSize, page};
},
};
export const actions = {
fetchPageData(context, params) {
return new Promise((resolve, reject) => {
context.commit('DEL_PAGE_DATA');
this.$axios
.get('/api/schedule/page', {
params,
})
.then((r) => {
const result = r.data;
result.schedules.forEach((elem, idx) => {
elem.no =
result.totalElement - (result.pageNo * result.pageSize + idx);
});
context.commit('SET_PAGE_DATA', result);
resolve(result);
})
.catch((e) => {
reject(e);
});
});
},
setPageParams(context, data) {
context.commit('SET_PAGE_PARAMS', data);
},
clearPageParams(context) {
context.commit('DEL_PAGE_PARAMS');
},
setPagination(context, params) {
context.commit('SET_PAGINATION', params);
},
};
export const mutations = {
SET_PAGE_DATA(state, payload) {
state.pageData = payload.schedules;
state.pageParams.total = Number(payload.totalElement);
state.pageParams.pageNo = Number(payload.pageNo);
state.pageParams.pageSize = Number(payload.pageSize);
},
DEL_PAGE_DATA(state) {
state.pageData = [];
},
SET_PAGE_PARAMS(state, params) {
state.pageParams = JSON.parse(JSON.stringify(params));
},
SET_PAGINATION: (state, data) => {
state.pageParams.pageNo = data.page;
state.pageParams.pageSize = data.size;
},
DEL_PAGE_PARAMS(state) {
state.pageParams = {
droneId: null,
startTime: null,
terminateTime: null,
pageNo: 1,
pageSize: 10,
total: 0,
};
},
};
export const state = () => ({
settings: {},
});
export const getters = {
getSettings: (state) => state.settings,
};
export const actions = {
setSettings(context, params) {
context.commit('CHANGE_SETTINGS', params);
},
async nuxtServerInit({ dispatch }, ctx) {
dispatch('Code/fetchCodes');
},
};
export const mutations = {
CHANGE_SETTINGS(state, payload) {
state.settings = payload;
},
};
export default [
{
title: 'No',
dataIndex: 'id',
key: 'id',
scopedSlots: {
customRender: 'id',
},
align: 'center',
ellipsis: true,
width: 50,
},
{
title: '모델명',
dataIndex: 'modelName',
key: 'modelName',
scopedSlots: {
customRender: 'modelName',
},
align: 'center',
ellipsis: true,
width: 200,
},
{
title: '제조사',
dataIndex: 'maker',
key: 'maker',
scopedSlots: {
customRender: 'maker',
},
align: 'center',
ellipsis: true,
width: 120,
},
{
title: '종류',
dataIndex: 'usageName',
key: 'usageName',
scopedSlots: {
customRender: 'usageName',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '무게',
dataIndex: 'weight',
key: 'weight',
scopedSlots: {
customRender: 'weight',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '제원',
dataIndex: 'specification',
key: 'specification',
scopedSlots: {
customRender: 'specification',
},
align: 'center',
ellipsis: true,
width: 60,
},
];
export default [
{
title: 'No',
dataIndex: 'id',
key: 'id',
scopedSlots: {
customRender: 'id',
},
align: 'center',
ellipsis: true,
width: 50,
},
{
title: '모델명',
dataIndex: 'modelName',
key: 'modelName',
scopedSlots: {
customRender: 'modelName',
},
align: 'center',
ellipsis: true,
width: 150,
},
{
title: 'Schedule ID',
dataIndex: 'scheduleId',
key: 'scheduleId',
scopedSlots: {
customRender: 'scheduleId',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '수평 속도',
dataIndex: 'horizontalSpeed',
key: 'horizontalSpeed',
scopedSlots: {
customRender: 'horizontalSpeed',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '수직 속도',
dataIndex: 'verticalSpeed',
key: 'verticalSpeed',
scopedSlots: {
customRender: 'verticalSpeed',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '지면 고도',
dataIndex: 'aboveGroundLevel',
key: 'aboveGroundLevel',
scopedSlots: {
customRender: 'aboveGroundLevel',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '해발 고도',
dataIndex: 'aboveSeaLevel',
key: 'aboveSeaLevel',
scopedSlots: {
customRender: 'aboveSeaLevel',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '위도',
dataIndex: 'latitude',
key: 'latitude',
scopedSlots: {
customRender: 'latitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '경도',
dataIndex: 'longitude',
key: 'longitude',
scopedSlots: {
customRender: 'longitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
];
export default [
{
title: 'No',
dataIndex: 'id',
key: 'id',
scopedSlots: {
customRender: 'id',
},
align: 'center',
ellipsis: true,
width: 50,
},
{
title: '모델명',
dataIndex: 'modelName',
key: 'modelName',
scopedSlots: {
customRender: 'modelName',
},
align: 'center',
ellipsis: true,
width: 200,
},
{
title: '시작 날짜 / 시간',
dataIndex: 'startTime',
key: 'startTime',
scopedSlots: {
customRender: 'startTime',
},
align: 'center',
ellipsis: true,
width: 120,
},
{
title: '시작 위도',
dataIndex: 'startLatitude',
key: 'startLatitude',
scopedSlots: {
customRender: 'startLatitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '시작 경도',
dataIndex: 'startLongitude',
key: 'startLongitude',
scopedSlots: {
customRender: 'startLongitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '종료 날짜 / 시간',
dataIndex: 'terminateTime',
key: 'terminateTime',
scopedSlots: {
customRender: 'terminateTime',
},
align: 'center',
ellipsis: true,
width: 120,
},
{
title: '종료 위도',
dataIndex: 'terminateLatitude',
key: 'terminateLatitude',
scopedSlots: {
customRender: 'terminateLatitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
{
title: '종료 경도',
dataIndex: 'terminateLongitude',
key: 'terminateLongitude',
scopedSlots: {
customRender: 'terminateLongitude',
},
align: 'center',
ellipsis: true,
width: 60,
},
];
export const droneCategory = [
{ label: '전체', value: 0 },
{ label: '촬영용', value: 1 },
{ label: '레이싱용', value: 2 },
{ label: '완구용', value: 3 },
];
/* eslint-disable no-mixed-operators */
export default function calcAngleFromCoord(oldVal, newVal) {
if ((oldVal == null || oldVal.latitude == null) || (newVal == null || newVal.latitude == null)) return undefined;
const x = newVal.latitude - oldVal.latitude;
const y = newVal.longitude - oldVal.longitude;
const radian = Math.atan2(y, x);
return radian * 180 / Math.PI;
}
function toRad(deg) {
return deg * (Math.PI / 180);
}
// use Haversine Formula
export default function calcDistanceFromCoord(oldVal, newVal) {
if ((oldVal == null || oldVal.latitude == null) || (newVal == null || newVal.latitude == null)) return undefined;
const R = 6371000;
const φ1 = toRad(oldVal.latitude);
const φ2 = toRad(newVal.latitude);
const Δφ = toRad(Number(newVal.latitude) - Number(oldVal.latitude));
const Δλ = toRad(Number(newVal.longitude) - Number(oldVal.longitude));
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2)
+ Math.cos(φ1) * Math.cos(φ2)
* Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const result = R * c;
return Number(result.toFixed(1)); // Distance in meter
}
import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord';
import calcAngleFromCoord from '@/utils/CommonFunction/calcAngleFromCoord';
export default {
data() {
return {
};
},
computed: {
calcAngle() {
return (oldVal, newVal) => calcAngleFromCoord(oldVal, newVal);
},
calcVelocity() {
return (oldVal, newVal) => `${calcDistanceFromCoord(oldVal, newVal)}m/s`;
},
calcDistance() {
return (logs) => {
let sum = 0;
for (let i = 1; i < logs.length; i += 1) {
const oldVal = logs[i - 1];
const newVal = logs[i];
sum += calcDistanceFromCoord(oldVal, newVal);
}
return sum;
};
},
},
watch: {
},
created() {
},
mounted() {
},
beforeDestroy() {
},
methods: {
},
};
/*
* 전역으로 사용되는 mixin 지정입니다.
*/
import calcDistanceFromCoord from '@/utils/CommonFunction/calcDistanceFromCoord';
import calcAngleFromCoord from '@/utils/CommonFunction/calcAngleFromCoord';
import { mapActions, mapGetters } from 'vuex';
export default {
data() {
return {
socketServerCnt: 5,
droneCnt: 1000,
selectedDroneLog: {},
accumulatedDistance: 0,
socketOnGoing: 0,
};
},
computed: {
...mapGetters('Drone/drone', {
getDroneLogs: 'getDroneLogs',
getSelectedLogList: 'getSelectedLogList',
getSelectedDroneId: 'getSelectedDroneId',
getSelectedLastLog: 'getSelectedLastLog',
getAccumulatedDistance: 'getAccumulatedDistance',
}),
...mapGetters('Drone/Map', {
getZoomLevel: 'getZoomLevel',
getBoundary: 'getBoundary',
isBoundary: 'isBoundary',
zoomToActivateTime: 'zoomToActivateTime',
getStateChange: 'getStateChange',
}),
mc_dateTime() {
return (dateTime) => {
if (dateTime == null || dateTime === 'Invalid Date' || dateTime === '') return '';
return this.$dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss');
};
},
mc_date() {
return (date) => {
if (date == null || date === 'Invalid Date' || date === '') return '';
return this.$dayjs(date).format('YYYY-MM-DD');
};
},
zoomToSee() {
return (index) => {
const div = index % 10;
switch (this.zoom) {
case 8:
return div < 2;
case 9:
return div < 4;
case 10:
return div < 6;
case 11:
return div < 8;
default:
return true;
}
};
},
},
methods: {
...mapActions('Drone/drone', {
setDroneLogs: 'setDroneLogs',
setSelectedLogList: 'setSelectedLogList',
setAccumulatedDistance: 'setAccumulatedDistance',
setSelectedDroneId: 'setSelectedDroneId',
}),
...mapActions('Drone/Map', {
clearStateChange: 'clearStateChange',
}),
connect() {
if (window.clientSocket == null || window.clientSocket?.readyState === 3) {
window.clientSocket = new WebSocket('ws://14.33.35.148:20206/drone');
window.clientSocket.onopen = () => {
console.log('socket on open');
};
window.clientSocket.onerror = () => {
console.log('socket on error');
};
window.clientSocket.onmessage = (data) => {
const logData = JSON.parse(data.data);
let logInfo = null;
const filterDroneLogs = logData.data.droneLog.filter((v, idx) => {
if (this.getSelectedDroneId && v.droneId === this.getSelectedDroneId) logInfo = v;
return this.isBoundary(v) && this.zoomToSee(idx);
});
if (this.socketOnGoing % this.zoomToActivateTime === 0 || this.getStateChange) {
this.setDroneLogs({ logs: filterDroneLogs, mutation: 'SET_DRONE_LOGS' });
this.setDroneLogs({ logs: logData.data.droneLog, mutation: 'SET_WHOLE_DRONE_LOGS' });
this.clearStateChange();
}
if (logInfo) {
this.setAccumulatedDistance(calcDistanceFromCoord(this.getSelectedLastLog, logInfo) || 0);
logInfo.angle = calcAngleFromCoord(this.getSelectedLastLog, logInfo) || 0;
logInfo.distance = this.getAccumulatedDistance;
this.setSelectedLogList(logInfo);
}
this.socketOnGoing += 1;
};
window.clientSocket.onclose = () => {
console.log('socket on close');
};
}
},
// connectMultiClient() {
// for (let j = 0; j < this.droneCnt * this.socketServerCnt; j += 1) {
// this.drones.push({ x: 0, y: 0 });
// }
// for (let i = 0; i < this.socketServerCnt; i += 1) {
// if (
// window[`wsSocket${i}`] == null
// || window[`wsSocket${i}`].readyState === 3
// ) {
// window[`wsSocket${i}`] = new WebSocket(
// 'ws://14.33.35.148:20206/drone',
// );
// window[`wsSocket${i}`].onopen = () => {
// console.log(`socket${i} on open`);
// };
// window[`wsSocket${i}`].onerror = (e) => {
// console.log(`socket${i} on error occur ${e}`);
// };
// window[`wsSocket${i}`].onmessage = (data) => {
// const logData = JSON.parse(data.data);
// console.log(
// `message${i} ${this.$dayjs().format('HH:mm:ss')} : `,
// logData.droneData,
// );
// setTimeout(() => {
// for (let k = 0; k < this.droneCnt; k += 1) {
// this.drones[i * (k + 1) + k].x = logData.droneData[k].x;
// this.drones[i * (k + 1) + k].y = logData.droneData[k].y;
// }
// }, 0);
// };
// window[`wsSocket${i}`].onclose = () => {
// console.log(`socket${i} closed`);
// };
// }
// }
// },
disconnect() {
if (window.clientSocket && window.clientSocket.readyState === 1) {
window.clientSocket.close();
window.clientSocket = null;
}
},
},
};
config:
target: "ws://localhost:8080/1"
phases:
- duration: 20
arrivalRate: 10
scenarios:
- engine: "ws"
flow:
- think: 1
This diff could not be displayed because it is too large.