diff --git a/README.md b/README.md index 9ae3311..c9778a0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ _MEAN Application_ ## Installation -## Development +### Setup mongoDB + +### Setup node and npm + +## Development and Execution + +### First run in dev mode + +### Run in Prodction ## License Information diff --git a/api/models/logs/budget.js b/api/models/logs/budget.js index 7bcac48..46f93b1 100644 --- a/api/models/logs/budget.js +++ b/api/models/logs/budget.js @@ -10,7 +10,7 @@ const LogBudgetSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, fraction: { diff --git a/api/models/logs/flag.js b/api/models/logs/flag.js index 0243ac1..13a0eea 100644 --- a/api/models/logs/flag.js +++ b/api/models/logs/flag.js @@ -10,7 +10,7 @@ const LogFlagSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, player: { diff --git a/api/models/logs/kill.js b/api/models/logs/kill.js index 944c6cf..b48761e 100644 --- a/api/models/logs/kill.js +++ b/api/models/logs/kill.js @@ -10,7 +10,7 @@ const LogKillSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, shooter: { diff --git a/api/models/logs/points.js b/api/models/logs/points.js index 39a82ef..28aa39b 100644 --- a/api/models/logs/points.js +++ b/api/models/logs/points.js @@ -10,7 +10,7 @@ const LogKillSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, ptBlufor: { diff --git a/api/models/logs/respawn.js b/api/models/logs/respawn.js index c81d062..a79486a 100644 --- a/api/models/logs/respawn.js +++ b/api/models/logs/respawn.js @@ -10,7 +10,7 @@ const LogRespawnSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, player: { diff --git a/api/models/logs/revive.js b/api/models/logs/revive.js index 9af4a82..c5c9b10 100644 --- a/api/models/logs/revive.js +++ b/api/models/logs/revive.js @@ -10,7 +10,7 @@ const LogReviveSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, medic: { diff --git a/api/models/logs/transport.js b/api/models/logs/transport.js index 92e75e0..09accf0 100644 --- a/api/models/logs/transport.js +++ b/api/models/logs/transport.js @@ -10,7 +10,7 @@ const LogTransportSchema = new Schema({ required: true }, time: { - type: Number, + type: Date, required: true }, driver: { diff --git a/api/routes/logs.js b/api/routes/logs.js index 52dcd1e..4891d1b 100644 --- a/api/routes/logs.js +++ b/api/routes/logs.js @@ -2,6 +2,7 @@ // modules const express = require('express'); +const async = require('async'); const logger = require('debug')('cc:logs'); const routerHandling = require('../middleware/router-handling'); @@ -26,18 +27,47 @@ function processLogRequest(model, filter, res, next) { err.status = require('./http-codes').notfound; return next(err) } - const updatedTimeItems = []; - for (let i =0; i { + const filter = {war: req.params.warId}; + const sort = {sort: {time: 1}}; + + const pointsObjects = LogPointsModel.find(filter, {}, sort); + const budgetObjects = LogBudgetModel.find(filter, {}, sort); + const respawnObjects = LogRespawnModel.find(filter, {}, sort); + const reviveObjects = LogReviveModel.find(filter, {}, sort); + const killObjects = LogKillModel.find(filter, {}, sort); + const transportObjects = LogTransportModel.find(filter, {}, sort); + const flagObjects = LogFlagModel.find(filter, {}, sort); + const resources = { + points: pointsObjects.exec.bind(pointsObjects), + budget: budgetObjects.exec.bind(budgetObjects), + respawn: respawnObjects.exec.bind(respawnObjects), + revive: reviveObjects.exec.bind(reviveObjects), + kill: killObjects.exec.bind(killObjects), + transport: transportObjects.exec.bind(transportObjects), + flag: flagObjects.exec.bind(flagObjects) + }; + + async.parallel(resources, function (error, results){ + if (error) { + res.status(500).send(error); + return; + } + res.locals.items = results; + next(); + }); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + logsRouter.route('/:warId/budget') .get((req, res, next) => { const filter = {war: req.params.warId}; diff --git a/api/tools/log-parse-tool.js b/api/tools/log-parse-tool.js index a619043..e363b90 100644 --- a/api/tools/log-parse-tool.js +++ b/api/tools/log-parse-tool.js @@ -1,6 +1,5 @@ 'use strict'; -const timeStringToDecimal = require('../tools/util').timeStringToDecimal; const playerArrayContains = require('./util').playerArrayContains; const parseWarLog = (lineArray, war) => { @@ -47,7 +46,7 @@ const parseWarLog = (lineArray, war) => { stats.kills.push({ war: war._id, - time: timeStringToDecimal(line.split(' ')[5]), + time: getFullTimeDate(war.date, line.split(' ')[5]), shooter: shooter ? shooter.name : null, target: target.name, friendlyFire: shooter ? target.fraction === shooter.fraction : false, @@ -71,7 +70,7 @@ const parseWarLog = (lineArray, war) => { stats.war['budgetBlufor'] = transformMoneyString(budg[11]); stats.war['budgetOpfor'] = transformMoneyString(budg[14]); } else { - stats.budget.push(getBudgetEntry(budg, war._id)); + stats.budget.push(getBudgetEntry(budg, war._id, war.date)); } } @@ -86,7 +85,7 @@ const parseWarLog = (lineArray, war) => { stats.flag.push({ war: war._id, - time: timeStringToDecimal(line.split(' ')[5]), + time: getFullTimeDate(war.date, line.split(' ')[5]), player: playerName, flagFraction: flagFraction, capture: capture @@ -105,7 +104,7 @@ const parseWarLog = (lineArray, war) => { stats.war['ptOpfor'] = parseInt(pt[14].slice(0, -1)); return true; } else { - stats.points.push(getPointsEntry(pt, line, war._id)) + stats.points.push(getPointsEntry(pt, line, war._id, war.date)) } } @@ -116,7 +115,7 @@ const parseWarLog = (lineArray, war) => { stats.clean.push(line); const resp = line.split(' '); const playerName = line.substring(line.lastIndexOf('Spieler:') + 9, line.lastIndexOf('-') - 1); - stats.respawn.push(getRespawnEntry(resp, playerName, war._id)); + stats.respawn.push(getRespawnEntry(resp, playerName, war._id, war.date)); } /** @@ -133,7 +132,7 @@ const parseWarLog = (lineArray, war) => { stats.revive.push({ war: war._id, - time: timeStringToDecimal(line.split(' ')[5]), + time: getFullTimeDate(war.date, line.split(' ')[5]), stabilized: stabilized, medic: medic.name, patient: patient.name, @@ -156,7 +155,7 @@ const parseWarLog = (lineArray, war) => { stats.transport.push({ war: war._id, - time: timeStringToDecimal(line.split(' ')[5]), + time: getFullTimeDate(war.date, line.split(' ')[5]), driver: driver.name, passenger: passenger ? passenger.name : null, fraction: driver.fraction, @@ -185,28 +184,28 @@ const parseWarLog = (lineArray, war) => { return stats; }; -const getRespawnEntry = (respawn, playerName, warId) => { +const getRespawnEntry = (respawn, playerName, warId, warDate) => { return { war: warId, - time: timeStringToDecimal(respawn[5]), + time: getFullTimeDate(warDate, respawn[5]), player: playerName } }; -const getPointsEntry = (pt, line, warId) => { +const getPointsEntry = (pt, line, warId, warDate) => { return { war: warId, - time: timeStringToDecimal(pt[5]), + time: getFullTimeDate(warDate, pt[5]), ptBlufor: parseInt(pt[12]), ptOpfor: parseInt(pt[15].slice(0, -1)), fraction: line.includes('no Domination') ? 'NONE' : line.includes('NATO +1') ? 'BLUFOR' : 'OPFOR' } }; -const getBudgetEntry = (budg, warId) => { +const getBudgetEntry = (budg, warId, warDate) => { return { war: warId, - time: timeStringToDecimal(budg[5]), + time: getFullTimeDate(warDate, budg[5]), fraction: budg[9] === 'NATO' ? 'BLUFOR' : 'OPFOR', oldBudget: transformMoneyString(budg[11]), newBudget: transformMoneyString(budg[14]) @@ -229,7 +228,14 @@ const transformMoneyString = (budgetString) => { } const budget = budgetString.split('e+'); return Math.round(parseFloat(budget[0]) * Math.pow(10, parseInt(budget[1]))); +}; +const getFullTimeDate = (date, timeString) => { + const returnDate = new Date(date); + const time = timeString.split(':'); + returnDate.setHours(time[0]); + returnDate.setMinutes(time[1]); + return returnDate; }; module.exports = parseWarLog; diff --git a/static/package.json b/static/package.json index c1c7093..61e2a2d 100644 --- a/static/package.json +++ b/static/package.json @@ -23,7 +23,7 @@ "@angular/platform-browser": "^4.4.4", "@angular/platform-browser-dynamic": "^4.4.4", "@angular/router": "^4.4.4", - "@swimlane/ngx-charts": "^6.0.2", + "@swimlane/ngx-charts": "^6.1.0", "@swimlane/ngx-datatable": "^10.2.3", "bootstrap": "^3.3.7", "core-js": "^2.4.1", @@ -31,6 +31,7 @@ "jquery": "^3.1.0", "jquery-ui": "^1.12.0", "jquery-ui-bundle": "^1.11.4", + "ngx-bootstrap": "^1.9.3", "ngx-clipboard": "^8.1.0", "ngx-cookie-service": "^1.0.9", "ngx-infinite-scroll": "^0.5.2", diff --git a/static/src/app/services/logs/logs.service.ts b/static/src/app/services/logs/logs.service.ts index 90d0cd9..f956b81 100644 --- a/static/src/app/services/logs/logs.service.ts +++ b/static/src/app/services/logs/logs.service.ts @@ -10,6 +10,11 @@ export class LogsService { private config: AppConfig) { } + getFullLog(warId: string) { + return this.http.get(this.config.apiLogsPath + '/' + warId) + .map(res => res.json()) + } + getBudgetLogs(warId: string, fraction = '') { const params = new URLSearchParams(); params.append('fraction', fraction); diff --git a/static/src/app/statistic/war-detail/war-detail.component.css b/static/src/app/statistic/war-detail/war-detail.component.css index 405964f..a249eb6 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.css +++ b/static/src/app/statistic/war-detail/war-detail.component.css @@ -43,7 +43,7 @@ } .chart-container { - width: 80%; + width: 95%; min-width: 500px; height: 400px; padding: 15px; diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index 2daa191..4d5e279 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -10,11 +10,11 @@ {{war.ptOpfor}} CSAT -
+

