1
0
Fork 0

First pass at declarative API

This commit is contained in:
Shawn Nock 2017-01-18 16:38:15 -05:00
commit 85f59df6cc
38 changed files with 11498 additions and 0 deletions

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
include: src/escapelib/web/*

32
setup.py Normal file
View File

@ -0,0 +1,32 @@
from setuptools import find_packages, setup
setup(
name='escapelib',
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='0.1.0',
description='Escape Room Controller Library',
url='https://github.com/MonadnockSystems/escapelib',
author='Monadnock Systems',
author_email='shawn@monadnock.ca',
license='APLv2',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: APLv2',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
keywords='escape room escaperoom',
packages=find_packages(where='src'),
package_dir={"": "src"},
install_requires=['twisted[tls]', 'klein', 'sysfs-gpio', 'crcmod',
'pyserial', 'attrs', 'jinja2', 'smbus-cffi'],
extras_require={
'test': ['tox']
},
)

View File

@ -0,0 +1 @@
from .game import Game

40
src/escapelib/game.py Normal file
View File

@ -0,0 +1,40 @@
import six
from escapelib.gpio import LocalOutput
from escapelib.nbus import SerialService
from escapelib.io import IOMeta
from escapelib.web import WebService
from twisted.internet import reactor
class Game(six.with_metaclass(IOMeta)):
"""Declaritive configuration object for escapelib games."""
__bus__ = None # type: escapelib.nbus.NBus
__bus_port__ = '/dev/ttyS0'
__bus_power_enable__ = LocalOutput(13)
__bus_tx_enable__ = LocalOutput(23)
__bus_baud__ = 1000000
__services__ = [] # type: List[Any]
__web_endpoint__ = 'tcp:8000'
def __init__(self):
# Configure Serial
if self.__bus_port__ is not None:
self.__services__.append(
SerialService(self.__bus_port__, self.__bus_baud__,
self.__bus_power_enable__,
self.__bus_tx_enable__))
# Configure WebUI
if self.__web_endpoint__ is not None:
self.__services__.append(
WebService(self.__web_endpoint__, self))
def register_services(self, application):
# type: (twisted.internet.application.Application) -> None
for service in self.__services__:
service.setServiceParent(application)

61
src/escapelib/gpio.py Normal file
View File

@ -0,0 +1,61 @@
"""Local GPIO functions."""
from escapelib import io
from escapelib.util import quit
from sysfs.gpio import (Controller, INPUT, OUTPUT, RISING)
from twisted.internet import defer, reactor
from twisted.python import log
__all__ = ('set_output', 'get_output', 'init_reset_pin', 'GPIO')
PINS = {}
Controller.available_pins = [] # type: List[int]
class Local(io.IO):
def __init__(self, gpio_num=None, *args, **kwargs):
if gpio_num not in Controller.available_pins:
Controller.available_pins.append(gpio_num)
io.IO.__init__(self, gpio_num, *args, **kwargs)
class LocalInput(Local, io.Input):
def __init__(self, gpio_num=None, callback=None, **kwargs):
Local.__init__(self, gpio_num, **kwargs)
if callback is not None:
reactor.addSystemEventTrigger(
'after', 'startup', Controller.alloc_pin,
gpio_num, INPUT, callback, RISING)
class LocalOutput(Local, io.Output):
def set(self, level): # type: (bool) -> None
return set_output(self._pin, level)
def get(self): # type: (bool) -> None
return get_output(self._pin)
@defer.inlineCallbacks
def set_output(pin_no, level):
"""Set the output to the specified value."""
if pin_no not in PINS:
try:
pin = yield Controller.alloc_pin(pin_no, OUTPUT)
PINS[pin_no] = pin
except Exception as e:
log.msg("Failed to allocate {}: {}".format(pin_no, e))
defer.returnValue(None)
pin = PINS[pin_no]
if level:
log.msg("Setting {}".format(pin_no))
pin.set()
else:
log.msg("Clearing {}".format(pin_no))
pin.reset()
def get_output(pin_no):
"""Return the current value (bool) of the output."""
pin = PINS[pin_no]
return pin.read()

60
src/escapelib/io.py Normal file
View File

@ -0,0 +1,60 @@
class IOptions(object):
def __init__(self):
self.inputs = []
self.outputs = []
def add_input(self, i):
self.inputs.append(i)
def add_output(self, o):
self.outputs.append(o)
class IOMeta(type):
def __init__(cls, name, bases, attrs):
print("Meta called against: {}".format(cls))
cls._io = IOptions()
for key, attr in attrs.items():
if hasattr(attr, 'attach_class'):
attr.attach_class(cls, key, cls._io)
if hasattr(attr, '_io'):
cls._io.inputs += attr._io.inputs
cls._io.outputs += attr._io.outputs
if isinstance(attr, Output):
cls._io.add_output(attr)
elif isinstance(attr, Input):
cls._io.add_input(attr)
class IO(object):
def __init__(self, i_id=None, **kwargs):
if not 'title' in kwargs:
self.title = None
if i_id is not None:
self.id = i_id
else:
self.id = 0
def attach_class(self, cls, name, options):
self.cls = cls
self.name = name
self.options = options
if self.title is None:
self.title = name.replace('_', ' ').capitalize()
def set(self, level): # type: (bool) -> None
raise NotImplementedError
def get(self): # type: () -> bool
raise NotImplementedError
class Input(IO):
pass
class Output(IO):
pass
class Sensor(IO):
pass

159
src/escapelib/lcd.py Normal file
View File

@ -0,0 +1,159 @@
"""HD44780 LCD driver for Trespassed."""
import time
from smbus import SMBus
from twisted.internet import reactor
from twisted.python import log
__all__ = ('lcd_write_status')
ADDR = 0x20
BUS = 1
LCD_DATA = 1
LCD_CMD = 0
LCD_LINE_ADDR = {0: 0x0,
1: 0x40}
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
LCD_WIDTH = 16
# MCP23008 Registers
IODIR = 0x00
IOCON = 0x05
IOCON_SREAD = (1 << 5)
IOCON_DISSLW = (1 << 4)
IOCON_ODR = (1 << 2)
IOCON_INTPOL = (1 << 1)
GPIO = 0x09
# HD44780 Pins
RS = (1 << 1)
ENABLE = (1 << 2)
DB4 = (1 << 3)
DB5 = (1 << 4)
DB6 = (1 << 5)
DB7 = (1 << 6)
LCD_BACKLIGHT = (1 << 7)
# HD44780 Commands
LCD_CLEAR = 1
LCD_HOME = (1 << 1)
LCD_ENTRY_MODE = (1 << 2)
CURSOR_INCR = (1 << 1)
CURSOR_DECR = (0 << 1)
SHIFT_ENABLE = (1 << 0)
SHIFT_DISABLE = (0 << 0)
DISPLAY_CTRL = (1 << 3)
DISPLAY_ON = (1 << 2)
DISPLAY_OFF = (0 << 2)
CURSOR_ON = (1 << 1)
CURSOR_OFF = (0 << 1)
BLINK_ON = (1 << 0)
BLINK_OFF = (0 << 0)
FUNC_SET = (1 << 4)
DATA_4BIT = (1 << 3)
DATA_8BIT = (0 << 3)
TWO_LINES = (1 << 2)
ONE_LINE = (0 << 2)
SET_ADDRESS = (1 << 7)
class LCD(object):
def __init__(self, addr=ADDR):
self.addr = addr
self.bypass = False
try:
self.bus = SMBus(BUS)
self._init_mcp()
self._init_hd44780()
except IOError as e:
log.err(e)
log.msg("Unable to initialize LCD. Disabling")
self.bypass = True
def _init_mcp(self):
# Set MCP23008 pins as outputs
self._write_reg(IODIR, 0x00)
# Enable Backlight
self._write_reg(0x09, 1 << 7)
def _init_hd44780(self):
# Initialize, set to 4bit mode
self._write4(DB4 | DB5)
# Again, this time with feeling!
self._write4(DB4 | DB5)
# Again, this time with feeling!
self._write4(DB4 | DB5)
self._write4(DB5)
# Now we're in 4bit mode, move up to normal commands
self.write_cmd(FUNC_SET | TWO_LINES | DATA_4BIT) # 2 lines, 4bit
self.write_cmd(LCD_CLEAR) # Clear Display
self.write_cmd(LCD_HOME) # Home Display
self.write_cmd(DISPLAY_CTRL | DISPLAY_ON) # Display ON
self.write_cmd(LCD_ENTRY_MODE | CURSOR_INCR) # Entry Mode
def _write4(self, bits, data=False):
# No shift, use DB* macros
message = bits | LCD_BACKLIGHT
if data:
message |= RS
else:
message &= ~RS
self._write_reg(GPIO, message | ENABLE)
self._latch(message)
def write_cmd(self, byte, data=False):
# Shifts the bytes up, DB* macros don't work
high = (byte & 0xf0) >> 4
low = byte & 0x0f
self._write4(high << 3, data)
self._write4(low << 3, data)
def _write_data(self, byte):
self.write_cmd(byte, data=True)
def _write_reg(self, reg, val):
if self.bypass:
return
self.bus.write_byte_data(self.addr, reg, val)
def _latch(self, bits):
time.sleep(E_PULSE)
self._write_reg(GPIO, bits & ~ENABLE)
time.sleep(E_DELAY)
def write(self, line, string):
self.write_cmd(SET_ADDRESS | LCD_LINE_ADDR[line])
string = string.ljust(LCD_WIDTH, " ")[:LCD_WIDTH]
for i in range(LCD_WIDTH):
self._write_data(ord(string[i]))
def shutdown(self):
self.write_cmd(DISPLAY_CTRL | DISPLAY_OFF)
self._write_reg(GPIO, 0) # Turn off backlight
LCD_SINGLETON = LCD()
def lcd_write_status(status):
"""Write the 16x2 status string to the display."""
lines = status.split('\n')[:2]
for i in range(len(lines)):
LCD_SINGLETON.write(i, lines[i])
reactor.addSystemEventTrigger('before', 'shutdown',
LCD_SINGLETON.shutdown)

133
src/escapelib/media.py Normal file
View File

@ -0,0 +1,133 @@
"""Media functionality for Trespassed."""
import attr
from pkg_resources import resource_filename
from twisted.internet import defer, error, protocol, reactor
from twisted.python import log
__all__ = ('play_music', 'play_sound_effect', 'play_sound_effect_once',
'stop_music', 'reset_played')
PLAY_ONCE_STATE = []
@attr.s
class MediaChannels(object):
video = attr.ib(default=None)
music = attr.ib(default=None)
sound_effect = attr.ib(default=None)
MEDIA_CHANNELS = MediaChannels()
class MediaProcessProtocol(protocol.ProcessProtocol):
def __init__(self, filename):
self.filename = filename
self.d = defer.Deferred()
def outReceived(self, data):
pass
def connectionMade(self):
log.msg("Started playing: {}".format(self.filename))
def processEnded(self, f):
log.msg("Stopped playing: {}".format(self.filename))
if isinstance(f.value, error.ProcessDone):
self.d.callback(self)
else:
self.d.errback(f)
@attr.s
class MediaHandler(object):
deferred = attr.ib()
transport = attr.ib()
def stop(self):
print("Trying to stop {}".format(self.transport.pid))
self.deferred.cancel()
self.transport.signalProcess('INT')
def addCallbacks(self, *args, **kwargs):
self.deferred.addCallbacks(*args, **kwargs)
def addErrback(self, *args, **kwargs):
self.deferred.addErrback(*args, **kwargs)
def addCallback(self, *args, **kwargs):
self.deferred.addCallback(*args, **kwargs)
def addBoth(self, *args, **kwargs):
self.deferred.addBoth(*args, **kwargs)
def play_media(args, channel_str):
# Add callback to clear channel on finish or error
pp = MediaProcessProtocol(args[-1])
# Play media
transport = reactor.spawnProcess(pp, args[0], args, {})
# Register media with channel
mh = MediaHandler(pp.d, transport)
setattr(MEDIA_CHANNELS, channel_str, mh)
return mh
def play_video(path):
args = ['omxplayer', resource_filename(__name__, path)]
return play_media(args, 'video')
def play_sound_effect(path):
"""Play `path` as a sound effect."""
args = ['mpg123', resource_filename(__name__, path)]
return play_media(args, 'sound_effect')
def play_sound_effect_once(path):
"""Ensure that the sound effect is played exactly once."""
global PLAY_ONCE_STATE
if path not in PLAY_ONCE_STATE:
PLAY_ONCE_STATE.append(path)
play_sound_effect(path)
def play_music(path):
"""Repeat `path` as the background music track."""
args = ['mpg123', resource_filename(__name__, path)]
mh = play_media(args, 'music')
def repeat_music(proto):
log.msg("Restarting Soundtrack")
play_music(path)
def clear_channel(_):
MEDIA_CHANNELS.music = None
return _
mh.addCallbacks(repeat_music, clear_channel)
MEDIA_CHANNELS.music = mh
return mh
def stop_music():
"""Turn off music."""
if (MEDIA_CHANNELS.music):
MEDIA_CHANNELS.music.stop()
def reset_played():
"""Clear the list of played, one-time sound effects."""
global PLAY_ONCE_STATE
PLAY_ONCE_STATE = []
def cleanup():
if MEDIA_CHANNELS.music is not None:
MEDIA_CHANNELS.music.stop()
reactor.addSystemEventTrigger('before', 'shutdown', cleanup)

134
src/escapelib/nbus.py Normal file
View File

@ -0,0 +1,134 @@
"""Serial Bus fuctionality for Trespassed."""
from enum import IntEnum
from functools import wraps
from time import time
import attr
import crcmod.predefined
import six
from escapelib.io import Input, Output, Sensor, IOMeta
from escapelib.status import set_status
from escapelib.util import delay
from twisted.application import service
from twisted.application.internet import TimerService
from twisted.internet import defer, protocol, reactor
from twisted.internet.serialport import SerialPort
from twisted.python import log
WATCHDOG = {}
WATCHDOG_TIMEOUT = 30
__all__ = ('SerialService', 'on_input', 'on_puzzle', 'NBusProp')
def on_input(gpio_str, active_low=False, prefix=False):
def wrap(f):
def f(*args):
return f(*args)
return wrap
on_puzzle = on_input
class PacketType(IntEnum):
Keepalive = 0
Input = 1
Output = 2
Solved = 3
Failed = 4
Log = 5
class NBusProp(six.with_metaclass(IOMeta)):
def __init__(self, bus_id, title=None):
self.bus_id = bus_id
self.title = title
def attach_class(self, cls, name, options):
self.cls = cls
self.name = name
if self.title is None:
self.title = name.replace('_', ' ').capitalize()
class NBusInput(Input):
pass
class NBusOutput(Output):
pass
class NBusAnalog(Sensor):
pass
@attr.s
class HDLCFrame(object):
_frame_data = attr.ib()
def is_valid(self):
r_crc = self._frame_data[-2:]
crc16 = crcmod.predefined.Crc('xmodem')
crc16.update(self._frame_data[:-2])
crc = crc16.digest()
return crc == r_crc
class NBusProtocol(protocol.Protocol):
_buf = b'' # type: bytes
_escape = False
def dataReceived(self, data):
#log.msg("RX: {}".format(hexlify(data)))
for byte in data:
if self._escape:
escaped_byte = byte & ~(1 << 5)
if escaped_byte not in b'\x7d\x7e':
log.msg(
'Warning: Unnecessary escaped value: {}'.format(escaped_byte))
self._buf += escaped_byte
self._escape = False
elif byte == b'\x7d':
self._escape = True
elif byte == b'\x7e' and len(self._buf) > 0:
self._check_crc()
self._buf = b''
else:
self._buf += byte
def _check_crc(self):
r_crc = self._buf[-2:]
crc16 = crcmod.predefined.Crc('xmodem')
crc16.update(packet[:-2])
crc = crc16.digest()
if crc != r_crc:
log.err("CRC Mismatch: {}".format(packet))
return
def _filter(self, packet):
pass
class SerialService(service.Service):
"""Service wrapper for twisted.internet.serialport."""
_serial = None # type: SerialPort
_port = None # type: str
bypass = False # type: bool
def __init__(self, port='/dev/ttyS0', baudrate=1000000,
power_gpio=None, tx_enable_gpio=None):
# type: (str, int) -> None
"""Instanatiate SerialPort handling service."""
self._port = port
self._baud = baudrate
def startService(self):
"""Start the NBus serial handling service."""
try:
self._serial = SerialPort(NBusProtocol(), self._port,
reactor, baudrate=self._baud)
except IOError as e:
log.msg(
"Failed to initialize serial. NBUS disabled: {}".format(e))
def stopService(self):
"""Stop the NBus serial handling service."""
self._serial.loseConnection()

62
src/escapelib/status.py Normal file
View File

@ -0,0 +1,62 @@
"""Status and log end-user display functions."""
from escapelib.lcd import lcd_write_status
from escapelib.timer import get_timer
from twisted.application.internet import TimerService
__all__ = ('get_status', 'set_status',
'status_service', 'get_log', 'clear_log')
class Status(object):
"""Return a 16x2 status string for output to LCD."""
def __init__(self, name, initial_status='Loading...'):
self.name = name
self.log = []
self.set(initial_status)
self.update()
def update(self):
self._status1 = "{:.10} {:5}".format(
self.name, get_timer().get_human())
lcd_write_status(self.get())
def set(self, status):
self._status2 = status
self.log.append({'when': get_timer().get_human(),
'status': status})
def clear_log(self):
self.log = []
def get(self):
return '{:16s}\n{:^16s}'.format(self._status1, self._status2)
STATUS = Status('Trespassed')
status_service = TimerService(1, STATUS.update)
def get_status(): # type: () -> str
"""Return 16x2 status string."""
global STATUS
return STATUS.get()
def set_status(s): # type: (str) -> None
"""Set the log/message portion of the status."""
global STATUS
STATUS.set(s)
def get_log(): # type: () -> List[Dict[str, str]]
"""Return the player log in chronological order."""
global STATUS
return list(reversed(STATUS.log))
def clear_log(): # type: () -> None
"""Empty the player log."""
global STATUS
STATUS.clear_log()

48
src/escapelib/timer.py Normal file
View File

@ -0,0 +1,48 @@
"""Countdown timer for player progress."""
import time
__all__ = ('get_timer')
class Timer(object):
time_base = None
running = False
last = None
def __init__(self, duration=3600, callback=None):
self.duration = duration
self.callback = callback
self.reset()
def reset(self):
self.time_base = time.time()
self.running = True
def stop(self):
self.last = self.get()
self.running = False
def get(self):
if not self.running:
return self.last()
elapsed = time.time() - self.time_base
r = self.duration - elapsed
if r <= 0 and self.callback:
self.callback()
return r
def get_human(self):
now = self.get()
minutes = int(now // 60)
seconds = int(now - (minutes * 60))
return "{:02}:{:02}".format(minutes, seconds)
TIMER = Timer()
def get_timer():
"""Return the global timer singleton."""
global TIMER
return TIMER

18
src/escapelib/util.py Normal file
View File

@ -0,0 +1,18 @@
"""Utility Functions for Trespassed."""
from twisted.internet import reactor, task
def delay(sec):
"""Return a deferred that fires `sec` seconds in the future."""
return task.deferLater(reactor, sec, lambda: None)
skip = True
def quit(*args, **kwargs):
"""End the Trespassed application."""
global skip
if not skip:
reactor.stop()
skip = False

72
src/escapelib/web.py Normal file
View File

@ -0,0 +1,72 @@
"""WebUI for Trespassed Game."""
import json
import jinja2
from klein import Klein
from pkg_resources import resource_filename
from escapelib.status import get_log, get_status
from twisted.application import service
from twisted.internet import defer, endpoints, reactor
from twisted.web.server import Site
from twisted.web.static import File
__all__ = ('WebService')
env = jinja2.Environment(
loader=jinja2.PackageLoader(__name__, 'web/templates'),
autoescape=True)
class WebUI(object):
app = Klein()
def __init__(self, game):
self.game = game
@app.route('/static/', branch=True)
def static(self, request):
return File(resource_filename(__name__, 'web/static/'))
@app.route('/')
def index(self, request):
page = env.get_template('index.html')
return page.render(status=get_status())
@app.route('/status.json')
def status(self, request):
request.responseHeaders.addRawHeader('Content-Type',
'application/json')
status1, status2 = get_status().split('\n')
return json.dumps({'status1': status1,
'status2': status2,
'log': get_log()})
@app.route('/outputs', methods=['GET', 'POST'])
@defer.inlineCallbacks
def outputs(self, request):
if request.method == 'POST':
args = json.loads(request.content.getvalue())
yield set_output(*args)
request.setResponseCode(200)
defer.returnValue('{}')
page = env.get_template('outputs.html')
defer.returnValue(page.render(outputs=self.game.__gpio__))
class WebService(service.Service):
"""Wrap Klein to play well with twistd."""
def __init__(self, endpoint_desc, game_obj):
"""Instatiate WebUI."""
self.endpoint_desc = endpoint_desc
self.ui = WebUI(game_obj)
def startService(self):
"""Start WebUI service."""
self.endpoint = endpoints.serverFromString(reactor, self.endpoint_desc)
self.endpoint.listen(Site(self.ui.app.resource()))

View File

@ -0,0 +1,587 @@
/*!
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #2e6da4;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
background-repeat: repeat-x;
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6760
src/escapelib/web/static/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,130 @@
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
/*
* Dashboard Fixes
*/
.tab-content {
margin-top: 10px;
}
.dl-horizontal {
margin-left: -50px;
}
.monospace {
font-family: monospace;
}
#status_div {
width: 16em;
}
#status {
font-size: 14pt;
margin-left: 10px;
}

