mirror of
https://github.com/Dahlgren/arma-server-web-admin.git
synced 2024-08-30 17:22:10 +00:00
Arma Reforger support
This commit is contained in:
@ -35,6 +35,7 @@ A simple to use web admin panel for Arma servers.
|
||||
- cwa (does not support linux)
|
||||
- ofp
|
||||
- ofpresistance
|
||||
- reforger
|
||||
|
||||
## Config
|
||||
|
||||
|
10
app.js
10
app.js
@ -1,5 +1,6 @@
|
||||
var express = require('express')
|
||||
var bodyParser = require('body-parser')
|
||||
var fs = require('fs.extra')
|
||||
var morgan = require('morgan')
|
||||
var path = require('path')
|
||||
var serveStatic = require('serve-static')
|
||||
@ -15,6 +16,15 @@ var Mods = require('./lib/mods')
|
||||
var Logs = require('./lib/logs')
|
||||
var Settings = require('./lib/settings')
|
||||
|
||||
if (config.game === 'reforger') {
|
||||
Logs = require('./lib/reforger/logs')
|
||||
Manager = require('./lib/reforger/manager')
|
||||
Missions = require('./lib/reforger/missions')
|
||||
Mods = require('./lib/reforger/mods')
|
||||
|
||||
fs.mkdirp(config.reforger.profiles) // Needs to be created in advance or game will store profiles elsewhere
|
||||
}
|
||||
|
||||
var app = express()
|
||||
var server = require('http').Server(app)
|
||||
var io = require('socket.io')(server)
|
||||
|
@ -1,8 +1,17 @@
|
||||
for (var environmentVariable of ['GAME_TYPE', 'GAME_PATH']) {
|
||||
['GAME_TYPE', 'GAME_PATH'].forEach(function (environmentVariable) {
|
||||
if (!process.env[environmentVariable]) {
|
||||
console.log('Missing required environment variable "' + environmentVariable + '"')
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
if (process.env.GAME_TYPE === 'reforger') {
|
||||
['REFORGER_CONFIGS', 'REFORGER_PROFILES', 'REFORGER_REGION', 'REFORGER_WORKSHOP'].forEach(function (environmentVariable) {
|
||||
if (!process.env[environmentVariable]) {
|
||||
console.log('Missing required environment variable "' + environmentVariable + '"')
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -19,6 +28,12 @@ module.exports = {
|
||||
username: process.env.AUTH_USERNAME,
|
||||
password: process.env.AUTH_PROCESS
|
||||
},
|
||||
reforger: {
|
||||
configs: process.env.REFORGER_CONFIGS,
|
||||
profiles: process.env.REFORGER_PROFILES,
|
||||
region: process.env.REFORGER_REGION,
|
||||
workshop: process.env.REFORGER_WORKSHOP
|
||||
},
|
||||
prefix: process.env.SERVER_PREFIX,
|
||||
suffix: process.env.SERVER_SUFFIX,
|
||||
logFormat: process.env.LOG_FORMAT || 'dev'
|
||||
|
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
game: 'arma3', // arma3, arma2oa, arma2, arma1, cwa, ofpresistance, ofp
|
||||
game: 'arma3', // arma3, arma2oa, arma2, arma1, cwa, ofpresistance, ofp, reforger
|
||||
path: 'path-to-arma3-directory',
|
||||
port: 3000,
|
||||
host: '0.0.0.0', // Can be either an IP or a Hostname
|
||||
@ -9,6 +9,12 @@ module.exports = {
|
||||
'-noSound',
|
||||
'-world=empty'
|
||||
],
|
||||
reforger: {
|
||||
configs: 'path-to-configs-folder', // Folder where server config files will be stored
|
||||
profiles: 'path-to-profiles-folder', // Folder where logs and persistence data will be stored. Each server will create a subfolder.
|
||||
region: 'EU', // Region in which servers will be hosted, https://community.bistudio.com/wiki/Arma_Reforger:Server_Hosting#region
|
||||
workshop: 'path-to-workshop-folder' // Folder where workshop mods will be stored
|
||||
},
|
||||
serverMods: [ // Mods used exclusively by server and not shared with clients
|
||||
'@mod1',
|
||||
'@mod2',
|
||||
|
@ -70,6 +70,7 @@ Mods.prototype.resolveModData = function (modPath, cb) {
|
||||
}
|
||||
|
||||
cb(null, {
|
||||
id: modPath,
|
||||
name: modPath,
|
||||
size: results.folderSize,
|
||||
formattedSize: filesize(results.folderSize),
|
||||
|
99
lib/reforger/logs.js
Normal file
99
lib/reforger/logs.js
Normal file
@ -0,0 +1,99 @@
|
||||
var async = require('async')
|
||||
var filesize = require('filesize')
|
||||
var fs = require('fs.extra')
|
||||
var glob = require('glob')
|
||||
var path = require('path')
|
||||
|
||||
var Logs = function (config) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
Logs.prototype.delete = function (filename, callback) {
|
||||
callback = callback || function () {}
|
||||
|
||||
this.getLogFile(filename, function (err, logFile) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
} else {
|
||||
if (logFile && logFile.path) {
|
||||
fs.unlink(logFile.path, callback)
|
||||
} else {
|
||||
return callback(new Error('File not found'))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Logs.prototype.logsPath = function () {
|
||||
return this.config.reforger.profiles
|
||||
}
|
||||
|
||||
Logs.prototype.logFiles = function (callback) {
|
||||
var directory = this.logsPath()
|
||||
|
||||
if (directory === null) {
|
||||
return callback(null, [])
|
||||
}
|
||||
|
||||
glob('**/*.log', { cwd: directory }, function (err, files) {
|
||||
if (err) {
|
||||
callback(err)
|
||||
return
|
||||
}
|
||||
|
||||
files = files.map(function (file) {
|
||||
return {
|
||||
name: file,
|
||||
path: path.join(directory, file)
|
||||
}
|
||||
})
|
||||
|
||||
async.filter(files, function (file, cb) {
|
||||
fs.stat(file.path, function (err, stat) {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
file.created = stat.birthtime.toISOString()
|
||||
file.modified = stat.mtime.toISOString()
|
||||
file.formattedSize = filesize(stat.size)
|
||||
file.size = stat.size
|
||||
cb(null, stat.isFile())
|
||||
})
|
||||
}, function (err, files) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
files.sort(function (a, b) {
|
||||
return b.created.localeCompare(a.created) // Descending order
|
||||
})
|
||||
|
||||
callback(null, files)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Logs.prototype.getLogFile = function (filename, callback) {
|
||||
this.logFiles(function (err, files) {
|
||||
if (err) {
|
||||
callback(err)
|
||||
} else {
|
||||
var validLogs = files.filter(function (file) {
|
||||
return file.name === filename
|
||||
})
|
||||
|
||||
if (validLogs.length > 0) {
|
||||
callback(null, validLogs[0])
|
||||
} else {
|
||||
callback(null, null)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Logs.prototype.readLogFile = function (filename, callback) {
|
||||
fs.readFile(filename, callback)
|
||||
}
|
||||
|
||||
module.exports = Logs
|
132
lib/reforger/manager.js
Normal file
132
lib/reforger/manager.js
Normal file
@ -0,0 +1,132 @@
|
||||
var events = require('events')
|
||||
var fs = require('fs')
|
||||
|
||||
var Server = require('./server')
|
||||
|
||||
var filePath = 'servers.json'
|
||||
|
||||
var Manager = function (config, logs) {
|
||||
this.config = config
|
||||
this.logs = logs
|
||||
this.serversArr = []
|
||||
this.serversHash = {}
|
||||
}
|
||||
|
||||
Manager.prototype = new events.EventEmitter()
|
||||
|
||||
Manager.prototype.addServer = function (options) {
|
||||
var server = this._addServer(options)
|
||||
this.save()
|
||||
return server
|
||||
}
|
||||
|
||||
Manager.prototype.removeServer = function (id) {
|
||||
var server = this.serversHash[id]
|
||||
|
||||
if (!server) {
|
||||
return {}
|
||||
}
|
||||
|
||||
var index = this.serversArr.indexOf(server)
|
||||
if (index > -1) {
|
||||
this.serversArr.splice(index, 1)
|
||||
}
|
||||
this.save()
|
||||
|
||||
if (server.pid) {
|
||||
server.stop()
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
Manager.prototype._addServer = function (data) {
|
||||
var server = new Server(this.config, this.logs, data)
|
||||
this.serversArr.push(server)
|
||||
this.serversArr.sort(function (a, b) {
|
||||
return a.title.localeCompare(b.title)
|
||||
})
|
||||
this.serversHash[server.id] = server
|
||||
|
||||
var self = this
|
||||
var save = function () {
|
||||
self.save()
|
||||
}
|
||||
var statusChanged = function () {
|
||||
self.emit('servers')
|
||||
}
|
||||
server.on('save', save)
|
||||
server.on('state', statusChanged)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
Manager.prototype.getServer = function (id) {
|
||||
return this.serversHash[id]
|
||||
}
|
||||
|
||||
Manager.prototype.getServers = function () {
|
||||
return this.serversArr
|
||||
}
|
||||
|
||||
Manager.prototype.load = function () {
|
||||
var self = this
|
||||
|
||||
fs.readFile(filePath, function (err, data) {
|
||||
if (err) {
|
||||
console.log('Could not load any existing servers configuration, starting fresh')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(data).forEach(function (server) {
|
||||
self._addServer(server)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Manager load error: ' + e)
|
||||
}
|
||||
|
||||
self.getServers().map(function (server) {
|
||||
if (server.auto_start) {
|
||||
server.start()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Manager.prototype.save = function () {
|
||||
var data = []
|
||||
var self = this
|
||||
|
||||
this.serversArr.sort(function (a, b) {
|
||||
return a.title.toLowerCase().localeCompare(b.title.toLowerCase())
|
||||
})
|
||||
|
||||
this.serversHash = {}
|
||||
this.serversArr.forEach(function (server) {
|
||||
data.push({
|
||||
admin_password: server.admin_password,
|
||||
auto_start: server.auto_start,
|
||||
battle_eye: server.battle_eye,
|
||||
dedicatedServerId: server.dedicatedServerId,
|
||||
max_players: server.max_players,
|
||||
missions: server.missions,
|
||||
mods: server.mods,
|
||||
password: server.password,
|
||||
port: server.port,
|
||||
title: server.title
|
||||
})
|
||||
|
||||
self.serversHash[server.id] = server
|
||||
})
|
||||
|
||||
fs.writeFile(filePath, JSON.stringify(data), function (err) {
|
||||
if (err) {
|
||||
console.error('Manager save error: ' + err)
|
||||
} else {
|
||||
self.emit('servers')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = Manager
|
93
lib/reforger/missions.js
Normal file
93
lib/reforger/missions.js
Normal file
@ -0,0 +1,93 @@
|
||||
var async = require('async')
|
||||
var events = require('events')
|
||||
var fs = require('fs.extra')
|
||||
var glob = require('glob')
|
||||
var path = require('path')
|
||||
|
||||
var stripBOM = require('./stripBOM')
|
||||
|
||||
var Missions = function (config) {
|
||||
this.config = config
|
||||
this.missions = []
|
||||
|
||||
this.updateMissions()
|
||||
}
|
||||
|
||||
Missions.prototype = new events.EventEmitter()
|
||||
|
||||
Missions.prototype.workshopPath = function () {
|
||||
return this.config.reforger.workshop
|
||||
}
|
||||
|
||||
Missions.prototype.updateMissions = function (cb) {
|
||||
var self = this
|
||||
var workshopFolder = this.workshopPath()
|
||||
|
||||
glob('**/ServerData.json', { cwd: workshopFolder }, function (err, files) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
async.map(files, function (filename, cb) {
|
||||
var serverDataFile = path.join(workshopFolder, filename)
|
||||
fs.readFile(serverDataFile, 'utf-8', function (err, data) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
try {
|
||||
var serverData = JSON.parse(stripBOM(data))
|
||||
fs.stat(serverDataFile, function (err, stat) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
var missions = serverData.revision.scenarios.map(function (scenario) {
|
||||
return {
|
||||
dateCreated: new Date(stat.ctime),
|
||||
dateModified: new Date(stat.mtime),
|
||||
missionName: scenario.name,
|
||||
name: scenario.gameId,
|
||||
size: 0,
|
||||
sizeFormatted: '',
|
||||
worldName: ''
|
||||
}
|
||||
})
|
||||
|
||||
cb(null, missions)
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return cb(err)
|
||||
}
|
||||
})
|
||||
}, function (err, missions) {
|
||||
if (!err) {
|
||||
missions = missions.flat()
|
||||
self.missions = missions
|
||||
self.emit('missions', missions)
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
cb(err, missions)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Missions.prototype.handleUpload = function (uploadedFile, cb) {
|
||||
cb(new Error('Not implemented'))
|
||||
}
|
||||
|
||||
Missions.prototype.delete = function (missionName, cb) {
|
||||
cb(new Error('Not implemented'))
|
||||
}
|
||||
|
||||
Missions.prototype.downloadSteamWorkshop = function (id, cb) {
|
||||
cb(new Error('Not implemented'))
|
||||
}
|
||||
|
||||
module.exports = Missions
|
78
lib/reforger/mods.js
Normal file
78
lib/reforger/mods.js
Normal file
@ -0,0 +1,78 @@
|
||||
var async = require('async')
|
||||
var events = require('events')
|
||||
var fs = require('fs.extra')
|
||||
var filesize = require('filesize')
|
||||
var glob = require('glob')
|
||||
var path = require('path')
|
||||
|
||||
var stripBOM = require('./stripBOM')
|
||||
|
||||
var Mods = function (config) {
|
||||
this.config = config
|
||||
this.mods = []
|
||||
}
|
||||
|
||||
Mods.prototype = new events.EventEmitter()
|
||||
|
||||
Mods.prototype.workshopPath = function () {
|
||||
return this.config.reforger.workshop
|
||||
}
|
||||
|
||||
Mods.prototype.delete = function (mod, cb) {
|
||||
var self = this
|
||||
var workshopFolder = this.workshopPath()
|
||||
|
||||
fs.rmrf(path.join(workshopFolder, mod), function (err) {
|
||||
cb(err)
|
||||
|
||||
if (!err) {
|
||||
self.updateMods()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Mods.prototype.updateMods = function () {
|
||||
var self = this
|
||||
var workshopFolder = this.workshopPath()
|
||||
|
||||
glob('**/ServerData.json', { cwd: workshopFolder }, function (err, files) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
async.map(files, function (filename, cb) {
|
||||
var serverDataFile = path.join(workshopFolder, filename)
|
||||
fs.readFile(serverDataFile, 'utf-8', function (err, data) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
try {
|
||||
var serverData = JSON.parse(stripBOM(data))
|
||||
var mod = {
|
||||
id: serverData.id,
|
||||
name: serverData.name,
|
||||
size: 0,
|
||||
formattedSize: filesize(0)
|
||||
}
|
||||
cb(null, mod)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return cb(err)
|
||||
}
|
||||
})
|
||||
}, function (err, mods) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
self.mods = mods
|
||||
self.emit('mods', mods)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = Mods
|
281
lib/reforger/server.js
Normal file
281
lib/reforger/server.js
Normal file
@ -0,0 +1,281 @@
|
||||
var childProcess = require('child_process')
|
||||
var events = require('events')
|
||||
var fs = require('fs.extra')
|
||||
var Gamedig = require('gamedig')
|
||||
var path = require('path')
|
||||
var publicIp = require('public-ip')
|
||||
var slugify = require('slugify')
|
||||
|
||||
var queryInterval = 5000
|
||||
|
||||
var Server = function (config, logs, options) {
|
||||
this.config = config
|
||||
this.logs = logs
|
||||
this.update(options)
|
||||
}
|
||||
|
||||
Server.prototype = new events.EventEmitter()
|
||||
|
||||
Server.prototype.createServerTitle = function (title) {
|
||||
if (this.config.prefix) {
|
||||
title = this.config.prefix + title
|
||||
}
|
||||
|
||||
if (this.config.suffix) {
|
||||
title = title + this.config.suffix
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
Server.prototype.generateId = function () {
|
||||
return slugify(this.title).replace(/\./g, '-')
|
||||
}
|
||||
|
||||
Server.prototype.update = function (options) {
|
||||
this.admin_password = options.admin_password
|
||||
this.auto_start = options.auto_start
|
||||
this.battle_eye = options.battle_eye
|
||||
this.dedicatedServerId = options.dedicatedServerId
|
||||
this.max_players = options.max_players
|
||||
this.missions = options.missions
|
||||
this.mods = options.mods || []
|
||||
this.password = options.password
|
||||
this.port = options.port || 2001
|
||||
this.title = options.title
|
||||
|
||||
this.id = this.generateId()
|
||||
this.port = parseInt(this.port, 10) // If port is a string then gamedig fails
|
||||
}
|
||||
|
||||
Server.prototype.steamQueryPort = function () {
|
||||
return 10000 + this.port
|
||||
}
|
||||
|
||||
Server.prototype.queryStatus = function () {
|
||||
if (!this.instance) {
|
||||
return
|
||||
}
|
||||
|
||||
var self = this
|
||||
Gamedig.query(
|
||||
{
|
||||
type: 'arma3',
|
||||
host: self.ip,
|
||||
port: self.steamQueryPort() - 1
|
||||
},
|
||||
function (state) {
|
||||
if (!self.instance) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.error) {
|
||||
self.state = null
|
||||
} else {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
self.emit('state')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Server.prototype.makeServerConfig = function () {
|
||||
var scenarioId = '{59AD59368755F41A}Missions/21_GM_Eden.conf'
|
||||
|
||||
if (this.missions && this.missions.length > 0) {
|
||||
scenarioId = this.missions[0].name
|
||||
}
|
||||
|
||||
return {
|
||||
dedicatedServerId: this.dedicatedServerId,
|
||||
region: this.config.reforger.region,
|
||||
gameHostBindAddress: '',
|
||||
gameHostBindPort: this.port,
|
||||
gameHostRegisterBindAddress: this.ip,
|
||||
gameHostRegisterPort: this.port,
|
||||
adminPassword: this.admin_password,
|
||||
game: {
|
||||
name: this.createServerTitle(this.title),
|
||||
password: this.password,
|
||||
scenarioId: scenarioId,
|
||||
playerCountLimit: this.max_players,
|
||||
autoJoinable: false,
|
||||
visible: true,
|
||||
gameProperties: {
|
||||
serverMaxViewDistance: 2500,
|
||||
serverMinGrassDistance: 50,
|
||||
networkViewDistance: 1000,
|
||||
disableThirdPerson: true,
|
||||
fastValidation: true,
|
||||
battlEye: this.battle_eye
|
||||
},
|
||||
mods: this.mods.map(function (mod) {
|
||||
return {
|
||||
modId: mod
|
||||
}
|
||||
})
|
||||
},
|
||||
a2sQueryEnabled: true,
|
||||
steamQueryPort: this.steamQueryPort()
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype.serverConfigDirectory = function () {
|
||||
return this.config.reforger.configs
|
||||
}
|
||||
|
||||
Server.prototype.serverConfigFile = function () {
|
||||
return path.join(this.serverConfigDirectory(), this.generateId() + '.json')
|
||||
}
|
||||
|
||||
Server.prototype.readServerConfig = function (cb) {
|
||||
var self = this
|
||||
fs.readFile(self.serverConfigFile(), 'utf8', function (err, data) {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
try {
|
||||
var serverConfig = JSON.parse(data)
|
||||
cb(null, serverConfig)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Server.prototype.saveServerConfig = function (config, cb) {
|
||||
var self = this
|
||||
fs.mkdirp(self.serverConfigDirectory(), function (err) {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
fs.writeFile(self.serverConfigFile(), JSON.stringify(config), cb)
|
||||
})
|
||||
}
|
||||
|
||||
Server.prototype.serverBinary = function () {
|
||||
return path.join(this.config.path, 'ArmaReforgerServer')
|
||||
}
|
||||
|
||||
Server.prototype.serverArguments = function () {
|
||||
var self = this
|
||||
var id = self.generateId()
|
||||
return [
|
||||
'-addonsDir',
|
||||
this.config.reforger.workshop,
|
||||
'-addonDownloadDir',
|
||||
this.config.reforger.workshop,
|
||||
'-config',
|
||||
this.serverConfigFile(),
|
||||
'-profile',
|
||||
path.join(this.config.reforger.profiles, id)
|
||||
].map(function (argument) {
|
||||
if (self.config.type === 'windows') {
|
||||
return argument.replace(/\//g, '\\')
|
||||
}
|
||||
|
||||
return argument
|
||||
})
|
||||
}
|
||||
|
||||
Server.prototype.start = function () {
|
||||
if (this.instance) {
|
||||
return this
|
||||
}
|
||||
|
||||
var self = this
|
||||
publicIp.v4().then(function (ip) {
|
||||
self.ip = ip
|
||||
var config = self.makeServerConfig()
|
||||
self.saveServerConfig(config, function (err) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
var instance = childProcess.spawn(self.serverBinary(), self.serverArguments(), { cwd: self.config.path })
|
||||
|
||||
instance.on('error', function (err) {
|
||||
console.error('Failed to start server', self.title, err)
|
||||
})
|
||||
|
||||
instance.on('close', function (code) {
|
||||
clearInterval(self.queryStatusInterval)
|
||||
self.state = null
|
||||
self.pid = null
|
||||
self.instance = null
|
||||
|
||||
self.emit('state')
|
||||
})
|
||||
|
||||
self.pid = instance.pid
|
||||
self.instance = instance
|
||||
self.headlessClientInstances = []
|
||||
self.queryStatusInterval = setInterval(function () {
|
||||
self.queryStatus()
|
||||
}, queryInterval)
|
||||
|
||||
self.emit('state')
|
||||
})
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Server.prototype.stop = function (cb) {
|
||||
var handled = false
|
||||
var self = this
|
||||
|
||||
var finalHandler = function () {
|
||||
if (!handled) {
|
||||
handled = true
|
||||
|
||||
self.readServerConfig(function (err, serverConfig) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (self.dedicatedServerId !== serverConfig.dedicatedServerId) {
|
||||
self.dedicatedServerId = serverConfig.dedicatedServerId
|
||||
self.emit('save')
|
||||
}
|
||||
})
|
||||
|
||||
if (cb) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.instance.on('close', finalHandler)
|
||||
|
||||
this.instance.kill()
|
||||
|
||||
setTimeout(finalHandler, 5000)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Server.prototype.toJSON = function () {
|
||||
return {
|
||||
admin_password: this.admin_password,
|
||||
auto_start: this.auto_start,
|
||||
battle_eye: this.battle_eye,
|
||||
dedicatedServerId: this.dedicatedServerId,
|
||||
id: this.id,
|
||||
max_players: this.max_players,
|
||||
missions: this.missions,
|
||||
mods: this.mods,
|
||||
password: this.password,
|
||||
pid: this.pid,
|
||||
port: this.port,
|
||||
state: this.state,
|
||||
title: this.title
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server
|
7
lib/reforger/stripBOM.js
Normal file
7
lib/reforger/stripBOM.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function stripBOM (data) {
|
||||
if (data.charCodeAt(0) === 0xFEFF) {
|
||||
return data.slice(1)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
@ -39,6 +39,7 @@
|
||||
"lodash": "^4.17.10",
|
||||
"morgan": "^1.8.1",
|
||||
"multer": "^1.3.0",
|
||||
"public-ip": "^4.0.4",
|
||||
"raw-loader": "^0.5.1",
|
||||
"serve-static": "^1.12.1",
|
||||
"slugify": "^1.1.0",
|
||||
|
@ -4,7 +4,7 @@ var Mission = require('app/models/mission')
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
comparator: function (a, b) {
|
||||
return a.get('name').toLowerCase().localeCompare(b.get('name').toLowerCase())
|
||||
return a.get('missionName').toLowerCase().localeCompare(b.get('missionName').toLowerCase())
|
||||
},
|
||||
model: Mission,
|
||||
url: '/api/missions/'
|
||||
|
@ -4,7 +4,7 @@ var ServerMod = require('app/models/server_mod')
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
comparator: function (a, b) {
|
||||
return a.get('name').toLowerCase().localeCompare(b.get('name').toLowerCase())
|
||||
return a.get('id').toLowerCase().localeCompare(b.get('id').toLowerCase())
|
||||
},
|
||||
model: ServerMod
|
||||
})
|
||||
|
@ -2,8 +2,9 @@ var Backbone = require('backbone')
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
idAttribute: 'name',
|
||||
idAttribute: 'id',
|
||||
urlRoot: '/api/mods/'
|
||||
})
|
||||
|
@ -8,6 +8,7 @@ module.exports = Backbone.Model.extend({
|
||||
allowed_file_patching: 1,
|
||||
auto_start: false,
|
||||
battle_eye: false,
|
||||
dedicatedServerId: '',
|
||||
file_patching: false,
|
||||
forcedDifficulty: '',
|
||||
max_players: null,
|
||||
@ -17,7 +18,7 @@ module.exports = Backbone.Model.extend({
|
||||
parameters: [],
|
||||
password: '',
|
||||
persistent: false,
|
||||
port: 2302,
|
||||
port: 2001,
|
||||
state: null,
|
||||
title: '',
|
||||
von: false,
|
||||
|
@ -2,9 +2,10 @@ var Backbone = require('backbone')
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
idAttribute: 'name',
|
||||
idAttribute: 'id',
|
||||
isNew: function () {
|
||||
return true
|
||||
}
|
||||
|
@ -16,6 +16,6 @@ module.exports = Marionette.CompositeView.extend({
|
||||
},
|
||||
|
||||
filter: function (child, index, collection) {
|
||||
return child.get('name').toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
return child.get('missionName').toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
}
|
||||
})
|
||||
|
@ -16,8 +16,13 @@ module.exports = Marionette.CompositeView.extend({
|
||||
},
|
||||
|
||||
filter: function (child, index, collection) {
|
||||
var id = child.get('id').toLowerCase()
|
||||
var name = child.get('name').toLowerCase()
|
||||
|
||||
if (id.indexOf(this.filterValue.toLowerCase()) >= 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (name.indexOf(this.filterValue.toLowerCase()) >= 0) {
|
||||
return true
|
||||
}
|
||||
|
@ -35,6 +35,13 @@ module.exports = Marionette.ItemView.extend({
|
||||
title = modFile.name
|
||||
}
|
||||
|
||||
var id = this.model.get('id')
|
||||
var name = this.model.get('name')
|
||||
if (id !== name) {
|
||||
title = name
|
||||
link = 'https://reforger.armaplatform.com/workshop/' + id
|
||||
}
|
||||
|
||||
return {
|
||||
link: link,
|
||||
title: title
|
||||
|
@ -7,6 +7,29 @@ var tpl = require('tpl/servers/info.html')
|
||||
module.exports = Marionette.LayoutView.extend({
|
||||
template: _.template(tpl),
|
||||
|
||||
templateHelpers: function () {
|
||||
var self = this
|
||||
var modNames = this.model.get('mods').map(function (modId) {
|
||||
var mod = self.mods.find(function (mod) {
|
||||
return mod.get('id') === modId
|
||||
})
|
||||
|
||||
if (!mod) {
|
||||
return modId
|
||||
}
|
||||
|
||||
return mod.get('name')
|
||||
})
|
||||
|
||||
return {
|
||||
modNames: modNames.join(', ')
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.mods = options.mods
|
||||
},
|
||||
|
||||
events: {
|
||||
'click #start': 'start',
|
||||
'click #stop': 'stop'
|
||||
|
@ -24,7 +24,7 @@ module.exports = Marionette.ItemView.extend({
|
||||
clone: function (e) {
|
||||
var title = this.model.get('title') + ' Clone'
|
||||
var clone = this.model.clone()
|
||||
clone.set({ id: null, title: title, auto_start: false })
|
||||
clone.set({ id: null, dedicatedServerId: null, title: title, auto_start: false })
|
||||
clone.save()
|
||||
},
|
||||
|
||||
|
@ -14,7 +14,7 @@ module.exports = Marionette.CompositeView.extend({
|
||||
},
|
||||
|
||||
filter: function (child, index, collection) {
|
||||
return child.get('name').toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
return child.get('missionName').toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
},
|
||||
|
||||
buildChildView: function (item, ChildViewType, childViewOptions) {
|
||||
|
@ -10,8 +10,8 @@ module.exports = ModListItemView.extend({
|
||||
template: template,
|
||||
templateHelpers: function () {
|
||||
var superTemplateHelpers = ModListItemView.prototype.templateHelpers.call(this)
|
||||
var name = this.model.get('name')
|
||||
var modSelected = this.options.selectedModsCollection.get(name)
|
||||
var id = this.model.get('id')
|
||||
var modSelected = this.options.selectedModsCollection.get(id)
|
||||
|
||||
return Object.assign({}, superTemplateHelpers, {
|
||||
isDisabledButton: function () {
|
||||
@ -27,6 +27,7 @@ module.exports = ModListItemView.extend({
|
||||
addMod: function (e) {
|
||||
e.preventDefault()
|
||||
this.options.selectedModsCollection.add(new ServerMod({
|
||||
id: this.model.get('id'),
|
||||
name: this.model.get('name')
|
||||
}))
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ module.exports = ModsView.extend({
|
||||
return this.model.get('mods')
|
||||
.map(function (mod) {
|
||||
return {
|
||||
name: mod
|
||||
id: mod
|
||||
}
|
||||
})
|
||||
},
|
||||
@ -69,7 +69,7 @@ module.exports = ModsView.extend({
|
||||
return {
|
||||
mods: this.selectedModsCollection.toJSON()
|
||||
.map(function (mod) {
|
||||
return mod.name
|
||||
return mod.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,6 @@ module.exports = Marionette.CompositeView.extend({
|
||||
|
||||
addMod: function (e) {
|
||||
e.preventDefault()
|
||||
this.collection.add(new ServerMod({ name: 'Unlisted' }))
|
||||
this.collection.add(new ServerMod({ id: 'Unlisted' }))
|
||||
}
|
||||
})
|
||||
|
@ -12,7 +12,7 @@ module.exports = ModListItemView.extend({
|
||||
events: {
|
||||
'click button.delete': 'delete',
|
||||
'change select#difficulty': 'changed',
|
||||
'change input#name': 'changed'
|
||||
'change input#id': 'changed'
|
||||
},
|
||||
|
||||
changed: function (e) {
|
||||
|
@ -39,7 +39,7 @@ module.exports = Marionette.LayoutView.extend({
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.infoView.show(new InfoView({ model: this.model }))
|
||||
this.infoView.show(new InfoView({ model: this.model, mods: this.mods }))
|
||||
this.missionsView.show(new MissionsView({ missions: this.missions, model: this.model }))
|
||||
this.modsView.show(new ModsView({ model: this.model, mods: this.mods, server: this.model }))
|
||||
this.parametersView.show(new ParametersListView({ model: this.model }))
|
||||
|
@ -1,5 +1,5 @@
|
||||
<td>
|
||||
<%-name%>
|
||||
<%-id%>
|
||||
<% if (link) { %>
|
||||
<a href='<%-link%>' target=_blank rel='noopener noreferrer'>
|
||||
<%-title%>
|
||||
|
@ -25,6 +25,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (typeof(dedicatedServerId) != "undefined" && dedicatedServerId) { %>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-1 control-label">Server ID</label>
|
||||
<div class="col-sm-11">
|
||||
<p class="form-control-static"><%= dedicatedServerId %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-1 control-label">Port</label>
|
||||
<div class="col-sm-11">
|
||||
@ -35,7 +44,7 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-1 control-label">Mods</label>
|
||||
<div class="col-sm-11">
|
||||
<p class="form-control-static"><%- mods.join(', ') %></p>
|
||||
<p class="form-control-static"><%- modNames %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<td>
|
||||
<%-name%>
|
||||
<%-id%>
|
||||
<% if (link) { %>
|
||||
<a href='<%-link%>' target=_blank rel='noopener noreferrer'>
|
||||
<%-title%>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<td>
|
||||
<form class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-sm-2 control-label">Name</label>
|
||||
<label for="id" class="col-sm-2 control-label">ID</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="name" placeholder="Name" value="<%- name %>">
|
||||
<input type="text" class="form-control" id="id" placeholder="ID" value="<%- id %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -13,7 +13,7 @@ module.exports = function (logsManager) {
|
||||
})
|
||||
})
|
||||
|
||||
router.delete('/:log', function (req, res) {
|
||||
router.delete('/:log(*)', function (req, res) {
|
||||
var filename = req.params.log
|
||||
logsManager.delete(filename, function (err) {
|
||||
if (err) {
|
||||
@ -24,7 +24,7 @@ module.exports = function (logsManager) {
|
||||
})
|
||||
})
|
||||
|
||||
router.get('/:log/:mode', function (req, res) {
|
||||
router.get('/:log(*)/:mode', function (req, res) {
|
||||
var requestedFilename = req.params.log
|
||||
var mode = req.params.mode === 'view' ? 'view' : 'download'
|
||||
|
||||
|
Reference in New Issue
Block a user