<commit_before>/* jshint node:true */

// node.js modules
var assert = require('assert'),
    net = require('net'),
    url = require('url'),
    util = require('util');

// 3rd-party modules
var _ = require('underscore'),
    async = require('async');

// Custom modules
var Cache = require('./Cache'),
    NTLM_AD_Proxy = require('./NTLM_AD_Proxy'),
    NTLM_No_Proxy = require('./NTLM_No_Proxy'),
    utils = require('./utils');

// Globals
var cache = new Cache();

module.exports = function(options) {

    // Overwrite the default options by the user-defined
    options = _.extend({
        badrequest: function(request, response, next) {
            response.sendStatus(400);
        },
        internalservererror: function(request, response, next) {
            response.sendStatus(500);
        },
        forbidden: function(request, response, next) {
            response.sendStatus(403);
        },

        prefix: '[express-ntlm]',
        debug: function() {

        },

        domaincontroller: null
    }, options);

    function ntlm_message_type(msg) {
        if (msg.toString('utf8', 0, 8) != 'NTLMSSP\0') {
            return new Error('Not a valid NTLM message:', msg.toString('hex'));
        }
        var msg_type = msg.readUInt8(8);
        if (!~[1, 2, 3].indexOf(msg_type)) {
            return new Error('Incorrect NTLM message Type', msg_type);
        }
        return msg_type;
    }

    function parse_ntlm_authenticate(msg) {
        var NTLMSSP_NEGOTIATE_UNICODE = utils.toBinary('00000001'),
            DomainNameLen = msg.readUInt16LE(0x1C),
            DomainNameBufferOffset = msg.readUInt32LE(0x20),
            DomainName = msg.slice(DomainNameBufferOffset, DomainNameBufferOffset + DomainNameLen),
            UserNameLen = msg.readUInt16LE(0x24),
            UserNameBufferOffset = msg.readUInt32LE(0x28),
            UserName = msg.slice(UserNameBufferOffset, UserNameBufferOffset + UserNameLen),
            WorkstationLen = msg.readUInt16LE(0x2C),
            WorkstationBufferOffset = msg.readUInt32LE(0x30),
            Workstation = msg.slice(WorkstationBufferOffset, WorkstationBufferOffset + WorkstationLen);

        if (utils.isFlagSet(msg.readUInt8(0x3C), utils.toBinary('00000001'))) {
            DomainName = DomainName.toString('utf16le');
            UserName = UserName.toString('utf16le');
            Workstation = Workstation.toString('utf16le');
        } else {
            DomainName = DomainName.toString();
            UserName = UserName.toString();
            Workstation = Workstation.toString();
        }

        return [UserName, DomainName, Workstation];
    }

    function decode_http_authorization_header(auth) {
        var ah = auth.split(' ');
        if (ah.length === 2) {
            if (ah[0] === 'NTLM') {
                return ['NTLM', new Buffer(ah[1], 'base64')];
            }
        }
        return false;
    }

    function handle_unauthorized(request, response, next) {
        response.statusCode = 401;
        response.setHeader('WWW-Authenticate', 'NTLM');
        response.end();
    }

    function connect_to_proxy(type1, callback) {
        var domain = options.domain,
            pdc = options.primarydomaincontroller,
            bdc = options.backupdomaincontroller;

        var proxy,
            ntlm_challenge;

        async.eachSeries(!options.domaincontroller ? [ -1 ] : typeof options.domaincontroller === 'string' ? [ options.domaincontroller ] : options.domaincontroller, function(server, eachDomaincontrollerCallback) {
            if (!server || ntlm_challenge) return eachDomaincontrollerCallback();

            if (server === -1) {
                options.debug(options.prefix, 'No domaincontroller was specified, all Authentication messages are valid.');
                proxy = new NTLM_No_Proxy();
            } else if (!server.indexOf('ldap')) {
                var serverurl = url.parse(server),
                    decoded_path = decodeURI(serverurl.path);
                options.debug(options.prefix, 'Initiating connection to Active Directory server ' + serverurl.host + ' (domain ' + domain + ') using base DN "' + decoded_path + '".');
                proxy = new NTLM_AD_Proxy(serverurl.host, serverurl.port, domain, decoded_path);
            } else {
                return eachDomaincontrollerCallback(new Error('Domaincontroller must be an AD and start with ldap://'));
            }

            proxy.negotiate(type1, function(error, challenge) {
                if (error) {
                    proxy.close();
                    proxy = null;
                    return eachDomaincontrollerCallback(error);
                }

                ntlm_challenge = challenge;

                return eachDomaincontrollerCallback();
            });
        }, function(error) {
            if (error) return callback(error);

            if (!proxy) {
                return callback(new Error('None of the Domain Controllers are available.'));
            }

            return callback(null, proxy, ntlm_challenge);
        });
    }

    function handle_type1(request, response, next, ntlm_message, callback) {
        cache.remove(request.connection.id);
        cache.clean();

        connect_to_proxy(ntlm_message, function(error, proxy, challenge) {
            if (error) return callback(error);

            response.statusCode = 401;
            response.setHeader('WWW-Authenticate', 'NTLM ' + challenge.toString('base64'));
            response.end();

            cache.add(request.connection.id, proxy);

            return callback();
        });
    }

    function handle_type3(request, response, next, ntlm_message, callback) {
        var proxy = cache.get_proxy(request.connection.id);

        var userDomainWorkstation = parse_ntlm_authenticate(ntlm_message),
            user = userDomainWorkstation[0],
            domain = userDomainWorkstation[1],
            workstation = userDomainWorkstation[2];

        if (!domain) {
            domain = options.domain;
        }
        proxy.authenticate(ntlm_message, function(error, result) {
            if (error) return callback(error);

            if (!result) {
                cache.remove(request.connection.id);
                options.debug(options.prefix, 'User ' + domain + '/' + user + ' authentication for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
                return options.forbidden(request, response, next);
            } else {
                var userData = {
                    DomainName: domain,
                    UserName: user,
                    Workstation: workstation
                };

                request.ntlm = userData;
                response.locals.ntlm = userData;
                request.connection.ntlm = userData;

                return next();
            }
        });
    }

    return function(request, response, next) {
        if (!request.connection.id) {
            request.connection.id = utils.uuidv4();
        }

        var auth_headers = request.headers.authorization;

        var user = request.connection.ntlm;
        if (user) {
            options.debug(options.prefix, 'Connection already authenticated ' + user.DomainName + '/' + user.UserName);
            if (auth_headers) {
                if (request.method != 'POST') {
                    request.ntlm = user;
                    response.locals.ntlm = user;
                    return next();
                }
            } else {
                request.ntlm = user;
                response.locals.ntlm = user;
                return next();
            }
        }

        if (!auth_headers) {
            options.debug(options.prefix, 'No Authorization header present');
            return handle_unauthorized(request, response, next);
        }

        var ah_data = decode_http_authorization_header(auth_headers);

        if (!ah_data) {
            options.debug(options.prefix, 'Error when parsing Authorization header for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
            return options.badrequest(request, response, next);
        }

        var ntlm_version = ntlm_message_type(ah_data[1]);

        if (ntlm_version instanceof Error) {
            options.debug(options.prefix, ntlm_version.stack);
            return options.badrequest(request, response, next);
        }

        if (ntlm_version === 1) {
            return handle_type1(request, response, next, ah_data[1], function(error) {
                if (error) {
                    options.debug(options.prefix, error.stack);
                    return options.internalservererror(request, response, next);
                }
            });
        }

        if (ntlm_version === 3) {
            if (typeof cache.get_proxy(request.connection.id) !== 'undefined') {
                return handle_type3(request, response, next, ah_data[1], function(error) {
                    if (error) {
                        options.debug(options.prefix, error.stack);
                        return options.internalservererror(request, response, next);
                    }
                });
            }
            options.debug(options.prefix, 'Unexpected NTLM message Type 3 in new connection for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
            return options.internalservererror(request, response, next);
        }
        options.debug(options.prefix, 'Type 2 message in client request');
        return options.badrequest(request, response, next);
    };
};
<commit_msg>add user data even if connection is forbidden<commit_after>/* jshint node:true */

// node.js modules
var assert = require('assert'),
    net = require('net'),
    url = require('url'),
    util = require('util');

// 3rd-party modules
var _ = require('underscore'),
    async = require('async');

// Custom modules
var Cache = require('./Cache'),
    NTLM_AD_Proxy = require('./NTLM_AD_Proxy'),
    NTLM_No_Proxy = require('./NTLM_No_Proxy'),
    utils = require('./utils');

// Globals
var cache = new Cache();

module.exports = function(options) {

    // Overwrite the default options by the user-defined
    options = _.extend({
        badrequest: function(request, response, next) {
            response.sendStatus(400);
        },
        internalservererror: function(request, response, next) {
            response.sendStatus(500);
        },
        forbidden: function(request, response, next) {
            response.sendStatus(403);
        },

        prefix: '[express-ntlm]',
        debug: function() {

        },

        domaincontroller: null
    }, options);

    function ntlm_message_type(msg) {
        if (msg.toString('utf8', 0, 8) != 'NTLMSSP\0') {
            return new Error('Not a valid NTLM message:', msg.toString('hex'));
        }
        var msg_type = msg.readUInt8(8);
        if (!~[1, 2, 3].indexOf(msg_type)) {
            return new Error('Incorrect NTLM message Type', msg_type);
        }
        return msg_type;
    }

    function parse_ntlm_authenticate(msg) {
        var NTLMSSP_NEGOTIATE_UNICODE = utils.toBinary('00000001'),
            DomainNameLen = msg.readUInt16LE(0x1C),
            DomainNameBufferOffset = msg.readUInt32LE(0x20),
            DomainName = msg.slice(DomainNameBufferOffset, DomainNameBufferOffset + DomainNameLen),
            UserNameLen = msg.readUInt16LE(0x24),
            UserNameBufferOffset = msg.readUInt32LE(0x28),
            UserName = msg.slice(UserNameBufferOffset, UserNameBufferOffset + UserNameLen),
            WorkstationLen = msg.readUInt16LE(0x2C),
            WorkstationBufferOffset = msg.readUInt32LE(0x30),
            Workstation = msg.slice(WorkstationBufferOffset, WorkstationBufferOffset + WorkstationLen);

        if (utils.isFlagSet(msg.readUInt8(0x3C), utils.toBinary('00000001'))) {
            DomainName = DomainName.toString('utf16le');
            UserName = UserName.toString('utf16le');
            Workstation = Workstation.toString('utf16le');
        } else {
            DomainName = DomainName.toString();
            UserName = UserName.toString();
            Workstation = Workstation.toString();
        }

        return [UserName, DomainName, Workstation];
    }

    function decode_http_authorization_header(auth) {
        var ah = auth.split(' ');
        if (ah.length === 2) {
            if (ah[0] === 'NTLM') {
                return ['NTLM', new Buffer(ah[1], 'base64')];
            }
        }
        return false;
    }

    function handle_unauthorized(request, response, next) {
        response.statusCode = 401;
        response.setHeader('WWW-Authenticate', 'NTLM');
        response.end();
    }

    function connect_to_proxy(type1, callback) {
        var domain = options.domain,
            pdc = options.primarydomaincontroller,
            bdc = options.backupdomaincontroller;

        var proxy,
            ntlm_challenge;

        async.eachSeries(!options.domaincontroller ? [ -1 ] : typeof options.domaincontroller === 'string' ? [ options.domaincontroller ] : options.domaincontroller, function(server, eachDomaincontrollerCallback) {
            if (!server || ntlm_challenge) return eachDomaincontrollerCallback();

            if (server === -1) {
                options.debug(options.prefix, 'No domaincontroller was specified, all Authentication messages are valid.');
                proxy = new NTLM_No_Proxy();
            } else if (!server.indexOf('ldap')) {
                var serverurl = url.parse(server),
                    decoded_path = decodeURI(serverurl.path);
                options.debug(options.prefix, 'Initiating connection to Active Directory server ' + serverurl.host + ' (domain ' + domain + ') using base DN "' + decoded_path + '".');
                proxy = new NTLM_AD_Proxy(serverurl.host, serverurl.port, domain, decoded_path);
            } else {
                return eachDomaincontrollerCallback(new Error('Domaincontroller must be an AD and start with ldap://'));
            }

            proxy.negotiate(type1, function(error, challenge) {
                if (error) {
                    proxy.close();
                    proxy = null;
                    return eachDomaincontrollerCallback(error);
                }

                ntlm_challenge = challenge;

                return eachDomaincontrollerCallback();
            });
        }, function(error) {
            if (error) return callback(error);

            if (!proxy) {
                return callback(new Error('None of the Domain Controllers are available.'));
            }

            return callback(null, proxy, ntlm_challenge);
        });
    }

    function handle_type1(request, response, next, ntlm_message, callback) {
        cache.remove(request.connection.id);
        cache.clean();

        connect_to_proxy(ntlm_message, function(error, proxy, challenge) {
            if (error) return callback(error);

            response.statusCode = 401;
            response.setHeader('WWW-Authenticate', 'NTLM ' + challenge.toString('base64'));
            response.end();

            cache.add(request.connection.id, proxy);

            return callback();
        });
    }

    function handle_type3(request, response, next, ntlm_message, callback) {
        var proxy = cache.get_proxy(request.connection.id);

        var userDomainWorkstation = parse_ntlm_authenticate(ntlm_message),
            user = userDomainWorkstation[0],
            domain = userDomainWorkstation[1],
            workstation = userDomainWorkstation[2];

        if (!domain) {
            domain = options.domain;
        }
        proxy.authenticate(ntlm_message, function(error, result) {
            if (error) return callback(error);
            var userData = {
                DomainName: domain,
                UserName: user,
                Workstation: workstation
            };

            request.ntlm = userData;
            response.locals.ntlm = userData;
            request.connection.ntlm = userData;

            if (!result) {
                cache.remove(request.connection.id);
                options.debug(options.prefix, 'User ' + domain + '/' + user + ' authentication for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
                return options.forbidden(request, response, next);
            } else {
                return next();
            }
        });
    }

    return function(request, response, next) {
        if (!request.connection.id) {
            request.connection.id = utils.uuidv4();
        }

        var auth_headers = request.headers.authorization;

        var user = request.connection.ntlm;
        if (user) {
            options.debug(options.prefix, 'Connection already authenticated ' + user.DomainName + '/' + user.UserName);
            if (auth_headers) {
                if (request.method != 'POST') {
                    request.ntlm = user;
                    response.locals.ntlm = user;
                    return next();
                }
            } else {
                request.ntlm = user;
                response.locals.ntlm = user;
                return next();
            }
        }

        if (!auth_headers) {
            options.debug(options.prefix, 'No Authorization header present');
            return handle_unauthorized(request, response, next);
        }

        var ah_data = decode_http_authorization_header(auth_headers);

        if (!ah_data) {
            options.debug(options.prefix, 'Error when parsing Authorization header for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
            return options.badrequest(request, response, next);
        }

        var ntlm_version = ntlm_message_type(ah_data[1]);

        if (ntlm_version instanceof Error) {
            options.debug(options.prefix, ntlm_version.stack);
            return options.badrequest(request, response, next);
        }

        if (ntlm_version === 1) {
            return handle_type1(request, response, next, ah_data[1], function(error) {
                if (error) {
                    options.debug(options.prefix, error.stack);
                    return options.internalservererror(request, response, next);
                }
            });
        }

        if (ntlm_version === 3) {
            if (typeof cache.get_proxy(request.connection.id) !== 'undefined') {
                return handle_type3(request, response, next, ah_data[1], function(error) {
                    if (error) {
                        options.debug(options.prefix, error.stack);
                        return options.internalservererror(request, response, next);
                    }
                });
            }
            options.debug(options.prefix, 'Unexpected NTLM message Type 3 in new connection for URI ' + request.protocol + '://' + request.get('host') + request.originalUrl);
            return options.internalservererror(request, response, next);
        }
        options.debug(options.prefix, 'Type 2 message in client request');
        return options.badrequest(request, response, next);
    };
};
<|endoftext|>


<commit_before>define('app/controllers/machine_power', ['ember'],
    /**
     * Machine Power Controller
     *
     * @returns Class
     */
    function () {
        return Ember.Object.extend(Ember.Evented, {

            //
            //  Properties
            //

            machines: [],
            callback: null,
            canStart: null,
            canReboot: null,
            canDestroy: null,
            canShutdown: null,
            canRename: null,

            //
            //  Methods
            //

            open: function (machines, callback) {
                this._clear();
                this.set('callback', callback);
                this.set('machines', machines instanceof Array ? machines : [machines]);
                Ember.run.next(this, function () {
                    $('#machine-power-popup').popup().popup('open');
                });
            },

            close: function () {
                $('#machine-power-popup').popup('close');
                this._clear();
            },

            act: function (action) {
                // Close current popup
                $('#machine-power-popup').popup('close');

                var machineNames = this.machines.toStringByProperty('name');

                var that = this;
                if (action == 'rename'){
                    var machine = this.machines[0];
                    this.close();
                    Ember.run.later(function(){
                        Mist.machineEditController.open(machine);
                    }, 350)
                    return;
                }
                if (action == 'script'){
                    var machine = this.machines[0];
                    console.log(machine)
                     Ember.run.later(function(){
                        Mist.machineRunScriptController.open(machine);
                    }, 350)
                    return;
                }
                Ember.run.later(function () {
                    Mist.dialogController.open({
                        type: DIALOG_TYPES.YES_NO,
                        head: 'Machine action',
                        body: [
                            {
                                paragraph: 'Are you sure you want to ' + action + ' ' + (that.machines.length > 1 ? 'these machines: ' : 'this machine: ') + machineNames + ' ?'
                            }
                        ],
                        callback: function (didConfirm) {
                            if (didConfirm) {
                                that._act(action);
                            }
                        }
                    });
                }, 500);
            },

            //
            //  Pseudo-Private Methods
            //

            _clear: function () {
                Ember.run(this, function () {
                    this.set('machines', []);
                    this.set('callback', null);
                    this.set('canStart', null);
                    this.set('canReboot', null);
                    this.set('canDestroy', null);
                    this.set('canShutdown', null);
                    this.set('canRename', null);
                });
            },

            _updateActions: function () {
                Ember.run(this, function () {
                    this.set('canStart', !this.machines.findBy('can_start', false));
                    this.set('canReboot', !this.machines.findBy('can_reboot', false));
                    this.set('canDestroy', !this.machines.findBy('can_destroy', false));
                    this.set('canShutdown', !this.machines.findBy('can_stop', false));
                    if(this.machines.length == 1 && this.machines[0].get('can_rename')){
                        this.set('canRename', true);
                    }else{
                        this.set('canRename', false);
                    }
                    this.trigger('onActionsChange');
                });
            },

            _giveCallback: function (success, action) {
                if (this.callback) this.callback(success, action);
            },

            _act: function (action) {
                this.machines.forEach(function (machine) {
                    if (action == 'shutdown') {
                        machine.shutdown();
                    } else if (action == 'destroy') {
                        machine.destroy();
                    } else if (action == 'reboot') {
                        machine.reboot();
                    } else if (action == 'start') {
                        machine.start();
                    }
                },this);
            },

            //
            //  Observers
            //

            machinesObserver: function () {
                Ember.run.once(this, '_updateActions');
            }.observes('machines')
        });
    }
);
<commit_msg>removed console.log<commit_after>define('app/controllers/machine_power', ['ember'],
    /**
     * Machine Power Controller
     *
     * @returns Class
     */
    function () {
        return Ember.Object.extend(Ember.Evented, {

            //
            //  Properties
            //

            machines: [],
            callback: null,
            canStart: null,
            canReboot: null,
            canDestroy: null,
            canShutdown: null,
            canRename: null,

            //
            //  Methods
            //

            open: function (machines, callback) {
                this._clear();
                this.set('callback', callback);
                this.set('machines', machines instanceof Array ? machines : [machines]);
                Ember.run.next(this, function () {
                    $('#machine-power-popup').popup().popup('open');
                });
            },

            close: function () {
                $('#machine-power-popup').popup('close');
                this._clear();
            },

            act: function (action) {
                // Close current popup
                $('#machine-power-popup').popup('close');

                var machineNames = this.machines.toStringByProperty('name');

                var that = this;
                if (action == 'rename'){
                    var machine = this.machines[0];
                    this.close();
                    Ember.run.later(function(){
                        Mist.machineEditController.open(machine);
                    }, 350)
                    return;
                }
                if (action == 'script'){
                    var machine = this.machines[0];
                    Ember.run.later(function(){
                        Mist.machineRunScriptController.open(machine);
                    }, 350)
                    return;
                }
                Ember.run.later(function () {
                    Mist.dialogController.open({
                        type: DIALOG_TYPES.YES_NO,
                        head: 'Machine action',
                        body: [
                            {
                                paragraph: 'Are you sure you want to ' + action + ' ' + (that.machines.length > 1 ? 'these machines: ' : 'this machine: ') + machineNames + ' ?'
                            }
                        ],
                        callback: function (didConfirm) {
                            if (didConfirm) {
                                that._act(action);
                            }
                        }
                    });
                }, 500);
            },

            //
            //  Pseudo-Private Methods
            //

            _clear: function () {
                Ember.run(this, function () {
                    this.set('machines', []);
                    this.set('callback', null);
                    this.set('canStart', null);
                    this.set('canReboot', null);
                    this.set('canDestroy', null);
                    this.set('canShutdown', null);
                    this.set('canRename', null);
                });
            },

            _updateActions: function () {
                Ember.run(this, function () {
                    this.set('canStart', !this.machines.findBy('can_start', false));
                    this.set('canReboot', !this.machines.findBy('can_reboot', false));
                    this.set('canDestroy', !this.machines.findBy('can_destroy', false));
                    this.set('canShutdown', !this.machines.findBy('can_stop', false));
                    if(this.machines.length == 1 && this.machines[0].get('can_rename')){
                        this.set('canRename', true);
                    }else{
                        this.set('canRename', false);
                    }
                    this.trigger('onActionsChange');
                });
            },

            _giveCallback: function (success, action) {
                if (this.callback) this.callback(success, action);
            },

            _act: function (action) {
                this.machines.forEach(function (machine) {
                    if (action == 'shutdown') {
                        machine.shutdown();
                    } else if (action == 'destroy') {
                        machine.destroy();
                    } else if (action == 'reboot') {
                        machine.reboot();
                    } else if (action == 'start') {
                        machine.start();
                    }
                },this);
            },

            //
            //  Observers
            //

            machinesObserver: function () {
                Ember.run.once(this, '_updateActions');
            }.observes('machines')
        });
    }
);
<|endoftext|>


<commit_before>/******************************************************************
Copyright (c) 2011 Tom Blackmore
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************/
//Copyright 2011 Tom Blackmore
//BSD licensed
var aircon = (function() {

    if(window.applicationCache) {
        var appCache = window.applicationCache;
        /*var stampDate = function() {
            var d = new Date();
            localStorage["ac_last_update"] =  d.getDate() + "-" + (d.getMonth() + 1) + "-" + d.getFullYear();
        };
        appCache.addEventListener("cached", stampDate, false);
        appCache.addEventListener("updateready", stampDate, false);
        appCache.addEventListener("obsolete", function() {
            localStorage["ac_last_update"] = null;
        }, false);*/
        var log = (function() {
            var cacheEvents = [];
            /* event comments taken from
            http://dev.opera.com/articles/view/offline-applications-html5-appcache/ */

            // cached: This is fired when all the resources have finished downloading, and
            // application is cached.
            cacheEvents[0] = 'cached';
            // checking: This gets fired when browser is checking for attempting to download the
            // manifest for the first time, or is checking if there is an updated version of the
            // manifest file.
            cacheEvents[1] = 'checking';
            // downloading: If the browser is downloading the cache for the first time, or if is
            // downloading an updated cache, then this is fired.
            cacheEvents[2] = 'downloading';
            // error: This can be fired for a number of reasons. If the manifest file can't be
            // found, then the application cache download process has to be aborted, in which case
            // this event can be fired. It can also be fired in case the manifest file is present,
            // but any of the files mentioned in the manifest file can't be downloaded properly.
            // It can even be fired in case the manifest file changes while the update is being run
            // (in which case the browser will wait a while before trying again), or in any other
            // case where there is a fatal error.
            cacheEvents[3] = 'error';
            // noupdate: If there is no updated version of the manifest file on the server, then
            // noupdate is fired.
            cacheEvents[4] = 'noupdate';
            // obsolete: This is fired if the manifest file cannot be found (404 error or 410 error).
            cacheEvents[5] = 'obsolete';
            // progress: This is fired for each and every file which is downloaded as part of the
            // AppCache.
            cacheEvents[6] = 'progress';
            // updateready: Once resources have finished re-downloading for an updated cached file,
            // then updateready is called. Once this happens, then we can use swapCache() (as
            // explained later in the article) to make the browser to use this newly updated cache.
            cacheEvents[6] = 'updateready';

            var cacheStatusValues = [];
            /* status comments taken from
            http://dev.opera.com/articles/view/offline-applications-html5-appcache/ */

            // uncached: If the page is not linked to the application cache.
            // Also, the the very first time the application cache is being downloaded,
            // then during the time it is being downloaded, the AppCache will have a status of uncached.
            cacheStatusValues[0] = 'uncached';
            // idle: When the browser has the latest version of the AppCache,
            // and there aren no updated versions to download, then the status is set to Idle.
            cacheStatusValues[1] = 'idle';
            // checking: The duration of when the page is checking for an updated manifest file,
            // then the status is set to Checking.
            cacheStatusValues[2] = 'checking';
            // downloading: The duration of when the page is actually downloading a new cache
            // (if an updated manifest file has been detected), the status is set to downloading
            cacheStatusValues[3] = 'downloading';
            // updateready: Once the browser finishes downloading that new cache,
            // it is ready to be used (but still not being used yet).
            // During this time, the status is set as updateready
            cacheStatusValues[4] = 'updateready';
            // obsolete: In case the manifest file cannot be found, then the status is set to
            // obsolete and the application cache gets deleted. It is important to know, that
            // in case the manifest file (or any file mentioned in the manifest file except
            // those which have a fallback) fail to load, then it will be counted as an error
            // and the old application cache will continue to be used.
            cacheStatusValues[5] = 'obsolete';

            var log = function(s) {
                console.log(s);
            };
            var start = function(func) {
                if(typeof(func) === "function") {
                    log = func;
                }
                var message;
                if(document.getElementsByTagName("HTML")[0].hasAttribute('manifest')) {
                    message = "using: " + document.getElementsByTagName("HTML")[0].manifest;
                } else {
                    message = "No manifest specified in HTML tag";
                }

                log("AC: - logging started -");
                log("AC: "  + message);
                //log("AC: date: " + localStorage["ac_last_update"]);

                for(var i=0; i < cacheEvents.length; i++) {
                    appCache.addEventListener(cacheEvents[i], logEvent, false);
                }
            };
            var stop = function() {
                log("AC: - logging stoped -");
                for(var i=0; i < cacheEvents.length; i++) {
                    appCache.removeEventListener(cacheEvents[i], null, false);
                }
            };
            var isOnline = function() {
                return navigator.onLine;
            };
            var logEvent = function(e) {
                var online, status, type, message;
                online = (isOnline()) ? 'yes' : 'no';
                status = cacheStatusValues[appCache.status];
                type = e.type;
                //message = 'o: ' + online;
                message=  type;
                //message+= ', s: ' + status;
                log('AC: '+ message);

                if (type == 'error' && navigator.onLine) {
                    message = ' There was an unknown error, check your Cache Manifest.';
                    log('AC: Error')
                    log("AC: " + message);
                }
            };
            return {
                start: start,
                stop: stop
            }
        })();
        return {
            log : log
        };
    } else {
        return {
            log : (function() {
                return {
                    start: function() {
                    }
                };
            })()
        };
    }
})();
aircon.log.start();<commit_msg>added license<commit_after>/******************************************************************
Copyright (c) 2011 Tom Blackmore
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************/
var ac = (function() {

    if(window.applicationCache) {
        var appCache = window.applicationCache;

        var log = (function() {
            var cacheEvents = [];
            /* event comments taken from
            http://dev.opera.com/articles/view/offline-applications-html5-appcache/ */

            // cached: This is fired when all the resources have finished downloading, and
            // application is cached.
            cacheEvents[0] = 'cached';
            // checking: This gets fired when browser is checking for attempting to download the
            // manifest for the first time, or is checking if there is an updated version of the
            // manifest file.
            cacheEvents[1] = 'checking';
            // downloading: If the browser is downloading the cache for the first time, or if is
            // downloading an updated cache, then this is fired.
            cacheEvents[2] = 'downloading';
            // error: This can be fired for a number of reasons. If the manifest file can't be
            // found, then the application cache download process has to be aborted, in which case
            // this event can be fired. It can also be fired in case the manifest file is present,
            // but any of the files mentioned in the manifest file can't be downloaded properly.
            // It can even be fired in case the manifest file changes while the update is being run
            // (in which case the browser will wait a while before trying again), or in any other
            // case where there is a fatal error.
            cacheEvents[3] = 'error';
            // noupdate: If there is no updated version of the manifest file on the server, then
            // noupdate is fired.
            cacheEvents[4] = 'noupdate';
            // obsolete: This is fired if the manifest file cannot be found (404 error or 410 error).
            cacheEvents[5] = 'obsolete';
            // progress: This is fired for each and every file which is downloaded as part of the
            // AppCache.
            cacheEvents[6] = 'progress';
            // updateready: Once resources have finished re-downloading for an updated cached file,
            // then updateready is called. Once this happens, then we can use swapCache() (as
            // explained later in the article) to make the browser to use this newly updated cache.
            cacheEvents[6] = 'updateready';

            var cacheStatusValues = [];
            /* status comments taken from
            http://dev.opera.com/articles/view/offline-applications-html5-appcache/ */

            // uncached: If the page is not linked to the application cache.
            // Also, the the very first time the application cache is being downloaded,
            // then during the time it is being downloaded, the AppCache will have a status of uncached.
            cacheStatusValues[0] = 'uncached';
            // idle: When the browser has the latest version of the AppCache,
            // and there aren no updated versions to download, then the status is set to Idle.
            cacheStatusValues[1] = 'idle';
            // checking: The duration of when the page is checking for an updated manifest file,
            // then the status is set to Checking.
            cacheStatusValues[2] = 'checking';
            // downloading: The duration of when the page is actually downloading a new cache
            // (if an updated manifest file has been detected), the status is set to downloading
            cacheStatusValues[3] = 'downloading';
            // updateready: Once the browser finishes downloading that new cache,
            // it is ready to be used (but still not being used yet).
            // During this time, the status is set as updateready
            cacheStatusValues[4] = 'updateready';
            // obsolete: In case the manifest file cannot be found, then the status is set to
            // obsolete and the application cache gets deleted. It is important to know, that
            // in case the manifest file (or any file mentioned in the manifest file except
            // those which have a fallback) fail to load, then it will be counted as an error
            // and the old application cache will continue to be used.
            cacheStatusValues[5] = 'obsolete';

            var log = function(s) {
                console.log(s);
            };
            var start = function(func) {
                if(typeof(func) === "function") {
                    log = func;
                }
                var message;
                if(document.getElementsByTagName("HTML")[0].hasAttribute('manifest')) {
                    message = "using: " + document.getElementsByTagName("HTML")[0].manifest;
                } else {
                    message = "No manifest specified in HTML tag";
                }

                log("AC: - logging started -");
                log("AC: "  + message);

                for(var i=0; i < cacheEvents.length; i++) {
                    appCache.addEventListener(cacheEvents[i], logEvent, false);
                }
            };
            var stop = function() {
                log("AC: - logging stoped -");
                for(var i=0; i < cacheEvents.length; i++) {
                    appCache.removeEventListener(cacheEvents[i], null, false);
                }
            };
            var isOnline = function() {
                return navigator.onLine;
            };
            var logEvent = function(e) {
                var online, status, type, message;
                online = (isOnline()) ? 'yes' : 'no';
                status = cacheStatusValues[appCache.status];
                type = e.type;
                //message = 'o: ' + online;
                message=  type;
                //message+= ', s: ' + status;
                log('AC: '+ message);

                if (type == 'error' && navigator.onLine) {
                    message = ' There was an unknown error, check your Cache Manifest.';
                    log('AC: Error')
                    log("AC: " + message);
                }
            };
            return {
                start: start,
                stop: stop
            }
        })();
        return {
            log : log
        };
    } else {
        return {
            log : (function() {
                return {
                    start: function() {
                    }
                };
            })()
        };
    }
})();
ac.log.start();<|endoftext|>


<commit_before>var nad = nad || {};

nad.bindKeys = function(ctrl) {
  _.each(nad.keyBindings, function (kb) {
    Mousetrap.bind(kb.key, ctrl[kb.callback]);
  });
};

nad.keyBindings = [
  {key: 'p', callback: 'power', description: 'Toggle power'},
  {key: 'm', callback: 'mute', description: 'Toggle mute'},
  {key: 's', callback: 'speakerA', description: 'Toggle headphones'},
  {key: 'i', callback: 'amp', description: 'Get amplifier model'},
  {key: '+', callback: 'volumeUp', description: 'Increase volume'},
  {key: '-', callback: 'volumeDown', description: 'Decrease volume'},
  {key: 'h', callback: 'showHelp',
   description: 'Togge list of keyboard shortcuts'}
];

nad.fmtCmd = function(data) {
  return 'Main.' + [data.Variable, data.Value].join(data.Operator);
};

nad.fromValue = function(v) {
  return v === 'On' || v === 'Off' ? v === 'On' : v;
}

nad.toValue = function(v) {
  if (_.isBoolean(v)) {
    return v ? 'On' : 'Off';
  }
  return v;
}

nad.send = function(ctrl, req) {
  req.Value = nad.toValue(req.Value);
  m.request({method: 'POST', url: '/api/v1/nad', data: req})
    .then(function (data) {
      var state = ctrl.model().state;
      state[data.Variable] = nad.fromValue(data.Value);
      ctrl.error({});
      ctrl.model({
        message: {request: req, reply: data},
        state: state,
      });
    }, ctrl.error);
};

nad.controller = function() {
  var ctrl = this;
  ctrl.error = m.prop({});
  ctrl.model = m.prop({
    state: {Power: false, Mute: false, SpeakerA: true},
    message: {}
  });
  ctrl.helpVisible = m.prop(false);
  ctrl.power = function() {
    nad.send(ctrl, {
      Variable: 'Power',
      Operator: '=',
      Value: !ctrl.model().state.Power
    });
  };
  ctrl.mute = function() {
    nad.send(ctrl, {
      Variable: 'Mute',
      Operator: '=',
      Value: !ctrl.model().state.Mute
    });
  };
  ctrl.volumeUp = function() {
    nad.send(ctrl, {
      Variable: 'Volume',
      Operator: '+',
    });
  };
  ctrl.volumeDown = function() {
    nad.send(ctrl, {
      Variable: 'Volume',
      Operator: '-',
    });
  };
  ctrl.source = function(value) {
    nad.send(ctrl, {
      Variable: 'Source',
      Operator: '=',
      Value: value
    });
  };
  ctrl.refreshSource = function() {
    nad.send(ctrl, {
      Variable: 'Source',
      Operator: '?'
    });
  };
  ctrl.amp = function() {
    nad.send(ctrl, {
      Variable: 'Model',
      Operator: '?'
    });
  };
  ctrl.speakerA = function() {
    nad.send(ctrl, {
      Variable: 'SpeakerA',
      Operator: '=',
      Value: !ctrl.model().state.SpeakerA
    });
  };
  ctrl.showHelp = function() {
    var visible = ctrl.helpVisible();
    m.startComputation();
    ctrl.helpVisible(!visible);
    m.endComputation();
  };
  nad.bindKeys(ctrl);
};

nad.console = function(ctrl) {
  var text;
  if (_.isEmpty(ctrl.model().message)) {
    text = ['These go to eleven!'];
  } else {
    text = ['sent:     ' + nad.fmtCmd(ctrl.model().message.request),
            'received: ' + nad.fmtCmd(ctrl.model().message.reply)];
  }
  return m('pre.console', text.join('\n'));
};

nad.onoff = function(ctrl, options) {
  var state = ctrl.model().state;
  if (!_.has(state, options.type)) {
    throw 'Unknown type: ' + options.type;
  }
  var isOn = state[options.type];
  var active = options.invert ? !isOn : isOn;
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg' + (active ? ' active' : ''),
    onclick: options.onclick
  }, options.icon);
};

nad.volume = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg',
    onclick: options.onclick
  }, options.icon);
};