View File

@ -0,0 +1,40 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -0,0 +1,66 @@
define(function() {
var prefix='';
'use strict';
function request(method, url, data, json_p) {
var promise = new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open(method, prefix+url);
if (json_p == true) {
if (data) {
data = JSON.stringify(data);
} else {
data = "";
}
req.setRequestHeader('Content-Type', 'application/json');
} else {
if (method == "POST") {
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
}
req.onload = function() {
if (req.status >= 200 && req.status <= 300) {
if (req.status != 204 && req.status != 201) {
if (json_p) {
resolve(JSON.parse(req.response));
} else {
resolve(req);
}
} else {
resolve(null);
}
}
else {
reject(req.response);
}
};
req.onerror = function() {
reject(Error("Network Error"));
};
req.send(data);
});
return promise;
};
return {
'post': function(url, data) {
return request('POST', url, data, false);
},
'get': function(url) {
return request('GET', url);
},
'put': function(url, data) {
return request('PUT', url, data);
},
'delete': function(url) {
return request('DELETE', url);
},
'post_json': function(url, data) {
return request('POST', url, data, true);
},
'put_json': function(url, data) {
return request('PUT', url, data, true);
},
'get_json': function(url) {
return request('GET', url, undefined, true);
}
}
});

2363
src/escapelib/web/static/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,31 @@
define(['ajax','util'], function(ajax, util) {
'use strict';
function fetch_status() {
var status = document.getElementById('status');
ajax.get_json('/status.json').then(function (resp) {
status.innerHTML = resp['status1'] + '\n' +
resp['status2'];
populate_log(resp['log']);
window.setTimeout(fetch_status, 1000);
})
}
fetch_status();
function populate_log(log) {
var list = document.getElementById('log_table');
while (list.hasChildNodes()) {
list.removeChild(list.lastChild);
}
for (var i = 0; i < log.length; i++) {
var tr = document.createElement('tr');
var td1 = document.createElement('td');
td1.innerText = log[i]['when'];
var td2 = document.createElement('td');
td2.innerText = log[i]['status'];
tr.appendChild(td1);
tr.appendChild(td2);
list.appendChild(tr);
}
}
});

