First pass at declarative API
This commit is contained in:
commit
85f59df6cc
|
@ -0,0 +1 @@
|
|||
include: src/escapelib/web/*
|
|
@ -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']
|
||||
},
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
from .game import Game
|
|
@ -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)
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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()))
|
|
@ -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
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
|
@ -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;
|
||||
}
|
|
@ -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 |
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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
|
@ -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);
|
||||
});
|
|
@ -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);
|
|
@ -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\">×</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
|
||||
}
|
||||
});
|
|
@ -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">«</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">»</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>
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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}
|
Loading…
Reference in New Issue