diff --git a/README.md b/README.md index cb31db5c..8e872416 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ A simple to use web admin panel for Arma servers. - cwa (does not support linux) - ofp - ofpresistance +- reforger ## Config diff --git a/app.js b/app.js index e8ddd73a..71a8bd66 100644 --- a/app.js +++ b/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) diff --git a/config.docker.js b/config.docker.js index e235de35..efa96c54 100644 --- a/config.docker.js +++ b/config.docker.js @@ -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_LOGS', 'REFORGER_PROFILES', '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, + logs: process.env.REFORGER_LOGS, + profiles: process.env.REFORGER_PROFILES, + workshop: process.env.REFORGER_WORKSHOP + }, prefix: process.env.SERVER_PREFIX, suffix: process.env.SERVER_SUFFIX, logFormat: process.env.LOG_FORMAT || 'dev' diff --git a/config.js.example b/config.js.example index 2731db86..58d43594 100644 --- a/config.js.example +++ b/config.js.example @@ -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 + logs: 'path-to-logs-folder', // Folder where server log files will be stored + profiles: 'path-to-profiles-folder', // Folder where logs and persistence data will be stored. Each server will create a subfolder. + 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', diff --git a/lib/mods/folderSize.js b/lib/mods/folderSize.js index 196073b8..4e13fd83 100644 --- a/lib/mods/folderSize.js +++ b/lib/mods/folderSize.js @@ -3,16 +3,15 @@ var fs = require('fs') var glob = require('glob') var path = require('path') -module.exports = function (modPath, config, callback) { - var basePath = path.resolve(config.path, modPath) +module.exports = function (modPath, callback) { var total = 0 - glob('**/*', { cwd: basePath, dot: true }, function (err, files) { + glob('**/*', { cwd: modPath, dot: true }, function (err, files) { if (err) { return callback(err, 0) } async.forEach(files, function (file, cb) { - fs.stat(path.join(basePath, file), function stat (err, stats) { + fs.stat(path.join(modPath, file), function stat (err, stats) { if (!err && (stats.isFile() || stats.isSymbolicLink())) { var size = stats.size || 0 total += size diff --git a/lib/mods/index.js b/lib/mods/index.js index b4b4e902..6a4dc90c 100644 --- a/lib/mods/index.js +++ b/lib/mods/index.js @@ -56,7 +56,8 @@ Mods.prototype.resolveModData = function (modPath, cb) { var self = this async.parallel({ folderSize: function (cb) { - folderSize(modPath, self.config, cb) + var basePath = path.resolve(self.config.path, modPath) + folderSize(basePath, cb) }, modFile: function (cb) { modFile(modPath, self.config, cb) @@ -70,6 +71,7 @@ Mods.prototype.resolveModData = function (modPath, cb) { } cb(null, { + id: modPath, name: modPath, size: results.folderSize, formattedSize: filesize(results.folderSize), diff --git a/lib/reforger/logs.js b/lib/reforger/logs.js new file mode 100644 index 00000000..54ec98cf --- /dev/null +++ b/lib/reforger/logs.js @@ -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.logs +} + +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 diff --git a/lib/reforger/manager.js b/lib/reforger/manager.js new file mode 100644 index 00000000..76fcb589 --- /dev/null +++ b/lib/reforger/manager.js @@ -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 diff --git a/lib/reforger/missions.js b/lib/reforger/missions.js new file mode 100644 index 00000000..f92a0835 --- /dev/null +++ b/lib/reforger/missions.js @@ -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 diff --git a/lib/reforger/mods.js b/lib/reforger/mods.js new file mode 100644 index 00000000..607f3185 --- /dev/null +++ b/lib/reforger/mods.js @@ -0,0 +1,93 @@ +var async = require('async') +var events = require('events') +var filesize = require('filesize') +var fs = require('fs.extra') +var glob = require('glob') +var path = require('path') + +var folderSize = require('../mods/folderSize') +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, 'addons', mod), function (err) { + cb(err) + + if (!err) { + self.updateMods() + } + }) +} + +Mods.prototype.updateMods = function () { + var self = this + + glob('**/ServerData.json', { cwd: this.workshopPath() }, function (err, files) { + if (err) { + console.log(err) + return + } + + async.map(files, self.resolveModData.bind(self), function (err, mods) { + if (err) { + console.log(err) + return + } + + self.mods = mods + self.emit('mods', mods) + }) + }) +} + +Mods.prototype.resolveModData = function (serverDataPath, cb) { + var serverDataFile = path.join(this.workshopPath(), serverDataPath) + var modPath = path.dirname(serverDataFile) + async.parallel({ + folderSize: function (cb) { + folderSize(modPath, cb) + }, + serverData: function (cb) { + fs.readFile(serverDataFile, 'utf-8', function (err, data) { + if (err) { + console.log(err) + return cb(err) + } + + try { + var serverData = JSON.parse(stripBOM(data)) + cb(null, serverData) + } catch (err) { + console.log(err) + return cb(err) + } + }) + } + }, function (err, results) { + if (err) { + return cb(err) + } + + cb(null, { + id: results.serverData.id, + name: results.serverData.name, + size: results.folderSize, + formattedSize: filesize(results.folderSize) + }) + }) +} + +module.exports = Mods diff --git a/lib/reforger/server.js b/lib/reforger/server.js new file mode 100644 index 00000000..692de32c --- /dev/null +++ b/lib/reforger/server.js @@ -0,0 +1,248 @@ +var childProcess = require('child_process') +var events = require('events') +var fs = require('fs.extra') +var Gamedig = require('gamedig') +var path = require('path') +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.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: '127.0.0.1', + 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 { + bindAddress: '', + bindPort: this.port, + publicAddress: '', + publicPort: this.port, + a2s: { + address: '127.0.0.1', + port: this.steamQueryPort() + }, + game: { + name: this.createServerTitle(this.title), + password: this.password, + passwordAdmin: this.admin_password, + scenarioId: scenarioId, + maxPlayers: parseInt(this.max_players, 10), + visible: true, + crossPlatform: 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 + } + }) + } + } +} + +Server.prototype.serverConfigDirectory = function () { + return this.config.reforger.configs +} + +Server.prototype.serverConfigFile = function () { + return path.join(this.serverConfigDirectory(), this.generateId() + '.json') +} + +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(), + '-logsDir', + path.join(this.config.reforger.logs, id), + '-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 + 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 finalHandler = function () { + if (!handled) { + handled = true + + 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, + 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 diff --git a/lib/reforger/stripBOM.js b/lib/reforger/stripBOM.js new file mode 100644 index 00000000..66be3e21 --- /dev/null +++ b/lib/reforger/stripBOM.js @@ -0,0 +1,7 @@ +module.exports = function stripBOM (data) { + if (data.charCodeAt(0) === 0xFEFF) { + return data.slice(1) + } + + return data +} diff --git a/public/js/app/collections/missions.js b/public/js/app/collections/missions.js index bf4b05c0..ac371dcb 100644 --- a/public/js/app/collections/missions.js +++ b/public/js/app/collections/missions.js @@ -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/' diff --git a/public/js/app/collections/server_mods.js b/public/js/app/collections/server_mods.js index abd3f327..0d78febd 100644 --- a/public/js/app/collections/server_mods.js +++ b/public/js/app/collections/server_mods.js @@ -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 }) diff --git a/public/js/app/models/mod.js b/public/js/app/models/mod.js index f5a278f5..4278507a 100644 --- a/public/js/app/models/mod.js +++ b/public/js/app/models/mod.js @@ -2,8 +2,9 @@ var Backbone = require('backbone') module.exports = Backbone.Model.extend({ defaults: { + id: '', name: '' }, - idAttribute: 'name', + idAttribute: 'id', urlRoot: '/api/mods/' }) diff --git a/public/js/app/models/server.js b/public/js/app/models/server.js index 7d13ddaa..f7c158a2 100644 --- a/public/js/app/models/server.js +++ b/public/js/app/models/server.js @@ -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, diff --git a/public/js/app/models/server_mod.js b/public/js/app/models/server_mod.js index 95e0eab1..00ef55e3 100644 --- a/public/js/app/models/server_mod.js +++ b/public/js/app/models/server_mod.js @@ -2,9 +2,10 @@ var Backbone = require('backbone') module.exports = Backbone.Model.extend({ defaults: { + id: '', name: '' }, - idAttribute: 'name', + idAttribute: 'id', isNew: function () { return true } diff --git a/public/js/app/views/missions/list.js b/public/js/app/views/missions/list.js index 86994e6b..1517a47e 100644 --- a/public/js/app/views/missions/list.js +++ b/public/js/app/views/missions/list.js @@ -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 } }) diff --git a/public/js/app/views/mods/list.js b/public/js/app/views/mods/list.js index 538d480f..1eff3787 100644 --- a/public/js/app/views/mods/list.js +++ b/public/js/app/views/mods/list.js @@ -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 } diff --git a/public/js/app/views/mods/list_item.js b/public/js/app/views/mods/list_item.js index 300dc44d..be20d31a 100644 --- a/public/js/app/views/mods/list_item.js +++ b/public/js/app/views/mods/list_item.js @@ -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 diff --git a/public/js/app/views/servers/info.js b/public/js/app/views/servers/info.js index fb5de699..677cb240 100644 --- a/public/js/app/views/servers/info.js +++ b/public/js/app/views/servers/info.js @@ -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' diff --git a/public/js/app/views/servers/list_item.js b/public/js/app/views/servers/list_item.js index 575e899c..795a9395 100644 --- a/public/js/app/views/servers/list_item.js +++ b/public/js/app/views/servers/list_item.js @@ -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() }, diff --git a/public/js/app/views/servers/missions/available/list.js b/public/js/app/views/servers/missions/available/list.js index f24bb632..9c6a9e39 100644 --- a/public/js/app/views/servers/missions/available/list.js +++ b/public/js/app/views/servers/missions/available/list.js @@ -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) { diff --git a/public/js/app/views/servers/mods/available/list_item.js b/public/js/app/views/servers/mods/available/list_item.js index 36d1b54a..8fd417d2 100644 --- a/public/js/app/views/servers/mods/available/list_item.js +++ b/public/js/app/views/servers/mods/available/list_item.js @@ -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') })) } diff --git a/public/js/app/views/servers/mods/index.js b/public/js/app/views/servers/mods/index.js index 07bfaa43..eb6cd1ea 100644 --- a/public/js/app/views/servers/mods/index.js +++ b/public/js/app/views/servers/mods/index.js @@ -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 }) } } diff --git a/public/js/app/views/servers/mods/selected/list.js b/public/js/app/views/servers/mods/selected/list.js index 4a8bc5ca..540ae1fd 100644 --- a/public/js/app/views/servers/mods/selected/list.js +++ b/public/js/app/views/servers/mods/selected/list.js @@ -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' })) } }) diff --git a/public/js/app/views/servers/mods/selected/list_item.js b/public/js/app/views/servers/mods/selected/list_item.js index 9e96fa12..569334aa 100644 --- a/public/js/app/views/servers/mods/selected/list_item.js +++ b/public/js/app/views/servers/mods/selected/list_item.js @@ -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) { diff --git a/public/js/app/views/servers/view.js b/public/js/app/views/servers/view.js index 785a5e5f..ac90ea8a 100644 --- a/public/js/app/views/servers/view.js +++ b/public/js/app/views/servers/view.js @@ -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 })) diff --git a/public/js/tpl/mods/list_item.html b/public/js/tpl/mods/list_item.html index c1209970..244dfce5 100644 --- a/public/js/tpl/mods/list_item.html +++ b/public/js/tpl/mods/list_item.html @@ -1,5 +1,5 @@ - <%-name%> + <%-id%> <% if (link) { %> <%-title%> diff --git a/public/js/tpl/servers/info.html b/public/js/tpl/servers/info.html index aa566eed..f6c433d5 100644 --- a/public/js/tpl/servers/info.html +++ b/public/js/tpl/servers/info.html @@ -25,6 +25,15 @@ +<% if (typeof(dedicatedServerId) != "undefined" && dedicatedServerId) { %> +
+ +
+

<%= dedicatedServerId %>

+
+
+<% } %> +
@@ -35,7 +44,7 @@
-

<%- mods.join(', ') %>

+

<%- modNames %>

diff --git a/public/js/tpl/servers/mods/available/list_item.html b/public/js/tpl/servers/mods/available/list_item.html index 8d1c6f00..fd2adbf9 100644 --- a/public/js/tpl/servers/mods/available/list_item.html +++ b/public/js/tpl/servers/mods/available/list_item.html @@ -1,5 +1,5 @@ - <%-name%> + <%-id%> <% if (link) { %>
<%-title%> diff --git a/public/js/tpl/servers/mods/selected/list_item.html b/public/js/tpl/servers/mods/selected/list_item.html index 2e7234a3..406d4545 100644 --- a/public/js/tpl/servers/mods/selected/list_item.html +++ b/public/js/tpl/servers/mods/selected/list_item.html @@ -1,9 +1,9 @@
- +
- +
diff --git a/routes/logs.js b/routes/logs.js index d86a5ffc..2efbeeea 100644 --- a/routes/logs.js +++ b/routes/logs.js @@ -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'