Teilnehmer:

- + Scoreboard @@ -101,6 +101,7 @@ [gradient]="gradient" [xAxis]="xAxis" [yAxis]="yAxis" + [curve]="monotoneYCurve" [legend]="legend" [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" @@ -118,6 +119,7 @@ [gradient]="gradient" [xAxis]="xAxis" [yAxis]="yAxis" + [curve]="monotoneYCurve" [legend]="legend" [legendTitle]="legendTitle" [showXAxisLabel]="showXAxisLabel" @@ -128,6 +130,113 @@ [roundDomains]="roundDomains">
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index 3992d53..10833f8 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -3,6 +3,8 @@ import {ActivatedRoute, Router} from "@angular/router"; import {WarService} from "../../services/logs/war.service"; import {War} from "../../models/model-interfaces"; import {LogsService} from "../../services/logs/logs.service"; +import {TabsetComponent} from "ngx-bootstrap"; +import * as d3 from "d3"; @Component({ @@ -12,14 +14,16 @@ import {LogsService} from "../../services/logs/logs.service"; }) export class WarDetailComponent { + @ViewChild('overview') private overviewContainer: ElementRef; + + @ViewChild('staticTabs') staticTabs: TabsetComponent; + war: War = {players: []}; fractionRadioSelect: string; playerChart: any[] = []; - @ViewChild('overview') private overviewContainer: ElementRef; - cellHeight = 40; rows = []; @@ -33,6 +37,21 @@ export class WarDetailComponent { pointData: any[] = []; budgetData: any[] = []; + killData: any[] = []; + friendlyFireData: any[] = []; + transportData: any[] = []; + reviveData: any[] = []; + stabilizedData: any[] = []; + flagData: any[] = []; + + tmpPointData; + tmpBudgetData; + tmpKillData; + tmpFrienlyFireData; + tmpTransportData; + tmpReviveData; + tmpStabilizeData; + tmpFlagCaptureData; colorScheme = { domain: ['#0000FF', '#B22222'] @@ -40,7 +59,15 @@ export class WarDetailComponent { yAxisLabelPoints = 'Punkte'; yAxisLabelBudget = 'Budget'; + yAxisLabelKill = 'Kills'; + yAxisLabelFriendlyFire = 'FriendlyFire'; + yAxisLabelTransport = 'Lufttransport'; + yAxisLabelRevive = 'Revive'; + yAxisLabelStabilize = 'Stabilisiert'; + yAxisLabelFlag = 'Flaggenbesitz'; + monotoneYCurve = d3.curveMonotoneY; + stepCurve = d3.curveStepAfter; gradient = false; yAxis = true; xAxis = true; @@ -51,7 +78,7 @@ export class WarDetailComponent { autoscale = true; timeline = false; roundDomains = true; - fractionInitialized: boolean = false; + fractionChartsInitialized: boolean = false; constructor(private route: ActivatedRoute, private router: Router, @@ -77,7 +104,29 @@ export class WarDetailComponent { "value": war.playersBlufor } ]; - Object.assign(this, [this.playerChart, this.pointData, this.budgetData]); + this.tmpPointData = [ + { + "name": "NATO", + "series": [] + }, + { + "name": "CSAT", + "series": [] + } + ]; + this.tmpBudgetData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpKillData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpFrienlyFireData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpTransportData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpReviveData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpStabilizeData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpFlagCaptureData = JSON.parse(JSON.stringify(this.tmpPointData)); + + Object.assign(this, [this.playerChart, this.pointData, this.budgetData, this.killData, + this.friendlyFireData, this.transportData, this.reviveData, this.stabilizedData, this.flagData]); + + this.fractionChartsInitialized = false; + this.staticTabs.tabs[0].active = true; this.scrollOverviewTop(); }); } @@ -107,71 +156,194 @@ export class WarDetailComponent { } loadFractionData() { - if (!this.fractionInitialized) { - const tmpPointData = [ - { - "name": "NATO", - "series": [] - }, - { - "name": "CSAT", - "series": [] - } - ]; - const tmpBudgetData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpKillData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpFrienlyFireData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpTransportData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpReviveData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpStabilizeData = JSON.parse(JSON.stringify(tmpPointData)); - const tmpFlagCaptureData = JSON.parse(JSON.stringify(tmpPointData)); + if (!this.fractionChartsInitialized) { + const startDateObj = new Date(this.war.date); + startDateObj.setHours(0); + startDateObj.setMinutes(1); - // POINTS - this.logsService.getPointsLogs(this.war._id).subscribe((data) => { - data.forEach(pointEntry => { + this.logsService.getFullLog(this.war._id).subscribe((data) => { + // POINTS + data.points.forEach(pointEntry => { const dateObj = new Date(this.war.date); const time = pointEntry.time.split(':'); dateObj.setHours(time[0]); dateObj.setMinutes(time[1]); - tmpPointData[0].series.push({ - "name": dateObj, + this.tmpPointData[0].series.push({ + "name": new Date(pointEntry.time), "value": pointEntry.ptBlufor }); - tmpPointData[1].series.push({ - "name": dateObj, + this.tmpPointData[1].series.push({ + "name": new Date(pointEntry.time), "value": pointEntry.ptOpfor }); }); - this.pointData = tmpPointData; - }); + this.pointData = this.tmpPointData; - // BUDGET - this.logsService.getBudgetLogs(this.war._id).subscribe((data) => { - const dateObj = new Date(this.war.date); - dateObj.setHours(0); - dateObj.setMinutes(0); - tmpBudgetData[0].series.push({ - "name": dateObj, + // BUDGET + this.tmpBudgetData[0].series.push({ + "name": startDateObj, "value": this.war.budgetBlufor }); - tmpBudgetData[1].series.push({ - "name": dateObj, + this.tmpBudgetData[1].series.push({ + "name": startDateObj, "value": this.war.budgetOpfor }); - data.forEach(budgetEntry => { - const time = budgetEntry.time.split(':'); - const dateObj = new Date(this.war.date); - dateObj.setHours(time[0]); - dateObj.setMinutes(time[1]); - tmpBudgetData[budgetEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ - "name": dateObj, + data.budget.forEach(budgetEntry => { + this.tmpBudgetData[budgetEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(budgetEntry.time), "value": budgetEntry.newBudget }); }); - this.budgetData = tmpBudgetData; - }); + this.budgetData = this.tmpBudgetData; - this.fractionInitialized = true; + // KILLS + let killCountBlufor = 0; + let killCountOpfor = 0; + let ffKillCountBlufor = 0; + let ffKillCountOpfor = 0; + this.tmpKillData[0].series.push({ + "name": startDateObj, + "value": killCountBlufor + }); + this.tmpKillData[1].series.push({ + "name": startDateObj, + "value": killCountOpfor + }); + this.tmpFrienlyFireData[0].series.push({ + "name": startDateObj, + "value": ffKillCountBlufor + }); + this.tmpFrienlyFireData[1].series.push({ + "name": startDateObj, + "value": ffKillCountOpfor + }); + + data.kill.forEach(killEntry => { + if (killEntry.fraction === 'BLUFOR') { + if (killEntry.friendlyFire === false) { + killCountBlufor++; + } else { + ffKillCountBlufor++; + } + } else { + if (killEntry.friendlyFire === false) { + killCountOpfor++; + } else { + ffKillCountOpfor++; + } + } + this.tmpKillData[killEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(killEntry.time), + "value": killEntry.fraction === 'BLUFOR' ? killCountBlufor : killCountOpfor + }); + this.tmpFrienlyFireData[killEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(killEntry.time), + "value": killEntry.fraction === 'BLUFOR' ? ffKillCountBlufor : ffKillCountOpfor + }); + }); + this.killData = this.tmpKillData; + this.friendlyFireData = this.tmpFrienlyFireData; + + // TRANSPORT + let transportCountBlufor = 0; + let transportCountOpfor = 0; + this.tmpTransportData[0].series.push({ + "name": startDateObj, + "value": transportCountBlufor + }); + this.tmpTransportData[1].series.push({ + "name": startDateObj, + "value": transportCountOpfor + }); + + data.transport.forEach(transportEntry => { + if (transportEntry.fraction === 'BLUFOR') { + transportCountBlufor++; + } else { + transportCountOpfor++; + } + this.tmpTransportData[transportEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(transportEntry.time), + "value": transportEntry.fraction === 'BLUFOR' ? transportCountBlufor : transportCountOpfor + }); + }); + this.transportData = this.tmpTransportData; + + // REVIVE & STABILIZE + let reviveCountBlufor = 0; + let reviveCountOpfor = 0; + let stabilizeCountBlufor = 0; + let stabilizeCountOpfor = 0; + this.tmpReviveData[0].series.push({ + "name": startDateObj, + "value": reviveCountBlufor + }); + this.tmpReviveData[1].series.push({ + "name": startDateObj, + "value": reviveCountOpfor + }); + this.tmpStabilizeData[0].series.push({ + "name": startDateObj, + "value": stabilizeCountBlufor + }); + this.tmpStabilizeData[1].series.push({ + "name": startDateObj, + "value": stabilizeCountOpfor + }); + data.revive.forEach(reviveEntry => { + if (reviveEntry.fraction === 'BLUFOR') { + if (reviveEntry.stabilized === false) { + reviveCountBlufor++; + } else { + reviveCountOpfor++; + } + } else { + if (reviveEntry.stabilized === false) { + stabilizeCountBlufor++; + } else { + stabilizeCountOpfor++; + } + } + this.tmpReviveData[reviveEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(reviveEntry.time), + "value": reviveEntry.fraction === 'BLUFOR' ? reviveCountBlufor : reviveCountOpfor + }); + this.tmpStabilizeData[reviveEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(reviveEntry.time), + "value": reviveEntry.fraction === 'BLUFOR' ? stabilizeCountBlufor : stabilizeCountOpfor + }); + }); + this.reviveData = this.tmpReviveData; + this.stabilizedData = this.tmpStabilizeData; + + + // FLAG + let flagStatusBlufor = 1; + let flagStatusOpfor = 1; + this.tmpFlagCaptureData[0].series.push({ + "name": startDateObj, + "value": flagStatusBlufor + }); + this.tmpFlagCaptureData[1].series.push({ + "name": startDateObj, + "value": flagStatusOpfor + }); + + data.flag.forEach(flagEntry => { + if (flagEntry.flagFraction === 'BLUFOR') { + flagStatusBlufor = flagEntry.capture ? 0 : 1 + } else { + flagStatusOpfor = flagEntry.capture ? 0 : 1; + } + this.tmpFlagCaptureData[flagEntry.flagFraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(flagEntry.time), + "value": flagEntry.flagFraction === 'BLUFOR' ? flagStatusBlufor : flagStatusOpfor + }); + }); + this.flagData = this.tmpFlagCaptureData; + + this.fractionChartsInitialized = true; + }); } } diff --git a/static/src/assets/fraction-btn.png b/static/src/assets/fraction-btn.png index 9d2c68e..56771c1 100644 Binary files a/static/src/assets/fraction-btn.png and b/static/src/assets/fraction-btn.png differ