diff --git a/lib/db/api.js b/lib/db/api.js index 3345dc38..1b07c8c2 100644 --- a/lib/db/api.js +++ b/lib/db/api.js @@ -130,6 +130,7 @@ dbapi.saveDevice = function(serial, device) { , statusChangedAt: r.now() , createdAt: r.now() , lastHeartbeatAt: r.now() + , reverseForwards: [] } return db.run(r.table('devices').get(serial).update(data)) .then(function(stats) { @@ -230,6 +231,12 @@ dbapi.setDeviceRotation = function(serial, rotation) { })) } +dbapi.setDeviceReverseForwards = function(serial, forwards) { + return db.run(r.table('devices').get(serial).update({ + reverseForwards: forwards + })) +} + dbapi.setDeviceChannel = function(serial, channel) { return db.run(r.table('devices').get(serial).update({ channel: channel diff --git a/lib/units/device/plugins/forward/index.js b/lib/units/device/plugins/forward/index.js index 127b3f30..e0eb0018 100644 --- a/lib/units/device/plugins/forward/index.js +++ b/lib/units/device/plugins/forward/index.js @@ -2,6 +2,7 @@ var net = require('net') var Promise = require('bluebird') var syrup = require('syrup') +var _ = require('lodash') var wire = require('../../../../wire') var logger = require('../../../../util/logger') @@ -61,33 +62,37 @@ module.exports = syrup.serial() }) } - plugin.createForward = function(port, to) { + plugin.createForward = function(id, forward) { log.info( - 'Creating reverse port forward from ":%d" to "%s:%d"' - , port - , to.host - , to.port + 'Creating reverse port forward "%s" from ":%d" to "%s:%d"' + , id + , forward.devicePort + , forward.targetHost + , forward.targetPort ) return connectService(1) .then(function(out) { var header = new Buffer(4) header.writeUInt16LE(0, 0) - header.writeUInt16LE(port, 2) + header.writeUInt16LE(forward.devicePort, 2) out.write(header) - return manager.add(port, out, to) + return manager.add(id, out, forward) }) } - plugin.removeForward = function(port) { - log.info('Removing reverse port forward ":%d"', port) - manager.remove(port) + plugin.removeForward = function(id) { + log.info('Removing reverse port forward "%s"', id) + manager.remove(id) return Promise.resolve() } plugin.connect = function(options) { var resolver = Promise.defer() - var conn = net.connect(options) + var conn = net.connect({ + host: options.targetHost + , port: options.targetPort + }) function connectListener() { resolver.resolve(conn) @@ -112,44 +117,29 @@ module.exports = syrup.serial() group.on('leave', plugin.reset) - manager.on('add', function(port, to) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceForwardAddEvent( - options.serial - , port - , to.host - , to.port - )) - ]) - }) + var pushForwards = _.debounce( + function() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.ReverseForwardsEvent( + options.serial + , manager.listAll() + )) + ]) + } + , 200 + ) - manager.on('remove', function(port) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceForwardRemoveEvent( - options.serial - , port - )) - ]) - }) + manager.on('add', pushForwards) + manager.on('remove', pushForwards) return startService() .then(awaitServer) .then(function() { - - plugin.createForward(3000, { - host: '127.0.0.1' - , port: 3000 - }) - router .on(wire.ForwardTestMessage, function(channel, message) { var reply = wireutil.reply(options.serial) - plugin.connect({ - host: message.targetHost - , port: message.targetPort - }) + plugin.connect(message) .then(function(conn) { conn.end() push.send([ @@ -166,10 +156,7 @@ module.exports = syrup.serial() }) .on(wire.ForwardCreateMessage, function(channel, message) { var reply = wireutil.reply(options.serial) - plugin.createForward(message.devicePort, { - host: message.targetHost - , port: message.targetPort - }) + plugin.createForward(message.id, message) .then(function() { push.send([ channel @@ -186,7 +173,7 @@ module.exports = syrup.serial() }) .on(wire.ForwardRemoveMessage, function(channel, message) { var reply = wireutil.reply(options.serial) - plugin.removeForward(message.devicePort) + plugin.removeForward(message.id) .then(function() { push.send([ channel diff --git a/lib/units/device/plugins/forward/util/manager.js b/lib/units/device/plugins/forward/util/manager.js index 8bd2d460..7ecf6d5d 100644 --- a/lib/units/device/plugins/forward/util/manager.js +++ b/lib/units/device/plugins/forward/util/manager.js @@ -7,45 +7,51 @@ var ForwardWriter = require('./writer') // Handles multiple ports function ForwardManager() { - var handlersByPort = Object.create(null) + var handlersById = Object.create(null) - this.has = function(port) { - return !!handlersByPort[port] + this.has = function(id) { + return !!handlersById[id] } - this.add = function(port, conn, to) { + this.add = function(id, conn, options) { function endListener() { - delete handlersByPort[port] - this.emit('remove', port, to) + delete handlersById[id] + this.emit('remove', id, options) } - var handler = new ForwardHandler(conn, to) + if (this.has(id)) { + this.remove(id) + } + + var handler = new ForwardHandler(conn, options) handler.on('end', endListener.bind(this)) - handlersByPort[port] = handler + handlersById[id] = handler - this.emit('add', port, to) + this.emit('add', id, options) } - this.remove = function(port) { - var handler = handlersByPort[port] + this.remove = function(id) { + var handler = handlersById[id] if (handler) { handler.end() } } this.removeAll = function() { - Object.keys(handlersByPort).forEach(function(port) { - handlersByPort[port].end() + Object.keys(handlersById).forEach(function(id) { + handlersById[id].end() }) } this.listAll = function() { - return Object.keys(handlersByPort).map(function(port) { - var handler = handlersByPort[port] + return Object.keys(handlersById).map(function(id) { + var handler = handlersById[id] return { - port: port - , to: handler.to + id: id + , devicePort: handler.options.devicePort + , targetHost: handler.options.targetHost + , targetPort: handler.options.targetPort } }) } @@ -56,7 +62,7 @@ function ForwardManager() { util.inherits(ForwardManager, events.EventEmitter) // Handles a single port -function ForwardHandler(conn, to) { +function ForwardHandler(conn, options) { var destHandlersById = Object.create(null) function endListener() { @@ -73,7 +79,7 @@ function ForwardHandler(conn, to) { if (packet) { if (!dest) { // Let's create a new connection - dest = destHandlersById[id] = new DestHandler(id, conn, to) + dest = destHandlersById[id] = new DestHandler(id, conn, options) dest.on('end', packetEndListener.bind(null, id)) } @@ -87,11 +93,16 @@ function ForwardHandler(conn, to) { } } + function readableListener() { + // No-op but must exist so that we get the 'end' event. + } + conn.pipe(new ForwardReader()) .on('end', endListener.bind(this)) .on('packet', packetListener) + .on('readable', readableListener) - this.to = to + this.options = options this.end = function() { conn.end() @@ -103,7 +114,7 @@ function ForwardHandler(conn, to) { util.inherits(ForwardHandler, events.EventEmitter) // Handles a single connection -function DestHandler(id, conn, to) { +function DestHandler(id, conn, options) { function endListener() { conn.removeListener('drain', drainListener) this.emit('end') @@ -133,7 +144,10 @@ function DestHandler(id, conn, to) { } } - var dest = net.connect(to) + var dest = net.connect({ + host: options.targetHost + , port: options.targetPort + }) .on('error', errorListener) var writer = dest.pipe(new ForwardWriter(id)) diff --git a/lib/units/processor/index.js b/lib/units/processor/index.js index a83b770e..c931460a 100644 --- a/lib/units/processor/index.js +++ b/lib/units/processor/index.js @@ -154,6 +154,10 @@ module.exports = function(options) { dbapi.setDeviceRotation(message.serial, message.rotation) appDealer.send([channel, data]) }) + .on(wire.ReverseForwardsEvent, function(channel, message, data) { + dbapi.setDeviceReverseForwards(message.serial, message.forwards) + appDealer.send([channel, data]) + }) .handler()) lifecycle.observe(function() { diff --git a/lib/units/websocket/index.js b/lib/units/websocket/index.js index bae570b2..2d63dca9 100644 --- a/lib/units/websocket/index.js +++ b/lib/units/websocket/index.js @@ -106,6 +106,7 @@ module.exports = function(options) { , provider: message.provider , present: true , owner: null + , reverseForwards: [] } }) }) @@ -240,6 +241,15 @@ module.exports = function(options) { } }) }) + .on(wire.ReverseForwardsEvent, function(channel, message) { + socket.emit('device.change', { + important: false + , data: { + serial: message.serial + , reverseForwards: message.forwards + } + }) + }) .handler() // Global messages @@ -676,7 +686,6 @@ module.exports = function(options) { if (!data.targetHost || data.targetHost === 'localhost') { data.targetHost = socket.request.ip } - socket.emit('forward.create', data) joinChannel(responseChannel) push.send([ channel @@ -687,7 +696,6 @@ module.exports = function(options) { ]) }) .on('forward.remove', function(channel, responseChannel, data) { - socket.emit('forward.remove', data) joinChannel(responseChannel) push.send([ channel diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 4f19b7ef..e75017b6 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -70,8 +70,7 @@ enum MessageType { AccountGetMessage = 62; AccountRemoveMessage = 55; SdStatusMessage = 61; - DeviceForwardAddEvent = 72; - DeviceForwardRemoveEvent = 73; + ReverseForwardsEvent = 72; } message Envelope { @@ -399,25 +398,26 @@ message ForwardTestMessage { } message ForwardCreateMessage { - required uint32 devicePort = 1; - required string targetHost = 2; - required uint32 targetPort = 3; -} - -message ForwardRemoveMessage { - required uint32 devicePort = 1; -} - -message DeviceForwardAddEvent { - required string serial = 1; + required string id = 1; required uint32 devicePort = 2; required string targetHost = 3; required uint32 targetPort = 4; } -message DeviceForwardRemoveEvent { - required string serial = 1; +message ForwardRemoveMessage { + required string id = 1; +} + +message ReverseForward { + required string id = 1; required uint32 devicePort = 2; + required string targetHost = 3; + required uint32 targetPort = 4; +} + +message ReverseForwardsEvent { + required string serial = 1; + repeated ReverseForward forwards = 2; } message BrowserOpenMessage { diff --git a/res/app/components/stf/control/control-service.js b/res/app/components/stf/control/control-service.js index 27a2322b..2273d9e5 100644 --- a/res/app/components/stf/control/control-service.js +++ b/res/app/components/stf/control/control-service.js @@ -172,7 +172,8 @@ module.exports = function ControlServiceFactory( this.createForward = function(forward) { return sendTwoWay('forward.create', { - devicePort: forward.devicePort + id: forward.id + , devicePort: forward.devicePort , targetHost: forward.targetHost , targetPort: forward.targetPort }) @@ -180,7 +181,7 @@ module.exports = function ControlServiceFactory( this.removeForward = function(forward) { return sendTwoWay('forward.remove', { - devicePort: forward.devicePort + id: forward.id }) } diff --git a/res/app/control-panes/advanced/port-forwarding/index.js b/res/app/control-panes/advanced/port-forwarding/index.js index 239e9dd7..a92201c9 100644 --- a/res/app/control-panes/advanced/port-forwarding/index.js +++ b/res/app/control-panes/advanced/port-forwarding/index.js @@ -3,13 +3,8 @@ require('./port-forwarding.css') module.exports = angular.module('stf.port-forwarding', [ require('stf/common-ui/table').name, require('stf/settings').name, - require('gettext').name, - require('angular-xeditable').name + require('gettext').name ]) - .run(function (editableOptions) { - editableOptions.theme = 'bs3' - - }) .run(["$templateCache", function ($templateCache) { $templateCache.put( 'control-panes/advanced/port-forwarding/port-forwarding.jade', diff --git a/res/app/control-panes/advanced/port-forwarding/port-forwarding-controller.js b/res/app/control-panes/advanced/port-forwarding/port-forwarding-controller.js index 4d04e151..8163c8a9 100644 --- a/res/app/control-panes/advanced/port-forwarding/port-forwarding-controller.js +++ b/res/app/control-panes/advanced/port-forwarding/port-forwarding-controller.js @@ -1,42 +1,67 @@ -var _ = require('lodash') +var uuid = require('node-uuid') +var Promise = require('bluebird') -module.exports = function PortForwardingCtrl($scope - , SettingsService) { - - var defaultPortForwards = { - targetHost: 'localhost' +module.exports = function PortForwardingCtrl( + $scope +, SettingsService +) { + function defaults(id) { + return { + id: id + , targetHost: 'localhost' , targetPort: 8080 , devicePort: 8080 , enabled: false + } } - $scope.reversePortForwards = [ - defaultPortForwards - ] + $scope.reversePortForwards = [defaults('_default')] SettingsService.bind($scope, { target: 'reversePortForwards' - , source: 'reversePortForwards' + , source: 'reversePortForwards' }) - $scope.enableForward = function (forward) { - forward.processing = true - return $scope.control.createForward(forward) - .finally(function () { - forward.processing = false + $scope.$watch('device.reverseForwards', function(newValue) { + var map = Object.create(null) + + if (newValue) { + newValue.forEach(function(forward) { + map[forward.id] = forward }) + } + + $scope.reversePortForwards.forEach(function(forward) { + var deviceForward = map[forward.id] + forward.enabled = !!(deviceForward && deviceForward.id === forward.id && + deviceForward.devicePort == forward.devicePort) + }) + }) + + $scope.applyForward = function (forward) { + return forward.enabled + ? $scope.control.createForward(forward) + : $scope.control.removeForward(forward) + } + + $scope.enableForward = function (forward) { + if (forward.enabled) { + return Promise.resolve() + } + + return $scope.control.createForward(forward) } $scope.disableForward = function (forward) { - forward.processing = true + if (!forward.enabled) { + return Promise.resolve() + } + return $scope.control.removeForward(forward) - .finally(function () { - forward.processing = false - }) } $scope.addRow = function () { - $scope.reversePortForwards.push(defaultPortForwards) + $scope.reversePortForwards.push(defaults(uuid.v4())) } $scope.removeRow = function (forward) { @@ -44,13 +69,4 @@ module.exports = function PortForwardingCtrl($scope $scope.reversePortForwards.splice( $scope.reversePortForwards.indexOf(forward), 1) } - - $scope.showApply = function () { - $scope.isShowApply = true - } - - $scope.applyTable = function () { - console.log('TODO: Sync back the changes') - $scope.isShowApply = false - } } diff --git a/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade b/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade index a5d8c4a0..a9af6c97 100644 --- a/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade +++ b/res/app/control-panes/advanced/port-forwarding/port-forwarding.jade @@ -9,7 +9,6 @@ i.fa.fa-plus.fa-fw .widget-content.padded(collapse='isCollapsed') - //form(editable-form, name='table') table.table.table-bordered.table-hover.table-condensed tr @@ -38,24 +37,17 @@ td(width='35%') div.input-group span.input-group-addon - input(type='checkbox', ng-model='forward.enabled') - input.form-control(type='number', min='0', ng-model='forward.devicePort', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Port"|translate}}', ng-change='showApply()') + input(type='checkbox', ng-model='forward.enabled', ng-change='applyForward(forward)') + input.form-control(type='number', min='0', ng-model='forward.devicePort', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Port"|translate}}', ng-change='disableForward(forward)') td i.fa.fa-arrow-right.fa-fw td(width='40%') - input.form-control(type='text', ng-model='forward.targetHost', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Hostname"|translate}}', ng-change='showApply()') + input.form-control(type='text', ng-model='forward.targetHost', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Hostname"|translate}}', ng-change='disableForward(forward)') td span : td(width='25%') - input.form-control(type='number', min='0', ng-model='forward.targetPort', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Port"|translate}}', ng-change='showApply()') + input.form-control(type='number', min='0', ng-model='forward.targetPort', ng-model-options="{ updateOn: 'default blur' }", placeholder='{{"Port"|translate}}', ng-change='disableForward(forward)') td button.btn.btn-sm.btn-danger-outline(ng-click='removeRow(forward)') i.fa.fa-trash-o //span(translate) Remove - - div(ng-show='isShowApply') - button.btn.btn-sm.btn-primary-outline.pull-right(ng-click='applyTable()') - //i.fa.fa-trash-o - span(translate) Apply - br - br