nad.source = function(ctrl) {
  var sources = ['CD', 'Tuner', 'Video', 'Disc/MDC', 'Tape2', 'Aux'];
  var model = ctrl.model();
  return m('select.form-control', {
    onchange: m.withAttr('value', ctrl.source)
  }, _.map(sources, function(src) {
    var val = src.toUpperCase();
    var selected = model.state.Source === val ? 'selected' : '';
    return m('option', {value: val, selected: selected}, src);
  }));
};

nad.refreshSource = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default',
    onclick: ctrl.refreshSource
  }, options.icon);
};

nad.amp = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg',
    onclick: ctrl.amp
  }, options.icon);
};

nad.error = function(ctrl) {
  var e = ctrl.error();
  var isError = !_.isEmpty(e);
  var text = isError ? e.message + ' (' + e.status + ')' : '';
  var cls = 'alert-danger' + (isError ? '' : ' hidden');
  return m('div.alert', {class: cls, role: 'alert'}, [
    m('strong', 'Error! '), text
  ]);
};

nad.help = function(ctrl) {
  if (!ctrl.helpVisible()) {
    return m('p.text-muted', 'Tip: Press ', m('code', 'h'),
             ' to display keyboard shortcuts');
  }
  var rows = _.map(nad.keyBindings, function (kb) {
    return m('tr', [
      m('td', m('center', m('code', kb.key))),
      m('td', kb.description)
    ]);
  });
  return m('table.table',
           m('thead', m('tr', [
             m('th', 'Key binding'),
             m('th', 'Description')
           ])),
           m('tbody', rows)
          );
};

