diff --git a/bower.json b/bower.json index 5a3a291c0..b99e86726 100755 --- a/bower.json +++ b/bower.json @@ -30,6 +30,7 @@ ], "dependencies": { "bootstrap": "~3.3.7", + "bootstrap-toggle": "~2.2.0", "datatables": "~1.10.4", "datatables-plugins": "~1.0.1", "flot": "~0.8.3", diff --git a/config/dhcpcd.conf b/config/dhcpcd.conf index c78b82904..971a5733d 100644 --- a/config/dhcpcd.conf +++ b/config/dhcpcd.conf @@ -10,8 +10,13 @@ require dhcp_server_identifier slaac private nohook lookup-hostname -# RaspAP-WebGui wireless configuration +# RaspAP wlan0 configuration interface wlan0 static ip_address=10.3.141.1/24 static routers=10.3.141.1 static domain_name_server=1.1.1.1 8.8.8.8 + +# RaspAP uap0 configuration +interface uap0 +static ip_address=192.168.50.1/24 +nohook wpa_supplicant diff --git a/config/dnsmasq.conf b/config/dnsmasq.conf index 6ae8e9085..51873f238 100644 --- a/config/dnsmasq.conf +++ b/config/dnsmasq.conf @@ -1,3 +1,12 @@ -domain-needed +# RaspAP wlan0 configuration for wired (ethernet) AP mode interface=wlan0 dhcp-range=10.3.141.50,10.3.141.255,255.255.255.0,12h + +# RaspAP uap0 configuration for wireless client AP mode +#interface=lo,uap0 # Use interfaces lo and uap0 +#bind-interfaces # Bind to the interfaces +#server=8.8.8.8 # Forward DNS requests to Google DNS +#domain-needed # Don't forward short names +#bogus-priv # Never forward addresses in the non-routed address spaces +#dhcp-range=192.168.50.50,192.168.50.150,12h + diff --git a/config/hostapd.conf b/config/hostapd.conf index ab44b673d..eea438879 100644 --- a/config/hostapd.conf +++ b/config/hostapd.conf @@ -16,3 +16,6 @@ country_code= #ieee80211n=1 # 802.11n support (Raspberry Pi 3) #wmm_enabled=1 # QoS support (Raspberry Pi 3) #ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40] # (Raspberry Pi 3) + +## RaspAP wireless client AP mode +#interface=uap0 \ No newline at end of file diff --git a/includes/config.php b/includes/config.php index c7204deda..7ee16c12d 100755 --- a/includes/config.php +++ b/includes/config.php @@ -11,6 +11,7 @@ define('RASPI_DNSMASQ_CONFIG', '/etc/dnsmasq.conf'); define('RASPI_DNSMASQ_LEASES', '/var/lib/misc/dnsmasq.leases'); define('RASPI_HOSTAPD_CONFIG', '/etc/hostapd/hostapd.conf'); +define('RASPI_DHCPCD_CONFIG', '/etc/dhcpcd.conf'); define('RASPI_WPA_SUPPLICANT_CONFIG', '/etc/wpa_supplicant/wpa_supplicant.conf'); define('RASPI_HOSTAPD_CTRL_INTERFACE', '/var/run/hostapd'); define('RASPI_WPA_CTRL_INTERFACE', '/var/run/wpa_supplicant'); diff --git a/includes/hostapd.php b/includes/hostapd.php index 202248128..92192165c 100755 --- a/includes/hostapd.php +++ b/includes/hostapd.php @@ -27,7 +27,11 @@ function DisplayHostAPDConfig() } elseif( isset($_POST['StartHotspot']) ) { if (CSRFValidate()) { $status->addMessage('Attempting to start hotspot', 'info'); - exec( 'sudo /etc/init.d/hostapd start', $return ); + if ($arrHostapdConf['WifiAPEnable'] == 1) { + exec('sudo /etc/raspap/hostapd/servicestart.sh --interface uap0 --seconds 5', $return ); + } else { + exec( 'sudo /etc/raspap/hostapd/servicestart.sh --seconds 5', $return ); + } foreach( $return as $line ) { $status->addMessage($line, 'info'); } @@ -80,7 +84,7 @@ function DisplayHostAPDConfig()
  • -
  • +
  • @@ -180,33 +184,46 @@ function DisplayHostAPDConfig()
    -

    +

    -
    -
    +
    +
    +
    +
    +
    + - - /> + /> +
    -
    - - /> +?> + /> +
    @@ -517,27 +534,40 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $status) } $good_input = true; - - // Check for Logging Checkbox - $logEnable = 0; - if($arrHostapdConf['LogEnable'] == 0) { - if(isset($_POST['logEnable'])) { - // Need code to enable logfile logging here - $logEnable = 1; - exec('sudo /etc/raspap/hostapd/enablelog.sh'); - } else { - exec('sudo /etc/raspap/hostapd/disablelog.sh'); - } - } else { - if(isset($_POST['logEnable'])) { - $logEnable = 1; - exec('sudo /etc/raspap/hostapd/enablelog.sh'); - } else { - exec('sudo /etc/raspap/hostapd/disablelog.sh'); - } - } - - write_php_ini(["LogEnable" => $logEnable],'/etc/raspap/hostapd.ini'); + + // Check for WiFi client AP mode checkbox + $wifiAPEnable = 0; + if($arrHostapdConf['WifiAPEnable'] == 0) { + if(isset($_POST['wifiAPEnable'])) { + $wifiAPEnable = 1; + } + } else { + if(isset($_POST['wifiAPEnable'])) { + $wifiAPEnable = 1; + } + } + + // Check for Logfile output checkbox + $logEnable = 0; + if($arrHostapdConf['LogEnable'] == 0) { + if(isset($_POST['logEnable'])) { + $logEnable = 1; + exec('sudo /etc/raspap/hostapd/enablelog.sh'); + } else { + exec('sudo /etc/raspap/hostapd/disablelog.sh'); + } + } else { + if(isset($_POST['logEnable'])) { + $logEnable = 1; + exec('sudo /etc/raspap/hostapd/enablelog.sh'); + } else { + exec('sudo /etc/raspap/hostapd/disablelog.sh'); + } + } + $cfg = []; + $cfg['LogEnable'] = $logEnable; + $cfg['WifiAPEnable'] = $wifiAPEnable; + write_php_ini($cfg,'/etc/raspap/hostapd.ini'); // Verify input if (empty($_POST['ssid']) || strlen($_POST['ssid']) > 32) { @@ -579,45 +609,91 @@ function SaveHostAPDConfig($wpa_array, $enc_types, $modes, $interfaces, $status) } if ($good_input) { - if ($tmp_file = fopen('/tmp/hostapddata', 'w')) { - // Fixed values - fwrite($tmp_file, 'driver=nl80211'.PHP_EOL); - fwrite($tmp_file, 'ctrl_interface='.RASPI_HOSTAPD_CTRL_INTERFACE.PHP_EOL); - fwrite($tmp_file, 'ctrl_interface_group=0'.PHP_EOL); - fwrite($tmp_file, 'auth_algs=1'.PHP_EOL); - fwrite($tmp_file, 'wpa_key_mgmt=WPA-PSK'.PHP_EOL); - fwrite($tmp_file, 'beacon_int=100'.PHP_EOL); - - fwrite($tmp_file, 'ssid='.$_POST['ssid'].PHP_EOL); - fwrite($tmp_file, 'channel='.$_POST['channel'].PHP_EOL); - if ($_POST['hw_mode'] === 'n') { - fwrite($tmp_file, 'hw_mode=g'.PHP_EOL); - fwrite($tmp_file, 'ieee80211n=1'.PHP_EOL); - // Enable basic Quality of service - fwrite($tmp_file, 'wme_enabled=1'.PHP_EOL); - } else { - fwrite($tmp_file, 'hw_mode='.$_POST['hw_mode'].PHP_EOL); - fwrite($tmp_file, 'ieee80211n=0'.PHP_EOL); - } + // Fixed values + $config = 'driver=nl80211'.PHP_EOL; + $config.= 'ctrl_interface='.RASPI_HOSTAPD_CTRL_INTERFACE.PHP_EOL; + $config.= 'ctrl_interface_group=0'.PHP_EOL; + $config.= 'auth_algs=1'.PHP_EOL; + $config.= 'wpa_key_mgmt=WPA-PSK'.PHP_EOL; + $config.= 'beacon_int=100'.PHP_EOL; + $config.= 'ssid='.$_POST['ssid'].PHP_EOL; + $config.= 'channel='.$_POST['channel'].PHP_EOL; + if ($_POST['hw_mode'] === 'n') { + $config.= 'hw_mode=g'.PHP_EOL; + $config.= 'ieee80211n=1'.PHP_EOL; + // Enable basic Quality of service + $config.= 'wme_enabled=1'.PHP_EOL; + } else { + $config.= 'hw_mode='.$_POST['hw_mode'].PHP_EOL; + $config.= 'ieee80211n=0'.PHP_EOL; + } + $config.= 'wpa_passphrase='.$_POST['wpa_passphrase'].PHP_EOL; + if ($wifiAPEnable == 1) { + $config.= 'interface=uap0'.PHP_EOL; + } else { + $config.= 'interface='.$_POST['interface'].PHP_EOL; + } + $config.= 'wpa='.$_POST['wpa'].PHP_EOL; + $config.= 'wpa_pairwise='.$_POST['wpa_pairwise'].PHP_EOL; + $config.= 'country_code='.$_POST['country_code'].PHP_EOL; + $config.= 'ignore_broadcast_ssid='.$ignore_broadcast_ssid.PHP_EOL; + + exec('echo "'.$config.'" > /tmp/hostapddata', $temp); + system( "sudo cp /tmp/hostapddata " . RASPI_HOSTAPD_CONFIG, $return ); + + if ($wifiAPEnable == 1) { + // Enable uap0 configuration in dnsmasq for Wifi client AP mode + $config = 'interface=lo,uap0 # Enable uap0 interface for wireless client AP mode'.PHP_EOL; + $config.= 'bind-interfaces # Bind to the interfaces'.PHP_EOL; + $config.= 'server=8.8.8.8 # Forward DNS requests to Google DNS'.PHP_EOL; + $config.= 'domain-needed # Don\'t forward short names'.PHP_EOL; + $config.= 'bogus-priv # Never forward addresses in the non-routed address spaces'.PHP_EOL; + $config.= 'dhcp-range=192.168.50.50,192.168.50.150,12h'.PHP_EOL; + } else { + // Fallback to default config + $config = 'domain-needed'.PHP_EOL; + $config.= 'interface='.$_POST['interface'].PHP_EOL; + $config.= 'dhcp-range=10.3.141.50,10.3.141.255,255.255.255.0,12h'.PHP_EOL; + } + exec('echo "'.$config.'" > /tmp/dhcpddata', $temp); + system('sudo cp /tmp/dhcpddata '.RASPI_DNSMASQ_CONFIG, $return); + + if ($wifiAPEnable == 1) { + // Enable uap0 configuration in dhcpcd for Wifi client AP mode + $config = PHP_EOL.'# RaspAP uap0 configuration'.PHP_EOL; + $config.= 'interface uap0'.PHP_EOL; + $config.= 'static ip_address=192.168.50.1/24'.PHP_EOL; + $config.= 'nohook wpa_supplicant'.PHP_EOL; + } else { + // Default config + $config = '# RaspAP wlan0 configuration'.PHP_EOL; + $config.= 'hostname'.PHP_EOL; + $config.= 'clientid'.PHP_EOL; + $config.= 'persistent'.PHP_EOL; + $config.= 'option rapid_commit'.PHP_EOL; + $config.= 'option domain_name_servers, domain_name, domain_search, host_name'.PHP_EOL; + $config.= 'option classless_static_routes'.PHP_EOL; + $config.= 'option ntp_servers'.PHP_EOL; + $config.= 'require dhcp_server_identifier'.PHP_EOL; + $config.= 'slaac private'.PHP_EOL; + $config.= 'nohook lookup-hostname'.PHP_EOL; + $config.= 'interface '.RASPI_WIFI_CLIENT_INTERFACE.PHP_EOL; + $config.= 'static ip_address=10.3.141.1/24'.PHP_EOL; + $config.= 'static routers=10.3.141.1'.PHP_EOL; + $config.= 'static domain_name_server=1.1.1.1 8.8.8.8'.PHP_EOL; + } + exec('echo "'.$config.'" > /tmp/dhcpddata', $temp); + system('sudo cp /tmp/dhcpddata '.RASPI_DHCPCD_CONFIG, $return); - fwrite($tmp_file, 'wpa_passphrase='.$_POST['wpa_passphrase'].PHP_EOL); - fwrite($tmp_file, 'interface='.$_POST['interface'].PHP_EOL); - fwrite($tmp_file, 'wpa='.$_POST['wpa'].PHP_EOL); - fwrite($tmp_file, 'wpa_pairwise='.$_POST['wpa_pairwise'].PHP_EOL); - fwrite($tmp_file, 'country_code='.$_POST['country_code'].PHP_EOL); - fwrite($tmp_file, 'ignore_broadcast_ssid='.$ignore_broadcast_ssid.PHP_EOL); - fclose($tmp_file); - - system( "sudo cp /tmp/hostapddata " . RASPI_HOSTAPD_CONFIG, $return ); - if( $return == 0 ) { - $status->addMessage('Wifi Hotspot settings saved', 'success'); - } else { - $status->addMessage('Unable to save wifi hotspot settings', 'danger'); - } + + if( $return == 0 ) { + $status->addMessage('Wifi Hotspot settings saved', 'success'); } else { $status->addMessage('Unable to save wifi hotspot settings', 'danger'); - return false; } + } else { + $status->addMessage('Unable to save wifi hotspot settings', 'danger'); + return false; } return true; diff --git a/index.php b/index.php index 143bbf2cd..831cd1963 100755 --- a/index.php +++ b/index.php @@ -69,6 +69,9 @@ + + + @@ -239,6 +242,9 @@ + + + diff --git a/installers/common.sh b/installers/common.sh index 5ab2a078c..884c1eeab 100755 --- a/installers/common.sh +++ b/installers/common.sh @@ -114,16 +114,18 @@ function create_raspap_directories() { sudo chown -R $raspap_user:$raspap_user "$raspap_dir" || install_error "Unable to change file ownership for '$raspap_dir'" } -# Generate logging enable/disable files for hostapd -function create_logging_scripts() { - install_log "Creating logging scripts" +# Generate hostapd logging and service control scripts +function create_hostapd_scripts() { + install_log "Creating hostapd logging & control scripts" sudo mkdir $raspap_dir/hostapd || install_error "Unable to create directory '$raspap_dir/hostapd'" - # Move existing shell scripts + # Move logging shell scripts sudo mv "$webroot_dir/installers/"*log.sh "$raspap_dir/hostapd" || install_error "Unable to move logging scripts" + # Move service control shell scripts + sudo mv "$webroot_dir/installers/"service*.sh "$raspap_dir/hostapd" || install_error "Unable to move service control scripts" # Make enablelog.sh and disablelog.sh not writable by www-data group. - sudo chown -c root:"$raspap_user" "$raspap_dir/hostapd/"*log.sh || install_error "Unable change owner and/or group." - sudo chmod 750 "$raspap_dir/hostapd/"*log.sh || install_error "Unable to change file permissions." + sudo chown -c root:"$raspap_user" "$raspap_dir/hostapd/"*.sh || install_error "Unable change owner and/or group." + sudo chmod 750 "$raspap_dir/hostapd/"*.sh || install_error "Unable to change file permissions." } @@ -203,7 +205,7 @@ function default_configuration() { lines=( 'echo 1 > \/proc\/sys\/net\/ipv4\/ip_forward #RASPAP' 'iptables -t nat -A POSTROUTING -j MASQUERADE #RASPAP' - + 'iptables -t nat -A POSTROUTING -s 192.168.50.0\/24 ! -d 192.168.50.0\/24 -j MASQUERADE #RASPAP' ) for line in "${lines[@]}"; do @@ -214,6 +216,10 @@ function default_configuration() { echo "Adding line $line" fi done + + # Force a reload of new settings in /etc/rc.local + sudo systemctl restart rc-local.service + sudo systemctl daemon-reload } @@ -246,6 +252,7 @@ function patch_system_files() { "/etc/init.d/dnsmasq start" "/etc/init.d/dnsmasq stop" "/bin/cp /tmp/dhcpddata /etc/dnsmasq.conf" + "/bin/cp /tmp/dhcpddata /etc/dhcpcd.conf" "/sbin/shutdown -h now" "/sbin/reboot" "/sbin/ip link set wlan[0-9] down" @@ -254,6 +261,7 @@ function patch_system_files() { "/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf" "/etc/raspap/hostapd/enablelog.sh" "/etc/raspap/hostapd/disablelog.sh" + "/etc/raspap/hostapd/servicestart.sh" ) # Check if sudoers needs patching @@ -318,13 +326,17 @@ function optimize_php() { function install_complete() { install_log "Installation completed!" - echo -n "The system needs to be rebooted as a final step. Reboot now? [y/N]: " - read answer - if [[ $answer != "y" ]]; then - echo "Installation reboot aborted." - exit 0 + # Prompt to reboot if wired ethernet (eth0) is connected. + # With default_configuration this will create an active AP on restart. + if ip a | grep -q ': eth0:.*state UP'; then + echo -n "The system needs to be rebooted as a final step. Reboot now? [y/N]: " + read answer + if [[ $answer != "y" ]]; then + echo "Installation reboot aborted." + exit 0 + fi + sudo shutdown -r now || install_error "Unable to execute shutdown" fi - sudo shutdown -r now || install_error "Unable to execute shutdown" } function install_raspap() { @@ -338,7 +350,7 @@ function install_raspap() { check_for_old_configs download_latest_files change_file_ownership - create_logging_scripts + create_hostapd_scripts move_config_file default_configuration patch_system_files diff --git a/installers/servicestart.sh b/installers/servicestart.sh new file mode 100755 index 000000000..9182ff15e --- /dev/null +++ b/installers/servicestart.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# When wireless client AP mode is enabled, this script handles starting +# up network services in a specific order and timing to avoid race conditions. + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +NAME=raspap +DESC="Service control for RaspAP" + +positional=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -i|--interface) + interface="$2" + shift # past argument + shift # past value + ;; + -s|--seconds) + seconds="$2" + shift # past argument + shift # past value + ;; +esac +done +set -- "${positional[@]}" + +echo "Stopping network services..." +systemctl stop hostapd.service +systemctl stop dnsmasq.service +systemctl stop dhcpcd.service + +echo "Removing uap0 interface..." +iw dev uap0 del + +if [ "${interface}" = "uap0" ]; then + echo "Adding uap0 interface..." + iw dev wlan0 interface add uap0 type __ap + + # Bring up uap0 interface + ifconfig uap0 up +fi + +# Start services, mitigating race conditions +echo "Starting hostapd service..." +systemctl start hostapd.service +sleep "${seconds}" + +echo "Starting dhcpcd service..." +systemctl start dhcpcd.service +sleep "${seconds}" + +echo "Starting dnsmasq service..." +systemctl start dnsmasq.service + +echo "RaspAP service start DONE" + diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index 40c0ffda7..d099f8ee6 100644 Binary files a/locale/en_US/LC_MESSAGES/messages.mo and b/locale/en_US/LC_MESSAGES/messages.mo differ diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index b564c5889..1beac6012 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -217,6 +217,9 @@ msgstr "Start wlan0" msgid "Stop wlan0" msgstr "Stop wlan0" +msgid "Connected Devices" +msgstr "Connected Devices" + #: includes/dhcp.php msgid "Server settings" msgstr "Server settings" diff --git a/vendor/bootstrap-toggle/Gruntfile.js b/vendor/bootstrap-toggle/Gruntfile.js new file mode 100644 index 000000000..9ac6fc521 --- /dev/null +++ b/vendor/bootstrap-toggle/Gruntfile.js @@ -0,0 +1,37 @@ +module.exports = function(grunt) { + 'use strict'; + + grunt.initConfig({ + clean: ['dist'], + uglify: { + options: { + preserveComments: 'some', + sourceMap: true + }, + build: { + expand: true, + cwd: 'js', + src: ['**/*.js', ['!**/*.min.js']], + dest: 'js', + ext: '.min.js', + } + }, + cssmin: { + options: { + keepBreaks: true + }, + build: { + expand: true, + cwd: 'css', + src: ['**/*.css', ['!**/*.min.css']], + dest: 'css', + ext: '.min.css', + } + } + }); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.registerTask('default', ['clean', 'uglify', 'cssmin']); + +}; \ No newline at end of file diff --git a/vendor/bootstrap-toggle/LICENSE b/vendor/bootstrap-toggle/LICENSE new file mode 100644 index 000000000..88bb5abfc --- /dev/null +++ b/vendor/bootstrap-toggle/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-2014 Min Hur, The New York Times Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/bootstrap-toggle/README.md b/vendor/bootstrap-toggle/README.md new file mode 100644 index 000000000..f9c0dcd48 --- /dev/null +++ b/vendor/bootstrap-toggle/README.md @@ -0,0 +1,175 @@ +# Bootstrap Toggle +Bootstrap Toggle is a highly flexible Bootstrap plugin that converts checkboxes into toggles. + +Visit http://www.bootstraptoggle.com for demos. + +## Getting Started + +### Installation +You can [download](https://github.com/minhur/bootstrap-toggle/archive/master.zip) the latest version of Bootstrap Toggle or use CDN to load the library. + +`Warning` If you are using Bootstrap v2.3.2, use `bootstrap2-toggle.min.js` and `bootstrap2-toggle.min.css` instead. + +```html + + +``` + +### Bower Install +```bash +bower install bootstrap-toggle +``` + +## Usage + +### Basic example +Simply add `data-toggle="toggle"` to convert checkboxes into toggles. + +```html + +``` + +### Stacked checkboxes +Refer to Bootstrap Form Controls documentation to create stacked checkboxes. Simply add `data-toggle="toggle"` to convert checkboxes into toggles. + +```html +
    + +
    +
    + +
    +``` + +### Inline Checkboxes +Refer to Bootstrap Form Controls documentation to create inline checkboxes. Simply add `data-toggle="toggle"` to a convert checkboxes into toggles. + +```html + + + +``` + +## API + +### Initialize by JavaScript +Initialize toggles with id `toggle-one` with a single line of JavaScript. + +```html + + +``` + +### Options +Options can be passed via data attributes or JavaScript. For data attributes, append the option name to `data-`, as in `data-on="Enabled"`. + +```html + + + +``` + +Name|Type|Default|Description| +---|---|---|--- +on|string/html|"On"|Text of the on toggle +off|string/html|"Off"|Text of the off toggle +size|string|"normal"|Size of the toggle. Possible values are `large`, `normal`, `small`, `mini`. +onstyle|string|"primary"|Style of the on toggle. Possible values are `default`, `primary`, `success`, `info`, `warning`, `danger` +offstyle|string|"default"|Style of the off toggle. Possible values are `default`, `primary`, `success`, `info`, `warning`, `danger` +style|string| |Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference. +width|integer|*null*|Sets the width of the toggle. if set to *null*, width will be calculated. +height|integer|*null*|Sets the height of the toggle. if set to *null*, height will be calculated. + +### Methods +Methods can be used to control toggles directly. + +```html + +``` + +Method|Example|Description +---|---|--- +initialize|$('#toggle-demo').bootstrapToggle()|Initializes the toggle plugin with options +destroy|$('#toggle-demo').bootstrapToggle('destroy')|Destroys the toggle +on|$('#toggle-demo').bootstrapToggle('on')|Sets the toggle to 'On' state +off|$('#toggle-demo').bootstrapToggle('off')|Sets the toggle to 'Off' state +toggle|$('#toggle-demo').bootstrapToggle('toggle')|Toggles the state of the toggle +enable|$('#toggle-demo').bootstrapToggle('enable')|Enables the toggle +disable|$('#toggle-demo').bootstrapToggle('disable')|Disables the toggle + +## Events + +### Event Propagation +Note All events are propagated to and from input element to the toggle. + +You should listen to events from the `` directly rather than look for custom events. + +```html + +
    + +``` + +### API vs Input +This also means that using the API or Input to trigger events will work both ways. + +```html + + + + + + +``` + +### Integration + +#### [KnockoutJS](http://knockoutjs.com) + +A binding for knockout is available here: [aAXEe/knockout-bootstrap-toggle](https://github.com/aAXEe/knockout-bootstrap-toggle) + +## Demos + +Visit http://www.bootstraptoggle.com for demos. diff --git a/vendor/bootstrap-toggle/bower.json b/vendor/bootstrap-toggle/bower.json new file mode 100644 index 000000000..9d941dff9 --- /dev/null +++ b/vendor/bootstrap-toggle/bower.json @@ -0,0 +1,32 @@ +{ + "name": "bootstrap-toggle", + "description": "Bootstrap Toggle is a highly flexible Bootstrap plugin that converts checkboxes into toggles", + "version": "2.2.1", + "keywords": [ + "bootstrap", + "toggle", + "bootstrap-toggle", + "switch", + "bootstrap-switch" + ], + "homepage": "http://www.bootstraptoggle.com", + "repository": { + "type": "git", + "url": "https://github.com/minhur/bootstrap-toggle.git" + }, + "license": "MIT", + "authors": [ + "Min Hur " + ], + "main": [ + "./js/bootstrap-toggle.min.js", + "./css/bootstrap-toggle.min.css" + ], + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/vendor/bootstrap-toggle/css/bootstrap-toggle.css b/vendor/bootstrap-toggle/css/bootstrap-toggle.css new file mode 100644 index 000000000..057d08b36 --- /dev/null +++ b/vendor/bootstrap-toggle/css/bootstrap-toggle.css @@ -0,0 +1,83 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ + + +.checkbox label .toggle, +.checkbox-inline .toggle { + margin-left: -20px; + margin-right: 5px; +} + +.toggle { + position: relative; + overflow: hidden; +} +.toggle input[type="checkbox"] { + display: none; +} +.toggle-group { + position: absolute; + width: 200%; + top: 0; + bottom: 0; + left: 0; + transition: left 0.35s; + -webkit-transition: left 0.35s; + -moz-user-select: none; + -webkit-user-select: none; +} +.toggle.off .toggle-group { + left: -100%; +} +.toggle-on { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 50%; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-off { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + right: 0; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-handle { + position: relative; + margin: 0 auto; + padding-top: 0px; + padding-bottom: 0px; + height: 100%; + width: 0px; + border-width: 0 1px; +} + +.toggle.btn { min-width: 59px; min-height: 34px; } +.toggle-on.btn { padding-right: 24px; } +.toggle-off.btn { padding-left: 24px; } + +.toggle.btn-lg { min-width: 79px; min-height: 45px; } +.toggle-on.btn-lg { padding-right: 31px; } +.toggle-off.btn-lg { padding-left: 31px; } +.toggle-handle.btn-lg { width: 40px; } + +.toggle.btn-sm { min-width: 50px; min-height: 30px;} +.toggle-on.btn-sm { padding-right: 20px; } +.toggle-off.btn-sm { padding-left: 20px; } + +.toggle.btn-xs { min-width: 35px; min-height: 22px;} +.toggle-on.btn-xs { padding-right: 12px; } +.toggle-off.btn-xs { padding-left: 12px; } + diff --git a/vendor/bootstrap-toggle/css/bootstrap-toggle.min.css b/vendor/bootstrap-toggle/css/bootstrap-toggle.min.css new file mode 100644 index 000000000..0d42ed09c --- /dev/null +++ b/vendor/bootstrap-toggle/css/bootstrap-toggle.min.css @@ -0,0 +1,28 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ +.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} +.toggle{position:relative;overflow:hidden} +.toggle input[type=checkbox]{display:none} +.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} +.toggle.off .toggle-group{left:-100%} +.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} +.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} +.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} +.toggle.btn{min-width:59px;min-height:34px} +.toggle-on.btn{padding-right:24px} +.toggle-off.btn{padding-left:24px} +.toggle.btn-lg{min-width:79px;min-height:45px} +.toggle-on.btn-lg{padding-right:31px} +.toggle-off.btn-lg{padding-left:31px} +.toggle-handle.btn-lg{width:40px} +.toggle.btn-sm{min-width:50px;min-height:30px} +.toggle-on.btn-sm{padding-right:20px} +.toggle-off.btn-sm{padding-left:20px} +.toggle.btn-xs{min-width:35px;min-height:22px} +.toggle-on.btn-xs{padding-right:12px} +.toggle-off.btn-xs{padding-left:12px} \ No newline at end of file diff --git a/vendor/bootstrap-toggle/css/bootstrap2-toggle.css b/vendor/bootstrap-toggle/css/bootstrap2-toggle.css new file mode 100644 index 000000000..3f48927ed --- /dev/null +++ b/vendor/bootstrap-toggle/css/bootstrap2-toggle.css @@ -0,0 +1,85 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap2-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ + + +label.checkbox .toggle, +label.checkbox.inline .toggle { + margin-left: -20px; + margin-right: 5px; +} +.toggle { + min-width: 40px; + height: 20px; + position: relative; + overflow: hidden; +} +.toggle input[type="checkbox"] { + display: none; +} +.toggle-group { + position: absolute; + width: 200%; + top: 0; + bottom: 0; + left: 0; + transition: left 0.35s; + -webkit-transition: left 0.35s; + -moz-user-select: none; + -webkit-user-select: none; +} +.toggle.off .toggle-group { + left: -100%; +} +.toggle-on { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 50%; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-off { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + right: 0; + margin: 0; + border: 0; + border-radius: 0; +} +.toggle-handle { + position: relative; + margin: 0 auto; + padding-top: 0px; + padding-bottom: 0px; + height: 100%; + width: 0px; + border-width: 0 1px; +} +.toggle-handle.btn-mini { + top: -1px; +} +.toggle.btn { min-width: 30px; } +.toggle-on.btn { padding-right: 24px; } +.toggle-off.btn { padding-left: 24px; } + +.toggle.btn-large { min-width: 40px; } +.toggle-on.btn-large { padding-right: 35px; } +.toggle-off.btn-large { padding-left: 35px; } + +.toggle.btn-small { min-width: 25px; } +.toggle-on.btn-small { padding-right: 20px; } +.toggle-off.btn-small { padding-left: 20px; } + +.toggle.btn-mini { min-width: 20px; } +.toggle-on.btn-mini { padding-right: 12px; } +.toggle-off.btn-mini { padding-left: 12px; } + diff --git a/vendor/bootstrap-toggle/css/bootstrap2-toggle.min.css b/vendor/bootstrap-toggle/css/bootstrap2-toggle.min.css new file mode 100644 index 000000000..1509c5730 --- /dev/null +++ b/vendor/bootstrap-toggle/css/bootstrap2-toggle.min.css @@ -0,0 +1,28 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap2-toggle.css v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ +label.checkbox .toggle,label.checkbox.inline .toggle{margin-left:-20px;margin-right:5px} +.toggle{min-width:40px;height:20px;position:relative;overflow:hidden} +.toggle input[type=checkbox]{display:none} +.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} +.toggle.off .toggle-group{left:-100%} +.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} +.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} +.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} +.toggle-handle.btn-mini{top:-1px} +.toggle.btn{min-width:30px} +.toggle-on.btn{padding-right:24px} +.toggle-off.btn{padding-left:24px} +.toggle.btn-large{min-width:40px} +.toggle-on.btn-large{padding-right:35px} +.toggle-off.btn-large{padding-left:35px} +.toggle.btn-small{min-width:25px} +.toggle-on.btn-small{padding-right:20px} +.toggle-off.btn-small{padding-left:20px} +.toggle.btn-mini{min-width:20px} +.toggle-on.btn-mini{padding-right:12px} +.toggle-off.btn-mini{padding-left:12px} \ No newline at end of file diff --git a/vendor/bootstrap-toggle/doc/header.png b/vendor/bootstrap-toggle/doc/header.png new file mode 100644 index 000000000..eb8d58be9 Binary files /dev/null and b/vendor/bootstrap-toggle/doc/header.png differ diff --git a/vendor/bootstrap-toggle/doc/nyt.png b/vendor/bootstrap-toggle/doc/nyt.png new file mode 100644 index 000000000..4025f263b Binary files /dev/null and b/vendor/bootstrap-toggle/doc/nyt.png differ diff --git a/vendor/bootstrap-toggle/doc/nytdev.svg b/vendor/bootstrap-toggle/doc/nytdev.svg new file mode 100644 index 000000000..39669eebe --- /dev/null +++ b/vendor/bootstrap-toggle/doc/nytdev.svg @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/bootstrap-toggle/doc/script.js b/vendor/bootstrap-toggle/doc/script.js new file mode 100644 index 000000000..863728ae9 --- /dev/null +++ b/vendor/bootstrap-toggle/doc/script.js @@ -0,0 +1,49 @@ ++function ($) { + 'use strict'; + + $('.example:not(.skip)').each(function() { + // fetch & encode html + var html = $('
    ').text($(this).html()).html() + // find number of space/tabs on first line (minus line break) + var count = html.match(/^(\s+)/)[0].length - 1 + // replace tabs/spaces on each lines with + var regex = new RegExp('\\n\\s{'+count+'}', 'g') + var code = html.replace(regex, '\n').replace(/\t/g, ' ').trim() + // other cleanup + code = code.replace(/=""/g,'') + // add code block to dom + $(this).after( $('').html(code) ) + }); + + $('code.highlight').each(function() { + hljs.highlightBlock(this) + }); + +}(jQuery); + +var Demo = function () {} + +Demo.prototype.init = function(selector) { + $(selector).bootstrapToggle(selector) +} +Demo.prototype.destroy = function(selector) { + $(selector).bootstrapToggle('destroy') +} +Demo.prototype.on = function(selector) { + $(selector).bootstrapToggle('on') +} +Demo.prototype.off = function(selector) { + $(selector).bootstrapToggle('off') +} +Demo.prototype.toggle = function(selector) { + $(selector).bootstrapToggle('toggle') +} +Demo.prototype.enable = function(selector) { + $(selector).bootstrapToggle('enable') +} +Demo.prototype.disable = function(selector) { + $(selector).bootstrapToggle('disable') +} + + +demo = new Demo() diff --git a/vendor/bootstrap-toggle/doc/stylesheet.css b/vendor/bootstrap-toggle/doc/stylesheet.css new file mode 100644 index 000000000..fe7a444c0 --- /dev/null +++ b/vendor/bootstrap-toggle/doc/stylesheet.css @@ -0,0 +1,112 @@ +header, footer { + padding: 20px; + background-image: url('header.png'); + background-size: 256px 256px; +} +footer { + color: #fff; + text-align: center; +} +.nyt-logo { + max-height: 40px; + margin-top: 5px; + margin-right: 5px; +} + +nav.navbar { + margin-bottom: 10px; + background-color: #fff; + border: 0px; + border-radius: 2px; +} +#navbar { + margin: 0px; +} +#navbar .navbar-nav li iframe { + margin-top: 15px; +} +#navbar .navbar-nav li:last-child iframe { + margin-right: 15px; +} + +@media screen and (max-width: 767px) { + #navbar .navbar-nav li iframe { + display: none; + } +} + +.mast-head { + margin: 10px 0; +} +.mast-head h1 { + margin-bottom: 15px; + color: #fff; +} +.mast-head p { + color: #fff; +} + +.mast-links { + padding-top: 10px; +} + +.mast-links > * { + vertical-align: middle; + margin-bottom: 10px; +} + +.mast-links > .btn { + margin-right: 30px; +} +main { + margin: 10px 20px; +} +main .container { + margin-bottom: 40px; +} + +code.hljs { + border: 1px solid #ccc; + padding: 1em; + white-space: pre; + margin-bottom: 10px; +} + +.example { + position: relative; + border: 1px solid #ccc; + padding: 1em 1em 0.5em 1em; + border-radius: 4px 4px 0 0; +} + +.example:after { + content: "Example"; + position: absolute; + top: 0px; + right: 0px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #ccc; + color: #9da0a4; + border-radius: 0px 4px 0px 4px; + border-width: 0px 0px 1px 1px; +} + +.example + code.hljs { + border-top: 0; + border-radius: 0px 0px 4px 4px; +} + +.example > * { + margin-bottom: 10px; +} + +.example > div.toggle { + margin-right: 10px; +} + +.table-striped code { + background-color: inherit; +} \ No newline at end of file diff --git a/vendor/bootstrap-toggle/index.html b/vendor/bootstrap-toggle/index.html new file mode 100644 index 000000000..3874fa25e --- /dev/null +++ b/vendor/bootstrap-toggle/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + Bootstrap Toggle + + + + + + + + + +
    + +
    +
    +

    Bootstrap Toggle

    +

    Bootstrap Toggle is a highly flexible Bootstrap plugin that converts checkboxes into toggles

    + +
    +
    +
    + +
    +
    +

    Getting Started

    +
    +

    Installation

    +

    You can download the latest version of Bootstrap Toggle or use CDN to load the library.

    +

    Warning If you are using Bootstrap v2.3.2, use bootstrap2-toggle.min.js and bootstrap2-toggle.min.css instead.

    + <link href="https://gitcdn.github.io/bootstrap-toggle/2.2.0/css/bootstrap-toggle.min.css" rel="stylesheet"> +<script src="https://gitcdn.github.io/bootstrap-toggle/2.2.0/js/bootstrap-toggle.min.js"></script> + +

    Bower Install

    +

    + bower install bootstrap-toggle +
    +
    +

    Usage

    +
    + +

    Basic example

    +

    Simply add data-toggle="toggle" to convert checkboxes into toggles.

    +
    + +
    + +

    Stacked checkboxes

    +

    Refer to Bootstrap Form Controls documentation to create stacked checkboxes. Simply add data-toggle="toggle" to convert checkboxes into toggles.

    +
    +
    + +
    +
    + +
    +
    + +

    Inline Checkboxes

    +

    Refer to Bootstrap Form Controls documentation to create inline checkboxes. Simply add data-toggle="toggle" to a convert checkboxes into toggles.

    +
    + + + +
    +
    + +
    +

    API

    +
    + +

    Initialize by JavaScript

    +

    Initialize toggles with id toggle-one with a single line of JavaScript.

    +
    + + +
    + +

    Options

    +

    Options can be passed via data attributes or JavaScript. For data attributes, append the option name to data-, as in data-on="Enabled".

    +
    + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    onstring | html"On"Text of the on toggle
    offstring | html"Off"Text of the off toggle
    sizestring"normal" + Size of the toggle. Possible values are:large,normal,small,mini
    + Refer to Bootstrap Button Sizes documentation for more information. +
    onstylestring"primary" + Style of the on toggle.
    Possible values are:default,primary,success,info,warning,danger
    + Refer to Bootstrap Button Options documentation for more information. +
    offstylestring"default" + Style of the off toggle.
    Possible values are:default,primary,success,info,warning,danger
    + Refer to Bootstrap Button Options documentation for more information. +
    stylestring + Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference. +
    widthintegernull + Sets the width of the toggle. if set to null, width will be calculated. +
    heightintegernull + Sets the height of the toggle. if set to null, height will be calculated. +
    +
    + +

    Methods

    +

    Methods can be used to control toggles directly.

    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MethodExampleDescriptionDemo
    initialize$('#toggle-demo').bootstrapToggle()Initializes the toggle plugin with options
    destroy$('#toggle-demo').bootstrapToggle('destroy')Destroys the toggle
    on$('#toggle-demo').bootstrapToggle('on')Sets the toggle to 'On' state
    off$('#toggle-demo').bootstrapToggle('off')Sets the toggle to 'Off' state
    toggle$('#toggle-demo').bootstrapToggle('toggle')Toggles the state of the toggle
    enable$('#toggle-demo').bootstrapToggle('enable')Enables the toggle
    disable$('#toggle-demo').bootstrapToggle('disable')Disables the toggle
    +
    +
    + + +
    +

    Events

    +
    + +

    Event Propagation

    +

    Note All events are propagated to and from input element to the toggle.

    +

    You should listen to events from the <input type="checkbox"> directly rather than look for custom events.

    +
    + +
    + +
    + +

    API vs Input

    +

    This also means that using the API or Input to trigger events will work both ways.

    +
    + + + + + + +
    +
    + +
    +

    Demos

    +
    + +

    Sizes

    +

    Bootstrap toggle is available in different sizes. Refer to Bootstrap Button Sizes documentation for more information.

    +
    + + + + +
    + +

    Custom Sizes

    +

    Bootstrap toggle can handle custom sizes by data-width and data-height options.

    +
    + + + +
    + +

    Colors

    +

    Bootstrap Toggle supports various colors. Refer to Bootstrap Button Options documentation for more information.

    +
    + + + + + + +
    + +

    Colors Mix

    +

    You can style on state as well as the off state.

    +
    + + +
    + +

    Custom Style

    +

    Customized styles can be applied as easily.

    +
    + + + + +
    + +

    Custom Text

    +

    The text can be changed easily with attributes or options.

    +
    + +
    + +

    Icons/Html Text

    +

    You can easily add icons or images since html is supported for on/off text.

    +
    + +
    + +

    Multiple Lines of Text

    +

    Toggles with multiple lines will adjust its heights.

    +
    + +
    + +

    Animation Speed

    +

    Transition speed can be easily controlled with css transition property on .toggle-group. You can also turn animation off completely.

    +
    + + + + +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/vendor/bootstrap-toggle/js/bootstrap-toggle.js b/vendor/bootstrap-toggle/js/bootstrap-toggle.js new file mode 100644 index 000000000..533914edd --- /dev/null +++ b/vendor/bootstrap-toggle/js/bootstrap-toggle.js @@ -0,0 +1,180 @@ +/*! ======================================================================== + * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 + * http://www.bootstraptoggle.com + * ======================================================================== + * Copyright 2014 Min Hur, The New York Times Company + * Licensed under MIT + * ======================================================================== */ + + + +function ($) { + 'use strict'; + + // TOGGLE PUBLIC CLASS DEFINITION + // ============================== + + var Toggle = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, this.defaults(), options) + this.render() + } + + Toggle.VERSION = '2.2.0' + + Toggle.DEFAULTS = { + on: 'On', + off: 'Off', + onstyle: 'primary', + offstyle: 'default', + size: 'normal', + style: '', + width: null, + height: null + } + + Toggle.prototype.defaults = function() { + return { + on: this.$element.attr('data-on') || Toggle.DEFAULTS.on, + off: this.$element.attr('data-off') || Toggle.DEFAULTS.off, + onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle, + offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle, + size: this.$element.attr('data-size') || Toggle.DEFAULTS.size, + style: this.$element.attr('data-style') || Toggle.DEFAULTS.style, + width: this.$element.attr('data-width') || Toggle.DEFAULTS.width, + height: this.$element.attr('data-height') || Toggle.DEFAULTS.height + } + } + + Toggle.prototype.render = function () { + this._onstyle = 'btn-' + this.options.onstyle + this._offstyle = 'btn-' + this.options.offstyle + var size = this.options.size === 'large' ? 'btn-lg' + : this.options.size === 'small' ? 'btn-sm' + : this.options.size === 'mini' ? 'btn-xs' + : '' + var $toggleOn = $('