--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+var storage = chrome.storage.sync;
+
+var settings_keys = [
+ 'url',
+ 'user',
+ 'pass',
+];
+
+var settings_defaults = {
+ 'url': 'http://192.168.0.100',
+ 'user': 'admin',
+ 'pass': '1234',
+};
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="outlet.svg">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ id="perspective21759" />
+ <inkscape:perspective
+ id="perspective21787"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="291.5045"
+ inkscape:cy="764.33071"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1-3"
+ showgrid="false"
+ inkscape:window-width="1440"
+ inkscape:window-height="879"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ transform="translate(-83.156683,-227.22927)"
+ id="layer1-3"
+ inkscape:label="Layer 1">
+ <rect
+ y="-520.10016"
+ x="365.11285"
+ height="16.601831"
+ width="15.092574"
+ id="rect3338"
+ style="fill:#dcdedf;fill-opacity:1;stroke:#000000;stroke-width:0.60370296;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ transform="scale(1,-1)"
+ inkscape:export-xdpi="671.18805"
+ inkscape:export-ydpi="671.18805" />
+ <path
+ d="m 181.40845,418.20724 a 5.7042236,5.8450623 0 1 1 -11.40845,0 5.7042236,5.8450623 0 1 1 11.40845,0 z"
+ sodipodi:ry="5.8450623"
+ sodipodi:rx="5.7042236"
+ sodipodi:cy="418.20724"
+ sodipodi:cx="175.70422"
+ id="path3332"
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ sodipodi:type="arc"
+ transform="matrix(0.30185148,0,0,-0.30185148,319.83513,641.55378)" />
+ <rect
+ y="-512.55389"
+ x="368.13138"
+ height="5.8224154"
+ width="1.5092574"
+ id="rect3334"
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ transform="scale(1,-1)" />
+ <rect
+ y="-511.8396"
+ x="375.67764"
+ height="4.4384866"
+ width="1.5092574"
+ id="rect3336"
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ transform="scale(1,-1)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:0.85022688px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="rect3563"
+ width="2.5937731"
+ height="1.1497731"
+ x="371.5748"
+ y="515.74774" />
+ </g>
+ </g>
+</svg>
--- /dev/null
+#!/bin/bash -e
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+case $1 in
+-h|--help)
+ echo "Usage: $0 [rev]"
+ exit 0
+ ;;
+esac
+
+json_value() {
+ local key=$1
+ sed -n -r \
+ -e '/^[[:space:]]*"'"${key}"'"/s|.*:[[:space:]]*"([^"]*)",?$|\1|p' \
+ manifest.json
+}
+
+PN=$(json_value name | sed 's:[[:space:]]:_:g' | tr '[:upper:]' '[:lower:]')
+PV=$(json_value version)
+rev=${1:-0}
+PVR="${PV}.${rev}"
+P="${PN}-${PVR}"
+
+rm -rf "${P}"
+mkdir "${P}"
+
+while read line ; do
+ [[ ${line} == */* ]] && mkdir -p "${P}/${line%/*}"
+ ln "${line}" "${P}/${line}"
+done < <(sed 's:#.*::' manifest.files)
+cp manifest.json "${P}/"
+
+sed -i \
+ -e '/"version"/s:"[^"]*",:"'${PVR}'",:' \
+ "${P}/manifest.json"
+
+zip="${P}.zip"
+zip -r "${zip}" "${P}"
+rm -rf "${P}"
+du -b "${zip}"
--- /dev/null
+images/outlet-128x128.png
+images/outlet-19x19.png
+images/outlet-38x38.png
+images/outlet.svg
+common.js
+options.html
+options.js
+popup.html
+popup.js
--- /dev/null
+{
+ "manifest_version": 2,
+ "minimum_chrome_version": "22",
+ "name": "Web Power Switch Manager",
+ "version": "1.0",
+ "description": "Quickly control Web Power Switches",
+ "icons": {
+ "128": "images/outlet-128x128.png"
+ },
+ "browser_action": {
+ "default_icon": {
+ "19": "images/outlet-19x19.png",
+ "38": "images/outlet-38x38.png"
+ },
+ "default_title": "Control Your Switch",
+ "default_popup": "popup.html"
+ },
+ "options_page": "options.html",
+ "permissions": [
+ "storage",
+ "http://*/"
+ ]
+}
--- /dev/null
+<!-- Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. -->
+<!doctype html>
+<html>
+
+<head>
+<title>Web Power Switch Manager Options</title>
+<script src='common.js'></script>
+<script src='options.js'></script>
+</head>
+
+<body background='images/outlet.svg'>
+<table>
+<tr>
+ <td>URL:</td><td><input type='text' id='url'></td>
+</tr>
+<tr>
+ <td>User:</td><td><input type='text' id='user'></td>
+</tr>
+<tr>
+ <td>Pass:</td><td><input type='text' id='pass'></td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+function update_settings() {
+ var setting = {};
+ setting[this.id] = this.value;
+ storage.set(setting);
+}
+
+window.onload = function() {
+ storage.get(settings_keys, function(settings) {
+ settings_keys.forEach(function(key) {
+ var field = document.getElementById(key);
+ field.value = settings[key] || settings_defaults[key];
+ field.oninput = update_settings;
+ });
+ });
+};
--- /dev/null
+<!-- Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it. -->
+<!doctype html>
+<html>
+
+<head>
+<title>Web Power Switch Manager</title>
+<script src='common.js'></script>
+<script src='popup.js'></script>
+<style>
+table#buttons {
+ margin: 0px;
+ padding: 0px;
+ border-collapse: collapse;
+}
+body {
+ margin: 0px;
+ padding: 0px;
+}
+td {
+ white-space: nowrap;
+}
+div#status {
+ white-space: nowrap;
+}
+</style>
+</head>
+
+<body>
+<div id='status'>Loading...</div>
+<table id='buttons'></table>
+</body>
+</html>
--- /dev/null
+// Written by Mike Frysinger <vapier@gmail.com>. Released into the public domain. Suck it.
+
+var url_base, user, pass;
+
+function fetchpage(url, callback) {
+ url = url_base + '/' + url;
+
+ var xhr = new XMLHttpRequest();
+ xhr.setstatus = false;
+ try {
+ xhr.onreadystatechange = function(state) {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ callback(xhr, state);
+ } else {
+ xhr.setstatus = true;
+ setstatus('Could not connect; check settings');
+ console.log('connect error', state);
+ }
+ }
+ }
+ xhr.onerror = function(error) {
+ if (!xhr.setstatus)
+ setstatus('onerror; see console');
+ console.log('xhr error:', error);
+ }
+
+ console.log('fetching', url)
+ xhr.withCredentials = true;
+ xhr.open('GET', url, true, user, pass);
+ xhr.responseType = 'document';
+ // The user/pass options above don't seem to work, so do it ourselves.
+ xhr.setRequestHeader('Authorization', 'Basic ' + btoa(user + ':' + pass));
+ xhr.send();
+ } catch(e) {
+ setstatus('Exception; see console');
+ console.log('exception:', e);
+ }
+}
+
+function onoff(o) {
+ return o.toUpperCase() === 'ON' ? 'OFF' : 'ON';
+}
+
+function toggleit(button) {
+ var outlet_num = button.id;
+ var old_status = button.data;
+ var new_status = onoff(button.data);
+ var url = 'outlet?' + outlet_num + '=' + new_status;
+
+ fetchpage(url, function(xhr, state) {
+ console.log('switch ' + outlet_num + ': ' + old_status + ' -> ' + new_status);
+ button.value = 'Switch ' + old_status;
+ button.data = new_status;
+ });
+}
+function toggle() {
+ toggleit(this);
+}
+
+function toggle_confirmed() {
+ clearTimeout(this.timeout);
+ this.onclick = toggle_confirm;
+ toggleit(this);
+}
+
+function toggle_confirm() {
+ var button = this;
+ this.onclick = toggle_confirmed;
+ this.oldvalue = this.value;
+ this.value = 'Confirm!?';
+ this.timeout = setTimeout(function() {
+ button.value = button.oldvalue;
+ button.onclick = toggle_confirm;
+ }, 5000);
+}
+
+function trim(str) {
+ return str.replace(/^\s+|\s+$/, '');
+}
+
+function initpopup(xhr, state) {
+ var tbl = document.getElementById('buttons');
+ var row, cell, button;
+
+ console.log(xhr, state);
+
+ // There is no clean API for extracting the current state.
+ // Example result:
+ /*
+ <tr bgcolor="#F4F4F4"><td align=center>1</td>
+ <td>Outlet 1</td><td>
+ <b><font color=red>OFF</font></b></td><td>
+ <a href=outlet?1=ON>Switch ON</a>
+ </td><td>
+ <!-- <a href=outlet?1=CCL>Cycle</a> -->
+ </td></tr>
+ */
+
+ var tr, trs = state.currentTarget.responseXML.querySelectorAll('tr');
+ for (var i = 0; tr = trs[i]; ++i) {
+ if (tr.bgColor != '#F4F4F4')
+ continue;
+
+ var outlet_num = trim(tr.children[0].innerText);
+ var outlet_name = trim(tr.children[1].innerText);
+ var current_status = trim(tr.children[2].innerText);
+ var new_status = trim(tr.children[3].innerText);
+ var confirmable = tr.children[3].children[0].hasAttribute('onclick');
+
+ row = tbl.insertRow(-1);
+ cell = row.insertCell(-1);
+ cell.innerText = outlet_name + ':';
+ cell = row.insertCell(-1);
+ button = document.createElement('input');
+ button.type = 'button';
+ button.id = outlet_num;
+ button.value = new_status;
+ button.data = current_status;
+ button.onclick = confirmable ? toggle_confirm : toggle;
+ cell.appendChild(button);
+ }
+
+ setstatus();
+}
+
+function setstatus(msg) {
+ var status = document.getElementById('status');
+ status.innerText = msg;
+ status.style.visibility = msg ? '' : 'hidden';
+ status.style.float = msg ? '' : 'left';
+ status.style.position = msg ? '' : 'absolute';
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ storage.get(settings_keys, function(settings) {
+ url_base = settings['url'] || settings_defaults['url'];
+ user = settings['user'] || settings_defaults['user'];
+ pass = settings['pass'] || settings_defaults['pass'];
+ fetchpage('index.htm', initpopup);
+ });
+});