View File

@ -0,0 +1,198 @@
define(["ajax", "util"], function (ajax, util) {
function valid_ip (el) {
return valid_ip_noel(el.value);
}
function valid_ip_noel (ip) {
var octets = ip.split('.');
if (octets.length != 4) {
return false;
}
for (var i = 0; i < octets.length; i++) {
if (!(/^[0-9]{1,3}$/.test(octets[i]))) {
return false;
}
var tmp = parseInt(octets[i])
if (tmp > 255) {
return false
}
}
return true;
}
function valid_mask (el, evt) {
var mask_l = el.value.split(".").length;
if (mask_l == 4) {
return valid_ip(el);
}
if (mask_l == 1) {
var mask = parseInt(el.value);
if (mask < 0 || mask > 32) {
return false;
}
/* Normalize bitmask to octets for uci, only after user is done with input */
if (evt && evt.type == "change") {
el.value = maskbits_to_octets(parseInt(el.value));
return valid_ip(el);
}
}
return false;
}
function maskbits_to_octets (mask) {
var mask_octets = [0, 0, 0, 0];
for (var byte_count = 0; mask > 0;) {
for (var bit_count = 7; bit_count >= 0 && mask > 0; mask--) {
mask_octets[byte_count] += (1 << bit_count);
bit_count -= 1;
}
byte_count += 1;
}
return mask_octets.join(".")
}
function valid_dns(el) {
var ips = el.value.split(',');
var r_list = [];
if (ips.length > 3) {
return false;
}
for (var i = 0; i < ips.length; i++) {
r_list.push(valid_ip_noel(ips[i]));
}
return r_list.reduce(function (prev, cur) {
return (prev && cur);
}, true);
}
function register_form_validator (form_el_id, field_valid_map) {
var form = document.getElementById(form_el_id);
form.addEventListener("submit", function (evt) {
return validate_form(form, field_valid_map, evt);
});
form.addEventListener("input", function (evt) {
validate_form(form, field_valid_map);
});
form.addEventListener("change", function (evt) {
validate_form(form, field_valid_map, evt);
});
}
function valid_wpa_key (el) {
var key_l = el.value.length;
var valid_hex = /^[0-9a-fA-F]$/
if (key_l < 8 || key_l > 64) {
return false;
}
if (key_l == 64) {
for (var i = 0; i < key_l; i++) {
if (!valid_hex.test(el.value[i])) {
return false;
}
}
}
return true;
}
var filled_values = {};
function validate_form (form, field_valid_map, evt) {
var r_list = [];
var els = form.getElementsByTagName('input')
for (var i = 0, l = els.length; i < l; i++) {
var el = els[i];
var r = true;
if (evt && evt.type == "change") {
if (el.value == "" && el.getAttribute("default")) {
el.value = el.getAttribute("default");
}
}
if (field_valid_map.hasOwnProperty(el.name)) {
r = field_valid_map[el.name](el, evt);
} else {
r = el.validity.valid
}
if (!r) {
el.parentElement.classList.add('has-error');
} else {
el.parentElement.classList.remove('has-error');
}
r_list.push(r);
}
var valid = r_list.reduce(function (prev, cur) {
return prev && cur;
}, true);
/* Prepopulate formdata, even if unposted to detect actual changes to form */
var data = new FormData(form);
if (filled_values.hasOwnProperty(form.getAttribute('id'))) {
var prev_values = filled_values[form.getAttribute('id')];
for (var field in prev_values) {
if (prev_values.hasOwnProperty(field)) {
var field_vals = data.getAll(field);
if (field_vals.length > 1) {
if (field_vals == prev_values[field]) {
data.delete(field);
}
} else {
if (field_vals[0] == prev_values[field]) {
data.delete(field);
}
}
}
}
}
if (evt && evt.type == "submit") {
/* If we're executing js, try to do nice async POSTS, w/o
* js fallback to form post w/o client-side validation */
evt.preventDefault();
if (valid) {
if (!util.formdata_empty(data)) {
ajax.post("", util.form_encode(data)).then(function(resp) {
var button = form.getElementsByTagName("button")[0];
button.textContent = "Saved";
button.setAttribute("disabled", "disabled");
util.populate_header();
});
}
}
} else {
var button = form.getElementsByTagName("button")[0];
if (valid) {
if (!util.formdata_empty(data)) {
button.textContent = "Save Changes";
button.removeAttribute("disabled");
} else {
button.textContent = "No Changes";
button.setAttribute("disabled", "disabled");
}
} else {
button.textContent = "Fix Errors";
button.setAttribute("disabled", "disabled");
}
}
return valid;
}
function fill_form (form_id, obj) {
var form = document.getElementById(form_id);
var slice = Array.prototype.slice;
var inputs = slice.call(form.getElementsByTagName("input"));
inputs = inputs.concat(slice.call(form.getElementsByTagName("select")));
for (var i = 0; i < inputs.length; i++) {
if (obj.hasOwnProperty(inputs[i].name)) {
inputs[i].value = obj[inputs[i].name];
}
}
/* Cache previous values so we can send only changes */
filled_values[form_id] = obj;
}
return {
"register": register_form_validator,
"fill": fill_form,
"field": {
"valid_ip": valid_ip,
"valid_mask": valid_mask,
"valid_dns": valid_dns,
"valid_wpa_key": valid_wpa_key
}
}
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
define(['ajax'], function(ajax) {
'use strict';
function button_handler(evt) {
if (evt.target.classList.contains('active')) {
evt.stopPropagation();
return;
}
console.log(evt);
var parts = evt.target.id.split('-');
var output_id = parseInt(parts[parts.length - 1]);
var to_state = parts[parts.length - 2] === 'on' ? true : false;
var partner_id = 'btn-' + (to_state ? 'off-' : 'on-') + output_id;
console.log(partner_id);
ajax.post_json('/outputs', [output_id, to_state]).then(
function () {
evt.target.classList.add('active');
var partner = document.getElementById(partner_id);
partner.classList.remove('active');
}, function () {
alert("Failed to set output");
});
evt.stopPropagation();
}
document.getElementById('output_table').addEventListener('click',
button_handler);
});

View File

@ -0,0 +1,37 @@
/*
RequireJS 2.1.22 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(ha){function L(b){return"[object Function]"===R.call(b)}function M(b){return"[object Array]"===R.call(b)}function x(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function Y(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));--d);}}function w(b,c){return la.call(b,c)}function g(b,c){return w(b,c)&&b[c]}function E(b,c){for(var d in b)if(w(b,d)&&c(b[d],d))break}function Z(b,c,d,k){c&&E(c,function(c,g){if(d||!w(b,g))!k||"object"!==typeof c||!c||M(c)||L(c)||c instanceof
RegExp?b[g]=c:(b[g]||(b[g]={}),Z(b[g],c,d,k))});return b}function y(b,c){return function(){return c.apply(b,arguments)}}function ia(b){throw b;}function ja(b){if(!b)return b;var c=ha;x(b.split("."),function(b){c=c[b]});return c}function G(b,c,d,g){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=g;d&&(c.originalError=d);return c}function ma(b){function c(a,n,b){var f,l,c,d,h,k,e,A;n=n&&n.split("/");var q=m.map,p=q&&q["*"];if(a){a=a.split("/");l=a.length-1;m.nodeIdCompat&&
V.test(a[l])&&(a[l]=a[l].replace(V,""));"."===a[0].charAt(0)&&n&&(l=n.slice(0,n.length-1),a=l.concat(a));l=a;for(c=0;c<l.length;c++)d=l[c],"."===d?(l.splice(c,1),--c):".."===d&&0!==c&&(1!==c||".."!==l[2])&&".."!==l[c-1]&&0<c&&(l.splice(c-1,2),c-=2);a=a.join("/")}if(b&&q&&(n||p)){l=a.split("/");c=l.length;a:for(;0<c;--c){h=l.slice(0,c).join("/");if(n)for(d=n.length;0<d;--d)if(b=g(q,n.slice(0,d).join("/")))if(b=g(b,h)){f=b;k=c;break a}!e&&p&&g(p,h)&&(e=g(p,h),A=c)}!f&&e&&(f=e,k=A);f&&(l.splice(0,k,
f),a=l.join("/"))}return(f=g(m.pkgs,a))?f:a}function d(a){F&&x(document.getElementsByTagName("script"),function(n){if(n.getAttribute("data-requiremodule")===a&&n.getAttribute("data-requirecontext")===h.contextName)return n.parentNode.removeChild(n),!0})}function p(a){var n=g(m.paths,a);if(n&&M(n)&&1<n.length)return n.shift(),h.require.undef(a),h.makeRequire(null,{skipMap:!0})([a]),!0}function e(a){var n,b=a?a.indexOf("!"):-1;-1<b&&(n=a.substring(0,b),a=a.substring(b+1,a.length));return[n,a]}function q(a,
n,b,f){var l,d,z=null,k=n?n.name:null,m=a,q=!0,A="";a||(q=!1,a="_@r"+(R+=1));a=e(a);z=a[0];a=a[1];z&&(z=c(z,k,f),d=g(r,z));a&&(z?A=d&&d.normalize?d.normalize(a,function(a){return c(a,k,f)}):-1===a.indexOf("!")?c(a,k,f):a:(A=c(a,k,f),a=e(A),z=a[0],A=a[1],b=!0,l=h.nameToUrl(A)));b=!z||d||b?"":"_unnormalized"+(U+=1);return{prefix:z,name:A,parentMap:n,unnormalized:!!b,url:l,originalName:m,isDefine:q,id:(z?z+"!"+A:A)+b}}function u(a){var b=a.id,c=g(t,b);c||(c=t[b]=new h.Module(a));return c}function v(a,
b,c){var f=a.id,l=g(t,f);if(!w(r,f)||l&&!l.defineEmitComplete)if(l=u(a),l.error&&"error"===b)c(l.error);else l.on(b,c);else"defined"===b&&c(r[f])}function B(a,b){var c=a.requireModules,f=!1;if(b)b(a);else if(x(c,function(b){if(b=g(t,b))b.error=a,b.events.error&&(f=!0,b.emit("error",a))}),!f)k.onError(a)}function C(){W.length&&(x(W,function(a){var b=a[0];"string"===typeof b&&(h.defQueueMap[b]=!0);H.push(a)}),W=[])}function D(a){delete t[a];delete aa[a]}function K(a,b,c){var f=a.map.id;a.error?a.emit("error",
a.error):(b[f]=!0,x(a.depMaps,function(f,d){var h=f.id,k=g(t,h);!k||a.depMatched[d]||c[h]||(g(b,h)?(a.defineDep(d,r[h]),a.check()):K(k,b,c))}),c[f]=!0)}function I(){var a,b,c=(a=1E3*m.waitSeconds)&&h.startTime+a<(new Date).getTime(),f=[],l=[],k=!1,g=!0;if(!ba){ba=!0;E(aa,function(a){var h=a.map,e=h.id;if(a.enabled&&(h.isDefine||l.push(a),!a.error))if(!a.inited&&c)p(e)?k=b=!0:(f.push(e),d(e));else if(!a.inited&&a.fetched&&h.isDefine&&(k=!0,!h.prefix))return g=!1});if(c&&f.length)return a=G("timeout",
"Load timeout for modules: "+f,null,f),a.contextName=h.contextName,B(a);g&&x(l,function(a){K(a,{},{})});c&&!b||!k||!F&&!ka||ca||(ca=setTimeout(function(){ca=0;I()},50));ba=!1}}function J(a){w(r,a[0])||u(q(a[0],null,!0)).init(a[1],a[2])}function P(a){a=a.currentTarget||a.srcElement;var b=h.onScriptLoad;a.detachEvent&&!da?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=h.onScriptError;a.detachEvent&&!da||a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}
function Q(){var a;for(C();H.length;){a=H.shift();if(null===a[0])return B(G("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));J(a)}h.defQueueMap={}}var ba,ea,h,S,ca,m={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},t={},aa={},fa={},H=[],r={},X={},ga={},R=1,U=1;S={require:function(a){return a.require?a.require:a.require=h.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?r[a.map.id]=a.exports:a.exports=r[a.map.id]=
{}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return g(m.config,a.map.id)||{}},exports:a.exports||(a.exports={})}}};ea=function(a){this.events=g(fa,a.id)||{};this.map=a;this.shim=g(m.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};ea.prototype={init:function(a,b,c,f){f=f||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=y(this,function(a){this.emit("error",
a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=f.ignore;f.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,--this.depCount,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;h.startTime=(new Date).getTime();var a=this.map;if(this.shim)h.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],y(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?
this.callPlugin():this.load()}},load:function(){var a=this.map.url;X[a]||(X[a]=!0,h.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var f=this.exports,l=this.factory;if(!this.inited)w(h.defQueueMap,c)||this.fetch();else if(this.error)this.emit("error",this.error);else if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(L(l)){try{f=h.execCb(c,l,b,f)}catch(d){a=d}this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:
this.usingExports&&(f=this.exports));if(a){if(this.events.error&&this.map.isDefine||k.onError!==ia)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",B(this.error=a);if("undefined"!==typeof console&&console.error)console.error(a);else k.onError(a)}}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,k.onResourceLoad)){var e=[];x(this.depMaps,function(a){e.push(a.normalizedMap||a)});k.onResourceLoad(h,
this.map,e)}D(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}},callPlugin:function(){var a=this.map,b=a.id,d=q(a.prefix);this.depMaps.push(d);v(d,"defined",y(this,function(f){var l,d,e=g(ga,this.map.id),N=this.map.name,p=this.map.parentMap?this.map.parentMap.name:null,r=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(N=f.normalize(N,function(a){return c(a,
p,!0)})||""),d=q(a.prefix+"!"+N,this.map.parentMap),v(d,"defined",y(this,function(a){this.map.normalizedMap=d;this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),f=g(t,d.id)){this.depMaps.push(d);if(this.events.error)f.on("error",y(this,function(a){this.emit("error",a)}));f.enable()}}else e?(this.map.url=h.nameToUrl(e),this.load()):(l=y(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=y(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];
E(t,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&D(a.map.id)});B(a)}),l.fromText=y(this,function(f,c){var d=a.name,e=q(d),N=T;c&&(f=c);N&&(T=!1);u(e);w(m.config,b)&&(m.config[d]=m.config[b]);try{k.exec(f)}catch(g){return B(G("fromtexteval","fromText eval for "+b+" failed: "+g,g,[b]))}N&&(T=!0);this.depMaps.push(e);h.completeLoad(d);r([d],l)}),f.load(a.name,r,l,m))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){aa[this.map.id]=this;this.enabling=this.enabled=!0;x(this.depMaps,
y(this,function(a,b){var c,f;if("string"===typeof a){a=q(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=g(S,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;v(a,"defined",y(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?v(a,"error",y(this,this.errback)):this.events.error&&v(a,"error",y(this,function(a){this.emit("error",a)}))}c=a.id;f=t[c];w(S,c)||!f||f.enabled||h.enable(a,this)}));E(this.pluginMaps,y(this,function(a){var b=
g(t,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:m,contextName:b,registry:t,defined:r,urlFetched:X,defQueue:H,defQueueMap:{},Module:ea,makeModuleMap:q,nextTick:k.nextTick,onError:B,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.shim,c={paths:!0,
bundles:!0,config:!0,map:!0};E(a,function(a,b){c[b]?(m[b]||(m[b]={}),Z(m[b],a,!0,!0)):m[b]=a});a.bundles&&E(a.bundles,function(a,b){x(a,function(a){a!==b&&(ga[a]=b)})});a.shim&&(E(a.shim,function(a,c){M(a)&&(a={deps:a});!a.exports&&!a.init||a.exportsFn||(a.exportsFn=h.makeShimExports(a));b[c]=a}),m.shim=b);a.packages&&x(a.packages,function(a){var b;a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(m.paths[b]=a.location);m.pkgs[b]=a.name+"/"+(a.main||"main").replace(na,"").replace(V,"")});E(t,
function(a,b){a.inited||a.map.unnormalized||(a.map=q(b,null,!0))});(a.deps||a.callback)&&h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ha,arguments));return b||a.exports&&ja(a.exports)}},makeRequire:function(a,n){function e(c,d,g){var m,p;n.enableBuildCallback&&d&&L(d)&&(d.__requireJsBuild=!0);if("string"===typeof c){if(L(d))return B(G("requireargs","Invalid require call"),g);if(a&&w(S,c))return S[c](t[a.id]);if(k.get)return k.get(h,
c,a,e);m=q(c,a,!1,!0);m=m.id;return w(r,m)?r[m]:B(G("notloaded",'Module name "'+m+'" has not been loaded yet for context: '+b+(a?"":". Use require([])")))}Q();h.nextTick(function(){Q();p=u(q(null,a));p.skipMap=n.skipMap;p.init(c,d,g,{enabled:!0});I()});return e}n=n||{};Z(e,{isBrowser:F,toUrl:function(b){var d,e=b.lastIndexOf("."),n=b.split("/")[0];-1!==e&&("."!==n&&".."!==n||1<e)&&(d=b.substring(e,b.length),b=b.substring(0,e));return h.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return w(r,
q(b,a,!1,!0).id)},specified:function(b){b=q(b,a,!1,!0).id;return w(r,b)||w(t,b)}});a||(e.undef=function(b){C();var c=q(b,a,!0),e=g(t,b);e.undefed=!0;d(b);delete r[b];delete X[c.url];delete fa[b];Y(H,function(a,c){a[0]===b&&H.splice(c,1)});delete h.defQueueMap[b];e&&(e.events.defined&&(fa[b]=e.events),D(b))});return e},enable:function(a){g(t,a.id)&&u(a).enable()},completeLoad:function(a){var b,c,d=g(m.shim,a)||{},e=d.exports;for(C();H.length;){c=H.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===
a&&(b=!0);J(c)}h.defQueueMap={};c=g(t,a);if(!b&&!w(r,a)&&c&&!c.inited)if(!m.enforceDefine||e&&ja(e))J([a,d.deps||[],d.exportsFn]);else return p(a)?void 0:B(G("nodefine","No define call for "+a,null,[a]));I()},nameToUrl:function(a,b,c){var d,e,p;(d=g(m.pkgs,a))&&(a=d);if(d=g(ga,a))return h.nameToUrl(d,b,c);if(k.jsExtRegExp.test(a))d=a+(b||"");else{d=m.paths;a=a.split("/");for(e=a.length;0<e;--e)if(p=a.slice(0,e).join("/"),p=g(d,p)){M(p)&&(p=p[0]);a.splice(0,e,p);break}d=a.join("/");d+=b||(/^data\:|\?/.test(d)||
c?"":".js");d=("/"===d.charAt(0)||d.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+d}return m.urlArgs?d+((-1===d.indexOf("?")?"?":"&")+m.urlArgs):d},load:function(a,b){k.load(h,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||oa.test((a.currentTarget||a.srcElement).readyState))O=null,a=P(a),h.completeLoad(a.id)},onScriptError:function(a){var b=P(a);if(!p(b.id)){var c=[];E(t,function(a,d){0!==d.indexOf("_@r")&&x(a.depMaps,function(a){a.id===b.id&&c.push(d);return!0})});
return B(G("scripterror",'Script error for "'+b.id+(c.length?'", needed by: '+c.join(", "):'"'),a,[b.id]))}}};h.require=h.makeRequire();return h}function pa(){if(O&&"interactive"===O.readyState)return O;Y(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return O=b});return O}var k,C,D,I,P,J,O,Q,u,U,qa=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ra=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,V=/\.js$/,na=/^\.\//;C=Object.prototype;var R=C.toString,la=C.hasOwnProperty,
F=!("undefined"===typeof window||"undefined"===typeof navigator||!window.document),ka=!F&&"undefined"!==typeof importScripts,oa=F&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,da="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),K={},v={},W=[],T=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(L(requirejs))return;v=requirejs;requirejs=void 0}"undefined"===typeof require||L(require)||(v=require,require=void 0);k=requirejs=function(b,
c,d,p){var e,q="_";M(b)||"string"===typeof b||(e=b,M(c)?(b=c,c=d,d=p):b=[]);e&&e.context&&(q=e.context);(p=g(K,q))||(p=K[q]=k.s.newContext(q));e&&p.configure(e);return p.require(b,c,d)};k.config=function(b){return k(b)};k.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=k);k.version="2.1.22";k.jsExtRegExp=/^\/|:|\?|\.js$/;k.isBrowser=F;C=k.s={contexts:K,newContext:ma};k({});x(["toUrl","undef","defined","specified"],function(b){k[b]=function(){var c=
K._;return c.require[b].apply(c,arguments)}});F&&(D=C.head=document.getElementsByTagName("head")[0],I=document.getElementsByTagName("base")[0])&&(D=C.head=I.parentNode);k.onError=ia;k.createNode=function(b,c,d){c=b.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");c.type=b.scriptType||"text/javascript";c.charset="utf-8";c.async=!0;return c};k.load=function(b,c,d){var g=b&&b.config||{},e;if(F){e=k.createNode(g,c,d);if(g.onNodeCreated)g.onNodeCreated(e,
g,c,d);e.setAttribute("data-requirecontext",b.contextName);e.setAttribute("data-requiremodule",c);!e.attachEvent||e.attachEvent.toString&&0>e.attachEvent.toString().indexOf("[native code")||da?(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)):(T=!0,e.attachEvent("onreadystatechange",b.onScriptLoad));e.src=d;Q=e;I?D.insertBefore(e,I):D.appendChild(e);Q=null;return e}if(ka)try{importScripts(d),b.completeLoad(c)}catch(q){b.onError(G("importscripts","importScripts failed for "+
c+" at "+d,q,[c]))}};F&&!v.skipDataMain&&Y(document.getElementsByTagName("script"),function(b){D||(D=b.parentNode);if(P=b.getAttribute("data-main"))return u=P,v.baseUrl||(J=u.split("/"),u=J.pop(),U=J.length?J.join("/")+"/":"./",v.baseUrl=U),u=u.replace(V,""),k.jsExtRegExp.test(u)&&(u=P),v.deps=v.deps?v.deps.concat(u):[u],!0});define=function(b,c,d){var g,e;"string"!==typeof b&&(d=c,c=b,b=null);M(c)||(d=c,c=null);!c&&L(d)&&(c=[],d.length&&(d.toString().replace(qa,"").replace(ra,function(b,d){c.push(d)}),
c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));T&&(g=Q||pa())&&(b||(b=g.getAttribute("data-requiremodule")),e=K[g.getAttribute("data-requirecontext")]);e?(e.defQueue.push([b,c,d]),e.defQueueMap[b]=!0):W.push([b,c,d])};define.amd={jQuery:!0};k.exec=function(b){return eval(b)};k(v)}})(this);

View File

@ -0,0 +1,98 @@
define(["jquery.min","ajax"], function ($, ajax) {
function formdata_is_empty_p (formdata) {
var first = formdata.entries().next();
return (first['done']) && (typeof first['value'] === "undefined");
}
function populate_header () {
ajax.get_json('server.json').then(function (server) {
document.getElementById("unit-id").textContent = server["listener_id"];
if (server["reset_required"] && !document.getElementById('reset_required')) {
var form = document.createElement('form');
form.setAttribute('id','reset_required');
form.setAttribute('method', 'post');
form.classList.add('navbar-form');
form.classList.add('navbar-right');
form.addEventListener('submit', reset_submit);
var button = document.createElement('button');
button.setAttribute('type', 'submit');
button.setAttribute('id', 'reset_button');
button.classList.add('btn');
button.classList.add('btn-danger');
button.innerText = "Reboot Required - Click Here";
form.appendChild(button);
var hidden = document.createElement('input');
hidden.setAttribute('type', 'hidden');
hidden.setAttribute('name', 'reset');
hidden.setAttribute('value', '1');
form.appendChild(hidden);
document.getElementById('navbar').appendChild(form);
}
});
}
function form_url_encode (str) {
return encodeURIComponent(str).replace("%20","+");
}
function form_tuple_encode (tuple) {
return form_url_encode(tuple[0])+"="+form_url_encode(tuple[1]);
}
function form_encode (formdata) {
var form_strings = [];
for (var tuple of formdata.entries()) {
form_strings.push(form_tuple_encode(tuple));
}
return form_strings.join("&");
}
function util_alert (string, success) {
var main_div = document.getElementById("navbar");
var alert_div = document.createElement("div");
alert_div.setAttribute("role", "alert");
alert_div.classList.add("alert", "alert-dissmissable");
alert_div.style.display = "none";
if (success) {
alert_div.classList.add("alert-success");
} else {
alert_div.classList.add("alert-danger");
}
alert_div.innerHTML = "<a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>";
alert_div.innerHTML += string
main_div.insertBefore(alert_div, main_div.firstChild);
//window.scrollTo(0,0);
$(alert_div).slideDown("slow");
window.setTimeout(function() {
$(alert_div).slideUp("slow");
}, 5000);
}
function reset_submit (evt) {
evt.preventDefault();
var data = new FormData(document.getElementById('reset_required'));
button_animation(0);
ajax.post("", form_encode(data)).then(function(resp) {
window.setTimeout(reset_wait, 5000);
});
}
function reset_wait () {
ajax.get_json('server.json').then(
function success() {
document.location = '/';
}, function fail() {
window.setTimeout(reset_wait, 1000);
});
}
function button_animation (count) {
var btn = document.getElementById('reset_button');
var btn_txt = "Rebooting";
for (var i = 0; i < count % 4; i++) {
btn_txt += '.';
}
btn.innerText = btn_txt;
btn.style.width = '100px';
window.setTimeout(button_animation, 1000, ++count);
}
return {
"form_encode": form_encode,
"alert": util_alert,
"populate_header": populate_header,
"formdata_empty": formdata_is_empty_p,
"reset_wait": reset_wait
}
});

View File

@ -0,0 +1,160 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{% block title %}{% endblock %}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="icon" href="img/favicon.ico">
{% block head_js %}
<script data-main="static/js/dashboard"
src="/static/js/require.min.js"></script>
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand">Trespassed</span>
<!-- <a class="navbar-brand"
href="#"><img src="/static/img/logo.png"></a> -->
<span id="unit-id"><span>
</div>
{% block navbar %}
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Help</a></li>
</ul>
</div>
{% endblock %}
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
{% block sidebar %}
<li class="active"><a href="/">Overview <span class="sr-only">(current)</span></a></li>
<li><a href="/listeners">Listeners</a></li>
<li><a href="/beacons">Beacons</a></li>
{% endblock %}
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2
main">
{% block content %}
<h1 class="page-header">Dashboard</h1>
<div class="row">
<div class="col-sm-4 col-md-4">
<h3 class="sub-header">Server</h3>
<dl id="server_status" class="dl-horizontal">
</dl>
</div>
<div class="col-sm-4 col-md-4">
<h3 class="sub-header">Wired</h3>
<dl id="wired_status" class="dl-horizontal">
<dt>IP Address:</dt>
<dd>192.168.1.5</dd>
</dl>
</div>
<div class="col-sm-4 col-md-4">
<h3 class="sub-header">Wireless</h3>
<dl id="wireless_status" class="dl-horizontal">
<dt>SSID:</dt>
<dd>C3Wireless</dd>
<dt>IP Address:</dt>
<dd>192.168.15.5</dd>
</dl>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<h3 class="sub-header">Recent Beacons</h3>
<table id="beacons_table" class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Distance</th>
<th>Estimated Error</th>
</tr>
</thead>
<tbody>
<tr>
<td>1,001</td>
<td>Lorem</td>
<td>5m</td>
</tr>
<tr>
<td>1,002</td>
<td>amet</td>
<td>10m</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock content %}
{% block pagination %}
{% if pagination %}
<nav aria-label="Page navigation" class="text-center">
<ul class="pagination">
<li {% if pagination['cur_page'] - 1 <= 0 %}
class="disabled">
<a href="#" aria-label="Previous">
{% else %}
><a href="?p={{ pagination['cur_page'] - 1 }}"
aria-label="Previous">
{% endif %}
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% if pagination['cur_page'] - 2 > 0 %}
<li><a href="?p={{ pagination['cur_page'] - 2 }}">
{{ pagination['cur_page'] - 2 }}</a></li>
{% endif %}
{% if pagination['cur_page'] - 1 > 0 %}
<li><a href="?p={{ pagination['cur_page'] - 1 }}">
{{ pagination['cur_page'] - 1 }}</a></li>
{% endif %}
<li class="active"><a href="#">
{{ pagination['cur_page'] }}</a></li>
{% if pagination['cur_page'] + 1 <= pagination['num_pages'] %}
<li><a href="?p={{ pagination['cur_page'] + 1 }}">
{{ pagination['cur_page'] + 1 }}</a></li>
{% endif %}
{% if pagination['cur_page'] + 2 <= pagination['num_pages'] %}
<li><a href="?p={{ pagination['cur_page'] + 2 }}">
{{ pagination['cur_page'] + 2 }}</a></li>
{% endif %}
<li
{% if pagination['cur_page'] + 1 > pagination['num_pages'] %}
class="disabled">
<a href="#" aria-label="Next">
{% else %}
><a href="?p={{ pagination['cur_page'] + 1 }}">
{% endif %}
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
{% endif %}
{% endblock pagination %}
</div>
</div>
</div>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block title %} Dashboard {% endblock %}
{% block head_js %}
<script data-main="static/js/dashboard"
src="/static/js/require.min.js"></script>
{% endblock %}
{% block sidebar %}
<li class="active"><a href="/">Overview <span class="sr-only">(current)</span></a></li>
<li><a href="/outputs">Output Control</a></li>
{% endblock %}
{% block content %}
<!-- <h1 class="page-header">Dashboard</h1> -->
<div class="row">
<div id="status_div" class="col-sm-4 col-md-4">
<h4 class="sub-header">LCD</h3>
<pre id="status" class="monospace">{{ status }}</pre>
</div>
<div id="log_div" class="col-sm-8 col-md-8">
<h4 class="sub-header">Log</h3>
<table class="table">
<thead>
<th>Time</th>
<th>Status</th>
</thead>
<tbody id="log_table">
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %} Override Outputs {% endblock %}
{% block head_js %}
<script data-main="static/js/outputs"
src="/static/js/require.min.js"></script>
{% endblock %}
{% block sidebar %}
<li><a href="/">Overview <span class="sr-only">(current)</span></a></li>
<li class="active"><a href="/outputs">Output Control</a></li>
{% endblock %}
{% block content %}
<!-- <h1 class="page-header">Dashboard</h1> -->
<div class="row">
<div id="table_div" class="col-sm-12 col-md-12">
<!-- <h4 class="sub-header">Log</h3> -->
<table class="table">
<thead>
<th>Output</th>
<th>Actions</th>
</thead>
<tbody id="output_table">
{% for output in outputs %}
<tr><td>{{ output.name }}</td>
<td>
{% if output.value | output_status %}
<button id="btn-on-{{ output.value }}" class="btn btn-primary btn-lg active">On</button>
<button id="btn-off-{{ output.value }}" class="btn btn-primary btn-lg">Off</button>
{% else %}
<button id="btn-on-{{ output.value }}" class="btn btn-primary btn-lg">On</button>
<button id="btn-off-{{ output.value }}" class="btn btn-primary btn-lg active">Off</button>
{% endif %}
</td>
<tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

75
tox.ini Normal file
View File

@ -0,0 +1,75 @@
[tox]
envlist = flake8,linters
[testenv]
deps = pycryptodome
[flake8]
ignore = D100,D101,D102,D103,D104,E226
max-complexity = 14
[testenv:flake8]
basepython = python3
skip_install = true
deps =
flake8
flake8-docstrings>=0.2.7
flake8-import-order>=0.9
commands =
flake8 src setup.py
[testenv:doc8]
basepython = python3
skip_install = true
deps =
sphinx
doc8
commands =
doc8 docs
[testenv:mypy]
basepython = python3
skip_install = true
deps =
mypy-lang
typed-ast
setenv =
MYPYPATH=stubs
commands =
mypy --fast-parser src/c3reference
[bandit]
skips: B311
[testenv:bandit]
basepython = python3
skip_install = true
deps =
bandit
commands =
bandit -r src/c3reference --ini tox.ini
[testenv:pylint]
basepython = python3
skip_install = true
deps =
pyflakes
pylint
commands =
pylint --rcfile=pylint.rc src/c3reference
[testenv:linters]
basepython = python3
skip_install = true
setenv =
{[testenv:mypy]setenv}
deps =
{[testenv:flake8]deps}
{[testenv:pylint]deps}
{[testenv:bandit]deps}
{[testenv:mypy]deps}
commands =
{[testenv:flake8]commands}
{[testenv:pylint]commands}
{[testenv:bandit]commands}
{[testenv:mypy]commands}