nad.view = function(ctrl) {
  return m('div.container', [
    m('div.row', [
      m('div.col-md-4', m('h1', [
        m('span', {class: 'glyphicon glyphicon-signal'}), ' amp remote'
      ]))
    ]),
    m('div.row', [
      m('div.col-md-4', nad.error(ctrl))
    ]),
    m('div.row', [
      m('div.col-md-4', [
        nad.console(ctrl)
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.power,
          type: 'Power',
          icon: m('span', {class: 'glyphicon glyphicon-off'})
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.mute,
          type: 'Mute',
          icon: m('span', {class: 'glyphicon glyphicon-volume-off'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.volume(ctrl, {
          onclick: ctrl.volumeUp,
          icon: m('span', {class: 'glyphicon glyphicon-volume-up'})
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.volume(ctrl, {
          onclick: ctrl.volumeDown,
          icon: m('span', {class: 'glyphicon glyphicon-volume-down'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.speakerA,
          type: 'SpeakerA',
          icon: m('span', {class: 'glyphicon glyphicon-headphones'}),
          invert: true
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.amp(ctrl, {
          icon: m('span', {class: 'glyphicon glyphicon-info-sign'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, nad.source(ctrl)),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.refreshSource(ctrl, {
          icon: m('span', {class: 'glyphicon glyphicon-refresh'})
        })
      ])
    ]),
    m('div.row', m('div.col-md-4', {class: 'top-spacing'}, nad.help(ctrl)))
  ]);
};

m.mount(document.getElementById('nad-remote'), nad);
<commit_msg>Initialize state when app loads<commit_after>var nad = nad || {};

nad.bindKeys = function(ctrl) {
  _.each(nad.keyBindings, function (kb) {
    Mousetrap.bind(kb.key, ctrl[kb.callback]);
  });
};

nad.keyBindings = [
  {key: 'p', callback: 'power', description: 'Toggle power'},
  {key: 'm', callback: 'mute', description: 'Toggle mute'},
  {key: 's', callback: 'speakerA', description: 'Toggle headphones'},
  {key: 'i', callback: 'amp', description: 'Get amplifier model'},
  {key: '+', callback: 'volumeUp', description: 'Increase volume'},
  {key: '-', callback: 'volumeDown', description: 'Decrease volume'},
  {key: 'h', callback: 'showHelp',
   description: 'Togge list of keyboard shortcuts'}
];

nad.initState = function(ctrl) {
  nad.get(ctrl, 'Power');
  nad.get(ctrl, 'Source');
  nad.get(ctrl, 'SpeakerA');
};

nad.fmtCmd = function(data) {
  return 'Main.' + [data.Variable, data.Value].join(data.Operator);
};

nad.fromValue = function(v) {
  return v === 'On' || v === 'Off' ? v === 'On' : v;
};

nad.toValue = function(v) {
  if (_.isBoolean(v)) {
    return v ? 'On' : 'Off';
  }
  return v;
};

nad.get = function(ctrl, variable) {
  m.request({method: 'GET', url: '/api/v1/nad/state/' + variable})
    .then(function (data) {
      var state = ctrl.model().state;
      state[data.Variable] = nad.fromValue(data.Value);
      ctrl.model({state: state});
    }, ctrl.error);
};

nad.send = function(ctrl, req) {
  req.Value = nad.toValue(req.Value);
  m.request({method: 'POST', url: '/api/v1/nad', data: req})
    .then(function (data) {
      var state = ctrl.model().state;
      state[data.Variable] = nad.fromValue(data.Value);
      ctrl.error({});
      ctrl.model({
        message: {request: req, reply: data},
        state: state,
      });
    }, ctrl.error);
};

nad.controller = function() {
  var ctrl = this;
  ctrl.error = m.prop({});
  ctrl.model = m.prop({
    state: {Power: false, Mute: false, SpeakerA: true},
    message: {}
  });
  ctrl.helpVisible = m.prop(false);
  ctrl.power = function() {
    nad.send(ctrl, {
      Variable: 'Power',
      Operator: '=',
      Value: !ctrl.model().state.Power
    });
  };
  ctrl.mute = function() {
    nad.send(ctrl, {
      Variable: 'Mute',
      Operator: '=',
      Value: !ctrl.model().state.Mute
    });
  };
  ctrl.volumeUp = function() {
    nad.send(ctrl, {
      Variable: 'Volume',
      Operator: '+',
    });
  };
  ctrl.volumeDown = function() {
    nad.send(ctrl, {
      Variable: 'Volume',
      Operator: '-',
    });
  };
  ctrl.source = function(value) {
    nad.send(ctrl, {
      Variable: 'Source',
      Operator: '=',
      Value: value
    });
  };
  ctrl.refreshSource = function() {
    nad.send(ctrl, {
      Variable: 'Source',
      Operator: '?'
    });
  };
  ctrl.amp = function() {
    nad.send(ctrl, {
      Variable: 'Model',
      Operator: '?'
    });
  };
  ctrl.speakerA = function() {
    nad.send(ctrl, {
      Variable: 'SpeakerA',
      Operator: '=',
      Value: !ctrl.model().state.SpeakerA
    });
  };
  ctrl.showHelp = function() {
    var visible = ctrl.helpVisible();
    m.startComputation();
    ctrl.helpVisible(!visible);
    m.endComputation();
  };
  nad.bindKeys(ctrl);
  nad.initState(ctrl);
};

nad.console = function(ctrl) {
  var text;
  if (_.isEmpty(ctrl.model().message)) {
    text = ['These go to eleven!'];
  } else {
    text = ['sent:     ' + nad.fmtCmd(ctrl.model().message.request),
            'received: ' + nad.fmtCmd(ctrl.model().message.reply)];
  }
  return m('pre.console', text.join('\n'));
};

nad.onoff = function(ctrl, options) {
  var state = ctrl.model().state;
  if (!_.has(state, options.type)) {
    throw 'Unknown type: ' + options.type;
  }
  var isOn = state[options.type];
  var active = options.invert ? !isOn : isOn;
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg' + (active ? ' active' : ''),
    onclick: options.onclick
  }, options.icon);
};

nad.volume = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg',
    onclick: options.onclick
  }, options.icon);
};

nad.source = function(ctrl) {
  var sources = ['CD', 'Tuner', 'Video', 'Disc/MDC', 'Tape2', 'Aux'];
  var model = ctrl.model();
  return m('select.form-control', {
    onchange: m.withAttr('value', ctrl.source)
  }, _.map(sources, function(src) {
    var val = src.toUpperCase();
    var selected = model.state.Source === val ? 'selected' : '';
    return m('option', {value: val, selected: selected}, src);
  }));
};

nad.refreshSource = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default',
    onclick: ctrl.refreshSource
  }, options.icon);
};

nad.amp = function(ctrl, options) {
  return m('button[type=button]', {
    class: 'btn btn-default btn-lg',
    onclick: ctrl.amp
  }, options.icon);
};

nad.error = function(ctrl) {
  var e = ctrl.error();
  var isError = !_.isEmpty(e);
  var text = isError ? e.message + ' (' + e.status + ')' : '';
  var cls = 'alert-danger' + (isError ? '' : ' hidden');
  return m('div.alert', {class: cls, role: 'alert'}, [
    m('strong', 'Error! '), text
  ]);
};

nad.help = function(ctrl) {
  if (!ctrl.helpVisible()) {
    return m('p.text-muted', 'Tip: Press ', m('code', 'h'),
             ' to display keyboard shortcuts');
  }
  var rows = _.map(nad.keyBindings, function (kb) {
    return m('tr', [
      m('td', m('center', m('code', kb.key))),
      m('td', kb.description)
    ]);
  });
  return m('table.table',
           m('thead', m('tr', [
             m('th', 'Key binding'),
             m('th', 'Description')
           ])),
           m('tbody', rows)
          );
};

nad.view = function(ctrl) {
  return m('div.container', [
    m('div.row', [
      m('div.col-md-4', m('h1', [
        m('span', {class: 'glyphicon glyphicon-signal'}), ' amp remote'
      ]))
    ]),
    m('div.row', [
      m('div.col-md-4', nad.error(ctrl))
    ]),
    m('div.row', [
      m('div.col-md-4', [
        nad.console(ctrl)
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.power,
          type: 'Power',
          icon: m('span', {class: 'glyphicon glyphicon-off'})
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.mute,
          type: 'Mute',
          icon: m('span', {class: 'glyphicon glyphicon-volume-off'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.volume(ctrl, {
          onclick: ctrl.volumeUp,
          icon: m('span', {class: 'glyphicon glyphicon-volume-up'})
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.volume(ctrl, {
          onclick: ctrl.volumeDown,
          icon: m('span', {class: 'glyphicon glyphicon-volume-down'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.onoff(ctrl, {
          onclick: ctrl.speakerA,
          type: 'SpeakerA',
          icon: m('span', {class: 'glyphicon glyphicon-headphones'}),
          invert: true
        })
      ]),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.amp(ctrl, {
          icon: m('span', {class: 'glyphicon glyphicon-info-sign'})
        })
      ])
    ]),
    m('div.row', [
      m('div.col-md-2', {class: 'top-spacing'}, nad.source(ctrl)),
      m('div.col-md-2', {class: 'top-spacing'}, [
        nad.refreshSource(ctrl, {
          icon: m('span', {class: 'glyphicon glyphicon-refresh'})
        })
      ])
    ]),
    m('div.row', m('div.col-md-4', {class: 'top-spacing'}, nad.help(ctrl)))
  ]);
};

m.mount(document.getElementById('nad-remote'), nad);
<|endoftext|>


<commit_before>
/*!
 * Express - View
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var extname = require('path').extname,
    mime = require('connect/utils').mime,
    utils = require('connect/utils'),
    http = require('http'),
    sys = require('sys'),
    fs = require('fs');

/**
 * Cache supported template engine exports to
 * increase performance by lowering the number
 * of calls to `require()`.
 *
 * @type Object
 */

var cache = {};

/**
 * Cache view contents to prevent I/O hits.
 *
 * @type Object
 */

var viewCache = {};

/**
 * Clear the view cache.
 *
 * @api public
 */

exports.clearCache = function(){
    viewCache = {};
};

/**
 * Cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheView(path) {
    fs.readFile(path, 'utf8', function(err, data){
        if (!err) {
            viewCache[path] = data;
        }
    });
}

/**
 * Synchronously cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheViewSync(path) {
    return viewCache[path] = fs.readFileSync(path, 'utf8');
}

/**
 * Return view root path for the given `app`.
 *
 * @param {express.Server} app
 * @return {String}
 * @api private
 */

function viewRoot(app) {
    return app.set('views') || process.cwd() + '/views';
}

/**
 * Setup view reloading, checking at the given `interval`
 * which defaults to 500 milliseconds.
 *
 * @param {Numbers} interval
 * @api private
 */

exports.watcher = function(interval){
    interval = interval === true
        ? 500
        : interval;
    (function cache(dir){
        fs.readdir(dir, function(err, files){
            if (!err) {
                files.forEach(function(file){
                    file = dir + '/' + file;
                    fs.stat(file, function(err, stats){
                        if (!err) {
                            if (stats.isFile()) {
                                fs.watchFile(file, { interval: interval }, function(curr, prev){
                                    if (curr.mtime > prev.mtime) {
                                        cacheView(file);
                                    }
                                });
                            } else if (stats.isDirectory()) {
                                cache(file);
                            }
                        }
                    });
                });
            }
        });
    })(viewRoot(this));
};

/**
 * Render `view` partial with the given `options`.
 *
 * Options:
 *   - `object` Single object with name derived from the view (unless `as` is present)
 *
 *   - `as` Variable name for each `collection` value, defaults to the view name.
 *     * as: 'something' will add the `something` local variable
 *     * as: this will use the collection value as the template context
 *     * as: global will merge the collection value's properties with `locals`
 *
 *   - `collection` Array of objects, the name is derived from the view name itself.
 *     For example _video.html_ will have a object _video_ available to it.
 *
 * @param  {String} view
 * @param  {Object|Array} options or collection
 * @return {String}
 * @api public
 */

http.ServerResponse.prototype.partial = function(view, options, ext){
    // Inherit parent view extension when not present
    if (ext && view.indexOf('.') < 0) {
        view += ext;
    }

    // Allow collection to be passed as second param
    if (Array.isArray(options)) {
        options = { collection: options };
    }

    // Defaults
    options = options || {};
    options.locals = options.locals || {};
    options.partial = true;
    options.layout = false;

    // Collection support
    var collection = options.collection;
    if (collection) {
        var name = options.as || view.split('.')[0],
            len = collection.length;
        delete options.collection;
        options.locals.collectionLength = len;
        return collection.map(function(val, i){
            options.locals.firstInCollection = i === 0;
            options.locals.indexInCollection = i;
            options.locals.lastInCollection = i === len - 1;
            options.object = val;
            return this.partial(view, options);
        }, this).join('');
    } else {
        if (options.object) {
            var name = options.as || view.split('.')[0];
            if (typeof name === 'string') {
                options.locals[name] = options.object;
            } else if (name === global) {
                utils.merge(options.locals, options.object);
            } else {
                options.context = options.object;
            }
        }
        return this.render(view, options);
    }
};

/**
 * Render `view` with the given `options` and optional callback `fn`.
 * When a callback function is given a response will _not_ be made
 * automatically, however otherwise a response of _200_ and _text/html_ is given.
 *
 * Options:
 *
 *  Most engines accept one or more of the following options,
 *  both _haml_ and _jade_ accept all:
 *
 *  - `context|scope`   Template evaluation context (`this`)
 *  - `locals`    Object containing local variables
 *  - `filename`  Filename used for the `cache` option
 *  - `cache`     Cache intermediate JavaScript in memory
 *  - `debug`     Output debugging information
 *
 * @param  {String} view
 * @param  {Object} options
 * @param  {Function} fn
 * @api public
 */

http.ServerResponse.prototype.render = function(view, options, fn){
    options = options || {};
    var defaultEngine = this.app.set('view engine');

    // Support "view engine" setting
    if (view.indexOf('.') < 0 && defaultEngine) {
        view += '.' + defaultEngine;
    }

    // Defaults
    var self = this,
        helpers = this.app.viewHelpers,
        dynamicHelpers = this.app.dynamicViewHelpers,
        root = viewRoot(this.app),
        ext = extname(view),
        partial = options.partial,
        layout = options.layout === undefined ? true : options.layout,
        layout = layout === true
            ? 'layout' + ext
            : layout;

    // Allow layout name without extension
    if (typeof layout === 'string' && layout.indexOf('.') < 0) {
        layout += ext;
    }

    // Normalize "context" to "scope", defaulting to this response
    options.scope = options.scope || options.context || this;

    // Auto-cache in production
    if (this.app.set('env') === 'production') {
        options.cache = true;
    }

    // Partials support
    if (options.partial) {
        root += '/partials';
    }

    // View path
    var path = view[0] === '/'
        ? view
        : root + '/' + view;

    // Pass filename to the engine and view
    options.locals = options.locals || {};
    options.locals.__filename = options.filename = path;

    // Dynamic helper support
    var keys = Object.keys(dynamicHelpers);
    for (var i = 0, len = keys.length; i < len; ++i) {
        var key = keys[i],
            val = dynamicHelpers[key];
        if (typeof val === 'function') {
            helpers[key] = val.call(this.app, this.req, this, this.req.params.path);
        }
    }

    // Always expose partial() as a local
    options.locals.partial = function(view, options){
        return self.partial.call(self, view, options, ext);
    };

    // Merge view helpers
    options.locals.__proto__ = helpers;

    function error(err) {
        if (fn) {
            fn(err);
        } else {
            self.req.next(err);
        }
    }

    // Cache contents
    try {
        var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
    } catch (err) {
        return error(err);
    }

    // Cache template engine exports
    var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));

    // Attempt render
    try {
        var str = engine.render(str, options);
    } catch (err) {
        return error(err);
    }

    // Layout support
    if (layout) {
        options.layout = false;
        options.locals.body = str;
        self.render(layout, options);
    } else if (partial) {
        return str;
    } else if (fn) {
        fn(null, str);
    } else {
        self.send(str);
    }
};
<commit_msg>< 78 cols in view.js so people stop complaining :D<commit_after>
/*!
 * Express - View
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var extname = require('path').extname,
    mime = require('connect/utils').mime,
    utils = require('connect/utils'),
    http = require('http'),
    sys = require('sys'),
    fs = require('fs');

/**
 * Cache supported template engine exports to
 * increase performance by lowering the number
 * of calls to `require()`.
 *
 * @type Object
 */

var cache = {};

/**
 * Cache view contents to prevent I/O hits.
 *
 * @type Object
 */

var viewCache = {};

/**
 * Clear the view cache.
 *
 * @api public
 */

exports.clearCache = function(){
    viewCache = {};
};

/**
 * Cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheView(path) {
    fs.readFile(path, 'utf8', function(err, data){
        if (!err) {
            viewCache[path] = data;
        }
    });
}

/**
 * Synchronously cache view at the given `path`.
 *
 * @param {String} path
 * @return {String}
 * @api private
 */

function cacheViewSync(path) {
    return viewCache[path] = fs.readFileSync(path, 'utf8');
}

/**
 * Return view root path for the given `app`.
 *
 * @param {express.Server} app
 * @return {String}
 * @api private
 */

function viewRoot(app) {
    return app.set('views') || process.cwd() + '/views';
}

/**
 * Setup view reloading, checking at the given `interval`
 * which defaults to 500 milliseconds.
 *
 * @param {Numbers} interval
 * @api private
 */

exports.watcher = function(interval){
    interval = interval === true
        ? 500
        : interval;

    function watch(file) {
        fs.watchFile(file, { interval: interval }, function(curr, prev){
            if (curr.mtime > prev.mtime) {
                cacheView(file);
            }
        });
    }

    (function traverse(dir){
        fs.readdir(dir, function(err, files){
            if (err) return;
            files.forEach(function(file){
                file = dir + '/' + file;
                fs.stat(file, function(err, stats){
                    if (err) return;
                    if (stats.isFile()) {
                        watch(file);
                    } else if (stats.isDirectory()) {
                        traverse(file);
                    }
                });
            });
        });
    })(viewRoot(this));
};

/**
 * Render `view` partial with the given `options`.
 *
 * Options:
 *   - `object` Single object with name derived from the view (unless `as` is present)
 *
 *   - `as` Variable name for each `collection` value, defaults to the view name.
 *     * as: 'something' will add the `something` local variable
 *     * as: this will use the collection value as the template context
 *     * as: global will merge the collection value's properties with `locals`
 *
 *   - `collection` Array of objects, the name is derived from the view name itself.
 *     For example _video.html_ will have a object _video_ available to it.
 *
 * @param  {String} view
 * @param  {Object|Array} options or collection
 * @return {String}
 * @api public
 */

http.ServerResponse.prototype.partial = function(view, options, ext){
    // Inherit parent view extension when not present
    if (ext && view.indexOf('.') < 0) {
        view += ext;
    }

    // Allow collection to be passed as second param
    if (Array.isArray(options)) {
        options = { collection: options };
    }

    // Defaults
    options = options || {};
    options.locals = options.locals || {};
    options.partial = true;
    options.layout = false;

    // Collection support
    var collection = options.collection;
    if (collection) {
        var name = options.as || view.split('.')[0],
            len = collection.length;
        delete options.collection;
        options.locals.collectionLength = len;
        return collection.map(function(val, i){
            options.locals.firstInCollection = i === 0;
            options.locals.indexInCollection = i;
            options.locals.lastInCollection = i === len - 1;
            options.object = val;
            return this.partial(view, options);
        }, this).join('');
    } else {
        if (options.object) {
            var name = options.as || view.split('.')[0];
            if (typeof name === 'string') {
                options.locals[name] = options.object;
            } else if (name === global) {
                utils.merge(options.locals, options.object);
            } else {
                options.context = options.object;
            }
        }
        return this.render(view, options);
    }
};

/**
 * Render `view` with the given `options` and optional callback `fn`.
 * When a callback function is given a response will _not_ be made
 * automatically, however otherwise a response of _200_ and _text/html_ is given.
 *
 * Options:
 *
 *  Most engines accept one or more of the following options,
 *  both _haml_ and _jade_ accept all:
 *
 *  - `context|scope`   Template evaluation context (`this`)
 *  - `locals`    Object containing local variables
 *  - `filename`  Filename used for the `cache` option
 *  - `cache`     Cache intermediate JavaScript in memory
 *  - `debug`     Output debugging information
 *
 * @param  {String} view
 * @param  {Object} options
 * @param  {Function} fn
 * @api public
 */

http.ServerResponse.prototype.render = function(view, options, fn){
    options = options || {};
    var defaultEngine = this.app.set('view engine');

    // Support "view engine" setting
    if (view.indexOf('.') < 0 && defaultEngine) {
        view += '.' + defaultEngine;
    }

    // Defaults
    var self = this,
        helpers = this.app.viewHelpers,
        dynamicHelpers = this.app.dynamicViewHelpers,
        root = viewRoot(this.app),
        ext = extname(view),
        partial = options.partial,
        layout = options.layout === undefined ? true : options.layout,
        layout = layout === true
            ? 'layout' + ext
            : layout;

    // Allow layout name without extension
    if (typeof layout === 'string' && layout.indexOf('.') < 0) {
        layout += ext;
    }

    // Normalize "context" to "scope", defaulting to this response
    options.scope = options.scope || options.context || this;

    // Auto-cache in production
    if (this.app.set('env') === 'production') {
        options.cache = true;
    }

    // Partials support
    if (options.partial) {
        root += '/partials';
    }

    // View path
    var path = view[0] === '/'
        ? view
        : root + '/' + view;

    // Pass filename to the engine and view
    options.locals = options.locals || {};
    options.locals.__filename = options.filename = path;

    // Dynamic helper support
    var keys = Object.keys(dynamicHelpers);
    for (var i = 0, len = keys.length; i < len; ++i) {
        var key = keys[i],
            val = dynamicHelpers[key];
        if (typeof val === 'function') {
            helpers[key] = val.call(
                this.app,
                this.req,
                this,
                this.req.params.path);
        }
    }

    // Always expose partial() as a local
    options.locals.partial = function(view, options){
        return self.partial.call(self, view, options, ext);
    };

    // Merge view helpers
    options.locals.__proto__ = helpers;

    function error(err) {
        if (fn) {
            fn(err);
        } else {
            self.req.next(err);
        }
    }

    // Cache contents
    try {
        var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
    } catch (err) {
        return error(err);
    }

    // Cache template engine exports
    var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));

    // Attempt render
    try {
        var str = engine.render(str, options);
    } catch (err) {
        return error(err);
    }

    // Layout support
    if (layout) {
        options.layout = false;
        options.locals.body = str;
        self.render(layout, options);
    } else if (partial) {
        return str;
    } else if (fn) {
        fn(null, str);
    } else {
        self.send(str);
    }
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

var fido2twi = require('./f2t-core.js');

var params = [].concat(process.argv);
params.shift(); // 'node'
params.shift(); // 'twi2fido'

console.log( fido2twi() );
<commit_msg>read parameters from the command line; print help if they're missing<commit_after>#!/usr/bin/env node

var fido2twi = require('./f2t-core.js');
var clog = console.log;

var params = [].concat(process.argv);
params.shift(); // 'node'
params.shift(); // 'twi2fido'

if( params.length < 2 ){
   clog('Usage:');
   clog('   fido2twi loginName sourceArea');
   clog('');
   clog('Parameters:');
   clog('');
   clog('loginName   -- login name (screen name) of a microblog in Twitter');
   clog('');
   clog('sourceArea  -- areatag of an echomail area in Fidonet');
   process.exit(1);
}

const loginName  = params[0];
const sourceArea = params[1];

console.log( fido2twi(loginName, sourceArea) );
<|endoftext|>