diff --git a/migrations/5.sql b/migrations/5.sql new file mode 100644 index 0000000..34f1001 --- /dev/null +++ b/migrations/5.sql @@ -0,0 +1,2 @@ +ALTER TABLE `devices` ADD COLUMN `ios_version` VARCHAR(64) DEFAULT NULL; +ALTER TABLE `devices` ADD COLUMN `ipa_version` VARCHAR(64) DEFAULT NULL; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 06249a4..4c422e1 100644 --- a/src/index.js +++ b/src/index.js @@ -17,9 +17,10 @@ const apiRoutes = require('./routes/api.js'); const timezones = require('../static/data/timezones.json'); // TODO: Create route classes -// TODO: iOS and IPA version // TODO: Fix devices scroll with DataTables -// TODO: Fix dropdown closes when table freshes +// TODO: Secure /api/config/:uuid endpoint with token +// TODO: Provider option to show/hide config options +// TODO: Accomodate for # in uuid name const defaultData = { title: config.title, @@ -36,6 +37,7 @@ dbMigrator.load(); app.set('view engine', 'mustache'); app.set('views', path.resolve(__dirname, 'views')); app.engine('mustache', mustacheExpress()); +app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' })); // for parsing application/x-www-form-urlencoded //app.use(bodyParser.raw({ type: 'application/x-www-form-urlencoded' })); app.use(express.static(path.resolve(__dirname, '../static'))); @@ -53,7 +55,7 @@ app.use('/api', apiRoutes); // Login middleware app.use(function(req, res, next) { - if (req.path === '/api/login' || req.path === '/login' || req.path.includes('/api/config/')) { + if (req.path === '/api/login' || req.path === '/login' || req.path.includes('/api/config')) { return next(); } if (req.session.loggedin) { @@ -256,7 +258,31 @@ app.get('/schedule/delete/:name', function(req, res) { // Settings UI Routes app.get('/settings', function(req, res) { - res.render('settings', defaultData); + var data = defaultData; + data.title = config.title; + data.host = config.db.host; + data.port = config.db.port; + data.username = config.db.username; + data.password = config.db.password; + data.database = config.db.database; + data.charset = config.db.charset; + data.styles = [ + { 'name': 'dark' }, + { 'name': 'light' } + ]; + data.styles.forEach(function(style) { + style.selected = style.name === config.style; + }); + data.languages = [ + { 'name': 'en' }, + { 'name': 'es' } + ]; + data.languages.forEach(function(locale) { + locale.selected = locale.name === config.locale; + }); + data.logging = config.logging ? 'checked' : ''; + console.log('Settings:', data); + res.render('settings', data); }); app.listen(config.port, config.interface, () => console.log(`Listening on port ${config.port}...`)); \ No newline at end of file diff --git a/src/models/config.js b/src/models/config.js index 6359f1f..11e73ed 100644 --- a/src/models/config.js +++ b/src/models/config.js @@ -4,7 +4,7 @@ const query = require('../db.js'); class Config { constructor(name, backendUrl, dataEndpoints, token, heartbeatMaxTime, minDelayLogout, - accountManager, deployEggs, nearbyTracker, autoLogin, isDefault) { + accountManager, deployEggs, nearbyTracker, autoLogin, isDefault) { this.name = name; this.backendUrl = backendUrl; this.dataEndpoints = dataEndpoints; @@ -50,13 +50,13 @@ class Config { return data; } static async create(name, backendUrl, dataEndpoints, token, heartbeatMaxTime, minDelayLogout, - accountManager, deployEggs, nearbyTracker, autoLogin, isDefault) { + accountManager, deployEggs, nearbyTracker, autoLogin, isDefault) { var sql = ` INSERT INTO configs (name, backend_url, data_endpoints, token, heartbeat_max_time, min_delay_logout, account_manager, deploy_eggs, nearby_tracker, auto_login, is_default) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; var args = [name, backendUrl, dataEndpoints, token, heartbeatMaxTime, minDelayLogout, - accountManager, deployEggs, nearbyTracker, autoLogin, isDefault]; + accountManager, deployEggs, nearbyTracker, autoLogin, isDefault]; var result = await query(sql, args); return result.affectedRows === 1; } diff --git a/src/models/device.js b/src/models/device.js index fb340f2..5417f64 100644 --- a/src/models/device.js +++ b/src/models/device.js @@ -3,19 +3,21 @@ const query = require('../db.js'); class Device { - constructor(uuid, config, lastSeen, clientip) { + constructor(uuid, config, lastSeen, clientip, iosVersion, ipaVersion) { this.uuid = uuid; this.config = config; this.lastSeen = lastSeen; this.clientip = clientip; + this.iosVersion = iosVersion; + this.ipaVersion = ipaVersion; } static async getAll() { - var devices = await query('SELECT uuid, config, last_seen, clientip FROM devices'); + var devices = await query('SELECT uuid, config, last_seen, clientip, ios_version, ipa_version FROM devices'); return devices; } static async getByName(uuid) { var sql = ` - SELECT uuid, config, last_seen, clientip + SELECT uuid, config, last_seen, clientip, ios_version, ipa_version FROM devices WHERE uuid = ?`; var args = [uuid]; @@ -27,21 +29,25 @@ class Device { result[0].uuid, result[0].config, result[0].last_seen, - result[0].clientip + result[0].clientip, + result[0].ios_version, + result[0].ipa_version ); } - static async create(uuid, config = null, lastSeen = null, clientip = null) { + static async create(uuid, config = null, lastSeen = null, clientip = null, iosVersion = null, ipaVersion = null) { var sql = ` - INSERT INTO devices (uuid, config, last_seen, clientip) - VALUES (?, ?, ?, ?)`; - var args = [uuid, config, lastSeen, clientip]; + INSERT INTO devices (uuid, config, last_seen, clientip, ios_version, ipa_version) + VALUES (?, ?, ?, ?, ?, ?)`; + var args = [uuid, config, lastSeen, clientip, iosVersion, ipaVersion]; var result = await query(sql, args); if (result.affectedRows === 1) { return new Device( uuid, config, lastSeen, - clientip + clientip, + iosVersion, + ipaVersion ); } return null; @@ -55,9 +61,9 @@ class Device { async save() { var sql = ` UPDATE devices - SET config = ?, last_seen = ?, clientip = ? + SET config = ?, last_seen = ?, clientip = ?, ios_version = ?, ipa_version = ? WHERE uuid = ?`; - var args = [this.config, this.lastSeen, this.clientip, this.uuid]; + var args = [this.config, this.lastSeen, this.clientip, this.iosVersion, this.ipaVersion, this.uuid]; var result = await query(sql, args); return result.affectedRows === 1; } diff --git a/src/models/log.js b/src/models/log.js index 74e205c..21f755d 100644 --- a/src/models/log.js +++ b/src/models/log.js @@ -15,7 +15,7 @@ class Log { this.timestamp = timestamp; this.message = message; } - static async getByDevice(uuid) { + static getByDevice(uuid) { var name = uuid + '.log'; var logFile = path.resolve(logsDir, name); if (!fs.existsSync(logFile)) { @@ -35,7 +35,7 @@ class Log { }); return logs; } - static async create(uuid, message) { + static create(uuid, message) { var name = uuid + '.log'; var logFile = path.resolve(logsDir, name); var msg = { @@ -47,7 +47,7 @@ class Log { if (err) throw err; }); } - static async delete(uuid) { + static delete(uuid) { var name = uuid + '.log'; var logFile = path.resolve(logsDir, name); if (fs.existsSync(logFile)) { @@ -56,7 +56,7 @@ class Log { } return false; } - static async deleteAll() { + static deleteAll() { fs.readdir(logsDir, function(err, files) { if (err) throw err; files.forEach(function(file) { diff --git a/src/routes/api.js b/src/routes/api.js index cd7b914..5074523 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -69,14 +69,43 @@ router.post('/account/change_password/:username', async function(req, res) { }); +// Settings API Routes +router.post('/settings/change_ui', function(req, res) { + var data = req.body; + var newConfig = config; + newConfig.title = data.title; + newConfig.locale = data.locale; + newConfig.style = data.style; + newConfig.logging = data.logging === 'on' ? 1 : 0; + fs.writeFileSync(path.resolve(__dirname, '../config.json'), JSON.stringify(newConfig, null, 2)); + res.redirect('/settings'); +}); + +router.post('/settings/change_db', function(req, res) { + var data = req.body; + var newConfig = config; + newConfig.db.host = data.host; + newConfig.db.port = data.port; + newConfig.db.username = data.username; + newConfig.db.password = data.password; + newConfig.db.database = data.database; + newConfig.db.charset = data.charset; + fs.writeFileSync(path.resolve(__dirname, '../config.json'), JSON.stringify(newConfig, null, 2)); + res.redirect('/settings'); +}); + + // Device API Routes router.get('/devices', async function(req, res) { try { var devices = await Device.getAll(); devices.forEach(function(device) { var exists = fs.existsSync(path.join(screenshotsDir, device.uuid + '.png')); - var image = exists ? `/screenshots/${device.uuid}.png` : '/img/offline.png'; - device.image = ``; + // Device received a config last 15 minutes + var delta = 15 * 60; + var isOffline = device.last_seen > (Math.round((new Date()).getTime() / 1000) - delta) ? 0 : 1; + var image = exists ? `/screenshots/${device.uuid}.png` : (isOffline ? '/img/offline.png' : '/img/online.png'); + device.image = ``; device.last_seen = utils.getDateTime(device.last_seen); device.buttons = `