Compare commits
11 Commits
6d03e450c9
...
36f5d6b396
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36f5d6b396 | ||
|
|
903117696e | ||
|
|
cae3f48063 | ||
|
|
cda4aa1b08 | ||
|
|
e6d0dcaf5f | ||
|
|
ba5e91f763 | ||
|
|
197cc2ae7f | ||
|
|
3d4e4636f7 | ||
|
|
d6de4e55e8 | ||
|
|
bc3455f5d0 | ||
|
|
a64a7ae702 |
48
backend/package-lock.json
generated
48
backend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"asty-astq": "^1.14.0",
|
"asty-astq": "^1.14.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
|
"memcached": "^2.2.2",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"sequelize": "^6.37.1",
|
"sequelize": "^6.37.1",
|
||||||
@@ -1918,6 +1919,12 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/connection-parse": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/connection-parse/-/connection-parse-0.0.7.tgz",
|
||||||
|
"integrity": "sha512-bTTG28diWg7R7/+qE5NZumwPbCiJOT8uPdZYu674brDjBWQctbaQbYlDKhalS+4i5HxIx+G8dZsnBHKzWpp01A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/console-browserify": {
|
"node_modules/console-browserify": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||||
@@ -3336,6 +3343,16 @@
|
|||||||
"minimalistic-assert": "^1.0.1"
|
"minimalistic-assert": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hashring": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-xCMovURClsQZ+TR30icCZj+34Fq1hs0y6YCASD6ZqdRfYRybb5Iadws2WS+w09mGM/kf9xyA5FCdJQGcgcraSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"connection-parse": "0.0.x",
|
||||||
|
"simple-lru-cache": "0.0.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||||
@@ -3795,6 +3812,22 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jackpot": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackpot/-/jackpot-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-rbWXX+A9ooq03/dfavLg9OXQ8YB57Wa7PY5c4LfU3CgFpwEhhl3WyXTQVurkaT7zBM5I9SSOaiLyJ4I0DQmC0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"retry": "0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jackpot/node_modules/retry": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-RgncoxLF1GqwAzTZs/K2YpZkWrdIYbXsmesdomi+iPilSzjUyr/wzNIuteoTVaWokzdwZIJ9NHRNQa/RUiOB2g==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest-worker": {
|
"node_modules/jest-worker": {
|
||||||
"version": "27.5.1",
|
"version": "27.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||||
@@ -4001,6 +4034,16 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memcached": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/memcached/-/memcached-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-lHwUmqkT9WdUUgRsAvquO4xsKXYaBd644Orz31tuth+w/BIfFNuJMWwsG7sa7H3XXytaNfPTZ5R/yOG3d9zJMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hashring": "3.2.x",
|
||||||
|
"jackpot": ">=0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
@@ -5632,6 +5675,11 @@
|
|||||||
"simple-concat": "^1.0.0"
|
"simple-concat": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-lru-cache": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-uEv/AFO0ADI7d99OHDmh1QfYzQk/izT1vCmu/riQfh7qjBVUUgRT87E5s5h7CxWCA/+YoZerykpEthzVrW3LIw=="
|
||||||
|
},
|
||||||
"node_modules/slash": {
|
"node_modules/slash": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"dev": "npx ts-node-dev src/app.ts",
|
"dev": "npx ts-node-dev src/app.ts",
|
||||||
"build": "npx tsc"
|
"build": "webpack && cp package.json ./dist/package.json"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"asty-astq": "^1.14.0",
|
"asty-astq": "^1.14.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
|
"memcached": "^2.2.2",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"sequelize": "^6.37.1",
|
"sequelize": "^6.37.1",
|
||||||
|
|||||||
@@ -7,15 +7,20 @@ import { addGameApis } from './gameservice'
|
|||||||
import { addCharacterApis } from './characterservice'
|
import { addCharacterApis } from './characterservice'
|
||||||
import { addRushStatsApis } from './rushstatsservice'
|
import { addRushStatsApis } from './rushstatsservice'
|
||||||
import { addDmApis } from './dmservice'
|
import { addDmApis } from './dmservice'
|
||||||
|
import { Memcache } from './memcache'
|
||||||
|
|
||||||
|
var Memcached = require('memcached')
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const jsonParser = json()
|
const jsonParser = json()
|
||||||
const port = 3001
|
const port = 3001
|
||||||
|
// const memcache = new memcached('localhost:11211', {})
|
||||||
|
const memcachep = new Memcache('localhost:11211')
|
||||||
|
|
||||||
addGameApis(app, jsonParser)
|
addGameApis(app, jsonParser, memcachep)
|
||||||
addCharacterApis(app, jsonParser)
|
addCharacterApis(app, jsonParser, memcachep)
|
||||||
addRushStatsApis(app, jsonParser)
|
addRushStatsApis(app, jsonParser, memcachep)
|
||||||
addDmApis(app, jsonParser)
|
addDmApis(app, jsonParser, memcachep)
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/frontend'))
|
app.use(express.static(__dirname + '/frontend'))
|
||||||
app.use('/', (req, res) => {
|
app.use('/', (req, res) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { database, Character, Game, Pick, App } from './db'
|
import { database, Character, Game, Pick, App } from './db'
|
||||||
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
||||||
|
|
||||||
export function addCharacterApis(app, jsonParser) {
|
export function addCharacterApis(app, jsonParser, memcace) {
|
||||||
app.get('/api/character/:characterId', async (req, res) => {
|
app.get('/api/character/:characterId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const character = await Character.findOne({
|
const character = await Character.findOne({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { database, Character, Game, Pick, App } from './db'
|
|||||||
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
||||||
import { fn, col } from 'sequelize'
|
import { fn, col } from 'sequelize'
|
||||||
|
|
||||||
export function addDmApis(app, jsonParser) {
|
export function addDmApis(app, jsonParser, memcache) {
|
||||||
app.get('/api/dm/:dmName', async (req, res) => {
|
app.get('/api/dm/:dmName', async (req, res) => {
|
||||||
const dmName = req.params.dmName
|
const dmName = req.params.dmName
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { database, Character, Game, Pick, App } from './db'
|
import { database, Character, Game, Pick, App } from './db'
|
||||||
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
import { OrderByParser, FilterParser, ParsingError } from './tokenizer'
|
||||||
|
|
||||||
export function addGameApis(app, jsonParser) {
|
export function addGameApis(app, jsonParser, memcache) {
|
||||||
app.get('/api/game/:gameId', async (req, res) => {
|
app.get('/api/game/:gameId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const game = await Game.findOne({ where: { id: req.params['gameId'] } })
|
const game = await Game.findOne({ where: { id: req.params['gameId'] } })
|
||||||
|
|||||||
29
backend/src/memcache.ts
Normal file
29
backend/src/memcache.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
var Memcached = require('memcached')
|
||||||
|
|
||||||
|
export class Memcache {
|
||||||
|
readonly memcached
|
||||||
|
|
||||||
|
readonly getCallback
|
||||||
|
readonly setCallback
|
||||||
|
readonly replaceCallback
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
this.memcached = new Memcached(url, {
|
||||||
|
timeout: 30
|
||||||
|
})
|
||||||
|
|
||||||
|
this.getCallback = promisify(this.memcached.get).bind(this.memcached)
|
||||||
|
this.setCallback = promisify(this.memcached.set).bind(this.memcached)
|
||||||
|
this.replaceCallback = promisify(this.memcached.replace).bind(this.memcached)
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string) {
|
||||||
|
return this.getCallback(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key: string, value) {
|
||||||
|
return this.setCallback(key, value, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,12 +43,21 @@ function monthIdToEndSeconds(monthId: number): number {
|
|||||||
return Number.MAX_SAFE_INTEGER
|
return Number.MAX_SAFE_INTEGER
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addRushStatsApis(app, jsonParser) {
|
export function addRushStatsApis(app, jsonParser, memcache) {
|
||||||
app.post('/api/serverstats/gamestats', jsonParser, async (req, res) => {
|
app.post('/api/serverstats/gamestats', jsonParser, async (req, res) => {
|
||||||
const monthId = req.body.monthId
|
const monthId = req.body.monthId
|
||||||
let startSeconds = monthIdToStartSeconds(monthId)
|
let startSeconds = monthIdToStartSeconds(monthId)
|
||||||
let endSeconds = monthIdToEndSeconds(monthId)
|
let endSeconds = monthIdToEndSeconds(monthId)
|
||||||
|
|
||||||
|
let cachedResponse = await memcache.get('gamestats' + req.body.monthId)
|
||||||
|
|
||||||
|
if (cachedResponse) {
|
||||||
|
console.info('cache hit - ' + 'gamestats' + req.body.monthId)
|
||||||
|
res.send(cachedResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('cache miss')
|
||||||
const serverGameStats = await database.query(serverGameStatsQuery, {
|
const serverGameStats = await database.query(serverGameStatsQuery, {
|
||||||
type: QueryTypes.SELECT,
|
type: QueryTypes.SELECT,
|
||||||
replacements: {
|
replacements: {
|
||||||
@@ -56,6 +65,8 @@ export function addRushStatsApis(app, jsonParser) {
|
|||||||
endSeconds
|
endSeconds
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await memcache.set('gamestats' + req.body.monthId, serverGameStats[0])
|
||||||
res.send(serverGameStats[0])
|
res.send(serverGameStats[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -64,6 +75,13 @@ export function addRushStatsApis(app, jsonParser) {
|
|||||||
let startSeconds = monthIdToStartSeconds(monthId)
|
let startSeconds = monthIdToStartSeconds(monthId)
|
||||||
let endSeconds = monthIdToEndSeconds(monthId)
|
let endSeconds = monthIdToEndSeconds(monthId)
|
||||||
|
|
||||||
|
let cachedResponse = await memcache.get('rolestats' + req.body.monthId)
|
||||||
|
|
||||||
|
if (cachedResponse) {
|
||||||
|
console.info('cache hit - ' + 'rolestats' + req.body.monthId)
|
||||||
|
res.send(cachedResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
const games = await Game.findAll({
|
const games = await Game.findAll({
|
||||||
include: [
|
include: [
|
||||||
{ model: Character, as: 'characterPickedForGame' },
|
{ model: Character, as: 'characterPickedForGame' },
|
||||||
@@ -110,6 +128,7 @@ export function addRushStatsApis(app, jsonParser) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await memcache.set('rolestats' + req.body.monthId, result)
|
||||||
res.send(result)
|
res.send(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
|
|||||||
const nodeExternals = require('webpack-node-externals');
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: 'production',
|
||||||
entry: './src/app.ts',
|
entry: './src/app.ts',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@@ -12,13 +13,15 @@ module.exports = {
|
|||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
}, node: {
|
||||||
|
__dirname: false
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'bundle.js',
|
filename: 'app.js',
|
||||||
path: path.resolve(__dirname, '../dist'),
|
path: path.resolve(__dirname, './dist'),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new NodePolyfillPlugin(),
|
new NodePolyfillPlugin(),
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"semi": false,
|
"semi": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 100,
|
"printWidth": 80,
|
||||||
"trailingComma": "none"
|
"trailingComma": "none"
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<v-btn to="/serverstats">Server Stats</v-btn>
|
<v-btn to="/serverstats">Server Stats</v-btn>
|
||||||
<v-btn to="/games">Games</v-btn>
|
<v-btn to="/games">Games</v-btn>
|
||||||
<v-btn to="/characters">Characters</v-btn>
|
<v-btn to="/characters">Characters</v-btn>
|
||||||
|
<!-- <v-btn to="/gamemasters">Gamemasters</v-btn> -->
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<router-view />
|
<router-view />
|
||||||
</v-main>
|
</v-main>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import GameDetails from '../vues/GameDetails.vue'
|
|||||||
import CharacterList from '../vues/CharacterList.vue'
|
import CharacterList from '../vues/CharacterList.vue'
|
||||||
import CharacterDetails from '../vues/CharacterDetails.vue'
|
import CharacterDetails from '../vues/CharacterDetails.vue'
|
||||||
import ServerStats from '../vues/ServerStats.vue'
|
import ServerStats from '../vues/ServerStats.vue'
|
||||||
|
import DmList from '../vues/DmList.vue'
|
||||||
|
|
||||||
const root = {
|
const root = {
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -17,6 +18,7 @@ const gameListRoute = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gameDetailsRoute = {
|
const gameDetailsRoute = {
|
||||||
|
name: 'game',
|
||||||
path: '/games/:gameId',
|
path: '/games/:gameId',
|
||||||
component: GameDetails
|
component: GameDetails
|
||||||
}
|
}
|
||||||
@@ -27,6 +29,7 @@ const characterListRoute = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const characterDetailsRoute = {
|
const characterDetailsRoute = {
|
||||||
|
name: 'character',
|
||||||
path: '/characters/:characterId',
|
path: '/characters/:characterId',
|
||||||
component: CharacterDetails
|
component: CharacterDetails
|
||||||
}
|
}
|
||||||
@@ -36,13 +39,19 @@ const serverStatsRoute = {
|
|||||||
component: ServerStats
|
component: ServerStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gameMasterListRoute = {
|
||||||
|
path: '/gamemasters',
|
||||||
|
component: DmList
|
||||||
|
}
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
root,
|
root,
|
||||||
gameListRoute,
|
gameListRoute,
|
||||||
gameDetailsRoute,
|
gameDetailsRoute,
|
||||||
characterListRoute,
|
characterListRoute,
|
||||||
characterDetailsRoute,
|
characterDetailsRoute,
|
||||||
serverStatsRoute
|
serverStatsRoute,
|
||||||
|
gameMasterListRoute
|
||||||
]
|
]
|
||||||
|
|
||||||
export default createRouter({
|
export default createRouter({
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ export interface Character {
|
|||||||
creationDate: number
|
creationDate: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DmStats {
|
||||||
|
name: string
|
||||||
|
gameCount: string
|
||||||
|
lastGame: number
|
||||||
|
games: Game
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameStats {
|
export interface GameStats {
|
||||||
Complete: number
|
Complete: number
|
||||||
Postponed: number
|
Postponed: number
|
||||||
|
|||||||
@@ -5,17 +5,21 @@
|
|||||||
<h2>Player: {{ character.playerName }}</h2>
|
<h2>Player: {{ character.playerName }}</h2>
|
||||||
<div>Total Game EB: {{ earnedEB }} --- Total Game IP: {{ earnedIP }}</div>
|
<div>Total Game EB: {{ earnedEB }} --- Total Game IP: {{ earnedIP }}</div>
|
||||||
<div>
|
<div>
|
||||||
Games Played: {{ gamesPlayedCount }} --- Games Applied: {{ gamesAppliedCount }} -- Pick Rate:
|
Games Played: {{ gamesPlayedCount }} --- Games Applied:
|
||||||
{{ pickRate.toFixed(2) }}%
|
{{ gamesAppliedCount }} -- Pick Rate: {{ pickRate.toFixed(2) }}%
|
||||||
</div>
|
</div>
|
||||||
<div>Last Game: {{ lastPlayedPostDate }}</div>
|
<div>Last Game: {{ lastPlayedPostDate }}</div>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<div class="flex-column game-list">
|
<div class="flex-column game-list">
|
||||||
<h3 class="game-list-title">{{ (games.played || []).length }} Games Played</h3>
|
<h3 class="game-list-title">
|
||||||
|
{{ (games.played || []).length }} Games Played
|
||||||
|
</h3>
|
||||||
<GameTable :gameList="games.played"></GameTable>
|
<GameTable :gameList="games.played"></GameTable>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-column game-list">
|
<div class="flex-column game-list">
|
||||||
<h3 class="game-list-title">{{ (games.applied || []).length }} Games Applied</h3>
|
<h3 class="game-list-title">
|
||||||
|
{{ (games.applied || []).length }} Games Applied
|
||||||
|
</h3>
|
||||||
<game-table :gameList="games.applied"></game-table>
|
<game-table :gameList="games.applied"></game-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,8 +69,12 @@ const pickRate = ref(0)
|
|||||||
const lastPlayedPostDate = ref()
|
const lastPlayedPostDate = ref()
|
||||||
|
|
||||||
async function loadCharacterDetails() {
|
async function loadCharacterDetails() {
|
||||||
const characterResponse = await axios.get(`/api/character/${characterId.value}`)
|
const characterResponse = await axios.get(
|
||||||
const gameDetails = await axios.post(`/api/character/${characterId.value}/gameHistory`)
|
`/api/character/${characterId.value}`
|
||||||
|
)
|
||||||
|
const gameDetails = await axios.post(
|
||||||
|
`/api/character/${characterId.value}/gameHistory`
|
||||||
|
)
|
||||||
|
|
||||||
character.value = characterResponse.data
|
character.value = characterResponse.data
|
||||||
games.value = {
|
games.value = {
|
||||||
@@ -86,7 +94,10 @@ function calculateDerivedEarnings(gamesPlayedList: Game[]) {
|
|||||||
earnedIP.value = runningEarnedIp
|
earnedIP.value = runningEarnedIp
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateDerivedGameStats(gamesPlayedList: Game[], gamesAppliedList: Game[]) {
|
function calculateDerivedGameStats(
|
||||||
|
gamesPlayedList: Game[],
|
||||||
|
gamesAppliedList: Game[]
|
||||||
|
) {
|
||||||
gamesPlayedCount.value = gamesPlayedList.length
|
gamesPlayedCount.value = gamesPlayedList.length
|
||||||
gamesAppliedCount.value = gamesAppliedList.length
|
gamesAppliedCount.value = gamesAppliedList.length
|
||||||
pickRate.value = (gamesPlayedList.length / gamesAppliedList.length) * 100
|
pickRate.value = (gamesPlayedList.length / gamesAppliedList.length) * 100
|
||||||
@@ -96,7 +107,9 @@ function calculateDerivedLastPlayedPostDate(gamesPlayedList: Game[]) {
|
|||||||
if (gamesPlayedList.length === 0) {
|
if (gamesPlayedList.length === 0) {
|
||||||
lastPlayedPostDate.value = 'N/A'
|
lastPlayedPostDate.value = 'N/A'
|
||||||
} else {
|
} else {
|
||||||
lastPlayedPostDate.value = new Date(gamesPlayedList[gamesPlayedList.length - 1].postdate * 1000)
|
lastPlayedPostDate.value = new Date(
|
||||||
|
gamesPlayedList[gamesPlayedList.length - 1].postdate * 1000
|
||||||
|
)
|
||||||
.toISOString()
|
.toISOString()
|
||||||
.split('T')[0]
|
.split('T')[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,29 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
class="filter flex-1-0"
|
class="filter flex-1-0"
|
||||||
placeholder="Filter by Name"
|
placeholder="Filter by Name"
|
||||||
v-model="filtervalue"
|
v-model="filterValue"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
<v-data-table-server
|
<v-data-table-server
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
v-model:items-per-page="resultePerPage"
|
|
||||||
:items="characters"
|
:items="characters"
|
||||||
:items-length="count"
|
:items-length="count"
|
||||||
v-model:page="page"
|
v-model:items-per-page="resultePerPage"
|
||||||
|
v-model:page="pageValue"
|
||||||
|
v-model:sort-by="sortValue"
|
||||||
@update:options="loadData"
|
@update:options="loadData"
|
||||||
>
|
>
|
||||||
<template v-slot:item.id="{ item }">
|
<template v-slot:item.id="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.id}`">{{ item.id }}</a>
|
<router-link
|
||||||
|
:to="{ name: 'character', params: { characterId: item.id } }"
|
||||||
|
>{{ item.id }}</router-link
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.characterName="{ item }">
|
<template v-slot:item.characterName="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.id}`">{{ item.characterName }}</a>
|
<router-link
|
||||||
|
:to="{ name: 'character', params: { characterId: item.id } }"
|
||||||
|
>{{ item.characterName }}</router-link
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.creationDate="{ item }">
|
<template v-slot:item.creationDate="{ item }">
|
||||||
{{ new Date(item.creationDate * 1000).toISOString().split('T')[0] }}
|
{{ new Date(item.creationDate * 1000).toISOString().split('T')[0] }}
|
||||||
@@ -26,9 +33,8 @@
|
|||||||
<template #bottom></template>
|
<template #bottom></template>
|
||||||
</v-data-table-server>
|
</v-data-table-server>
|
||||||
<v-pagination
|
<v-pagination
|
||||||
:model-value="page"
|
v-model:model-value="pageValue"
|
||||||
:length="pageCount"
|
:length="pageCount"
|
||||||
@update:modelValue="setPage($event)"
|
|
||||||
></v-pagination>
|
></v-pagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -36,12 +42,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Character } from '../types'
|
import { Character } from '../types'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onBeforeMount, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter, LocationQueryValue } from 'vue-router'
|
||||||
import { VDataTable } from 'vuetify/components'
|
import { VDataTable } from 'vuetify/components'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
type ReadonlyHeaders = VDataTable['$props']['headers']
|
type ReadonlyHeaders = VDataTable['$props']['headers']
|
||||||
|
type SortItems = VDataTable['sortBy']
|
||||||
|
|
||||||
const headers: ReadonlyHeaders = [
|
const headers: ReadonlyHeaders = [
|
||||||
{ title: 'ID', align: 'start', sortable: true, key: 'id' },
|
{ title: 'ID', align: 'start', sortable: true, key: 'id' },
|
||||||
@@ -49,7 +56,12 @@ const headers: ReadonlyHeaders = [
|
|||||||
{ title: 'Role', align: 'start', sortable: true, key: 'role' },
|
{ title: 'Role', align: 'start', sortable: true, key: 'role' },
|
||||||
{ title: 'Player', align: 'start', sortable: true, key: 'playerName' },
|
{ title: 'Player', align: 'start', sortable: true, key: 'playerName' },
|
||||||
{ title: 'Status', align: 'start', sortable: true, key: 'status' },
|
{ title: 'Status', align: 'start', sortable: true, key: 'status' },
|
||||||
{ title: 'Creation Date', align: 'start', sortable: true, key: 'creationDate' }
|
{
|
||||||
|
title: 'Creation Date',
|
||||||
|
align: 'start',
|
||||||
|
sortable: true,
|
||||||
|
key: 'creationDate'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -57,18 +69,17 @@ const router = useRouter()
|
|||||||
|
|
||||||
const characters = ref<Character[]>([])
|
const characters = ref<Character[]>([])
|
||||||
const pageCount = ref(1)
|
const pageCount = ref(1)
|
||||||
const page = ref(1)
|
|
||||||
const resultePerPage = ref(10)
|
const resultePerPage = ref(10)
|
||||||
|
|
||||||
const count = ref(10)
|
const count = ref(10)
|
||||||
|
|
||||||
const filtervalue = ref('')
|
const pageValue = ref(1)
|
||||||
|
const filterValue = ref('')
|
||||||
|
const sortValue = ref<SortItems>([])
|
||||||
|
|
||||||
async function loadData({ page, itemsPerPage, sortBy }: any) {
|
async function loadData({ page, itemsPerPage, sortBy }: any) {
|
||||||
let sortString = 'id'
|
let sortString = 'id asc'
|
||||||
if (sortBy[0]) {
|
if (sortBy[0]) {
|
||||||
console.log(sortBy[0].key)
|
|
||||||
console.log(sortBy[0].order)
|
|
||||||
sortString = `${sortBy[0].key} ${sortBy[0].order}`
|
sortString = `${sortBy[0].key} ${sortBy[0].order}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,47 +87,85 @@ async function loadData({ page, itemsPerPage, sortBy }: any) {
|
|||||||
page: `${page - 1}`,
|
page: `${page - 1}`,
|
||||||
count: `${itemsPerPage}`,
|
count: `${itemsPerPage}`,
|
||||||
orderBy: sortString,
|
orderBy: sortString,
|
||||||
filter: filtervalue.value ? `characterName:"${filtervalue.value}"` : ''
|
filter: filterValue.value ? `characterName:"${filterValue.value}"` : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
characters.value = response.data.characterData
|
characters.value = response.data.characterData
|
||||||
pageCount.value = response.data.pageCount
|
pageCount.value = response.data.pageCount
|
||||||
count.value = response.data.totalCount
|
count.value = response.data.totalCount
|
||||||
}
|
|
||||||
|
|
||||||
function setPage(targetPage: number) {
|
router.replace({
|
||||||
router.replace({ query: { page: targetPage, filter: route.query.filter } })
|
query: {
|
||||||
}
|
page: pageValue.value,
|
||||||
|
sort: sortString === 'id asc' ? '' : sortString,
|
||||||
watch(route, (newValue, oldValue) => {
|
filter: filterValue.value
|
||||||
if (!route.query.page) {
|
}
|
||||||
router.replace({ query: { page: 1, filter: route.query.filter } })
|
})
|
||||||
page.value = 1
|
}
|
||||||
} else {
|
|
||||||
page.value = Number(route.query.page)
|
async function reloadData() {
|
||||||
|
loadData({
|
||||||
|
page: pageValue.value,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
sortBy: sortValue.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSortFromQuery(query: string | LocationQueryValue[]): SortItems {
|
||||||
|
if (typeof route.query.sort === 'string') {
|
||||||
|
const key = route.query.sort.split(' ')[0]
|
||||||
|
const order = route.query.sort.split(' ')[1] === 'asc' ? 'asc' : 'desc'
|
||||||
|
return [{ key, order }]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(route, (newRoute, oldRoute) => {
|
||||||
|
if (newRoute.query.page) {
|
||||||
|
pageValue.value = Number(newRoute.query.page)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRoute.query.sort) {
|
||||||
|
sortValue.value = buildSortFromQuery(newRoute.query.sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRoute.query.filter) {
|
||||||
|
filterValue.value = newRoute.query.filter.toString()
|
||||||
}
|
}
|
||||||
page.value = Number(route.query.page)
|
|
||||||
loadData({ page: page.value, itemsPerPage: resultePerPage.value, sortBy: [] })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let debounce: ReturnType<typeof setTimeout>
|
let debounce: ReturnType<typeof setTimeout>
|
||||||
watch(filtervalue, (newFilter: string, oldFilter: string) => {
|
watch(filterValue, (newFilter: string, oldFilter: string) => {
|
||||||
clearTimeout(debounce)
|
clearTimeout(debounce)
|
||||||
debounce = setTimeout(() => {
|
debounce = setTimeout(() => {
|
||||||
router.replace({ query: { page: route.query.page, filter: newFilter } })
|
router.replace({ query: { page: route.query.page, filter: newFilter } })
|
||||||
}, 500)
|
}, 500)
|
||||||
loadData({ page: page.value, itemsPerPage: resultePerPage.value, sortBy: [] })
|
loadData({
|
||||||
|
page: pageValue.value,
|
||||||
|
itemsPerPage: resultePerPage.value,
|
||||||
|
sortBy: []
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onBeforeMount(async () => {
|
||||||
|
// Initialize Filter
|
||||||
if (!route.query.page) {
|
if (!route.query.page) {
|
||||||
router.replace({ query: { page: 1 } })
|
pageValue.value = 1
|
||||||
page.value = 1
|
|
||||||
} else {
|
} else {
|
||||||
page.value = Number(route.query.page)
|
pageValue.value = Number(route.query.page)
|
||||||
|
count.value = Number(route.query.page) * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Sort
|
||||||
|
if (!route.query.sort) {
|
||||||
|
sortValue.value = []
|
||||||
|
} else {
|
||||||
|
sortValue.value = buildSortFromQuery(route.query.sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Filter
|
||||||
if (route.query.filter) {
|
if (route.query.filter) {
|
||||||
filtervalue.value = route.query.filter.toString()
|
filterValue.value = route.query.filter.toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
<template>
|
<template>Hello World</template>
|
||||||
|
|
||||||
</template>
|
<style></style>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { DmStats } from '../types'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/components'
|
||||||
|
|
||||||
|
type ReadonlyHeaders = VDataTable['$props']['headers']
|
||||||
|
|
||||||
|
const headers: ReadonlyHeaders = [
|
||||||
|
{ title: 'Game Master', align: 'start', sortable: true, key: 'id' },
|
||||||
|
{ title: 'Game Count', align: 'start', sortable: true, key: 'characterName' },
|
||||||
|
{ title: 'Last Game', align: 'start', sortable: true, key: 'role' }
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
@@ -23,23 +23,41 @@
|
|||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<div class="character-list">
|
<div class="character-list">
|
||||||
<h3>{{ picks.length }} Character Picks</h3>
|
<h3>{{ picks.length }} Character Picks</h3>
|
||||||
<v-data-table-virtual :headers="pickHeaders" :items="picks" height="500px" fixed-header>
|
<v-data-table-virtual
|
||||||
|
:headers="pickHeaders"
|
||||||
|
:items="picks"
|
||||||
|
height="500px"
|
||||||
|
fixed-header
|
||||||
|
>
|
||||||
<template v-slot:item.characterId="{ item }">
|
<template v-slot:item.characterId="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.characterId}`">{{ item.characterId }}</a>
|
<a v-bind:href="`/#/characters/${item.characterId}`">{{
|
||||||
|
item.characterId
|
||||||
|
}}</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.characterName="{ item }">
|
<template v-slot:item.characterName="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.characterId}`">{{ item.characterName }}</a>
|
<a v-bind:href="`/#/characters/${item.characterId}`">{{
|
||||||
|
item.characterName
|
||||||
|
}}</a>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table-virtual>
|
</v-data-table-virtual>
|
||||||
</div>
|
</div>
|
||||||
<div class="character-list">
|
<div class="character-list">
|
||||||
<h3>{{ apps.length }} Character Apps</h3>
|
<h3>{{ apps.length }} Character Apps</h3>
|
||||||
<v-data-table-virtual :headers="appHeaders" :items="apps" height="500px" fixed-header>
|
<v-data-table-virtual
|
||||||
|
:headers="appHeaders"
|
||||||
|
:items="apps"
|
||||||
|
height="500px"
|
||||||
|
fixed-header
|
||||||
|
>
|
||||||
<template v-slot:item.characterId="{ item }">
|
<template v-slot:item.characterId="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.characterId}`">{{ item.characterId }}</a>
|
<a v-bind:href="`/#/characters/${item.characterId}`">{{
|
||||||
|
item.characterId
|
||||||
|
}}</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.characterName="{ item }">
|
<template v-slot:item.characterName="{ item }">
|
||||||
<a v-bind:href="`/#/characters/${item.characterId}`">{{ item.characterName }}</a>
|
<a v-bind:href="`/#/characters/${item.characterId}`">{{
|
||||||
|
item.characterName
|
||||||
|
}}</a>
|
||||||
</template>
|
</template>
|
||||||
</v-data-table-virtual>
|
</v-data-table-virtual>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,22 +3,27 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
class="filter flex-1-0"
|
class="filter flex-1-0"
|
||||||
placeholder="Filter by Name"
|
placeholder="Filter by Name"
|
||||||
v-model="filtervalue"
|
v-model="filterValue"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</div>
|
</div>
|
||||||
<v-data-table-server
|
<v-data-table-server
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
v-model:items-per-page="resultsPerPage"
|
|
||||||
:items="games"
|
:items="games"
|
||||||
:items-length="count"
|
:items-length="count"
|
||||||
v-model:page="page"
|
v-model:items-per-page="resultsPerPage"
|
||||||
|
v-model:page="pageValue"
|
||||||
|
v-model:sort-by="sortValue"
|
||||||
@update:options="loadData"
|
@update:options="loadData"
|
||||||
>
|
>
|
||||||
<template v-slot:item.id="{ item }">
|
<template v-slot:item.id="{ item }">
|
||||||
<a v-bind:href="`/#/games/${item.id}`">{{ item.id }}</a>
|
<router-link :to="{ name: 'game', params: { gameId: item.id } }">{{
|
||||||
|
item.id
|
||||||
|
}}</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.title="{ item }">
|
<template v-slot:item.title="{ item }">
|
||||||
<a v-bind:href="`/#/games/${item.id}`">{{ item.title }}</a>
|
<router-link :to="{ name: 'game', params: { gameId: item.id } }">{{
|
||||||
|
item.title
|
||||||
|
}}</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.postdate="{ item }">
|
<template v-slot:item.postdate="{ item }">
|
||||||
{{ new Date(item.postdate * 1000).toISOString().split('T')[0] }}
|
{{ new Date(item.postdate * 1000).toISOString().split('T')[0] }}
|
||||||
@@ -27,9 +32,8 @@
|
|||||||
<template #bottom></template>
|
<template #bottom></template>
|
||||||
</v-data-table-server>
|
</v-data-table-server>
|
||||||
<v-pagination
|
<v-pagination
|
||||||
:model-value="page"
|
v-model:model-value="pageValue"
|
||||||
:length="pageCount"
|
:length="pageCount"
|
||||||
@update:modelValue="setPage($event)"
|
|
||||||
></v-pagination>
|
></v-pagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -45,12 +49,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Game } from '../types'
|
import { Game } from '../types'
|
||||||
import GameTable from './GameTable.vue'
|
import GameTable from './GameTable.vue'
|
||||||
import { onMounted, watch, ref } from 'vue'
|
import { onBeforeMount, watch, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter, LocationQueryValue } from 'vue-router'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { VDataTable } from 'vuetify/components'
|
import { VDataTable } from 'vuetify/components'
|
||||||
|
|
||||||
type ReadonlyHeaders = VDataTable['$props']['headers']
|
type ReadonlyHeaders = VDataTable['$props']['headers']
|
||||||
|
type SortItems = VDataTable['sortBy']
|
||||||
|
|
||||||
const headers: ReadonlyHeaders = [
|
const headers: ReadonlyHeaders = [
|
||||||
{ title: 'ID', align: 'start', sortable: true, key: 'id' },
|
{ title: 'ID', align: 'start', sortable: true, key: 'id' },
|
||||||
@@ -64,15 +69,16 @@ const router = useRouter()
|
|||||||
|
|
||||||
const games = ref<Game[]>([])
|
const games = ref<Game[]>([])
|
||||||
const pageCount = ref(1)
|
const pageCount = ref(1)
|
||||||
let page = ref(1)
|
|
||||||
const resultsPerPage = ref(10)
|
const resultsPerPage = ref(10)
|
||||||
|
|
||||||
let count = ref(10)
|
const count = ref(0)
|
||||||
|
|
||||||
const filtervalue = ref<string>('')
|
const pageValue = ref(1)
|
||||||
|
const filterValue = ref('')
|
||||||
|
const sortValue = ref<SortItems>([])
|
||||||
|
|
||||||
async function loadData({ page, itemsPerPage, sortBy }: any) {
|
async function loadData({ page, itemsPerPage, sortBy }: any) {
|
||||||
let sortString = 'id'
|
let sortString = 'id asc'
|
||||||
if (sortBy[0]) {
|
if (sortBy[0]) {
|
||||||
sortString = `${sortBy[0].key} ${sortBy[0].order}`
|
sortString = `${sortBy[0].key} ${sortBy[0].order}`
|
||||||
}
|
}
|
||||||
@@ -81,50 +87,80 @@ async function loadData({ page, itemsPerPage, sortBy }: any) {
|
|||||||
page: `${page - 1}`,
|
page: `${page - 1}`,
|
||||||
count: `${itemsPerPage}`,
|
count: `${itemsPerPage}`,
|
||||||
orderBy: sortString,
|
orderBy: sortString,
|
||||||
filter: filtervalue.value ? `title:"${filtervalue.value}"` : ''
|
filter: filterValue.value ? `title:"${filterValue.value}"` : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
games.value = response.data.gameData
|
games.value = response.data.gameData
|
||||||
pageCount.value = response.data.pageCount
|
pageCount.value = response.data.pageCount
|
||||||
count.value = response.data.totalCount
|
count.value = response.data.totalCount
|
||||||
|
|
||||||
|
router.replace({
|
||||||
|
query: {
|
||||||
|
page: pageValue.value,
|
||||||
|
sort: sortString === 'id asc' ? '' : sortString,
|
||||||
|
filter: filterValue.value
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPage(targetPage: number) {
|
async function reloadData() {
|
||||||
router.replace({ query: { page: targetPage, filter: route.query.filter } })
|
loadData({
|
||||||
|
page: pageValue.value,
|
||||||
|
itemsPerPage: 10,
|
||||||
|
sortBy: sortValue.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(route, (newValue, oldValue) => {
|
function buildSortFromQuery(query: string | LocationQueryValue[]): SortItems {
|
||||||
if (!route.query.page) {
|
if (typeof route.query.sort === 'string') {
|
||||||
router.replace({ query: { page: 1, filter: route.query.filter } })
|
const key = route.query.sort.split(' ')[0]
|
||||||
page.value = 1
|
const order = route.query.sort.split(' ')[1] === 'asc' ? 'asc' : 'desc'
|
||||||
} else {
|
return [{ key, order }]
|
||||||
page.value = Number(route.query.page)
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(route, (newRoute, oldRoute) => {
|
||||||
|
if (newRoute.query.page) {
|
||||||
|
pageValue.value = Number(newRoute.query.page)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.query.filter) {
|
if (newRoute.query.sort) {
|
||||||
filtervalue.value = route.query.filter.toString()
|
sortValue.value = buildSortFromQuery(newRoute.query.sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newRoute.query.filter) {
|
||||||
|
filterValue.value = newRoute.query.filter.toString()
|
||||||
}
|
}
|
||||||
loadData({ page: page.value, itemsPerPage: resultsPerPage.value, sortBy: [] })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let debounce: ReturnType<typeof setTimeout>
|
let debounce: ReturnType<typeof setTimeout>
|
||||||
watch(filtervalue, (newFilter: string, oldFilter: string) => {
|
watch(filterValue, (newFilter: string, oldFilter: string) => {
|
||||||
clearTimeout(debounce)
|
clearTimeout(debounce)
|
||||||
debounce = setTimeout(() => {
|
debounce = setTimeout(() => {
|
||||||
router.replace({ query: { page: route.query.page, filter: newFilter } })
|
reloadData()
|
||||||
}, 500)
|
}, 500)
|
||||||
loadData({ page: page.value, itemsPerPage: resultsPerPage.value, sortBy: [] })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onBeforeMount(async () => {
|
||||||
|
// Initialize Filter
|
||||||
if (!route.query.page) {
|
if (!route.query.page) {
|
||||||
router.replace({ query: { page: 1 } })
|
pageValue.value = 1
|
||||||
page.value = 1
|
|
||||||
} else {
|
} else {
|
||||||
page.value = Number(route.query.page)
|
pageValue.value = Number(route.query.page)
|
||||||
|
count.value = Number(route.query.page) * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Sort
|
||||||
|
if (!route.query.sort) {
|
||||||
|
sortValue.value = []
|
||||||
|
} else {
|
||||||
|
sortValue.value = buildSortFromQuery(route.query.sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Filter
|
||||||
if (route.query.filter) {
|
if (route.query.filter) {
|
||||||
filtervalue.value = route.query.filter.toString()
|
filterValue.value = route.query.filter.toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-data-table-virtual :headers="headers" :items="gameList" height="500px" fixed-header>
|
<v-data-table-virtual
|
||||||
|
:headers="headers"
|
||||||
|
:items="gameList"
|
||||||
|
height="500px"
|
||||||
|
fixed-header
|
||||||
|
>
|
||||||
<template v-slot:item.id="{ item }">
|
<template v-slot:item.id="{ item }">
|
||||||
<a v-bind:href="`/#/games/${item.id}`">{{ item.id }}</a>
|
<a v-bind:href="`/#/games/${item.id}`">{{ item.id }}</a>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<v-select class="date-selector" v-model="dateSelect" variant="underlined" :items="dateItems">
|
<v-select
|
||||||
|
class="date-selector"
|
||||||
|
v-model="dateSelect"
|
||||||
|
variant="underlined"
|
||||||
|
:items="dateItems"
|
||||||
|
>
|
||||||
</v-select>
|
</v-select>
|
||||||
<div class="stat-bar d-flex flex-row justify-space-around">
|
<div class="stat-bar d-flex flex-row justify-space-around">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
@@ -18,13 +23,15 @@
|
|||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<v-label class="title">Total / Average EB</v-label>
|
<v-label class="title">Total / Average EB</v-label>
|
||||||
<v-label
|
<v-label
|
||||||
>{{ Math.floor(gameStats.TotalEB) }} / {{ Math.floor(gameStats.AverageEB) }}</v-label
|
>{{ Math.floor(gameStats.TotalEB) }} /
|
||||||
|
{{ Math.floor(gameStats.AverageEB) }}</v-label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<v-label class="title">Total / Average IP</v-label>
|
<v-label class="title">Total / Average IP</v-label>
|
||||||
<v-label
|
<v-label
|
||||||
>{{ Math.floor(gameStats.TotalIP) }} / {{ Math.floor(gameStats.AverageIP) }}</v-label
|
>{{ Math.floor(gameStats.TotalIP) }} /
|
||||||
|
{{ Math.floor(gameStats.AverageIP) }}</v-label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,9 +40,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left">Role</th>
|
<th class="text-left">Role</th>
|
||||||
<th class="text-left">
|
<th class="text-left">Characters</th>
|
||||||
Characters
|
|
||||||
</th>
|
|
||||||
<th class="text-left">App Count</th>
|
<th class="text-left">App Count</th>
|
||||||
<th class="text-left">Games Played</th>
|
<th class="text-left">Games Played</th>
|
||||||
<th class="text-left">Pick Rate %</th>
|
<th class="text-left">Pick Rate %</th>
|
||||||
@@ -51,7 +56,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-left">{{ roleStat.apps }}</td>
|
<td class="text-left">{{ roleStat.apps }}</td>
|
||||||
<td class="text-left">{{ roleStat.picks }}</td>
|
<td class="text-left">{{ roleStat.picks }}</td>
|
||||||
<td class="text-left">{{ Math.floor((roleStat.picks / roleStat.apps) * 100) }}%</td>
|
<td class="text-left">
|
||||||
|
{{ Math.floor((roleStat.picks / roleStat.apps) * 100) }}%
|
||||||
|
</td>
|
||||||
<td class="text-left">
|
<td class="text-left">
|
||||||
{{ Math.floor((roleStat.picks / gameStats.Complete) * 100) }}%
|
{{ Math.floor((roleStat.picks / gameStats.Complete) * 100) }}%
|
||||||
</td>
|
</td>
|
||||||
@@ -231,7 +238,9 @@ function updateChart(stats: RoleStats, tag: ChartType) {
|
|||||||
{
|
{
|
||||||
label: 'Game Type',
|
label: 'Game Type',
|
||||||
data: [
|
data: [
|
||||||
gameStats.value.Complete - gameStats.value.Events - gameStats.value.Fixes,
|
gameStats.value.Complete -
|
||||||
|
gameStats.value.Events -
|
||||||
|
gameStats.value.Fixes,
|
||||||
gameStats.value.Postponed,
|
gameStats.value.Postponed,
|
||||||
gameStats.value.Pending,
|
gameStats.value.Pending,
|
||||||
gameStats.value.Events,
|
gameStats.value.Events,
|
||||||
|
|||||||
36
install.sh
36
install.sh
@@ -1,11 +1,31 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "Creating user rushstats"
|
||||||
|
echo "---"
|
||||||
|
if [id rushstats >/dev/null 2>&1]; then
|
||||||
|
echo "rushstats user already exists, skipping."
|
||||||
|
else
|
||||||
|
useradd -s /sbin/nologin rushstats
|
||||||
|
echo "User rushstats created with /sbin/nologin"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "---"
|
echo "---"
|
||||||
echo "Moving the distributable folder into /srv/gamestats"
|
echo "Moving the distributable folder into /srv/gamestats"
|
||||||
echo "---"
|
echo "---"
|
||||||
cp -r "$parent_path"/dist /srv/gamestats
|
if [ -d /srv/gamestats ]; then
|
||||||
|
echo "/srv/gamestats already exists; removing"
|
||||||
|
rm -r /srv/rushstats
|
||||||
|
fi
|
||||||
|
mkdir /srv/gamestats
|
||||||
|
cp -r "$parent_path"/dist/* /srv/gamestats
|
||||||
|
cp -r "$parent_path"/loader/* /srv/gamestats
|
||||||
|
chown -R rushstats:rushstats /srv/gamestats
|
||||||
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "---"
|
echo "---"
|
||||||
@@ -31,14 +51,18 @@ echo "[Install]" >> $SYSTEMD_SERVICE_FILE
|
|||||||
echo "WantedBy=multi-user.target" >> $SYSTEMD_SERVICE_FILE
|
echo "WantedBy=multi-user.target" >> $SYSTEMD_SERVICE_FILE
|
||||||
echo "" >> $SYSTEMD_SERVICE_FILE
|
echo "" >> $SYSTEMD_SERVICE_FILE
|
||||||
|
|
||||||
echo ""
|
echo "Created new service."
|
||||||
echo "---"
|
echo cat $SYSTEMD_SERVICE_FILE
|
||||||
echo "Creating the cron job to refresh the database."
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "---"
|
echo "---"
|
||||||
echo "Reloading daemons and starting service"
|
echo "Reloading daemons and starting service"
|
||||||
echo "---"
|
echo "---"
|
||||||
systemctl daeomon-reload
|
systemctl daemon-reload
|
||||||
|
systemctl enable rushstats.service
|
||||||
systemctl start rushstats.service
|
systemctl start rushstats.service
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "Creating the cron job to refresh the database."
|
||||||
|
echo "---"
|
||||||
|
|||||||
15
loader/chron-script.sh
Normal file
15
loader/chron-script.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||||
|
|
||||||
|
PYTHON_VEN="$parent_path"/venv/bin/python
|
||||||
|
if [ ! -f $PYTHON_VEN ]; then
|
||||||
|
echo "Setting up new VENV"
|
||||||
|
python3 -m venv venv
|
||||||
|
echo "Installing requirements."
|
||||||
|
source ./venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
deactivate
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Start DB Creation"
|
||||||
|
$parent_path/venv/bin/python createrushdatabase.py
|
||||||
@@ -12,6 +12,10 @@ Game = namedtuple('Game',
|
|||||||
|
|
||||||
Link = namedtuple('Link', ['gameId', 'gameTitle', 'characterId', 'characterName'])
|
Link = namedtuple('Link', ['gameId', 'gameTitle', 'characterId', 'characterName'])
|
||||||
|
|
||||||
|
SET_WAL_PRAGMA = """
|
||||||
|
PRAGMA journal_mode=WAL;
|
||||||
|
"""
|
||||||
|
|
||||||
APPS_TABLE_CREATE = """
|
APPS_TABLE_CREATE = """
|
||||||
CREATE TABLE IF NOT EXISTS "Apps" (
|
CREATE TABLE IF NOT EXISTS "Apps" (
|
||||||
"gameId" INTEGER,
|
"gameId" INTEGER,
|
||||||
@@ -170,6 +174,7 @@ def loadAppsAndPicks(characterNameToId, gameTitleToId, gameFileName):
|
|||||||
def createTables(dbName):
|
def createTables(dbName):
|
||||||
with sqlite3.connect(dbName) as connection:
|
with sqlite3.connect(dbName) as connection:
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
cursor.execute(SET_WAL_PRAGMA)
|
||||||
cursor.execute(CHARACTER_TABLE_CREATE)
|
cursor.execute(CHARACTER_TABLE_CREATE)
|
||||||
cursor.execute(GAMES_TABLE_CREATE)
|
cursor.execute(GAMES_TABLE_CREATE)
|
||||||
cursor.execute(APPS_TABLE_CREATE)
|
cursor.execute(APPS_TABLE_CREATE)
|
||||||
|
|||||||
5
loader/requirements.txt
Normal file
5
loader/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
certifi==2024.6.2
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
idna==3.7
|
||||||
|
requests==2.32.3
|
||||||
|
urllib3==2.2.1
|
||||||
2
setup.sh
2
setup.sh
@@ -22,7 +22,7 @@ cp -f ./backend/dist/* ./dist
|
|||||||
cd "$parent_path"/loader
|
cd "$parent_path"/loader
|
||||||
python3 createrushdatabase.py
|
python3 createrushdatabase.py
|
||||||
cd ../
|
cd ../
|
||||||
cp -r ./loader/testdb.db ./dist/testdb.db
|
cp -r ./loader/* ./dist
|
||||||
|
|
||||||
# Move the package into the dist folder and install the needed modules
|
# Move the package into the dist folder and install the needed modules
|
||||||
cd "$parent_path"
|
cd "$parent_path"
|
||||||
|
|||||||
Reference in New Issue
Block a user