diff --git a/server/www/packages/packages-common/qrcode/__init__.py b/server/www/packages/packages-common/qrcode/__init__.py new file mode 100644 index 0000000..f6aa53f --- /dev/null +++ b/server/www/packages/packages-common/qrcode/__init__.py @@ -0,0 +1,25 @@ +from qrcode.main import QRCode +from qrcode.main import make # noqa +from qrcode.constants import ( # noqa + ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H) + +from qrcode import image # noqa + + +def run_example(data="http://www.lincolnloop.com", *args, **kwargs): + """ + Build an example QR Code and display it. + + There's an even easier way than the code here though: just use the ``make`` + shortcut. + """ + qr = QRCode(*args, **kwargs) + qr.add_data(data) + + im = qr.make_image() + im.show() + + +if __name__ == '__main__': # pragma: no cover + import sys + run_example(*sys.argv[1:]) diff --git a/server/www/packages/packages-common/qrcode/base.py b/server/www/packages/packages-common/qrcode/base.py new file mode 100644 index 0000000..26c521f --- /dev/null +++ b/server/www/packages/packages-common/qrcode/base.py @@ -0,0 +1,361 @@ +from qrcode import constants + +EXP_TABLE = list(range(256)) + +LOG_TABLE = list(range(256)) + +for i in range(8): + EXP_TABLE[i] = 1 << i + +for i in range(8, 256): + EXP_TABLE[i] = ( + EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ + EXP_TABLE[i - 8]) + +for i in range(255): + LOG_TABLE[EXP_TABLE[i]] = i + +RS_BLOCK_OFFSET = { + constants.ERROR_CORRECT_L: 0, + constants.ERROR_CORRECT_M: 1, + constants.ERROR_CORRECT_Q: 2, + constants.ERROR_CORRECT_H: 3, +} + +RS_BLOCK_TABLE = [ + + # L + # M + # Q + # H + + # 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + # 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + # 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + # 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + # 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + # 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + # 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + # 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + # 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + # 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + + # 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + + # 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + + # 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + + # 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + + # 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12, 7, 37, 13], + + # 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + + # 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + + # 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + + # 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + + # 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + + # 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + + # 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + + # 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + + # 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + + # 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + + # 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + + # 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + + # 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + + # 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + + # 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + + # 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + + # 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + + # 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + + # 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + + # 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + + # 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + + # 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + + # 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + + # 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + + # 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16] + +] + + +def glog(n): + if n < 1: # pragma: no cover + raise ValueError("glog(%s)" % n) + return LOG_TABLE[n] + + +def gexp(n): + return EXP_TABLE[n % 255] + + +class Polynomial: + + def __init__(self, num, shift): + if not num: # pragma: no cover + raise Exception("%s/%s" % (len(num), shift)) + + offset = 0 + + for item in num: + if item != 0: + break + offset += 1 + + self.num = [0] * (len(num) - offset + shift) + for i in range(len(num) - offset): + self.num[i] = num[i + offset] + + def __getitem__(self, index): + return self.num[index] + + def __iter__(self): + return iter(self.num) + + def __len__(self): + return len(self.num) + + def __mul__(self, other): + num = [0] * (len(self) + len(other) - 1) + + for i, item in enumerate(self): + for j, other_item in enumerate(other): + num[i + j] ^= gexp(glog(item) + glog(other_item)) + + return Polynomial(num, 0) + + def __mod__(self, other): + difference = len(self) - len(other) + if difference < 0: + return self + + ratio = glog(self[0]) - glog(other[0]) + + num = self[:] + + num = [ + item ^ gexp(glog(other_item) + ratio) + for item, other_item in zip(self, other)] + if difference: + num.extend(self[-difference:]) + + # recursive call + return Polynomial(num, 0) % other + + +class RSBlock: + + def __init__(self, total_count, data_count): + self.total_count = total_count + self.data_count = data_count + + +def rs_blocks(version, error_correction): + if error_correction not in RS_BLOCK_OFFSET: # pragma: no cover + raise Exception( + "bad rs block @ version: %s / error_correction: %s" % + (version, error_correction)) + offset = RS_BLOCK_OFFSET[error_correction] + rs_block = RS_BLOCK_TABLE[(version - 1) * 4 + offset] + + blocks = [] + + for i in range(0, len(rs_block), 3): + count, total_count, data_count = rs_block[i:i + 3] + for j in range(count): + blocks.append(RSBlock(total_count, data_count)) + + return blocks diff --git a/server/www/packages/packages-common/qrcode/console_scripts.py b/server/www/packages/packages-common/qrcode/console_scripts.py new file mode 100644 index 0000000..5d1a904 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/console_scripts.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +""" +qr - Convert stdin (or the first argument) to a QR Code. + +When stdout is a tty the QR Code is printed to the terminal and when stdout is +a pipe to a file an image is written. The default image format is PNG. +""" +import sys +import optparse +import os +import qrcode +# The next block is added to get the terminal to display properly on MS platforms +if sys.platform.startswith(('win', 'cygwin')): + import colorama + colorama.init() + +default_factories = { + 'pil': 'qrcode.image.pil.PilImage', + 'pymaging': 'qrcode.image.pure.PymagingImage', + 'svg': 'qrcode.image.svg.SvgImage', + 'svg-fragment': 'qrcode.image.svg.SvgFragmentImage', + 'svg-path': 'qrcode.image.svg.SvgPathImage', +} + +error_correction = { + 'L': qrcode.ERROR_CORRECT_L, + 'M': qrcode.ERROR_CORRECT_M, + 'Q': qrcode.ERROR_CORRECT_Q, + 'H': qrcode.ERROR_CORRECT_H, +} + + +def main(args=sys.argv[1:]): + parser = optparse.OptionParser(usage=__doc__.strip()) + parser.add_option( + "--factory", help="Full python path to the image factory class to " + "create the image with. You can use the following shortcuts to the " + "built-in image factory classes: {0}.".format( + ", ".join(sorted(default_factories.keys())))) + parser.add_option( + "--optimize", type=int, help="Optimize the data by looking for chunks " + "of at least this many characters that could use a more efficient " + "encoding method. Use 0 to turn off chunk optimization.") + parser.add_option( + "--error-correction", type='choice', + choices=sorted(error_correction.keys()), default='M', + help="The error correction level to use. Choices are L (7%), " + "M (15%, default), Q (25%), and H (30%).") + opts, args = parser.parse_args(args) + + qr = qrcode.QRCode( + error_correction=error_correction[opts.error_correction]) + + if opts.factory: + module = default_factories.get(opts.factory, opts.factory) + if '.' not in module: + parser.error("The image factory is not a full python path") + module, name = module.rsplit('.', 1) + imp = __import__(module, {}, [], [name]) + image_factory = getattr(imp, name) + else: + image_factory = None + + if args: + data = args[0] + else: + # Use sys.stdin.buffer if available (Python 3) avoiding + # UnicodeDecodeErrors. + stdin_buffer = getattr(sys.stdin, 'buffer', sys.stdin) + data = stdin_buffer.read() + if opts.optimize is None: + qr.add_data(data) + else: + qr.add_data(data, optimize=opts.optimize) + + if image_factory is None and os.isatty(sys.stdout.fileno()): + qr.print_ascii(tty=True) + return + + img = qr.make_image(image_factory=image_factory) + + sys.stdout.flush() + # Use sys.stdout.buffer if available (Python 3), avoiding + # UnicodeDecodeErrors. + stdout_buffer = getattr(sys.stdout, 'buffer', None) + if not stdout_buffer: + if sys.platform == 'win32': # pragma: no cover + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + stdout_buffer = sys.stdout + + img.save(stdout_buffer) + + +if __name__ == "__main__": + main() diff --git a/server/www/packages/packages-common/qrcode/constants.py b/server/www/packages/packages-common/qrcode/constants.py new file mode 100644 index 0000000..385dda0 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/constants.py @@ -0,0 +1,5 @@ +# QR error correct levels +ERROR_CORRECT_L = 1 +ERROR_CORRECT_M = 0 +ERROR_CORRECT_Q = 3 +ERROR_CORRECT_H = 2 diff --git a/server/www/packages/packages-common/qrcode/exceptions.py b/server/www/packages/packages-common/qrcode/exceptions.py new file mode 100644 index 0000000..b37bd30 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/exceptions.py @@ -0,0 +1,2 @@ +class DataOverflowError(Exception): + pass diff --git a/server/www/packages/packages-common/qrcode/image/__init__.py b/server/www/packages/packages-common/qrcode/image/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/www/packages/packages-common/qrcode/image/base.py b/server/www/packages/packages-common/qrcode/image/base.py new file mode 100644 index 0000000..d0bfd8f --- /dev/null +++ b/server/www/packages/packages-common/qrcode/image/base.py @@ -0,0 +1,62 @@ +class BaseImage(object): + """ + Base QRCode image output class. + """ + kind = None + allowed_kinds = None + + def __init__(self, border, width, box_size, *args, **kwargs): + self.border = border + self.width = width + self.box_size = box_size + self.pixel_size = (self.width + self.border*2) * self.box_size + self._img = self.new_image(**kwargs) + + def drawrect(self, row, col): + """ + Draw a single rectangle of the QR code. + """ + raise NotImplementedError("BaseImage.drawrect") + + def save(self, stream, kind=None): + """ + Save the image file. + """ + raise NotImplementedError("BaseImage.save") + + def pixel_box(self, row, col): + """ + A helper method for pixel-based image generators that specifies the + four pixel coordinates for a single rect. + """ + x = (col + self.border) * self.box_size + y = (row + self.border) * self.box_size + return [(x, y), (x + self.box_size - 1, y + self.box_size - 1)] + + def new_image(self, **kwargs): # pragma: no cover + """ + Build the image class. Subclasses should return the class created. + """ + return None + + def get_image(self, **kwargs): + """ + Return the image class for further processing. + """ + return self._img + + def check_kind(self, kind, transform=None): + """ + Get the image type. + """ + if kind is None: + kind = self.kind + allowed = not self.allowed_kinds or kind in self.allowed_kinds + if transform: + kind = transform(kind) + if not allowed: + allowed = kind in self.allowed_kinds + if not allowed: + raise ValueError( + "Cannot set %s type to %s" % (type(self).__name__, kind)) + return kind diff --git a/server/www/packages/packages-common/qrcode/image/pil.py b/server/www/packages/packages-common/qrcode/image/pil.py new file mode 100644 index 0000000..a56d2f9 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/image/pil.py @@ -0,0 +1,50 @@ +# Needed on case-insensitive filesystems +from __future__ import absolute_import + +# Try to import PIL in either of the two ways it can be installed. +try: + from PIL import Image, ImageDraw +except ImportError: # pragma: no cover + import Image + import ImageDraw + +import qrcode.image.base + + +class PilImage(qrcode.image.base.BaseImage): + """ + PIL image builder, default format is PNG. + """ + kind = "PNG" + + def new_image(self, **kwargs): + back_color = kwargs.get("fill_color", "white") + fill_color = kwargs.get("back_color", "black") + + if fill_color.lower() != "black" or back_color.lower() != "white": + if back_color.lower() == "transparent": + mode = "RGBA" + back_color = None + else: + mode = "RGB" + else: + mode = "1" + + img = Image.new(mode, (self.pixel_size, self.pixel_size), back_color) + self.fill_color = fill_color + self._idr = ImageDraw.Draw(img) + return img + + def drawrect(self, row, col): + box = self.pixel_box(row, col) + self._idr.rectangle(box, fill=self.fill_color) + + def save(self, stream, format=None, **kwargs): + if format is None: + format = kwargs.get("kind", self.kind) + if "kind" in kwargs: + del kwargs["kind"] + self._img.save(stream, format=format, **kwargs) + + def __getattr__(self, name): + return getattr(self._img, name) diff --git a/server/www/packages/packages-common/qrcode/image/pure.py b/server/www/packages/packages-common/qrcode/image/pure.py new file mode 100644 index 0000000..34f75fe --- /dev/null +++ b/server/www/packages/packages-common/qrcode/image/pure.py @@ -0,0 +1,49 @@ +from pymaging import Image +from pymaging.colors import RGB +from pymaging.formats import registry +from pymaging.shapes import Line +from pymaging.webcolors import Black, White +from pymaging_png.png import PNG + +import qrcode.image.base + + +class PymagingImage(qrcode.image.base.BaseImage): + """ + pymaging image builder, default format is PNG. + """ + kind = "PNG" + allowed_kinds = ("PNG",) + + def __init__(self, *args, **kwargs): + """ + Register PNG with pymaging. + """ + registry.formats = [] + registry.names = {} + registry._populate() + registry.register(PNG) + + super(PymagingImage, self).__init__(*args, **kwargs) + + def new_image(self, **kwargs): + return Image.new(RGB, self.pixel_size, self.pixel_size, White) + + def drawrect(self, row, col): + (x, y), (x2, y2) = self.pixel_box(row, col) + for r in range(self.box_size): + line_y = y + r + line = Line(x, line_y, x2, line_y) + self._img.draw(line, Black) + + def save(self, stream, kind=None): + self._img.save(stream, self.check_kind(kind)) + + def check_kind(self, kind, transform=None, **kwargs): + """ + pymaging (pymaging_png at least) uses lower case for the type. + """ + if transform is None: + transform = lambda x: x.lower() + return super(PymagingImage, self).check_kind( + kind, transform=transform, **kwargs) diff --git a/server/www/packages/packages-common/qrcode/image/svg.py b/server/www/packages/packages-common/qrcode/image/svg.py new file mode 100644 index 0000000..e99a66f --- /dev/null +++ b/server/www/packages/packages-common/qrcode/image/svg.py @@ -0,0 +1,159 @@ +from decimal import Decimal +# On Python 2.6 must install lxml since the older xml.etree.ElementTree +# version can not be used to create SVG images. +try: + import lxml.etree as ET +except ImportError: + import xml.etree.ElementTree as ET +import qrcode.image.base + + +class SvgFragmentImage(qrcode.image.base.BaseImage): + """ + SVG image builder + + Creates a QR-code image as a SVG document fragment. + """ + + _SVG_namespace = "http://www.w3.org/2000/svg" + kind = "SVG" + allowed_kinds = ("SVG",) + + def __init__(self, *args, **kwargs): + ET.register_namespace("svg", self._SVG_namespace) + super(SvgFragmentImage, self).__init__(*args, **kwargs) + # Save the unit size, for example the default box_size of 10 is '1mm'. + self.unit_size = self.units(self.box_size) + + def drawrect(self, row, col): + self._img.append(self._rect(row, col)) + + def units(self, pixels, text=True): + """ + A box_size of 10 (default) equals 1mm. + """ + units = Decimal(pixels) / 10 + if not text: + return units + return '%smm' % units + + def save(self, stream, kind=None): + self.check_kind(kind=kind) + self._write(stream) + + def new_image(self, **kwargs): + return self._svg() + + def _svg(self, tag=None, version='1.1', **kwargs): + if tag is None: + tag = ET.QName(self._SVG_namespace, "svg") + dimension = self.units(self.pixel_size) + return ET.Element( + tag, width=dimension, height=dimension, version=version, + **kwargs) + + def _rect(self, row, col, tag=None): + if tag is None: + tag = ET.QName(self._SVG_namespace, "rect") + x, y = self.pixel_box(row, col)[0] + return ET.Element( + tag, x=self.units(x), y=self.units(y), + width=self.unit_size, height=self.unit_size) + + def _write(self, stream): + ET.ElementTree(self._img).write(stream, xml_declaration=False) + + +class SvgImage(SvgFragmentImage): + """ + Standalone SVG image builder + + Creates a QR-code image as a standalone SVG document. + """ + background = None + + def _svg(self, tag='svg', **kwargs): + svg = super(SvgImage, self)._svg(tag=tag, **kwargs) + svg.set("xmlns", self._SVG_namespace) + if self.background: + svg.append( + ET.Element( + 'rect', fill=self.background, x='0', y='0', width='100%', + height='100%')) + return svg + + def _rect(self, row, col): + return super(SvgImage, self)._rect(row, col, tag="rect") + + def _write(self, stream): + ET.ElementTree(self._img).write(stream, encoding="UTF-8", + xml_declaration=True) + + +class SvgPathImage(SvgImage): + """ + SVG image builder with one single element (removes white spaces + between individual QR points). + """ + + QR_PATH_STYLE = 'fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' + + def __init__(self, *args, **kwargs): + self._points = set() + super(SvgPathImage, self).__init__(*args, **kwargs) + + def _svg(self, viewBox=None, **kwargs): + if viewBox is None: + dimension = self.units(self.pixel_size, text=False) + viewBox = '0 0 %(d)s %(d)s' % {'d': dimension} + return super(SvgPathImage, self)._svg(viewBox=viewBox, **kwargs) + + def drawrect(self, row, col): + # (x, y) + self._points.add((col, row)) + + def _generate_subpaths(self): + """Generates individual QR points as subpaths""" + + rect_size = self.units(self.box_size, text=False) + + for point in self._points: + x_base = self.units( + (point[0]+self.border)*self.box_size, text=False) + y_base = self.units( + (point[1]+self.border)*self.box_size, text=False) + + yield ( + 'M %(x0)s %(y0)s L %(x0)s %(y1)s L %(x1)s %(y1)s L %(x1)s ' + '%(y0)s z' % dict( + x0=x_base, y0=y_base, + x1=x_base+rect_size, y1=y_base+rect_size, + )) + + def make_path(self): + subpaths = self._generate_subpaths() + + return ET.Element( + ET.QName("path"), + style=self.QR_PATH_STYLE, + d=' '.join(subpaths), + id="qr-path" + ) + + def _write(self, stream): + self._img.append(self.make_path()) + super(SvgPathImage, self)._write(stream) + + +class SvgFillImage(SvgImage): + """ + An SvgImage that fills the background to white. + """ + background = 'white' + + +class SvgPathFillImage(SvgPathImage): + """ + An SvgPathImage that fills the background to white. + """ + background = 'white' diff --git a/server/www/packages/packages-common/qrcode/main.py b/server/www/packages/packages-common/qrcode/main.py new file mode 100644 index 0000000..4fddd37 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/main.py @@ -0,0 +1,422 @@ +from qrcode import constants, exceptions, util +from qrcode.image.base import BaseImage + +import six +from bisect import bisect_left + + +def make(data=None, **kwargs): + qr = QRCode(**kwargs) + qr.add_data(data) + return qr.make_image() + + +def _check_version(version): + if version < 1 or version > 40: + raise ValueError( + "Invalid version (was %s, expected 1 to 40)" % version) + + +def _check_box_size(size): + if int(size) <= 0: + raise ValueError( + "Invalid box size (was %s, expected larger than 0)" % size) + + +class QRCode: + + def __init__(self, version=None, + error_correction=constants.ERROR_CORRECT_M, + box_size=10, border=4, + image_factory=None): + _check_box_size(box_size) + self.version = version and int(version) + self.error_correction = int(error_correction) + self.box_size = int(box_size) + # Spec says border should be at least four boxes wide, but allow for + # any (e.g. for producing printable QR codes). + self.border = int(border) + self.image_factory = image_factory + if image_factory is not None: + assert issubclass(image_factory, BaseImage) + self.clear() + + def clear(self): + """ + Reset the internal data. + """ + self.modules = None + self.modules_count = 0 + self.data_cache = None + self.data_list = [] + + def add_data(self, data, optimize=20): + """ + Add data to this QR Code. + + :param optimize: Data will be split into multiple chunks to optimize + the QR size by finding to more compressed modes of at least this + length. Set to ``0`` to avoid optimizing at all. + """ + if isinstance(data, util.QRData): + self.data_list.append(data) + else: + if optimize: + self.data_list.extend(util.optimal_data_chunks(data)) + else: + self.data_list.append(util.QRData(data)) + self.data_cache = None + + def make(self, fit=True): + """ + Compile the data into a QR Code array. + + :param fit: If ``True`` (or if a size has not been provided), find the + best fit for the data to avoid data overflow errors. + """ + if fit or (self.version is None): + self.best_fit(start=self.version) + self.makeImpl(False, self.best_mask_pattern()) + + def makeImpl(self, test, mask_pattern): + _check_version(self.version) + self.modules_count = self.version * 4 + 17 + self.modules = [None] * self.modules_count + + for row in range(self.modules_count): + + self.modules[row] = [None] * self.modules_count + + for col in range(self.modules_count): + self.modules[row][col] = None # (col + row) % 3 + + self.setup_position_probe_pattern(0, 0) + self.setup_position_probe_pattern(self.modules_count - 7, 0) + self.setup_position_probe_pattern(0, self.modules_count - 7) + self.setup_position_adjust_pattern() + self.setup_timing_pattern() + self.setup_type_info(test, mask_pattern) + + if self.version >= 7: + self.setup_type_number(test) + + if self.data_cache is None: + self.data_cache = util.create_data( + self.version, self.error_correction, self.data_list) + self.map_data(self.data_cache, mask_pattern) + + def setup_position_probe_pattern(self, row, col): + for r in range(-1, 8): + + if row + r <= -1 or self.modules_count <= row + r: + continue + + for c in range(-1, 8): + + if col + c <= -1 or self.modules_count <= col + c: + continue + + if (0 <= r and r <= 6 and (c == 0 or c == 6) + or (0 <= c and c <= 6 and (r == 0 or r == 6)) + or (2 <= r and r <= 4 and 2 <= c and c <= 4)): + self.modules[row + r][col + c] = True + else: + self.modules[row + r][col + c] = False + + def best_fit(self, start=None): + """ + Find the minimum size required to fit in the data. + """ + if start is None: + start = 1 + _check_version(start) + + # Corresponds to the code in util.create_data, except we don't yet know + # version, so optimistically assume start and check later + mode_sizes = util.mode_sizes_for_version(start) + buffer = util.BitBuffer() + for data in self.data_list: + buffer.put(data.mode, 4) + buffer.put(len(data), mode_sizes[data.mode]) + data.write(buffer) + + needed_bits = len(buffer) + self.version = bisect_left(util.BIT_LIMIT_TABLE[self.error_correction], + needed_bits, start) + if self.version == 41: + raise exceptions.DataOverflowError() + + # Now check whether we need more bits for the mode sizes, recursing if + # our guess was too low + if mode_sizes is not util.mode_sizes_for_version(self.version): + self.best_fit(start=self.version) + return self.version + + def best_mask_pattern(self): + """ + Find the most efficient mask pattern. + """ + min_lost_point = 0 + pattern = 0 + + for i in range(8): + self.makeImpl(True, i) + + lost_point = util.lost_point(self.modules) + + if i == 0 or min_lost_point > lost_point: + min_lost_point = lost_point + pattern = i + + return pattern + + def print_tty(self, out=None): + """ + Output the QR Code only using TTY colors. + + If the data has not been compiled yet, make it first. + """ + if out is None: + import sys + out = sys.stdout + + if not out.isatty(): + raise OSError("Not a tty") + + if self.data_cache is None: + self.make() + + modcount = self.modules_count + out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n") + for r in range(modcount): + out.write("\x1b[1;47m \x1b[40m") + for c in range(modcount): + if self.modules[r][c]: + out.write(" ") + else: + out.write("\x1b[1;47m \x1b[40m") + out.write("\x1b[1;47m \x1b[0m\n") + out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n") + out.flush() + + def print_ascii(self, out=None, tty=False, invert=False): + """ + Output the QR Code using ASCII characters. + + :param tty: use fixed TTY color codes (forces invert=True) + :param invert: invert the ASCII characters (solid <-> transparent) + """ + if out is None: + import sys + if sys.version_info < (2, 7): + # On Python versions 2.6 and earlier, stdout tries to encode + # strings using ASCII rather than stdout.encoding, so use this + # workaround. + import codecs + out = codecs.getwriter(sys.stdout.encoding)(sys.stdout) + else: + out = sys.stdout + + if tty and not out.isatty(): + raise OSError("Not a tty") + + if self.data_cache is None: + self.make() + + modcount = self.modules_count + codes = [six.int2byte(code).decode('cp437') + for code in (255, 223, 220, 219)] + if tty: + invert = True + if invert: + codes.reverse() + + def get_module(x, y): + if (invert and self.border and + max(x, y) >= modcount+self.border): + return 1 + if min(x, y) < 0 or max(x, y) >= modcount: + return 0 + return self.modules[x][y] + + for r in range(-self.border, modcount+self.border, 2): + if tty: + if not invert or r < modcount+self.border-1: + out.write('\x1b[48;5;232m') # Background black + out.write('\x1b[38;5;255m') # Foreground white + for c in range(-self.border, modcount+self.border): + pos = get_module(r, c) + (get_module(r+1, c) << 1) + out.write(codes[pos]) + if tty: + out.write('\x1b[0m') + out.write('\n') + out.flush() + + def make_image(self, image_factory=None, **kwargs): + """ + Make an image from the QR Code data. + + If the data has not been compiled yet, make it first. + """ + _check_box_size(self.box_size) + if self.data_cache is None: + self.make() + + if image_factory is not None: + assert issubclass(image_factory, BaseImage) + else: + image_factory = self.image_factory + if image_factory is None: + # Use PIL by default + from qrcode.image.pil import PilImage + image_factory = PilImage + + im = image_factory( + self.border, self.modules_count, self.box_size, **kwargs) + for r in range(self.modules_count): + for c in range(self.modules_count): + if self.modules[r][c]: + im.drawrect(r, c) + return im + + def setup_timing_pattern(self): + for r in range(8, self.modules_count - 8): + if self.modules[r][6] is not None: + continue + self.modules[r][6] = (r % 2 == 0) + + for c in range(8, self.modules_count - 8): + if self.modules[6][c] is not None: + continue + self.modules[6][c] = (c % 2 == 0) + + def setup_position_adjust_pattern(self): + pos = util.pattern_position(self.version) + + for i in range(len(pos)): + + for j in range(len(pos)): + + row = pos[i] + col = pos[j] + + if self.modules[row][col] is not None: + continue + + for r in range(-2, 3): + + for c in range(-2, 3): + + if (r == -2 or r == 2 or c == -2 or c == 2 or + (r == 0 and c == 0)): + self.modules[row + r][col + c] = True + else: + self.modules[row + r][col + c] = False + + def setup_type_number(self, test): + bits = util.BCH_type_number(self.version) + + for i in range(18): + mod = (not test and ((bits >> i) & 1) == 1) + self.modules[i // 3][i % 3 + self.modules_count - 8 - 3] = mod + + for i in range(18): + mod = (not test and ((bits >> i) & 1) == 1) + self.modules[i % 3 + self.modules_count - 8 - 3][i // 3] = mod + + def setup_type_info(self, test, mask_pattern): + data = (self.error_correction << 3) | mask_pattern + bits = util.BCH_type_info(data) + + # vertical + for i in range(15): + + mod = (not test and ((bits >> i) & 1) == 1) + + if i < 6: + self.modules[i][8] = mod + elif i < 8: + self.modules[i + 1][8] = mod + else: + self.modules[self.modules_count - 15 + i][8] = mod + + # horizontal + for i in range(15): + + mod = (not test and ((bits >> i) & 1) == 1) + + if i < 8: + self.modules[8][self.modules_count - i - 1] = mod + elif i < 9: + self.modules[8][15 - i - 1 + 1] = mod + else: + self.modules[8][15 - i - 1] = mod + + # fixed module + self.modules[self.modules_count - 8][8] = (not test) + + def map_data(self, data, mask_pattern): + inc = -1 + row = self.modules_count - 1 + bitIndex = 7 + byteIndex = 0 + + mask_func = util.mask_func(mask_pattern) + + data_len = len(data) + + for col in six.moves.xrange(self.modules_count - 1, 0, -2): + + if col <= 6: + col -= 1 + + col_range = (col, col-1) + + while True: + + for c in col_range: + + if self.modules[row][c] is None: + + dark = False + + if byteIndex < data_len: + dark = (((data[byteIndex] >> bitIndex) & 1) == 1) + + if mask_func(row, c): + dark = not dark + + self.modules[row][c] = dark + bitIndex -= 1 + + if bitIndex == -1: + byteIndex += 1 + bitIndex = 7 + + row += inc + + if row < 0 or self.modules_count <= row: + row -= inc + inc = -inc + break + + def get_matrix(self): + """ + Return the QR Code as a multidimensonal array, including the border. + + To return the array without a border, set ``self.border`` to 0 first. + """ + if self.data_cache is None: + self.make() + + if not self.border: + return self.modules + + width = len(self.modules) + self.border*2 + code = [[False]*width] * self.border + x_border = [False]*self.border + for module in self.modules: + code.append(x_border + module + x_border) + code += [[False]*width] * self.border + + return code diff --git a/server/www/packages/packages-common/qrcode/mecard.py b/server/www/packages/packages-common/qrcode/mecard.py new file mode 100644 index 0000000..ce05582 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/mecard.py @@ -0,0 +1,33 @@ +import six + +# {'code': 'N', 'label': 'Name', 'required': True, 'multipart': [ +# 'Last Name', 'First Name']}, +PROPERTIES = { + 'NICKNAME': {'label': 'Nickname'}, + 'BDAY': {'label': 'Birthday', 'date': True}, + 'TEL': {'label': 'Phone'}, + 'EMAIL': {'label': 'E-mail'}, + 'ADR': {'label': 'Address', 'multipart': [ + 'PO Box', 'Room Number', 'House Number', 'City', 'Prefecture', + 'Zip Code', 'Country']}, + 'URL': {'label': 'URL'}, + 'MEMO': {'label': 'Note'}, +} + + +def build_code(data): + notation = [] + + name = data['N'] + if not isinstance(name, six.text_type): + name = ','.join(name) + notation.append('N', name) + + for prop in PROPERTIES: + value = data.get(prop['code']) + if not value: + continue + if prop['date']: + value = value.strftime('%Y%m%d') + elif prop['multipart']: + value = ','.join(value) diff --git a/server/www/packages/packages-common/qrcode/speedy.py b/server/www/packages/packages-common/qrcode/speedy.py new file mode 100644 index 0000000..11ec3aa --- /dev/null +++ b/server/www/packages/packages-common/qrcode/speedy.py @@ -0,0 +1,8 @@ +import string +import qrcode + +qr = qrcode.QRCode() + +qr.add_data(string.letters*13) +qr.make() +print(qr.version) diff --git a/server/www/packages/packages-common/qrcode/util.py b/server/www/packages/packages-common/qrcode/util.py new file mode 100644 index 0000000..89dcf09 --- /dev/null +++ b/server/www/packages/packages-common/qrcode/util.py @@ -0,0 +1,556 @@ +import re +import math + +import six +from six.moves import xrange + +from qrcode import base, exceptions + +# QR encoding modes. +MODE_NUMBER = 1 << 0 +MODE_ALPHA_NUM = 1 << 1 +MODE_8BIT_BYTE = 1 << 2 +MODE_KANJI = 1 << 3 + +# Encoding mode sizes. +MODE_SIZE_SMALL = { + MODE_NUMBER: 10, + MODE_ALPHA_NUM: 9, + MODE_8BIT_BYTE: 8, + MODE_KANJI: 8, +} +MODE_SIZE_MEDIUM = { + MODE_NUMBER: 12, + MODE_ALPHA_NUM: 11, + MODE_8BIT_BYTE: 16, + MODE_KANJI: 10, +} +MODE_SIZE_LARGE = { + MODE_NUMBER: 14, + MODE_ALPHA_NUM: 13, + MODE_8BIT_BYTE: 16, + MODE_KANJI: 12, +} + +ALPHA_NUM = six.b('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:') +RE_ALPHA_NUM = re.compile(six.b('^[') + re.escape(ALPHA_NUM) + six.b(']*\Z')) + +# The number of bits for numeric delimited data lengths. +NUMBER_LENGTH = {3: 10, 2: 7, 1: 4} + +PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] +] + +G15 = ( + (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | + (1 << 0)) +G18 = ( + (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | + (1 << 2) | (1 << 0)) +G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1) + +PAD0 = 0xEC +PAD1 = 0x11 + +# Precompute bit count limits, indexed by error correction level and code size +_data_count = lambda block: block.data_count +BIT_LIMIT_TABLE = [ + [0] + [8*sum(map(_data_count, base.rs_blocks(version, error_correction))) + for version in xrange(1, 41)] + for error_correction in xrange(4) +] + + +def BCH_type_info(data): + d = data << 10 + while BCH_digit(d) - BCH_digit(G15) >= 0: + d ^= (G15 << (BCH_digit(d) - BCH_digit(G15))) + + return ((data << 10) | d) ^ G15_MASK + + +def BCH_type_number(data): + d = data << 12 + while BCH_digit(d) - BCH_digit(G18) >= 0: + d ^= (G18 << (BCH_digit(d) - BCH_digit(G18))) + return (data << 12) | d + + +def BCH_digit(data): + digit = 0 + while data != 0: + digit += 1 + data >>= 1 + return digit + + +def pattern_position(version): + return PATTERN_POSITION_TABLE[version - 1] + + +def mask_func(pattern): + """ + Return the mask function for the given mask pattern. + """ + if pattern == 0: # 000 + return lambda i, j: (i + j) % 2 == 0 + if pattern == 1: # 001 + return lambda i, j: i % 2 == 0 + if pattern == 2: # 010 + return lambda i, j: j % 3 == 0 + if pattern == 3: # 011 + return lambda i, j: (i + j) % 3 == 0 + if pattern == 4: # 100 + return lambda i, j: (math.floor(i / 2) + math.floor(j / 3)) % 2 == 0 + if pattern == 5: # 101 + return lambda i, j: (i * j) % 2 + (i * j) % 3 == 0 + if pattern == 6: # 110 + return lambda i, j: ((i * j) % 2 + (i * j) % 3) % 2 == 0 + if pattern == 7: # 111 + return lambda i, j: ((i * j) % 3 + (i + j) % 2) % 2 == 0 + raise TypeError("Bad mask pattern: " + pattern) # pragma: no cover + + +def mode_sizes_for_version(version): + if version < 10: + return MODE_SIZE_SMALL + elif version < 27: + return MODE_SIZE_MEDIUM + else: + return MODE_SIZE_LARGE + + +def length_in_bits(mode, version): + if mode not in ( + MODE_NUMBER, MODE_ALPHA_NUM, MODE_8BIT_BYTE, MODE_KANJI): + raise TypeError("Invalid mode (%s)" % mode) # pragma: no cover + + if version < 1 or version > 40: # pragma: no cover + raise ValueError( + "Invalid version (was %s, expected 1 to 40)" % version) + + return mode_sizes_for_version(version)[mode] + + +def lost_point(modules): + modules_count = len(modules) + + lost_point = 0 + + lost_point = _lost_point_level1(modules, modules_count) + lost_point += _lost_point_level2(modules, modules_count) + lost_point += _lost_point_level3(modules, modules_count) + lost_point += _lost_point_level4(modules, modules_count) + + return lost_point + + +def _lost_point_level1(modules, modules_count): + lost_point = 0 + + modules_range = xrange(modules_count) + row_range_first = (0, 1) + row_range_last = (-1, 0) + row_range_standard = (-1, 0, 1) + + col_range_first = ((0, 1), (1,)) + col_range_last = ((-1, 0), (-1,)) + col_range_standard = ((-1, 0, 1), (-1, 1)) + + for row in modules_range: + + if row == 0: + row_range = row_range_first + elif row == modules_count-1: + row_range = row_range_last + else: + row_range = row_range_standard + + for col in modules_range: + + sameCount = 0 + dark = modules[row][col] + + if col == 0: + col_range = col_range_first + elif col == modules_count-1: + col_range = col_range_last + else: + col_range = col_range_standard + + for r in row_range: + + row_offset = row + r + + if r != 0: + col_idx = 0 + else: + col_idx = 1 + + for c in col_range[col_idx]: + + if dark == modules[row_offset][col + c]: + sameCount += 1 + + if sameCount > 5: + lost_point += (3 + sameCount - 5) + + return lost_point + + +def _lost_point_level2(modules, modules_count): + lost_point = 0 + + modules_range = xrange(modules_count - 1) + + for row in modules_range: + this_row = modules[row] + next_row = modules[row+1] + for col in modules_range: + count = 0 + if this_row[col]: + count += 1 + if next_row[col]: + count += 1 + if this_row[col + 1]: + count += 1 + if next_row[col + 1]: + count += 1 + if count == 0 or count == 4: + lost_point += 3 + + return lost_point + + +def _lost_point_level3(modules, modules_count): + modules_range_short = xrange(modules_count-6) + + lost_point = 0 + for row in xrange(modules_count): + this_row = modules[row] + for col in modules_range_short: + if (this_row[col] + and not this_row[col + 1] + and this_row[col + 2] + and this_row[col + 3] + and this_row[col + 4] + and not this_row[col + 5] + and this_row[col + 6]): + lost_point += 40 + + for col in xrange(modules_count): + for row in modules_range_short: + if (modules[row][col] + and not modules[row + 1][col] + and modules[row + 2][col] + and modules[row + 3][col] + and modules[row + 4][col] + and not modules[row + 5][col] + and modules[row + 6][col]): + lost_point += 40 + + return lost_point + + +def _lost_point_level4(modules, modules_count): + modules_range = xrange(modules_count) + dark_count = 0 + + for row in modules_range: + this_row = modules[row] + for col in modules_range: + if this_row[col]: + dark_count += 1 + + ratio = abs(100 * dark_count / modules_count / modules_count - 50) / 5 + return ratio * 10 + + +def optimal_data_chunks(data, minimum=4): + """ + An iterator returning QRData chunks optimized to the data content. + + :param minimum: The minimum number of bytes in a row to split as a chunk. + """ + data = to_bytestring(data) + re_repeat = ( + six.b('{') + six.text_type(minimum).encode('ascii') + six.b(',}')) + num_pattern = re.compile(six.b('\d') + re_repeat) + num_bits = _optimal_split(data, num_pattern) + alpha_pattern = re.compile( + six.b('[') + re.escape(ALPHA_NUM) + six.b(']') + re_repeat) + for is_num, chunk in num_bits: + if is_num: + yield QRData(chunk, mode=MODE_NUMBER, check_data=False) + else: + for is_alpha, sub_chunk in _optimal_split(chunk, alpha_pattern): + if is_alpha: + mode = MODE_ALPHA_NUM + else: + mode = MODE_8BIT_BYTE + yield QRData(sub_chunk, mode=mode, check_data=False) + + +def _optimal_split(data, pattern): + while data: + match = re.search(pattern, data) + if not match: + break + start, end = match.start(), match.end() + if start: + yield False, data[:start] + yield True, data[start:end] + data = data[end:] + if data: + yield False, data + + +def to_bytestring(data): + """ + Convert data to a (utf-8 encoded) byte-string if it isn't a byte-string + already. + """ + if not isinstance(data, six.binary_type): + data = six.text_type(data).encode('utf-8') + return data + + +def optimal_mode(data): + """ + Calculate the optimal mode for this chunk of data. + """ + if data.isdigit(): + return MODE_NUMBER + if RE_ALPHA_NUM.match(data): + return MODE_ALPHA_NUM + return MODE_8BIT_BYTE + + +class QRData: + """ + Data held in a QR compatible format. + + Doesn't currently handle KANJI. + """ + + def __init__(self, data, mode=None, check_data=True): + """ + If ``mode`` isn't provided, the most compact QR data type possible is + chosen. + """ + if check_data: + data = to_bytestring(data) + + if mode is None: + self.mode = optimal_mode(data) + else: + self.mode = mode + if mode not in (MODE_NUMBER, MODE_ALPHA_NUM, MODE_8BIT_BYTE): + raise TypeError("Invalid mode (%s)" % mode) # pragma: no cover + if check_data and mode < optimal_mode(data): # pragma: no cover + raise ValueError( + "Provided data can not be represented in mode " + "{0}".format(mode)) + + self.data = data + + def __len__(self): + return len(self.data) + + def write(self, buffer): + if self.mode == MODE_NUMBER: + for i in xrange(0, len(self.data), 3): + chars = self.data[i:i + 3] + bit_length = NUMBER_LENGTH[len(chars)] + buffer.put(int(chars), bit_length) + elif self.mode == MODE_ALPHA_NUM: + for i in xrange(0, len(self.data), 2): + chars = self.data[i:i + 2] + if len(chars) > 1: + buffer.put( + ALPHA_NUM.find(chars[0]) * 45 + + ALPHA_NUM.find(chars[1]), 11) + else: + buffer.put(ALPHA_NUM.find(chars), 6) + else: + if six.PY3: + # Iterating a bytestring in Python 3 returns an integer, + # no need to ord(). + data = self.data + else: + data = [ord(c) for c in self.data] + for c in data: + buffer.put(c, 8) + + def __repr__(self): + return repr(self.data) + + +class BitBuffer: + + def __init__(self): + self.buffer = [] + self.length = 0 + + def __repr__(self): + return ".".join([str(n) for n in self.buffer]) + + def get(self, index): + buf_index = math.floor(index / 8) + return ((self.buffer[buf_index] >> (7 - index % 8)) & 1) == 1 + + def put(self, num, length): + for i in range(length): + self.put_bit(((num >> (length - i - 1)) & 1) == 1) + + def __len__(self): + return self.length + + def put_bit(self, bit): + buf_index = self.length // 8 + if len(self.buffer) <= buf_index: + self.buffer.append(0) + if bit: + self.buffer[buf_index] |= (0x80 >> (self.length % 8)) + self.length += 1 + + +def create_bytes(buffer, rs_blocks): + offset = 0 + + maxDcCount = 0 + maxEcCount = 0 + + dcdata = [0] * len(rs_blocks) + ecdata = [0] * len(rs_blocks) + + for r in range(len(rs_blocks)): + + dcCount = rs_blocks[r].data_count + ecCount = rs_blocks[r].total_count - dcCount + + maxDcCount = max(maxDcCount, dcCount) + maxEcCount = max(maxEcCount, ecCount) + + dcdata[r] = [0] * dcCount + + for i in range(len(dcdata[r])): + dcdata[r][i] = 0xff & buffer.buffer[i + offset] + offset += dcCount + + # Get error correction polynomial. + rsPoly = base.Polynomial([1], 0) + for i in range(ecCount): + rsPoly = rsPoly * base.Polynomial([1, base.gexp(i)], 0) + + rawPoly = base.Polynomial(dcdata[r], len(rsPoly) - 1) + + modPoly = rawPoly % rsPoly + ecdata[r] = [0] * (len(rsPoly) - 1) + for i in range(len(ecdata[r])): + modIndex = i + len(modPoly) - len(ecdata[r]) + if (modIndex >= 0): + ecdata[r][i] = modPoly[modIndex] + else: + ecdata[r][i] = 0 + + totalCodeCount = 0 + for rs_block in rs_blocks: + totalCodeCount += rs_block.total_count + + data = [None] * totalCodeCount + index = 0 + + for i in range(maxDcCount): + for r in range(len(rs_blocks)): + if i < len(dcdata[r]): + data[index] = dcdata[r][i] + index += 1 + + for i in range(maxEcCount): + for r in range(len(rs_blocks)): + if i < len(ecdata[r]): + data[index] = ecdata[r][i] + index += 1 + + return data + + +def create_data(version, error_correction, data_list): + + buffer = BitBuffer() + for data in data_list: + buffer.put(data.mode, 4) + buffer.put(len(data), length_in_bits(data.mode, version)) + data.write(buffer) + + # Calculate the maximum number of bits for the given version. + rs_blocks = base.rs_blocks(version, error_correction) + bit_limit = 0 + for block in rs_blocks: + bit_limit += block.data_count * 8 + + if len(buffer) > bit_limit: + raise exceptions.DataOverflowError( + "Code length overflow. Data size (%s) > size available (%s)" % + (len(buffer), bit_limit)) + + # Terminate the bits (add up to four 0s). + for i in range(min(bit_limit - len(buffer), 4)): + buffer.put_bit(False) + + # Delimit the string into 8-bit words, padding with 0s if necessary. + delimit = len(buffer) % 8 + if delimit: + for i in range(8 - delimit): + buffer.put_bit(False) + + # Add special alternating padding bitstrings until buffer is full. + bytes_to_fill = (bit_limit - len(buffer)) // 8 + for i in range(bytes_to_fill): + if i % 2 == 0: + buffer.put(PAD0, 8) + else: + buffer.put(PAD1, 8) + + return create_bytes(buffer, rs_blocks) diff --git a/server/www/teleport/app/eom_app/app/database/create.py b/server/www/teleport/app/eom_app/app/database/create.py index 3a045a3..8d21d92 100644 --- a/server/www/teleport/app/eom_app/app/database/create.py +++ b/server/www/teleport/app/eom_app/app/database/create.py @@ -24,7 +24,8 @@ def create_and_init(db, step_begin, step_end): `account_pwd` varchar(128) DEFAULT NULL, `account_status` int(11) DEFAULT 0, `account_lock` int(11) DEFAULT 0, -`account_desc` varchar(255) +`account_desc` varchar(255), +`oath_secret` varchar(64), );""".format(db.table_prefix, db.auto_increment)) _db_exec(db, step_begin, step_end, '创建表 auth', """CREATE TABLE `{}auth`( @@ -53,7 +54,7 @@ PRIMARY KEY (`name` ASC) _db_exec(db, step_begin, step_end, '创建表 group', """CREATE TABLE `{}group` ( `group_id` integer PRIMARY KEY {}, -`group_name` varchar(255) DEFAULT'' +`group_name` varchar(255) DEFAULT '' );""".format(db.table_prefix, db.auto_increment)) _db_exec(db, step_begin, step_end, '创建表 host_info', """CREATE TABLE `{}host_info`( @@ -99,7 +100,7 @@ PRIMARY KEY (`name` ASC) _db_exec(db, step_begin, step_end, '建立管理员账号', - 'INSERT INTO `{}account` VALUES (1, 100, "admin", "{}", 0, 0, "超级管理员");'.format(db.table_prefix, _admin_sec_password) + 'INSERT INTO `{}account` VALUES (1, 100, "admin", "{}", 0, 0, "超级管理员", "");'.format(db.table_prefix, _admin_sec_password) ) _db_exec(db, step_begin, step_end, diff --git a/server/www/teleport/app/eom_app/app/database/export.py b/server/www/teleport/app/eom_app/app/database/export.py index fb3fbe6..378f391 100644 --- a/server/www/teleport/app/eom_app/app/database/export.py +++ b/server/www/teleport/app/eom_app/app/database/export.py @@ -48,7 +48,7 @@ def export_database(db): else: return 'Unknown Database Type' - _fields = ['account_id', 'account_type', 'account_name', 'account_pwd', 'account_status', 'account_lock', 'account_desc'] + _fields = ['account_id', 'account_type', 'account_name', 'account_pwd', 'account_status', 'account_lock', 'account_desc', 'oath_secret'] ret.append(_export_table(db, 'account', _fields)) _fields = ['auth_id', 'account_name', 'host_id', 'host_auth_id'] ret.append(_export_table(db, 'auth', _fields)) @@ -66,101 +66,3 @@ def export_database(db): ret.append(_export_table(db, 'log', _fields)) return '\r\n'.join(ret) - - -def create_and_init(db, step_begin, step_end): - try: - _db_exec(db, step_begin, step_end, '创建表 account', """CREATE TABLE `{}account` ( -`account_id` integer PRIMARY KEY {}, -`account_type` int(11) DEFAULT 0, -`account_name` varchar(32) DEFAULT NULL, -`account_pwd` varchar(128) DEFAULT NULL, -`account_status` int(11) DEFAULT 0, -`account_lock` int(11) DEFAULT 0, -`account_desc` varchar(255) -);""".format(db.table_prefix, db.auto_increment)) - - _db_exec(db, step_begin, step_end, '创建表 auth', """CREATE TABLE `{}auth`( -`auth_id` INTEGER PRIMARY KEY {}, -`account_name` varchar(255), -`host_id` INTEGER, -`host_auth_id` int(11) NOT NULL -);""".format(db.table_prefix, db.auto_increment)) - - # 注意,这个key表原名为cert,考虑到其中存放的是ssh密钥对,与证书无关,因此改名为key - # 这也是升级到数据库版本5的标志! - _db_exec(db, step_begin, step_end, '创建表 key', """CREATE TABLE `{}key` ( -`cert_id` integer PRIMARY KEY {}, -`cert_name` varchar(255), -`cert_pub` varchar(2048) DEFAULT '', -`cert_pri` varchar(4096) DEFAULT '', -`cert_desc` varchar(255) -); -""".format(db.table_prefix, db.auto_increment)) - - _db_exec(db, step_begin, step_end, '创建表 config', """CREATE TABLE `{}config` ( -`name` varchar(128) NOT NULL, -`value` varchar(255), -PRIMARY KEY (`name` ASC) -);""".format(db.table_prefix)) - - _db_exec(db, step_begin, step_end, '创建表 group', """CREATE TABLE `{}group` ( -`group_id` integer PRIMARY KEY {}, -`group_name` varchar(255) DEFAULT'' -);""".format(db.table_prefix, db.auto_increment)) - - _db_exec(db, step_begin, step_end, '创建表 host_info', """CREATE TABLE `{}host_info`( -`host_id` integer PRIMARY KEY {}, -`group_id` int(11) DEFAULT 0, -`host_sys_type` int(11) DEFAULT 1, -`host_ip` varchar(32) DEFAULT '', -`host_port` int(11) DEFAULT 0, -`protocol` int(11) DEFAULT 0, -`host_lock` int(11) DEFAULT 0, -`host_desc` varchar(255) DEFAULT '' -);""".format(db.table_prefix, db.auto_increment)) - - _db_exec(db, step_begin, step_end, '创建表 auth_info', """CREATE TABLE `{}auth_info`( -`id` INTEGER PRIMARY KEY {}, -`host_id` INTEGER, -`auth_mode` INTEGER, -`user_name` varchar(255), -`user_pswd` varchar(255), -`user_param` varchar(255), -`cert_id` INTEGER, -`encrypt` INTEGER, -`log_time` varchar(60) -);""".format(db.table_prefix, db.auto_increment)) - - _db_exec(db, step_begin, step_end, '创建表 key', """CREATE TABLE `{}log` ( -`id` INTEGER PRIMARY KEY {}, -`session_id` varchar(32), -`account_name` varchar(64), -`host_ip` varchar(32), -`host_port` INTEGER, -`sys_type` INTEGER DEFAULT 0, -`auth_type` INTEGER, -`protocol` INTEGER, -`user_name` varchar(64), -`ret_code` INTEGER, -`begin_time` INTEGER, -`end_time` INTEGER, -`log_time` varchar(64) -);""".format(db.table_prefix, db.auto_increment)) - - _admin_sec_password = sec_generate_password('admin') - - _db_exec(db, step_begin, step_end, - '建立管理员账号', - 'INSERT INTO `{}account` VALUES (1, 100, "admin", "{}", 0, 0, "超级管理员");'.format(db.table_prefix, _admin_sec_password) - ) - - _db_exec(db, step_begin, step_end, - '设定数据库版本', - 'INSERT INTO `{}config` VALUES ("db_ver", "{}");'.format(db.table_prefix, db.DB_VERSION) - ) - - return True - except: - log.e('ERROR\n') - return False diff --git a/server/www/teleport/app/eom_app/app/database/upgrade.py b/server/www/teleport/app/eom_app/app/database/upgrade.py index c9175f3..8f58df4 100644 --- a/server/www/teleport/app/eom_app/app/database/upgrade.py +++ b/server/www/teleport/app/eom_app/app/database/upgrade.py @@ -526,3 +526,38 @@ class DatabaseUpgrade: log.e('failed.\n') self.step_end(_step, -1) return False + + def _upgrade_to_v6(self): + _step = self.step_begin('检查数据库版本v6...') + + # 服务端升级到版本2.2.9.1时,为增加双因子认证,为account表增加oath_secret字段 + # db_ret = self.db.is_field_exists('{}account'.format(self.db.table_prefix), 'oath_secret') + # if db_ret is None: + # self.step_end(_step, -1, '无法连接到数据库') + # return False + # if db_ret: + # self.step_end(_step, 0, '跳过 v5 到 v6 的升级操作') + # return True + + self.step_end(_step, 0, '需要升级到v6') + + try: + + _step = self.step_begin(' - 在account表中加入oath_secret字段') + if not self.db.exec('ALTER TABLE {}account ADD oath_secret VARCHAR(64) DEFAULT ""'.format(self.db.table_prefix)): + self.step_end(_step, -1, '失败') + return False + + _step = self.step_begin(' - 更新数据库版本号') + if not self.db.exec('UPDATE `{}config` SET `value`="6" WHERE `name`="db_ver";'.format(self.db.table_prefix)): + self.step_end(_step, -1, '无法更新数据库版本号') + return False + else: + self.step_end(_step, 0) + return True + + + except: + log.e('failed.\n') + self.step_end(_step, -1) + return False diff --git a/server/www/teleport/app/eom_app/app/db.py b/server/www/teleport/app/eom_app/app/db.py index 74bdf10..d071340 100644 --- a/server/www/teleport/app/eom_app/app/db.py +++ b/server/www/teleport/app/eom_app/app/db.py @@ -19,7 +19,7 @@ __all__ = ['get_db', 'DbItem'] class TPDatabase: # 注意,每次调整数据库结构,必须增加版本号,并且在升级接口中编写对应的升级操作 - DB_VERSION = 5 + DB_VERSION = 6 DB_TYPE_UNKNOWN = 0 DB_TYPE_SQLITE = 1 @@ -185,7 +185,30 @@ class TPDatabase: if ret is None: return None if len(ret) == 0: + return False + else: + return True + else: + log.e('Unknown database type.\n') + return None + + def is_field_exists(self, table_name, field_name): + if self.db_type == self.DB_TYPE_SQLITE: + ret = self.query('PRAGMA table_info(`{}`);'.format(table_name)) + print(ret) + if ret is None: return None + if len(ret) == 0: + return False + else: + return True + elif self.db_type == self.DB_TYPE_MYSQL: + ret = self.query('DESC `{}` `{}`;'.format(table_name, field_name)) + print(ret) + if ret is None: + return None + if len(ret) == 0: + return False else: return True else: @@ -312,8 +335,6 @@ class TPDatabase: return False def export_to_sql(self): - # TODO: not implement. - return export_database(self) diff --git a/server/www/teleport/app/eom_app/app/oath.py b/server/www/teleport/app/eom_app/app/oath.py new file mode 100644 index 0000000..7f798bd --- /dev/null +++ b/server/www/teleport/app/eom_app/app/oath.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import os +import io +import qrcode +import base64 +import binascii +import hmac +import time +import hashlib +import struct + +__all__ = ['gen_oath_secret', 'verify_oath_code', 'gen_oath_qrcode'] + + +def gen_oath_secret(): + return _convert_secret_to_base32(binascii.b2a_hex(os.urandom(16))).replace('=', '') + + +def _convert_secret_to_base32(secret): + return base64.b32encode(base64.b16decode(secret.upper())).decode() + + +def get_totp_token(secret, factor=None): + # 通过 secret 得到 原始密钥 key + + # 需要对padding符进行处理 + _len = len(secret) + _pad = 8 - (_len % 8) + if _pad > 0: + secret += '=' * _pad + + key = base64.b32decode(secret) + if factor is None: + factor = int(time.time()) // 30 # input 为次数, 30为默认密码刷新间隔值 + msg = struct.pack(">Q", factor) + + # 然后使用 HMAC-SHA1算法计算hash + hsh = hmac.new(key, msg, hashlib.sha1).digest() + + # 将hsh转换成数字(默认为6位) + i = hsh[-1] & 0xf # 以最后一个字节的后4个bits为数字,作为接下来的索引 + f = hsh[i:i + 4] # 以i为索引, 取hsh中的4个字节 + n = struct.unpack('>I', f)[0] & 0x7fffffff # 将4个字节按big-endian转换为无符号整数, 转换时去掉最高位的符号位 + # 等价于 n = ((f[0] & 0x7f) << 24) | ((f[1] & 0xff) << 16) | ((f[2] & 0xff) << 8) | (f[3] & 0xff) + + # 将 n % 1000000 得到6位数字, 不足补零 + r = '%06d' % (n % 1000000) # r 即为 生成的动态密码 + + return r + + +def verify_oath_code(secret, code): + # secret = '6OHEKKJPLMUBJ4EHCT5ZT5YLUQ' + cur_input = int(time.time()) // 30 + window = 3 + for i in range(cur_input - (window - 1) // 2, cur_input + window // 2 + 1): # [cur_input-(window-1)//2, cur_input + window//2] + if get_totp_token(secret, i) == code: + return True + return False + + +def gen_oath_qrcode(username, secret): + msg = 'otpauth://totp/{}?secret={}&issuer=teleport'.format(username, secret) + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=4, + border=4, + ) + qr.add_data(msg) + qr.make(fit=True) + img = qr.make_image() + + out = io.BytesIO() + img.save(out, "jpeg", quality=100) + return out.getvalue() diff --git a/server/www/teleport/app/eom_app/controller/__init__.py b/server/www/teleport/app/eom_app/controller/__init__.py index 10a32f9..40485b9 100644 --- a/server/www/teleport/app/eom_app/controller/__init__.py +++ b/server/www/teleport/app/eom_app/controller/__init__.py @@ -40,6 +40,10 @@ controllers = [ (r'/auth/get-captcha', auth.GetCaptchaHandler), (r'/auth/verify-captcha', auth.VerifyCaptchaHandler), (r'/auth/modify-pwd', auth.ModifyPwd), + (r'/auth/oath-verify', auth.OathVerifyHandler), + (r'/auth/oath-secret-qrcode', auth.OathSecretQrCodeHandler), + (r'/auth/oath-secret-reset', auth.OathSecretResetHandler), + (r'/auth/oath-update-secret', auth.OathUpdateSecretHandler), (r'/group/list', group.GetListHandler), (r'/group/', group.IndexHandler), @@ -49,9 +53,10 @@ controllers = [ (r'/cert/', cert.IndexHandler), (r'/cert', cert.IndexHandler), - (r'/pwd', pwd.IndexHandler), + # (r'/pwd', pwd.IndexHandler), (r'/user', user.IndexHandler), (r'/user/list', user.GetListHandler), + (r'/user/personal', user.PersonalHandler), #(r"/log/replay/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(cfg.data_path, 'replay')}), (r"/log/replay/(.*)", record.ReplayStaticFileHandler, {"path": os.path.join(cfg.data_path, 'replay')}), @@ -120,6 +125,8 @@ controllers = [ (r'/uidesign', index.UIDesignHandler), (r'/uidesign/without-sidebar', index.UIDesignWithoutSidebarHandler), - (r'/uidesign/table', index.UIDesignTableHandler) + (r'/uidesign/table', index.UIDesignTableHandler), + + # (r'/test/oath-code', index.OathCodeHandler) ] diff --git a/server/www/teleport/app/eom_app/controller/auth.py b/server/www/teleport/app/eom_app/controller/auth.py index 86e5df5..da10174 100644 --- a/server/www/teleport/app/eom_app/controller/auth.py +++ b/server/www/teleport/app/eom_app/controller/auth.py @@ -8,6 +8,7 @@ from eom_app.module import user from eom_common.eomcore.logger import * from .base import TPBaseHandler, TPBaseUserAuthHandler, TPBaseJsonHandler, TPBaseUserAuthJsonHandler from eom_app.app.util import gen_captcha +from eom_app.app.oath import gen_oath_secret, gen_oath_qrcode, verify_oath_code class LoginHandler(TPBaseHandler): @@ -33,27 +34,40 @@ class LoginHandler(TPBaseHandler): class VerifyUser(TPBaseJsonHandler): def post(self): - code = self.get_session('captcha') - if code is None: - return self.write_json(-1, '验证码已失效') - - self.del_session('captcha') + # code = self.get_session('captcha') + # if code is None: + # return self.write_json(-1, '验证码已失效') + # + # self.del_session('captcha') args = self.get_argument('args', None) if args is not None: args = json.loads(args) - captcha = args['captcha'] - username = args['username'] - userpwd = args['userpwd'] + login_type = args['type'].strip() + captcha = args['captcha'].strip() + username = args['username'].strip() + password = args['password'].strip() + oath = args['oath'].strip() remember = args['remember'] else: return self.write_json(-1, '参数错误') - if code.lower() != captcha.lower(): - return self.write_json(-1, '验证码错误') + if login_type == 'password': + oath = None + code = self.get_session('captcha') + if code is None: + return self.write_json(-1, '验证码已失效') + self.del_session('captcha') + if code.lower() != captcha.lower(): + return self.write_json(-1, '验证码错误') + elif login_type == 'oath': + if len(oath) == 0: + return self.write_json(-1, '身份验证器动态验证码错误') + + self.del_session('captcha') try: - user_id, account_type, nickname, locked = user.verify_user(username, userpwd) + user_id, account_type, nickname, locked = user.verify_user(username, password, oath) if locked == 1: return self.write_json(-1, '账号被锁定,请联系管理员!') if user_id == 0: @@ -81,7 +95,7 @@ class VerifyUser(TPBaseJsonHandler): _user['type'] = account_type if remember: - self.set_session('user', _user, 12*60*60) + self.set_session('user', _user, 12 * 60 * 60) else: self.set_session('user', _user) return self.write_json(0) @@ -150,3 +164,104 @@ class ModifyPwd(TPBaseUserAuthJsonHandler): except: log.e('modify password failed.') return self.write_json(-4, '发生异常') + + +class OathVerifyHandler(TPBaseUserAuthJsonHandler): + def post(self): + args = self.get_argument('args', None) + if args is not None: + try: + args = json.loads(args) + code = args['code'] + except: + return self.write_json(-2, '参数错误') + else: + return self.write_json(-1, '参数错误') + + # secret = self.get_session('tmp_oath_secret', None) + # if secret is None: + # return self.write_json(-1, '内部错误!') + # self.del_session('tmp_oath_secret') + + user_info = self.get_current_user() + if not user.verify_oath(user_info['id'], code): + return self.write_json(-3, '验证失败!') + else: + return self.write_json(0) + + +class OathSecretQrCodeHandler(TPBaseUserAuthJsonHandler): + def get(self): + secret = self.get_session('tmp_oath_secret', None) + print('tmp-oath-secret:', secret) + + user_info = self.get_current_user() + img_data = gen_oath_qrcode(user_info['name'], secret) + + # secret = '6OHEKKJPLMUBJ4EHCT5ZT5YLUQ' + # + # print('TOPT should be:', get_totp_token(secret)) + # # cur_input = int(time.time()) // 30 + # # print('cur-input', cur_input, int(time.time())) + # # window = 10 + # # for i in range(cur_input - (window - 1) // 2, cur_input + window // 2 + 1): # [cur_input-(window-1)//2, cur_input + window//2] + # # print(get_totp_token(secret, i)) + # + # msg = 'otpauth://totp/Admin?secret={}&issuer=teleport'.format(secret) + # qr = qrcode.QRCode( + # version=1, + # error_correction=qrcode.constants.ERROR_CORRECT_L, + # box_size=4, + # border=4, + # ) + # qr.add_data(msg) + # qr.make(fit=True) + # img = qr.make_image() + # + # # img = qrcode.make(msg) + # out = io.BytesIO() + # img.save(out, "jpeg", quality=100) + # # web.header('Content-Type','image/jpeg') + # # img.save('test.png') + self.set_header('Content-Type', 'image/jpeg') + self.write(img_data) + + +class OathSecretResetHandler(TPBaseUserAuthJsonHandler): + def post(self): + oath_secret = gen_oath_secret() + self.set_session('tmp_oath_secret', oath_secret) + return self.write_json(0, data={"tmp_oath_secret": oath_secret}) + + +class OathUpdateSecretHandler(TPBaseUserAuthJsonHandler): + def post(self): + args = self.get_argument('args', None) + if args is not None: + try: + args = json.loads(args) + code = args['code'] + except: + return self.write_json(-2, '参数错误') + else: + return self.write_json(-1, '参数错误') + + secret = self.get_session('tmp_oath_secret', None) + if secret is None: + return self.write_json(-1, '内部错误!') + self.del_session('tmp_oath_secret') + + if verify_oath_code(secret, code): + user_info = self.get_current_user() + try: + ret = user.update_oath_secret(user_info['id'], secret) + if 0 != ret: + return self.write_json(ret) + except: + log.e('update user oath-secret failed.') + return self.write_json(-2, '发生异常') + + # self.set_session('oath_secret', secret) + return self.write_json(0) + else: + return self.write_json(-3, '验证失败!') diff --git a/server/www/teleport/app/eom_app/controller/index.py b/server/www/teleport/app/eom_app/controller/index.py index 65950cb..1710131 100644 --- a/server/www/teleport/app/eom_app/controller/index.py +++ b/server/www/teleport/app/eom_app/controller/index.py @@ -27,4 +27,11 @@ class UIDesignWithoutSidebarHandler(TPBaseHandler): class UIDesignTableHandler(TPBaseHandler): def get(self): + # from hashlib import sha1 + # import hmac + # my_sign = hmac.new('key', 'msg', sha1).digest() + # # my_sign = base64.b64encode(my_sign) + # # print my_sign + self.render('uidesign/table.mako') + diff --git a/server/www/teleport/app/eom_app/controller/user.py b/server/www/teleport/app/eom_app/controller/user.py index a50bec1..d0c270b 100644 --- a/server/www/teleport/app/eom_app/controller/user.py +++ b/server/www/teleport/app/eom_app/controller/user.py @@ -16,6 +16,12 @@ class IndexHandler(TPBaseAdminAuthHandler): self.render('user/index.mako') +class PersonalHandler(TPBaseAdminAuthHandler): + def get(self): + user_info = self.get_current_user() + self.render('user/personal.mako', user=user_info) + + class AuthHandler(TPBaseAdminAuthHandler): def get(self, user_name): group_list = host.get_group_list() diff --git a/server/www/teleport/app/eom_app/module/user.py b/server/www/teleport/app/eom_app/module/user.py index 337a23f..640f808 100644 --- a/server/www/teleport/app/eom_app/module/user.py +++ b/server/www/teleport/app/eom_app/module/user.py @@ -6,20 +6,26 @@ from eom_app.app.configs import app_cfg from eom_app.app.const import * from eom_app.app.db import get_db, DbItem from eom_app.app.util import sec_generate_password, sec_verify_password +from eom_app.app.oath import verify_oath_code -def verify_user(name, password): +def verify_user(name, password, oath_code): cfg = app_cfg() + + if cfg.app_mode == APP_MODE_MAINTENANCE: + if name == 'admin' and password == 'admin': + return 1, 100, '系统管理员', 0 + db = get_db() - sql = 'SELECT `account_id`, `account_type`, `account_name`, `account_pwd`, `account_lock` FROM `{}account` WHERE `account_name`="{}";'.format(db.table_prefix, name) + sql = 'SELECT `account_id`, `account_type`, `account_desc`, `account_pwd`, `account_lock`, `oath_secret` FROM `{}account` WHERE `account_name`="{}";'.format(db.table_prefix, name) db_ret = db.query(sql) if db_ret is None: # 特别地,如果无法取得数据库连接,有可能是新安装的系统,尚未建立数据库,此时应该处于维护模式 # 因此可以特别地处理用户验证:用户名admin,密码admin可以登录为管理员 if cfg.app_mode == APP_MODE_MAINTENANCE: if name == 'admin' and password == 'admin': - return 1, 100, 'admin', 0 + return 1, 100, '系统管理员', 0 return 0, 0, '', 0 if len(db_ret) != 1: @@ -27,8 +33,9 @@ def verify_user(name, password): user_id = db_ret[0][0] account_type = db_ret[0][1] - name = db_ret[0][2] + desc = db_ret[0][2] locked = db_ret[0][4] + oath_secret = db_ret[0][5] if locked == 1: return 0, 0, '', locked @@ -42,7 +49,27 @@ def verify_user(name, password): sql = 'UPDATE `{}account` SET `account_pwd`="{}" WHERE `account_id`={}'.format(db.table_prefix, _new_sec_password, int(user_id)) db.exec(sql) - return user_id, account_type, name, locked + if oath_code is not None: + if not verify_oath_code(oath_secret, oath_code): + return 0, 0, '', 0 + + return user_id, account_type, desc, locked + + +def verify_oath(user_id, oath_code): + db = get_db() + + sql = 'SELECT `oath_secret` FROM `{}account` WHERE `account_id`={};'.format(db.table_prefix, user_id) + db_ret = db.query(sql) + if db_ret is None: + return False + + if len(db_ret) != 1: + return False + + oath_secret = db_ret[0][0] + + return verify_oath_code(oath_secret, oath_code) def modify_pwd(old_pwd, new_pwd, user_id): @@ -66,6 +93,16 @@ def modify_pwd(old_pwd, new_pwd, user_id): return -102 +def update_oath_secret(user_id, oath_secret): + db = get_db() + sql = 'UPDATE `{}account` SET `oath_secret`="{}" WHERE `account_id`={}'.format(db.table_prefix, oath_secret, int(user_id)) + db_ret = db.exec(sql) + if db_ret: + return 0 + else: + return -102 + + def get_user_list(with_admin=False): db = get_db() ret = list() diff --git a/server/www/teleport/static/css/auth.css b/server/www/teleport/static/css/auth.css index 882cdaa..7b7d0c6 100644 --- a/server/www/teleport/static/css/auth.css +++ b/server/www/teleport/static/css/auth.css @@ -1 +1 @@ -@charset "utf-8";body{padding-top:70px;padding-bottom:24px;background-color:#ececed}#head nav.navbar{height:70px;line-height:70px;background-color:#333;color:#fff}#head .logo .desc{display:block;float:right;color:#ccc;margin-top:10px;font-size:18px}#foot nav.navbar{min-height:24px;height:24px;line-height:24px;background-color:#ddd;color:#fff;font-size:12px;border-top:1px solid #ccc}#foot nav.navbar .container{height:24px}#foot nav.navbar p{margin:0 auto;text-align:center;color:#333}#content{margin:10px 0 50px 0}.auth-box{margin-top:30px;min-height:120px;border:1px solid #ccc;border-radius:8px;background-color:rgba(255,255,255,0.6)}.auth-box .header{min-height:50px;height:50px;border:none;box-shadow:none;border-bottom:1px solid #ccc}.auth-box .header .title{display:inline-block;float:left;margin-left:60px;height:24px;margin-top:25px;line-height:16px;font-size:20px;color:#999}.auth-box .header .selected{border-bottom:1px solid #69c;color:#555}.auth-box .header .title:hover{border-bottom:1px solid #999}.auth-box .inputarea{margin:30px}.auth-box .inputarea .input-group-addon{padding:0 5px 0 5px}.auth-box .inputarea p.input-addon-desc{text-align:right;padding:0 5px 0 5px;color:#999}#leftside{width:560px;height:560px;padding-top:60px;background:url(../img/login/side-001.jpg) 0 0 no-repeat}@media screen and (max-width:990px){#leftside{display:none}}#leftside h1{font-size:24px;color:#888}#leftside p{font-size:18px;color:#888;padding-left:24px}.auth-box .inputbox{margin-bottom:10px}.auth-box-lg .inputbox{margin-bottom:20px}.auth-box .op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin:5px 20px 10px 20px}.auth-box .op_error{background:#fbb}.auth-box .op_wait{background:#ccc}.auth-box .quick-area{padding:80px 0 80px 0}.auth-box .quick-area .quick-disc{text-align:center;margin-bottom:20px}.auth-box .quick-area .quick-no{padding-top:80px;padding-bottom:100px}.auth-box .quick-area .quick-yes{text-align:center}.auth-box .quick-area .quick-yes .quick-account{display:inline-block;margin:auto;margin-bottom:20px}.auth-box .quick-area .quick-yes .quick-account:hover .quick-image{box-shadow:0 0 8px #00c2f6}.auth-box .quick-area .quick-yes .quick-image{display:block;width:82px;height:82px;line-height:80px;font-size:64px;margin:auto;border:1px solid #a4cdf6;box-shadow:0 0 6px #a7d1fb}.auth-box .quick-area .quick-yes .quick-name{display:block;margin-top:5px} \ No newline at end of file +@charset "utf-8";body{padding-top:70px;padding-bottom:24px;background-color:#ececed}#head nav.navbar{height:70px;line-height:70px;background-color:#333;color:#fff}#head .logo .desc{display:block;float:right;color:#ccc;margin-top:10px;font-size:18px}#foot nav.navbar{min-height:24px;height:24px;line-height:24px;background-color:#ddd;color:#fff;font-size:12px;border-top:1px solid #ccc}#foot nav.navbar .container{height:24px}#foot nav.navbar p{margin:0 auto;text-align:center;color:#333}#content{margin:10px 0 50px 0}.auth-box{margin-top:30px;min-height:120px;border:1px solid #ccc;border-radius:8px;background-color:rgba(255,255,255,0.6)}.auth-box .header{min-height:50px;height:50px;border:none;box-shadow:none;border-bottom:1px solid #ccc}.auth-box .header .title{display:inline-block;float:left;margin-left:60px;height:24px;margin-top:25px;line-height:16px;font-size:20px;color:#999}.auth-box .header .selected{border-bottom:2px solid #4882cc;color:#555}.auth-box .header .title:hover{border-bottom:2px solid #5396eb}.auth-box .inputarea{margin:30px}.auth-box .inputarea .input-group-addon{padding:0 5px 0 5px}.auth-box .inputarea p.input-addon-desc{text-align:right;padding:0 5px 0 5px;color:#999}#leftside{width:560px;height:560px;padding-top:60px;background:url(../img/login/side-001.jpg) 0 0 no-repeat}@media screen and (max-width:990px){#leftside{display:none}}#leftside h1{font-size:24px;color:#888}#leftside p{font-size:18px;color:#888;padding-left:24px}.auth-box .inputbox{margin-bottom:10px}.auth-box-lg .inputbox{margin-bottom:20px}.auth-box .op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin:5px 20px 10px 20px}.auth-box .op_error{background:#fbb}.auth-box .op_wait{background:#ccc}.auth-box .quick-area{padding:80px 0 80px 0}.auth-box .quick-area .quick-disc{text-align:center;margin-bottom:20px}.auth-box .quick-area .quick-no{padding-top:80px;padding-bottom:100px}.auth-box .quick-area .quick-yes{text-align:center}.auth-box .quick-area .quick-yes .quick-account{display:inline-block;margin:auto;margin-bottom:20px}.auth-box .quick-area .quick-yes .quick-account:hover .quick-image{box-shadow:0 0 8px #00c2f6}.auth-box .quick-area .quick-yes .quick-image{display:block;width:82px;height:82px;line-height:80px;font-size:64px;margin:auto;border:1px solid #a4cdf6;box-shadow:0 0 6px #a7d1fb}.auth-box .quick-area .quick-yes .quick-name{display:block;margin-top:5px} \ No newline at end of file diff --git a/server/www/teleport/static/css/doc.css b/server/www/teleport/static/css/doc.css index 69d39d3..eca947a 100644 --- a/server/www/teleport/static/css/doc.css +++ b/server/www/teleport/static/css/doc.css @@ -1 +1 @@ -@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.page-header-fixed{padding-top:48px}.header{border:none;min-height:48px;height:48px;top:0;width:100%;position:fixed;z-index:999}.header .top-navbar{min-height:48px;height:48px;line-height:48px;background-color:#3a3a3a;color:#ccc}.header .top-navbar a{color:#d5d5d5}.header .top-navbar a:hover{color:#5a8fee}.header .top-navbar .brand{float:left;display:inline-block;padding:12px 0;margin:0}.header .top-navbar .brand .site-logo{display:block;width:86px;height:24px;background:url(../img/site-logo-small.png) no-repeat}.header .top-navbar .title-container{float:left;display:inline-block;margin:0;padding:0}.header .top-navbar .title-container .title{font-size:18px}.header .top-navbar .status-container{float:right}.footer{width:100%;height:36px;line-height:36px;background-color:#d5d5d5;border-top:1px solid #a2a2a2;border-bottom:1px solid #efefef;z-index:998;text-align:center}.footer.footer-fixed-bottom{bottom:0;position:fixed}.row-sm .col-sm-1,.row-sm .col-sm-2,.row-sm .col-sm-3,.row-sm .col-sm-4,.row-sm .col-sm-5,.row-sm .col-sm-6,.row-sm .col-sm-7,.row-sm .col-sm-8,.row-sm .col-sm-9,.row-sm .col-sm-10,.row-sm .col-sm-11{padding-right:5px;padding-left:5px}.sidebar{background-color:#fff;width:285px;position:fixed}.search-box{padding:10px;border-bottom:1px solid #eee;margin-bottom:10px}.tree-view{overflow-x:auto;overflow-y:auto;padding:0 10px}.content{margin-top:15px;margin-bottom:56px;min-height:360px;background-color:#fff;border-radius:5px;padding:10px;margin-left:300px}.jstree-ocl{cursor:default}.jstree-default .fa-folder:before{color:#f59c1a}.jstree-default .jstree-wholerow{cursor:default}.jstree-default>ul>li{padding:4px 0}.jstree-default .jstree-node{margin-left:12px}.jstree-default .jstree-open>.jstree-anchor>.fa-folder:before{content:'\f07c';color:#a26307}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl,.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl{background:none}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl:before,.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl:before{font-style:normal;font-family:"FontAwesome"}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl:before{content:'\f054';color:#ccc}.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl:before{content:'\f078';color:#666}.jstree-default>.jstree-no-dots .jstree-loading>.jstree-ocl{background:url("img/loading_01.gif") center center no-repeat}.jstree-default>.jstree-no-dots .jstree-loading>.jstree-ocl:before{content:''}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file +@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.input-group :-moz-placeholder{color:#d2d2d2}.input-group ::-moz-placeholder{color:#d2d2d2}.input-group input:-ms-input-placeholder,.input-group textarea:-ms-input-placeholder{color:#d2d2d2}.input-group input::-webkit-input-placeholder,.input-group textarea::-webkit-input-placeholder{color:#d2d2d2}.page-header-fixed{padding-top:48px}.header{border:none;min-height:48px;height:48px;top:0;width:100%;position:fixed;z-index:999}.header .top-navbar{min-height:48px;height:48px;line-height:48px;background-color:#3a3a3a;color:#ccc}.header .top-navbar a{color:#d5d5d5}.header .top-navbar a:hover{color:#5a8fee}.header .top-navbar .brand{float:left;display:inline-block;padding:12px 0;margin:0}.header .top-navbar .brand .site-logo{display:block;width:86px;height:24px;background:url(../img/site-logo-small.png) no-repeat}.header .top-navbar .title-container{float:left;display:inline-block;margin:0;padding:0}.header .top-navbar .title-container .title{font-size:18px}.header .top-navbar .status-container{float:right}.footer{width:100%;height:36px;line-height:36px;background-color:#d5d5d5;border-top:1px solid #a2a2a2;border-bottom:1px solid #efefef;z-index:998;text-align:center}.footer.footer-fixed-bottom{bottom:0;position:fixed}.row-sm .col-sm-1,.row-sm .col-sm-2,.row-sm .col-sm-3,.row-sm .col-sm-4,.row-sm .col-sm-5,.row-sm .col-sm-6,.row-sm .col-sm-7,.row-sm .col-sm-8,.row-sm .col-sm-9,.row-sm .col-sm-10,.row-sm .col-sm-11{padding-right:5px;padding-left:5px}.sidebar{background-color:#fff;width:285px;position:fixed}.search-box{padding:10px;border-bottom:1px solid #eee;margin-bottom:10px}.tree-view{overflow-x:auto;overflow-y:auto;padding:0 10px}.content{margin-top:15px;margin-bottom:56px;min-height:360px;background-color:#fff;border-radius:5px;padding:10px;margin-left:300px}.jstree-ocl{cursor:default}.jstree-default .fa-folder:before{color:#f59c1a}.jstree-default .jstree-wholerow{cursor:default}.jstree-default>ul>li{padding:4px 0}.jstree-default .jstree-node{margin-left:12px}.jstree-default .jstree-open>.jstree-anchor>.fa-folder:before{content:'\f07c';color:#a26307}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl,.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl{background:none}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl:before,.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl:before{font-style:normal;font-family:"FontAwesome"}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl:before{content:'\f054';color:#ccc}.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl:before{content:'\f078';color:#666}.jstree-default>.jstree-no-dots .jstree-loading>.jstree-ocl{background:url("img/loading_01.gif") center center no-repeat}.jstree-default>.jstree-no-dots .jstree-loading>.jstree-ocl:before{content:''}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file diff --git a/server/www/teleport/static/css/main.css b/server/www/teleport/static/css/main.css index df5d315..270a3eb 100644 --- a/server/www/teleport/static/css/main.css +++ b/server/www/teleport/static/css/main.css @@ -1 +1 @@ -@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.admin{background-color:#930;color:#fff;padding:5px 15px;border-radius:5px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5)}.header .container-fluid{padding-left:0}.header .breadcrumb-container{display:inline-block;padding-top:6px}.header .breadcrumb{background-color:transparent;padding-left:20px;font-size:16px}.header.navbar{min-height:48px;height:48px;margin:0}.header.navbar .brand{display:inline-block;float:left;width:180px;height:48px;padding:12px 0 0;text-align:center;margin:0 auto;background-color:#3a3a3a}.header.navbar .brand .navbar-logo{display:inline-block;width:93px;height:30px;background:url(../img/site-logo-small.png) no-repeat}.header.navbar .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#ccc;content:"\f105";font-family:'FontAwesome'}.page-sidebar-fixed .sidebar{position:fixed}.sidebar{top:0;bottom:0;left:0;width:180px;padding-top:48px;z-index:1010;background-color:#3a3a3a}.sidebar .nav-menu>li>a{padding:8px 0 8px 20px;line-height:24px;font-size:13px;color:#c2c2c2;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:focus{background-color:#3a3a3a;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:hover{background-color:#2d2d2d;border-left:5px solid #005c74}.sidebar .nav-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .nav-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .nav-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:1px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-menu li .menu-caret:after{display:inline-block;width:12px;height:12px;margin-left:5px;top:1px;position:relative;border:none;font-family:'FontAwesome';font-style:normal}.sidebar .nav-menu li .menu-caret:after{content:'\f0da'}.sidebar .nav-menu li.expand .menu-caret:after{content:'\f0d7'}.sidebar .nav-menu>li.super-admin>a:hover{background-color:#620;border-left:5px solid #4d1a00}.sidebar .nav-menu>li.super-admin>a.active{background-color:#930;border-left:5px solid #930}.sidebar .nav-menu>li.super-admin>a.active:hover{border-left:5px solid #c40}.sidebar .nav-menu>li>a>i.icon{float:left;margin-top:1px;margin-right:15px;text-align:center;line-height:24px;font-size:14px}.sidebar .sub-menu{padding:0;margin:0;background-color:#292929;position:relative;list-style-type:none;border-top:1px solid #202020;border-bottom:1px solid #464646}.sidebar .sub-menu>li>a{padding:8px 0 8px 40px;line-height:20px;font-size:13px;display:block;position:relative;color:#889097;border-left:5px solid #292929}.sidebar .sub-menu>li>a:before{display:inline-block;padding-right:8px;line-height:20px;content:"\f105";font-family:'FontAwesome'}.sidebar .sub-menu>li>a:hover{color:#fff;border-left:5px solid #005c74}.sidebar .sub-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .sub-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .sub-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:-2px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-profile{padding:15px 10px;color:#ccc;background-color:#333;border-bottom:1px solid #464646}.sidebar .nav-profile a.title{color:#ccc}.sidebar .nav-profile a.title:hover{color:#fff;background-color:transparent}.sidebar .nav-profile a.title:focus{background-color:transparent}.sidebar .nav-profile .image{float:left;margin-top:3px;font-size:24px;color:#69f;width:36px;height:36px;border-radius:5px;background-color:#eee;text-align:center;margin-right:10px;overflow:hidden}.sidebar .nav-profile .image img{margin-top:-3px}.sidebar .nav-profile .name{display:block;font-size:16px}.sidebar .nav-profile .role{display:block;font-size:12px;color:#999}.sidebar .nav-profile .dropdown-menu{font-size:13px}.sidebar .nav-profile .dropdown-menu>li>a{padding:5px 20px}.sidebar .nav-profile .dropdown-menu>li>a:hover{background-color:#ccc}.sidebar .nav-profile .dropdown-menu .divider{margin:5px 0}.sidebar .badge{margin-top:-10px;margin-left:5px}.content{margin-left:180px}.page-content{padding:15px}.page-content-dashboard{padding:20px 25px}.widget{overflow:hidden;border-radius:3px;padding:15px;margin-bottom:20px;color:#fff}.widget.widget-stats{position:relative}.widget .stats-icon{font-size:52px;top:12px;right:21px;width:56px;height:56px;text-align:center;line-height:56px;margin-left:15px;color:#fff;position:absolute;opacity:.2}.widget .stats-title{color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-split{height:2px;margin:0 -15px 10px;background:rgba(0,0,0,0.2)}.widget .stats-content{font-size:24px;font-weight:300;margin-bottom:10px}.widget .stats-desc{display:inline-block;color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-action{display:inline-block;float:right}.widget a{color:#eee;color:rgba(255,255,255,0.7)}.widget a:hover{color:#fff}.widget.widget-info{background-color:#33b7d0}.widget.widget-primary{background-color:#348fe2}.widget.widget-success{background-color:#368142}.widget.widget-warning{background-color:#f57523}.widget.widget-danger{background-color:#d34242}.panel{border:none;box-shadow:none;border-radius:3px}.panel .panel-heading{padding:6px 15px;color:#fff}.panel .panel-heading .panel-title{font-size:14px}.panel .panel-heading .panel-heading-btn{float:right}.panel .panel-heading .panel-heading-btn .btn{display:inline-block;padding:0;border:none;text-align:center}.panel .panel-heading .panel-heading-btn .btn.btn-xs{width:18px;height:18px;line-height:18px;font-size:12px}.panel .panel-heading .panel-heading-btn .btn.btn-circle{border-radius:50%}.place-holder-h200{width:100%;height:300px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel2-holder{width:100%;height:1150px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel-time{color:#cecece}.box{border:none;box-shadow:none;border-radius:3px;background-color:#fff;padding:15px;margin-bottom:15px}.box-fluid{border:none;box-shadow:none;border-radius:3px;margin-bottom:15px}.box.box-sm,.box-fluid.box-sm{padding:5px 15px}.box .box-title,.box-fluid .box-title{margin-bottom:10px}.box .box-title .title,.box-fluid .box-title .title{display:inline-block;font-size:18px;color:#333;height:30px;line-height:30px}.box .box-title .btn-sm,.box-fluid .box-title .btn-sm{padding:3px 8px;margin-top:-5px}.box .nav-tabs,.box-fluid .nav-tabs{font-size:14px;font-weight:bold}.box .nav-tabs>li:first-child,.box-fluid .nav-tabs>li:first-child{margin-left:50px}.box .tab-content>.tab-pane,.box-fluid .tab-content>.tab-pane{background-color:#fff;padding:20px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:3px;border-bottom-right-radius:3px}.box-license{line-height:30px}.box-license .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.box-btn-bar{line-height:30px}.box-btn-bar a.btn{margin-right:20px}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.page-filter{height:36px;line-height:36px;margin-bottom:10px}.page-filter .form-control{margin-top:5px;margin-right:4px}.btn.btn-sm .dropdown-menu li a{font-size:11px}.invite{text-align:center;padding-bottom:20px}.invite .code{color:#2f3991;font-size:36px;font-weight:700;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.invite .link{padding:5px;color:#2f3991;font-size:13px;font-weight:700;background-color:#eee;border-radius:5px}.invite-send-box{width:300px;margin:0 auto}.form-group .input-group{margin-bottom:5px}.op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin-top:5px}.op_error{background:#fbb}.op_wait{background:#ccc}.table-data td.loading{text-align:left;padding:20px}.table-data .btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.more-action{position:absolute !important}.more-action .dropdown-menu{background-color:rgba(60,60,60,0.9);color:#fff;font-size:13px}.more-action .dropdown-menu.dropdown-menu-left{margin-left:-120px}.more-action .dropdown-menu>li>a{padding:5px 20px;color:#fff}.more-action .dropdown-menu>li>a:hover,.more-action .dropdown-menu>li>a:active,.more-action .dropdown-menu>li>a:visited{background-color:#0084a7}.more-action .dropdown-menu .divider{margin:5px 0;background-color:#666}.popover-inline-edit input,.popover-inline-edit .btn{height:30px}.popover-inline-edit .popover-title{background-color:#ddd}.popover-inline-edit .popover-content{padding:20px 10px}.popover-inline-edit .popover{padding:0;max-width:500px}.popover-inline-edit .popover .popover-content{padding:10px 10px 20px 10px}.popover-inline-edit .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ddd}.user-info-table{font-size:14px}.user-info-table tbody>tr>td{padding:8px}.user-info-table .user-field{min-width:100px;width:100px;color:#999;text-align:right}.user-info-table .user-value{color:#333;font-weight:bold}.user-info-table .user-value a{font-weight:normal}.breadcrumb.breadcrumb-trans{background-color:transparent}.biz-box{display:inline-block;width:20%;max-width:20%}.biz-box .bb-inner{background-color:#368142;margin:3px;border-radius:4px}.biz-box .bb-name{color:#fff;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;padding-top:1px;padding-bottom:1px}.biz-box .bb-ver{font-size:11px;height:16px;text-align:center;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-bottom-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.3)}.biz-box.mp-disabled .mp-inner{background-color:#e5e5e5}.biz-box.mp-disabled .mp-name{color:#999}.biz-box.mp-success .mp-inner{background-color:#368142}.biz-box.mp-success .mp-name{color:#fff}.biz-box.mp-danger .mp-inner{background-color:#d34242}.biz-box.mp-danger .mp-name{color:#fff}.biz-box.mp-warning .mp-inner{background-color:#f57523}.biz-box.mp-warning .mp-name{color:#fff}textarea.textarea-resize-y{resize:vertical}textarea.textarea-resize-none{resize:none}textarea.textarea-code{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}textarea.cert_pub{width:100%;height:64px;border:1px solid #e2e2e2;background-color:#e4ffe5}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file +@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.input-group :-moz-placeholder{color:#d2d2d2}.input-group ::-moz-placeholder{color:#d2d2d2}.input-group input:-ms-input-placeholder,.input-group textarea:-ms-input-placeholder{color:#d2d2d2}.input-group input::-webkit-input-placeholder,.input-group textarea::-webkit-input-placeholder{color:#d2d2d2}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.admin{background-color:#930;color:#fff;padding:5px 15px;border-radius:5px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5)}.header .container-fluid{padding-left:0}.header .breadcrumb-container{display:inline-block;padding-top:6px}.header .breadcrumb{background-color:transparent;padding-left:20px;font-size:16px}.header.navbar{min-height:48px;height:48px;margin:0}.header.navbar .brand{display:inline-block;float:left;width:180px;height:48px;padding:12px 0 0;text-align:center;margin:0 auto;background-color:#3a3a3a}.header.navbar .brand .navbar-logo{display:inline-block;width:93px;height:30px;background:url(../img/site-logo-small.png) no-repeat}.header.navbar .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#ccc;content:"\f105";font-family:'FontAwesome'}.page-sidebar-fixed .sidebar{position:fixed}.sidebar{top:0;bottom:0;left:0;width:180px;padding-top:48px;z-index:1010;background-color:#3a3a3a}.sidebar .nav-menu>li>a{padding:8px 0 8px 20px;line-height:24px;font-size:13px;color:#c2c2c2;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:focus{background-color:#3a3a3a;border-left:5px solid #3a3a3a}.sidebar .nav-menu>li>a:hover{background-color:#2d2d2d;border-left:5px solid #005c74}.sidebar .nav-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .nav-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .nav-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:1px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-menu li .menu-caret:after{display:inline-block;width:12px;height:12px;margin-left:5px;top:1px;position:relative;border:none;font-family:'FontAwesome';font-style:normal}.sidebar .nav-menu li .menu-caret:after{content:'\f0da'}.sidebar .nav-menu li.expand .menu-caret:after{content:'\f0d7'}.sidebar .nav-menu>li.super-admin>a:hover{background-color:#620;border-left:5px solid #4d1a00}.sidebar .nav-menu>li.super-admin>a.active{background-color:#930;border-left:5px solid #930}.sidebar .nav-menu>li.super-admin>a.active:hover{border-left:5px solid #c40}.sidebar .nav-menu>li>a>i.icon{float:left;margin-top:1px;margin-right:15px;text-align:center;line-height:24px;font-size:14px}.sidebar .sub-menu{padding:0;margin:0;background-color:#292929;position:relative;list-style-type:none;border-top:1px solid #202020;border-bottom:1px solid #464646}.sidebar .sub-menu>li>a{padding:8px 0 8px 40px;line-height:20px;font-size:13px;display:block;position:relative;color:#889097;border-left:5px solid #292929}.sidebar .sub-menu>li>a:before{display:inline-block;padding-right:8px;line-height:20px;content:"\f105";font-family:'FontAwesome'}.sidebar .sub-menu>li>a:hover{color:#fff;border-left:5px solid #005c74}.sidebar .sub-menu>li>a.active{color:#fff;background-color:#0084a7;border-left:5px solid #0084a7}.sidebar .sub-menu>li>a.active:hover{border-left:5px solid #00acda}.sidebar .sub-menu>li>a.active:after{content:"\e251";font-family:'Glyphicons Halflings';position:relative;top:-2px;display:inline-block;font-style:normal;font-weight:400;float:right;color:#e9e9e9;font-size:20px;line-height:24px;margin-right:-6px}.sidebar .nav-profile{padding:15px 10px;color:#ccc;background-color:#333;border-bottom:1px solid #464646}.sidebar .nav-profile a.title{color:#ccc}.sidebar .nav-profile a.title:hover{color:#fff;background-color:transparent}.sidebar .nav-profile a.title:focus{background-color:transparent}.sidebar .nav-profile .image{float:left;margin-top:3px;font-size:24px;color:#69f;width:36px;height:36px;border-radius:5px;background-color:#eee;text-align:center;margin-right:10px;overflow:hidden}.sidebar .nav-profile .image img{margin-top:-3px}.sidebar .nav-profile .name{display:block;font-size:16px}.sidebar .nav-profile .role{display:block;font-size:12px;color:#999}.sidebar .nav-profile .dropdown-menu{font-size:13px}.sidebar .nav-profile .dropdown-menu>li>a{padding:5px 20px}.sidebar .nav-profile .dropdown-menu>li>a:hover{background-color:#ccc}.sidebar .nav-profile .dropdown-menu .divider{margin:5px 0}.sidebar .badge{margin-top:-10px;margin-left:5px}.content{margin-left:180px}.page-content{padding:15px}.page-content-dashboard{padding:20px 25px}.widget{overflow:hidden;border-radius:3px;padding:15px;margin-bottom:20px;color:#fff}.widget.widget-stats{position:relative}.widget .stats-icon{font-size:52px;top:12px;right:21px;width:56px;height:56px;text-align:center;line-height:56px;margin-left:15px;color:#fff;position:absolute;opacity:.2}.widget .stats-title{color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-split{height:2px;margin:0 -15px 10px;background:rgba(0,0,0,0.2)}.widget .stats-content{font-size:24px;font-weight:300;margin-bottom:10px}.widget .stats-desc{display:inline-block;color:#fff;color:rgba(255,255,255,0.6)}.widget .stats-action{display:inline-block;float:right}.widget a{color:#eee;color:rgba(255,255,255,0.7)}.widget a:hover{color:#fff}.widget.widget-info{background-color:#33b7d0}.widget.widget-primary{background-color:#348fe2}.widget.widget-success{background-color:#368142}.widget.widget-warning{background-color:#f57523}.widget.widget-danger{background-color:#d34242}.panel{border:none;box-shadow:none;border-radius:3px}.panel .panel-heading{padding:6px 15px;color:#fff}.panel .panel-heading .panel-title{font-size:14px}.panel .panel-heading .panel-heading-btn{float:right}.panel .panel-heading .panel-heading-btn .btn{display:inline-block;padding:0;border:none;text-align:center}.panel .panel-heading .panel-heading-btn .btn.btn-xs{width:18px;height:18px;line-height:18px;font-size:12px}.panel .panel-heading .panel-heading-btn .btn.btn-circle{border-radius:50%}.place-holder-h200{width:100%;height:300px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel2-holder{width:100%;height:1150px;background-color:#eee;border:1px solid #ccc;line-height:200px;text-align:center}.dashboard-panel-time{color:#cecece}.box{border:none;box-shadow:none;border-radius:3px;background-color:#fff;padding:15px;margin-bottom:15px}.box-fluid{border:none;box-shadow:none;border-radius:3px;margin-bottom:15px}.box.box-sm,.box-fluid.box-sm{padding:5px 15px}.box .box-title,.box-fluid .box-title{margin-bottom:10px}.box .box-title .title,.box-fluid .box-title .title{display:inline-block;font-size:18px;color:#333;height:30px;line-height:30px}.box .box-title .btn-sm,.box-fluid .box-title .btn-sm{padding:3px 8px;margin-top:-5px}.box .nav-tabs,.box-fluid .nav-tabs{font-size:14px;font-weight:bold}.box .nav-tabs>li:first-child,.box-fluid .nav-tabs>li:first-child{margin-left:50px}.box .tab-content>.tab-pane,.box-fluid .tab-content>.tab-pane{background-color:#fff;padding:20px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:3px;border-bottom-right-radius:3px}.box-license{line-height:30px}.box-license .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.box-btn-bar{line-height:30px}.box-btn-bar a.btn{margin-right:20px}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.page-filter{height:36px;line-height:36px;margin-bottom:10px}.page-filter .form-control{margin-top:5px;margin-right:4px}.btn.btn-sm .dropdown-menu li a{font-size:11px}.invite{text-align:center;padding-bottom:20px}.invite .code{color:#2f3991;font-size:36px;font-weight:700;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.invite .link{padding:5px;color:#2f3991;font-size:13px;font-weight:700;background-color:#eee;border-radius:5px}.invite-send-box{width:300px;margin:0 auto}.form-group .input-group{margin-bottom:5px}.op_box{display:block;padding:5px;border-radius:3px;text-align:center;margin-top:5px}.op_error{background:#fbb}.op_wait{background:#ccc}.table-data td.loading{text-align:left;padding:20px}.table-data .btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.more-action{position:absolute !important}.more-action .dropdown-menu{background-color:rgba(60,60,60,0.9);color:#fff;font-size:13px}.more-action .dropdown-menu.dropdown-menu-left{margin-left:-120px}.more-action .dropdown-menu>li>a{padding:5px 20px;color:#fff}.more-action .dropdown-menu>li>a:hover,.more-action .dropdown-menu>li>a:active,.more-action .dropdown-menu>li>a:visited{background-color:#0084a7}.more-action .dropdown-menu .divider{margin:5px 0;background-color:#666}.popover-inline-edit input,.popover-inline-edit .btn{height:30px}.popover-inline-edit .popover-title{background-color:#ddd}.popover-inline-edit .popover-content{padding:20px 10px}.popover-inline-edit .popover{padding:0;max-width:500px}.popover-inline-edit .popover .popover-content{padding:10px 10px 20px 10px}.popover-inline-edit .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ddd}.user-info-table{font-size:14px}.user-info-table tbody>tr>td{padding:8px}.user-info-table .user-field{min-width:100px;width:100px;color:#999;text-align:right}.user-info-table .user-value{color:#333;font-weight:bold}.user-info-table .user-value a{font-weight:normal}.breadcrumb.breadcrumb-trans{background-color:transparent}.biz-box{display:inline-block;width:20%;max-width:20%}.biz-box .bb-inner{background-color:#368142;margin:3px;border-radius:4px}.biz-box .bb-name{color:#fff;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center;padding-top:1px;padding-bottom:1px}.biz-box .bb-ver{font-size:11px;height:16px;text-align:center;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-bottom-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.3)}.biz-box.mp-disabled .mp-inner{background-color:#e5e5e5}.biz-box.mp-disabled .mp-name{color:#999}.biz-box.mp-success .mp-inner{background-color:#368142}.biz-box.mp-success .mp-name{color:#fff}.biz-box.mp-danger .mp-inner{background-color:#d34242}.biz-box.mp-danger .mp-name{color:#fff}.biz-box.mp-warning .mp-inner{background-color:#f57523}.biz-box.mp-warning .mp-name{color:#fff}textarea.textarea-resize-y{resize:vertical}textarea.textarea-resize-none{resize:none}textarea.textarea-code{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}textarea.cert_pub{width:100%;height:64px;border:1px solid #e2e2e2;background-color:#e4ffe5}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important} \ No newline at end of file diff --git a/server/www/teleport/static/css/sub.css b/server/www/teleport/static/css/sub.css index 08a9bdf..73393db 100644 --- a/server/www/teleport/static/css/sub.css +++ b/server/www/teleport/static/css/sub.css @@ -1 +1 @@ -@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5);min-height:48px;height:48px;top:0;width:100%;position:fixed;z-index:999}.header .top-navbar{min-height:48px;height:48px;line-height:48px;background-color:#3a3a3a;color:#ccc}.header .top-navbar a{color:#d5d5d5}.header .top-navbar a:hover{color:#5a8fee}.header .top-navbar .brand{float:left;display:inline-block;padding:12px 0;margin:0}.header .top-navbar .brand .site-logo{display:block;width:86px;height:24px;background:url(../img/site-logo-small.png) no-repeat}.header .top-navbar .title-container{float:left;display:inline-block;margin:0;padding:0;margin-left:20px}.header .top-navbar .title-container .title{font-size:16px}.header .top-navbar .breadcrumb-container{float:left;display:inline-block;margin:0;padding:0}.header .top-navbar .breadcrumb-container .breadcrumb{background-color:#3a3a3a;height:48px;margin:0;border-radius:0;border:none;padding:0 0 0 20px;font-size:16px;color:#ccc}.header .top-navbar .breadcrumb-container .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#555;content:'|'}.header .top-navbar .breadcrumb-container .breadcrumb .title{font-size:18px}.header .top-navbar .breadcrumb-container .breadcrumb .sub-title{font-size:14px;color:#b3b3b3}.header .top-navbar .status-container{float:right}.page-content{margin-top:10px;margin-bottom:44px}.footer{width:100%;height:24px;line-height:24px;background-color:#d5d5d5;border-top:1px solid #a2a2a2;border-bottom:1px solid #efefef;z-index:998;text-align:center;font-size:12px}.footer.footer-fixed-bottom{bottom:0;position:fixed}.row-sm .col-sm-1,.row-sm .col-sm-2,.row-sm .col-sm-3,.row-sm .col-sm-4,.row-sm .col-sm-5,.row-sm .col-sm-6,.row-sm .col-sm-7,.row-sm .col-sm-8,.row-sm .col-sm-9,.row-sm .col-sm-10,.row-sm .col-sm-11{padding-right:5px;padding-left:5px}.content{margin-top:15px;margin-bottom:20px;background-color:#fff;border-radius:5px;padding:10px}.content:last-child{margin-bottom:54px}.table-host{width:100%;border-top:10px solid #b3cfe7;border-bottom:1px solid #b3cfe7}.table-host .cell-host-id{border-left:1px solid #e7e7e7;padding:5px;text-align:center;width:168px;vertical-align:middle}.table-host .cell-host-id .host-id{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:13px;color:#999;display:inline-block}.table-host .cell-host-id .host-name{display:block;width:168px;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:16px;margin:auto;margin-bottom:10px}.table-host .cell-host-id .td-ip-item{width:10em;height:18px;padding:2px 4px;margin:1px auto;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.table-host .cell-host-id .td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.table-host .cell-host-id .actions{margin-top:20px}.table-host .cell-host-id .actions a{margin-left:5px;margin-right:5px}.table-host .cell-host-id .actions a:first-child{margin-left:0}.table-host .cell-host-id .actions a:last-child{margin-right:0}.table-host .cell-detail{border-left:1px solid #e7e7e7;border-right:1px solid #e7e7e7;vertical-align:top}.table-host .cell-detail tr{border-top:1px solid #e7e7e7}.table-host .cell-detail tr:last-child{border-bottom:1px solid #e7e7e7}.table-host .cell-detail .row-host-info{background-color:#ececed}.table-host .cell-detail.host-offline{background-color:#ffcecc;text-align:center;vertical-align:middle}.table-host .cell-detail.host-offline .host-offline-msg{color:#802506;font-size:24px}.table-host .cell-log td{border:1px solid #e7e7e7}.table-host .cell-log td .host-log{font-size:12px;outline:none;width:100%;height:120px;overflow-y:auto;resize:none;border:none;padding:5px}.table-host .cell-log td .host-log div{margin-bottom:3px}.table-host .cell-log td .host-log div .datetime{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.log-box{margin-top:15px}.log-box .log-list{margin-top:5px;border:1px solid #e7e7e7;font-size:12px;outline:none;width:100%;max-height:480px;overflow-y:auto;resize:none;padding:5px}.log-box .log-list div{margin-bottom:3px}.log-box .log-list div:hover{background-color:#f3f3f3}.log-box .log-list div .log-dt{padding:0 3px;padding-top:2px;padding-bottom:1px;margin-right:3px;background-color:#f57523;color:#fff;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.log-box .log-list div .log-hid{padding:0 3px;margin-right:3px;background-color:#348fe2;color:#fff}.log-box .log-list div .log-hname{padding:0 3px;margin-right:3px;background-color:#348fe2;color:#fff}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.mp{display:inline-block;width:20%;max-width:20%}.mp .mp-inner{background-color:#e5e5e5;margin:3px;border-radius:4px}.mp .mp-name{color:#999;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}.mp .mp-name.with-target{padding-top:17px;padding-bottom:1px}.mp .mp-target{display:inline-block;float:left;position:absolute;font-size:11px;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.1)}.mp.mp-disabled .mp-inner{background-color:#e5e5e5}.mp.mp-disabled .mp-name{color:#999}.mp.mp-success .mp-inner{background-color:#368142}.mp.mp-success .mp-name{color:#fff}.mp.mp-danger .mp-inner{background-color:#d34242}.mp.mp-danger .mp-name{color:#fff}.mp.mp-warning .mp-inner{background-color:#f57523}.mp.mp-warning .mp-name{color:#fff}.host-offline{background-color:#ffcecc;height:36px;line-height:36px;padding:0 10px;color:#802506;font-size:20px;cursor:pointer}.host-offline .tips{display:none;font-size:12px}.host-offline:hover .tips{display:inline-block}.host-no-strategy{color:#999;font-size:16px}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat} \ No newline at end of file +@charset "utf-8";body{font-family:"Open Sans","Helvetica Neue","Microsoft YaHei","微软雅黑",Helvetica,Arial,sans-serif;font-size:13px;background-color:#e9e9e9;color:#333}html,body{height:100%}#page-container{min-width:1260px}a{text-decoration:none}a:link{text-decoration:none}a:hover{text-decoration:none}a:active{text-decoration:none}a:visited{text-decoration:none}select{outline:none}label{font-weight:normal}.clear-float{clear:both}.bigger{font-size:120%}.normal-text{font-size:13px;color:#333}.mono{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}hr.hr-sm{margin-top:5px;margin-bottom:5px}.btn-group-sm>.btn,.btn-sm{padding:2px 5px}.btn.btn-sm{padding:3px 8px}.btn.btn-icon{padding:3px 6px}.btn.btn-icon.btn-sm{padding:0;font-size:14px;height:24px;width:24px;line-height:24px;border-radius:0}.form-group-sm .input-group .input-group-btn>.btn{height:30px;padding:0 8px}.pop-menu-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040}.form-group{margin-bottom:5px}.badge{display:inline-block;min-width:8px;padding:5px 10px;border-radius:10px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.badge.badge-plain{text-shadow:none}.badge.badge-sm{font-size:11px;padding:3px 6px;margin-top:0;border-radius:8px;text-shadow:none}.badge.badge-sup{margin-left:-6px;margin-top:-16px}.badge.badge-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.badge.badge-info{background-color:#33b7d0}.badge.badge-primary{background-color:#348fe2}.badge.badge-success{background-color:#368142}.badge.badge-warning{background-color:#f57523}.badge.badge-danger{background-color:#d34242}.label{display:inline-block;min-width:8px;padding:5px 10px;border-radius:5px;text-align:center;white-space:nowrap;vertical-align:middle;font-size:13px;font-weight:400;line-height:1em;background-color:#888;color:#fff;text-shadow:1px 1px 0 #525252}.label.label-plain{text-shadow:none}.label.label-sm{font-size:11px;padding:3px 8px;margin-top:0;border-radius:5px;text-shadow:none}.label.label-ignore{background-color:#e5e5e5;color:#999;text-shadow:none}.label.label-info{background-color:#33b7d0}.label.label-primary{background-color:#348fe2}.label.label-success{background-color:#368142}.label.label-warning{background-color:#f57523}.label.label-danger{background-color:#d34242}.progress.progress-sm{height:18px;margin-bottom:2px;background-color:#aaa}.progress.progress-sm.button{cursor:pointer}.progress.progress-sm .progress-bar{display:block;font-size:11px;float:none}.alert-sm{padding:5px;margin-bottom:10px}.modal-dialog-sm .modal-header{padding:10px}.modal-dialog-sm .modal-body{padding:10px}.modal-dialog-sm .modal-footer{padding:10px}.modal-dialog-sm .form-horizontal .form-group{margin-right:-5px;margin-left:-5px}.modal-dialog-sm .col-sm-1,.modal-dialog-sm .col-sm-2,.modal-dialog-sm .col-sm-3,.modal-dialog-sm .col-sm-4,.modal-dialog-sm .col-sm-5,.modal-dialog-sm .col-sm-6,.modal-dialog-sm .col-sm-7,.modal-dialog-sm .col-sm-8,.modal-dialog-sm .col-sm-9,.modal-dialog-sm .col-sm-10,.modal-dialog-sm .col-sm-11{padding-right:5px;padding-left:5px}.btn-single-line{white-space:nowrap}.btn-single-line .btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-single-line .btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.remote-action-group{margin-bottom:3px;height:28px;min-width:390px}.remote-action-group ul{display:block;height:28px;margin:0;padding:0}.remote-action-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.remote-action-group ul li.remote-action-btn{background:none;padding:0;border:none}.remote-action-group ul li.remote-action-input{background:none;padding:4px 0}.remote-action-group ul li.remote-action-input select{border:none}.remote-action-group ul li.remote-action-chk-protocol{width:86px}.remote-action-group ul li.remote-action-username,.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol{width:96px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.remote-action-group ul li.remote-action-username{font-size:90%;color:#999}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-protocol,.remote-action-group ul li.remote-action-chk-protocol{color:#000}.remote-action-group ul li.remote-action-name,.remote-action-group ul li.remote-action-chk-protocol{font-weight:bold}.remote-action-group ul li.remote-action-password,.remote-action-group ul li.remote-action-sshkey,.remote-action-group ul li.remote-action-noauth{text-align:center;padding:4px 8px;width:45px}.remote-action-group ul li.remote-action-password{background-color:#e3ffe3;color:#999}.remote-action-group ul li.remote-action-sshkey{background-color:#fbe9c8;color:#666}.remote-action-group ul li.remote-action-noauth{background-color:#e0e0e0;color:#666}.remote-action-group ul li .btn{line-height:1.5;margin:0;padding:4px 8px;font-size:12px;border-radius:0}.remote-action-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.remote-action-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.remote-action-group ul li select{margin-top:-3px}.remote-action-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:first-child .btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.remote-action-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.remote-action-group ul li:last-child .btn{border-top-right-radius:4px;border-bottom-right-radius:4px}hr.small{margin:5px 0}.dlg-protocol-group{margin-bottom:3px}.dlg-protocol-group ul{display:block;height:28px;margin:0;padding:0}.dlg-protocol-group ul li{float:left;position:relative;display:block;height:28px;padding:4px 5px;background-color:#eee;border-top:1px solid #ccc;border-right:1px solid #ccc;border-bottom:1px solid #ccc}.dlg-protocol-group ul li.item-name{width:120px}.dlg-protocol-group ul li.item-btn{background:none;padding:0;border:none}.dlg-protocol-group ul li.item-input{background:none;border:none;padding:0}.dlg-protocol-group ul li .form-control{line-height:1.5;margin:0;padding:4px 5px;font-size:12px;height:28px;border-radius:0;border-left:none;width:100px}.dlg-protocol-group ul li label{padding:0;display:block;float:left;margin-top:1px;cursor:pointer}.dlg-protocol-group ul li input[type=checkbox]{display:block;float:left;margin:3px 5px 0 0}.dlg-protocol-group ul li:first-child{border-left:1px solid #ccc;border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:first-child .btn,.dlg-protocol-group ul li:first-child .form-control{border-top-left-radius:4px;border-bottom-left-radius:4px}.dlg-protocol-group ul li:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.dlg-protocol-group ul li:last-child .btn,.dlg-protocol-group ul li:last-child .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{padding-right:5px;padding-left:5px}.form-group-sm .form-control-static{padding:6px 0}.input-group :-moz-placeholder{color:#d2d2d2}.input-group ::-moz-placeholder{color:#d2d2d2}.input-group input:-ms-input-placeholder,.input-group textarea:-ms-input-placeholder{color:#d2d2d2}.input-group input::-webkit-input-placeholder,.input-group textarea::-webkit-input-placeholder{color:#d2d2d2}.table{margin-bottom:10px}.table>thead>tr>th{padding:5px 5px;outline:none;white-space:nowrap;font-weight:normal;text-align:center;background-color:#ededed}.table>tbody>tr>td{padding:5px;text-align:center;vertical-align:middle}.table>tbody>tr>td .nowrap{white-space:nowrap}.table.table-data thead .sorting,.table.table-data thead .sorting_asc,.table.table-data thead .sorting_desc{cursor:pointer;position:relative}.table.table-data thead .sorting>span:after,.table.table-data thead .sorting_asc>span:after,.table.table-data thead .sorting_desc>span:after{bottom:4px;padding-left:5px;display:inline-block;font-family:'FontAwesome';opacity:.8}.table.table-data thead .sorting>span:after{opacity:.2;content:"\f0dc"}.table.table-data thead .sorting_asc>span:after{content:"\f0de"}.table.table-data thead .sorting_desc>span:after{content:"\f0dd"}.host-id{display:block;font-size:16px;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;color:#333}.host-id.not-active{font-size:14px;font-weight:400;color:#999}.host-desc{font-size:12px;color:#999;display:inline-block;white-space:nowrap;width:160px;overflow:hidden;text-overflow:ellipsis}a.host-desc:hover:before{display:inline-block;padding-right:3px;line-height:12px;content:"\f040";font-family:'FontAwesome'}.td-ip-list{padding-right:20px;padding-left:5px}.td-ip-show-more{font-size:14px;width:12px;float:right;display:block}.td-ip-item{min-width:12em;width:12em;height:18px;padding:2px 4px;margin:1px 0;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.td-ip-item a{display:inline-block;width:14px;float:right;font-size:14px}.page-header-fixed{padding-top:48px}.header{border:none;box-shadow:0 0 3px rgba(0,0,0,0.5);min-height:48px;height:48px;top:0;width:100%;position:fixed;z-index:999}.header .top-navbar{min-height:48px;height:48px;line-height:48px;background-color:#3a3a3a;color:#ccc}.header .top-navbar a{color:#d5d5d5}.header .top-navbar a:hover{color:#5a8fee}.header .top-navbar .brand{float:left;display:inline-block;padding:12px 0;margin:0}.header .top-navbar .brand .site-logo{display:block;width:86px;height:24px;background:url(../img/site-logo-small.png) no-repeat}.header .top-navbar .title-container{float:left;display:inline-block;margin:0;padding:0;margin-left:20px}.header .top-navbar .title-container .title{font-size:16px}.header .top-navbar .breadcrumb-container{float:left;display:inline-block;margin:0;padding:0}.header .top-navbar .breadcrumb-container .breadcrumb{background-color:#3a3a3a;height:48px;margin:0;border-radius:0;border:none;padding:0 0 0 20px;font-size:16px;color:#ccc}.header .top-navbar .breadcrumb-container .breadcrumb>li+li:before{font-size:18px;padding:0 5px;color:#555;content:'|'}.header .top-navbar .breadcrumb-container .breadcrumb .title{font-size:18px}.header .top-navbar .breadcrumb-container .breadcrumb .sub-title{font-size:14px;color:#b3b3b3}.header .top-navbar .status-container{float:right}.page-content{margin-top:10px;margin-bottom:44px}.footer{width:100%;height:24px;line-height:24px;background-color:#d5d5d5;border-top:1px solid #a2a2a2;border-bottom:1px solid #efefef;z-index:998;text-align:center;font-size:12px}.footer.footer-fixed-bottom{bottom:0;position:fixed}.row-sm .col-sm-1,.row-sm .col-sm-2,.row-sm .col-sm-3,.row-sm .col-sm-4,.row-sm .col-sm-5,.row-sm .col-sm-6,.row-sm .col-sm-7,.row-sm .col-sm-8,.row-sm .col-sm-9,.row-sm .col-sm-10,.row-sm .col-sm-11{padding-right:5px;padding-left:5px}.content{margin-top:15px;margin-bottom:20px;background-color:#fff;border-radius:5px;padding:10px}.content:last-child{margin-bottom:54px}.table-host{width:100%;border-top:10px solid #b3cfe7;border-bottom:1px solid #b3cfe7}.table-host .cell-host-id{border-left:1px solid #e7e7e7;padding:5px;text-align:center;width:168px;vertical-align:middle}.table-host .cell-host-id .host-id{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:13px;color:#999;display:inline-block}.table-host .cell-host-id .host-name{display:block;width:168px;text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:16px;margin:auto;margin-bottom:10px}.table-host .cell-host-id .td-ip-item{width:10em;height:18px;padding:2px 4px;margin:1px auto;color:#333;text-align:center;white-space:nowrap;border-radius:9px;line-height:11px;font-size:11px;background:#dfdfdf !important}.table-host .cell-host-id .td-ip-item span{display:inline-block;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace;font-size:11px;font-weight:400}.table-host .cell-host-id .actions{margin-top:20px}.table-host .cell-host-id .actions a{margin-left:5px;margin-right:5px}.table-host .cell-host-id .actions a:first-child{margin-left:0}.table-host .cell-host-id .actions a:last-child{margin-right:0}.table-host .cell-detail{border-left:1px solid #e7e7e7;border-right:1px solid #e7e7e7;vertical-align:top}.table-host .cell-detail tr{border-top:1px solid #e7e7e7}.table-host .cell-detail tr:last-child{border-bottom:1px solid #e7e7e7}.table-host .cell-detail .row-host-info{background-color:#ececed}.table-host .cell-detail.host-offline{background-color:#ffcecc;text-align:center;vertical-align:middle}.table-host .cell-detail.host-offline .host-offline-msg{color:#802506;font-size:24px}.table-host .cell-log td{border:1px solid #e7e7e7}.table-host .cell-log td .host-log{font-size:12px;outline:none;width:100%;height:120px;overflow-y:auto;resize:none;border:none;padding:5px}.table-host .cell-log td .host-log div{margin-bottom:3px}.table-host .cell-log td .host-log div .datetime{font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.log-box{margin-top:15px}.log-box .log-list{margin-top:5px;border:1px solid #e7e7e7;font-size:12px;outline:none;width:100%;max-height:480px;overflow-y:auto;resize:none;padding:5px}.log-box .log-list div{margin-bottom:3px}.log-box .log-list div:hover{background-color:#f3f3f3}.log-box .log-list div .log-dt{padding:0 3px;padding-top:2px;padding-bottom:1px;margin-right:3px;background-color:#f57523;color:#fff;font-family:Consolas,Lucida Console,Monaco,Courier,'Courier New',monospace}.log-box .log-list div .log-hid{padding:0 3px;margin-right:3px;background-color:#348fe2;color:#fff}.log-box .log-list div .log-hname{padding:0 3px;margin-right:3px;background-color:#348fe2;color:#fff}.page-nav{height:30px;line-height:30px}.page-nav .breadcrumb{padding:0;margin:0;border-radius:0;background-color:transparent}.page-nav .pagination{margin:0 0}.page-nav .btn{margin-top:-3px}.mp{display:inline-block;width:20%;max-width:20%}.mp .mp-inner{background-color:#e5e5e5;margin:3px;border-radius:4px}.mp .mp-name{color:#999;padding:9px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}.mp .mp-name.with-target{padding-top:17px;padding-bottom:1px}.mp .mp-target{display:inline-block;float:left;position:absolute;font-size:11px;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:4px;border-bottom-right-radius:4px;color:rgba(255,255,255,0.85);background-color:rgba(0,0,0,0.1)}.mp.mp-disabled .mp-inner{background-color:#e5e5e5}.mp.mp-disabled .mp-name{color:#999}.mp.mp-success .mp-inner{background-color:#368142}.mp.mp-success .mp-name{color:#fff}.mp.mp-danger .mp-inner{background-color:#d34242}.mp.mp-danger .mp-name{color:#fff}.mp.mp-warning .mp-inner{background-color:#f57523}.mp.mp-warning .mp-name{color:#fff}.host-offline{background-color:#ffcecc;height:36px;line-height:36px;padding:0 10px;color:#802506;font-size:20px;cursor:pointer}.host-offline .tips{display:none;font-size:12px}.host-offline:hover .tips{display:inline-block}.host-no-strategy{color:#999;font-size:16px}#gritter-notice-wrapper{z-index:9999}.gritter-bottom,.gritter-item,.gritter-top{background:rgba(0,0,0,0.8) !important}.gritter-top{border-top-left-radius:3px;border-top-right-radius:3px}.gritter-bottom{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.gritter-close,.gritter-light .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%}.gritter-close:before,.gritter-light .gritter-close:before{content:'\f00d' !important;font-family:FontAwesome !important;font-size:9px !important;width:16px !important;height:16px !important;line-height:16px !important;color:#fff !important;text-indent:0 !important;position:absolute !important;text-align:center !important;right:0 !important;top:0 !important}.gritter-title{font-size:13px !important;line-height:16px !important;padding-bottom:5px !important;font-weight:400 !important;color:#fff !important;text-shadow:none !important}.gritter-item{color:#aaa !important;font-size:13px !important;padding:2px 15px 5px !important}.gritter-error .gritter-bottom,.gritter-error .gritter-item,.gritter-error .gritter-top{background:rgba(123,32,32,0.9) !important}.gritter-error .gritter-title{color:#fff !important}.gritter-error .gritter-item{color:#ddd !important}.gritter-error .gritter-close{left:auto !important;right:5px !important;top:5px !important;width:16px !important;height:16px !important;line-height:16px !important;display:block !important;border-radius:50%;background:#e33b3b !important}.gritter-success .gritter-bottom,.gritter-success .gritter-item,.gritter-success .gritter-top{background:rgba(1,65,16,0.9) !important}.gritter-success .gritter-title{color:#ddd !important}.gritter-success .gritter-item{color:#ccc !important}.gritter-success .gritter-close{background:#0eb320 !important}.icon{display:inline-block}.icon16{width:16px;height:16px;line-height:16px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/mimetype-16.png") !important}.icon16.icon-disk{background-position:0 0 !important}.icon16.icon-folder{background-position:-16px 0 !important}.icon16.icon-file{background-position:0 -16px !important}.icon16.icon-txt{background-position:-16px -16px !important}.icon16.icon-help{background-position:-32px -16px !important}.icon16.icon-sys{background-position:-48px -16px !important}.icon16.icon-exe{background-position:-64px -16px !important}.icon16.icon-office{background-position:0 -32px !important}.icon16.icon-word{background-position:-16px -32px !important}.icon16.icon-excel{background-position:-32px -32px !important}.icon16.icon-ppt{background-position:-48px -32px !important}.icon16.icon-access{background-position:-64px -32px !important}.icon16.icon-visio{background-position:-80px -32px !important}.icon16.icon-audio{background-position:0 -48px !important}.icon16.icon-video{background-position:-16px -48px !important}.icon16.icon-pic{background-position:-32px -48px !important}.icon16.icon-pdf{background-position:-48px -48px !important}.icon16.icon-font{background-position:-64px -48px !important}.icon16.icon-script{background-position:0 -64px !important}.icon16.icon-html{background-position:-16px -64px !important}.icon16.icon-py{background-position:-32px -64px !important}.icon16.icon-h{background-position:-48px -64px !important}.icon16.icon-c{background-position:-64px -64px !important}.icon16.icon-cpp{background-position:-80px -64px !important}.icon16.icon-cs{background-position:-96px -64px !important}.icon16.icon-php{background-position:-112px -64px !important}.icon16.icon-ruby{background-position:-128px -64px !important}.icon16.icon-java{background-position:-144px -64px !important}.icon16.icon-vs{background-position:-160px -64px !important}.icon16.icon-js{background-position:-176px -64px !important}.icon16.icon-archive{background-position:0 -80px !important}.icon16.icon-rar{background-position:-16px -80px !important}.icon16.icon-zip{background-position:-32px -80px !important}.icon16.icon-7z{background-position:-48px -80px !important}.icon16.icon-tar{background-position:-64px -80px !important}.icon16.icon-gz{background-position:-80px -80px !important}.icon16.icon-jar{background-position:-96px -80px !important}.icon16.icon-bz2{background-position:-112px -80px !important}.icon24{width:24px;height:24px;line-height:24px;margin-right:3px;margin-top:0 !important;margin-bottom:-2px !important;vertical-align:top;background-image:url("img/desktop/icons-tree-24x24.png") !important}.icon24.icon-disk{background-position:0 0 !important}.icon24.icon-folder{background-position:-24px 0 !important}.icon24.icon-folder-open{background-position:-48px 0 !important}.os-icon-windows:after{color:#00bcf6;content:"\f17a";font-size:18px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-linux:after{color:#fff;content:"\f17c";font-size:18px;width:24px;height:24px;line-height:24px;background-color:#333;border-radius:50%;display:inline-block;font-family:'FontAwesome'}.os-icon-macos:after{color:#a7a7a7;content:"\f179";font-size:20px;width:24px;height:24px;line-height:24px;display:inline-block;font-family:'FontAwesome'}.os-icon-ubuntu:after,.os-icon-debian:after,.os-icon-centos:after,.os-icon-redhat:after{content:" ";width:24px;height:24px;line-height:24px;display:inline-block}.os-icon-ubuntu:after{background:url(../img/os-icon/ubuntu-24x24.png) no-repeat}.os-icon-debian:after{background:url(../img/os-icon/debian-24x24.png) no-repeat}.os-icon-centos:after{background:url(../img/os-icon/centos-24x24.png) no-repeat}.os-icon-redhat:after{background:url(../img/os-icon/redhat-24x24.png) no-repeat} \ No newline at end of file diff --git a/server/www/teleport/static/img/qrcode/google-oath-appstore.png b/server/www/teleport/static/img/qrcode/google-oath-appstore.png new file mode 100644 index 0000000..66146e0 Binary files /dev/null and b/server/www/teleport/static/img/qrcode/google-oath-appstore.png differ diff --git a/server/www/teleport/static/img/qrcode/google-oath-baidu.png b/server/www/teleport/static/img/qrcode/google-oath-baidu.png new file mode 100644 index 0000000..b0421fd Binary files /dev/null and b/server/www/teleport/static/img/qrcode/google-oath-baidu.png differ diff --git a/server/www/teleport/static/img/qrcode/google-oath-googleplay.png b/server/www/teleport/static/img/qrcode/google-oath-googleplay.png new file mode 100644 index 0000000..2de59fe Binary files /dev/null and b/server/www/teleport/static/img/qrcode/google-oath-googleplay.png differ diff --git a/server/www/teleport/static/img/qrcode/xiaomi-oath-appstore.png b/server/www/teleport/static/img/qrcode/xiaomi-oath-appstore.png new file mode 100644 index 0000000..af86f8a Binary files /dev/null and b/server/www/teleport/static/img/qrcode/xiaomi-oath-appstore.png differ diff --git a/server/www/teleport/static/img/qrcode/xiaomi-oath-xiaomi.png b/server/www/teleport/static/img/qrcode/xiaomi-oath-xiaomi.png new file mode 100644 index 0000000..fddbe7b Binary files /dev/null and b/server/www/teleport/static/img/qrcode/xiaomi-oath-xiaomi.png differ diff --git a/server/www/teleport/static/js/ui/auth/login.js b/server/www/teleport/static/js/ui/auth/login.js index c423a7d..9065ba9 100644 --- a/server/www/teleport/static/js/ui/auth/login.js +++ b/server/www/teleport/static/js/ui/auth/login.js @@ -1,151 +1,218 @@ -"use strict"; - -ywl.on_init = function (cb_stack, cb_args) { - if (ywl.page_options.user_name.length > 0) { - $('#username_account').val(ywl.page_options.user_name); - } - - $('#captcha_image').attr('src', '/auth/get-captcha?' + Math.random()); - - ywl.app = ywl.create_app(); - cb_stack - .add(ywl.app.init) - .exec(); -}; - -ywl.create_app = function () { - var _app = {}; - - _app.dom_login_account = null; - - _app.init = function (cb_stack, cb_args) { - _app.dom_login_account = $('#login-type-account'); - - $('#btn-login-account').click(_app.login_account); - - $('#captcha_image').click(function () { - $(this).attr('src', '/auth/get-captcha?' + Math.random()); - $('#captcha').focus().val(''); - }); - $('#username_account').keydown(function (event) { - $('[data-toggle="popover"]').popover('hide'); - if (event.which === 13) { - $('#password_account').focus(); - } - }); - $('#password_account').keydown(function (event) { - $('[data-toggle="popover"]').popover('hide'); - if (event.which === 13) { - $('#captcha').focus(); - } - }); - $('#captcha').keydown(function (event) { - $('[data-toggle="popover"]').popover('hide'); - if (event.which === 13) { - _app.login_account(); - } - }); - - cb_stack.exec(); - }; - - _app.login_account = function () { - var str_username = ''; - var str_password = ''; - var str_captcha = ''; - var is_remember = false; - - var dom_username = $('#username_account'); - var dom_password = $('#password_account'); - var dom_captcha = $('#captcha'); - var dom_remember = $('#remember-me'); - - str_username = dom_username.val(); - str_password = dom_password.val(); - str_captcha = dom_captcha.val(); - is_remember = dom_remember.is(':checked'); - - if (str_username.length === 0) { - show_op_box('error', '缺少账号!'); - dom_username.attr('data-content', "请填写您的账号!").popover('show'); - dom_username.focus(); - return; - } - - if (str_password.length === 0) { - show_op_box('error', '缺少密码!'); - dom_password.attr('data-content', "请填写密码!").popover('show'); - dom_password.focus(); - return; - } - - if (str_captcha.length !== 4) { - show_op_box('error', '验证码错误!'); - dom_captcha.attr('data-content', "验证码为4位数字和字母的组合,请重新填写!").popover('show').focus(); - return; - } - - $('#btn_login').attr('disabled', 'disabled'); - show_op_box('wait', ' 正在进行身份认证,请稍候...'); - - // 先判断一下captcha是否正确,如果不正确,拒绝登录 - ywl.ajax_post_json('/auth/verify-captcha', {captcha: str_captcha}, - function (ret) { - if (ret.code === TPE_OK) { - // 验证成功 - hide_op_box(); - show_op_box('wait', ' 正在登录TELEPORT,请稍候...'); - _app.do_account_login(str_username, str_password, str_captcha, is_remember); - } - else { - hide_op_box(); - show_op_box('error', '验证码错误!'); - $('#captcha_image').attr('src', '/auth/get-captcha?' + Math.random()); - $('#captcha').focus().val(''); - } - - $('#btn_login').removeAttr('disabled'); - }, - function () { - hide_op_box(); - show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!'); - $('#btn_login').removeAttr('disabled'); - } - ); - }; - - _app.do_account_login = function (username, userpwd, captcha, is_remember) { - ywl.ajax_post_json('/auth/verify-user', {username: username, userpwd: userpwd, captcha: captcha, remember: is_remember}, - function (ret) { - if (ret.code === TPE_OK) { - window.location.href = ywl.page_options.ref; - } else { - hide_op_box(); - show_op_box('error', '无法登录TELEPORT:' + ret.message); - console.log(ret); - } - - $('#btn_login').removeAttr('disabled'); - }, - function () { - hide_op_box(); - show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!'); - $('#btn_login').removeAttr('disabled'); - } - ); - }; - - return _app; -}; - -function hide_op_box() { - $('#login_message').hide(); -} - -function show_op_box(op_type, op_msg) { - var obj_box = $('#login_message'); - - obj_box.html(op_msg); - obj_box.removeClass().addClass('op_box op_' + op_type); - obj_box.show(); -} +"use strict"; + +var LOGIN_TYPE_PASSWORD = 1; // 使用用户名密码登录(额外需要验证码) +var LOGIN_TYPE_OATH = 2; // 使用用户名密码登录(额外需要身份验证器的动态验证码) + +ywl.on_init = function (cb_stack, cb_args) { + if (ywl.page_options.user_name.length > 0) { + $('#username-account').val(ywl.page_options.user_name); + } + + $('#captcha-image').attr('src', '/auth/get-captcha?' + Math.random()); + + ywl.app = ywl.create_app(); + cb_stack + .add(ywl.app.init) + .exec(); +}; + +ywl.create_app = function () { + var _app = {}; + + _app.login_type = LOGIN_TYPE_PASSWORD; + + _app.dom = { + btn_login_type_password: $('#login-type-password'), + btn_login_type_oath: $('#login-type-oath'), + area_captcha: $('#login-area-captcha'), + area_oath: $('#login-area-oath'), + captcha_image: $('#captcha-image'), + + input_username: $('#username-account'), + input_password: $('#password-account'), + input_captcha: $('#captcha'), + input_oath: $('#oath-code'), + + remember: $('#remember-me'), + btn_login: $('#btn-login'), + + message: $('#message') + }; + + +// _app.dom_login_account = null; +// _app.dom_login_google = null; + + _app.init = function (cb_stack, cb_args) { +// _app.dom_login_account = $('#login-type-account'); +// _app.dom_login_google = $('#login-type-google'); + + _app.dom.btn_login_type_password.click(function () { + _app.login_type = LOGIN_TYPE_PASSWORD; + _app.dom.btn_login_type_oath.removeClass('selected'); + $(this).addClass('selected'); + _app.dom.area_oath.slideUp(100); + _app.dom.area_captcha.slideDown(100); + }); + _app.dom.btn_login_type_oath.click(function () { + _app.login_type = LOGIN_TYPE_OATH; + _app.dom.btn_login_type_password.removeClass('selected'); + $(this).addClass('selected'); + _app.dom.area_oath.slideDown(100); + _app.dom.area_captcha.slideUp(100); + }); + + + _app.dom.btn_login.click(_app.login_account); + + _app.dom.captcha_image.click(function () { + $(this).attr('src', '/auth/get-captcha?' + Math.random()); + _app.dom.input_captcha.focus().val(''); + }); + _app.dom.input_username.keydown(function (event) { + $('[data-toggle="popover"]').popover('hide'); + if (event.which === 13) { + _app.dom.input_password.focus(); + } + }); + _app.dom.input_password.keydown(function (event) { + $('[data-toggle="popover"]').popover('hide'); + if (event.which === 13) { + if(_app.login_type === LOGIN_TYPE_PASSWORD) + _app.dom.input_captcha.focus(); + else if(_app.login_type === LOGIN_TYPE_OATH) + _app.dom.input_oath.focus(); + } + }); + _app.dom.input_captcha.keydown(function (event) { + $('[data-toggle="popover"]').popover('hide'); + if (event.which === 13) { + _app.login_account(); + } + }); + + cb_stack.exec(); + }; + + _app.login_account = function () { +// var str_username = ''; +// var str_password = ''; +// var str_captcha = ''; +// var str_gcode = ''; +// var is_remember = false; +// +// var dom_username = $('#username-account'); +// var dom_password = $('#password-account'); +// var dom_captcha = $('#captcha'); +// var dom_gcode = $('#oath-code'); +// var dom_remember = $('#remember-me'); + + var str_username = _app.dom.input_username.val(); + var str_password = _app.dom.input_password.val(); + var str_captcha = _app.dom.input_captcha.val(); + var str_oath = _app.dom.input_oath.val(); + var is_remember = _app.dom.remember.is(':checked'); + + if (str_username.length === 0) { + show_op_box('error', '缺少账号!'); + _app.dom.input_username.attr('data-content', "请填写您的账号!").popover('show'); + _app.dom.input_username.focus(); + return; + } + + if (str_password.length === 0) { + show_op_box('error', '缺少密码!'); + _app.dom.input_password.attr('data-content', "请填写密码!").popover('show'); + _app.dom.input_password.focus(); + return; + } + + if (_app.login_type === 'account') { + if (str_captcha.length !== 4) { + show_op_box('error', '验证码错误!'); + _app.dom.input_captcha.attr('data-content', "验证码为4位数字和字母的组合,请重新填写!").popover('show').focus(); + return; + } + } else if (_app.login_type === 'google') { + if (str_oath.length !== 6) { + show_op_box('error', '身份验证器动态验证码错误!'); + _app.dom.input_oath.attr('data-content', "身份验证器动态验证码为6位数字,请重新填写!").popover('show').focus(); + return; + } + } + + _app.dom.btn_login.attr('disabled', 'disabled'); + _app.show_op_box('wait', ' 正在进行身份认证,请稍候...'); + + // 先判断一下captcha是否正确,如果不正确,拒绝登录 + if (_app.login_type === LOGIN_TYPE_PASSWORD) { + ywl.ajax_post_json('/auth/verify-captcha', {captcha: str_captcha}, + function (ret) { + if (ret.code === TPE_OK) { + // 验证成功 + _app.hide_op_box(); + _app.show_op_box('wait', ' 正在登录TELEPORT,请稍候...'); + _app.do_account_login(str_username, str_password, str_captcha, str_oath, is_remember); + } + else { + _app.hide_op_box(); + _app.show_op_box('error', '验证码错误!'); + _app.dom.captcha_image.attr('src', '/auth/get-captcha?' + Math.random()); + _app.dom.input_captcha.focus().val(''); + } + + _app.dom.btn_login.removeAttr('disabled'); + }, + function () { + _app.hide_op_box(); + _app.show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!'); + _app.dom.btn_login.removeAttr('disabled'); + } + ); + } else { + _app.do_account_login(str_username, str_password, str_captcha, str_oath, is_remember); + } + }; + + _app.do_account_login = function (username, password, captcha, oath, is_remember) { + var login_type = ''; + if(_app.login_type === LOGIN_TYPE_PASSWORD) { + login_type = 'password'; + } else if(_app.login_type === LOGIN_TYPE_OATH) { + login_type = 'oath'; + } + var args = {type:login_type, username: username, password: password, captcha: captcha, oath: oath, remember: is_remember}; + console.log(args); + ywl.ajax_post_json('/auth/verify-user', args, + function (ret) { + if (ret.code === TPE_OK) { + window.location.href = ywl.page_options.ref; + } else { + _app.hide_op_box(); + _app.show_op_box('error', '无法登录TELEPORT:' + ret.message); + console.log(ret); + } + + _app.dom.btn_login.removeAttr('disabled'); + }, + function () { + _app.hide_op_box(); + _app.show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!'); + _app.dom.btn_login.removeAttr('disabled'); + } + ); + }; + + _app.hide_op_box = function() { + _app.dom.message.hide(); + }; + + _app.show_op_box = function(op_type, op_msg) { + _app.dom.message.html(op_msg); + _app.dom.message.removeClass().addClass('op_box op_' + op_type); + _app.dom.message.show(); + }; + + return _app; +}; + diff --git a/server/www/teleport/static/js/ui/user/personal.js b/server/www/teleport/static/js/ui/user/personal.js new file mode 100644 index 0000000..a6ddfe7 --- /dev/null +++ b/server/www/teleport/static/js/ui/user/personal.js @@ -0,0 +1,145 @@ +"use strict"; + +ywl.on_init = function (cb_stack, cb_args) { + ywl.dom = { + btn_reset_oath_code: $('#btn-reset-oath-code'), + btn_verify_oath_code: $('#btn-verify-oath-code'), + btn_verify_oath_code_and_save: $('#btn-verify-oath-and-save'), + btn_modify_password: $('#btn-modify-password'), + btn_toggle_oath_download: $('#toggle-oath-download'), + + oath_app_download_box: $('#oath-app-download-box'), + + input_current_password: $('#current-password'), + input_new_password: $('#new-password-1'), + input_new_password_confirm: $('#new-password-2'), + input_oath_code: $('#oath-code'), + input_oath_code_verify: $('#oath-code-verify'), + + dlg_reset_oath_code: $('#dialog-reset-oath-code'), + oath_secret_image: $('#oath-secret-qrcode'), + tmp_oath_secret: $('#tmp-oath-secret'), + }; + +// ywl.dom.tmp_oath_secret.text(ywl.page_options.tmp_oath_secret); + + ywl.clear_password_input = function () { + ywl.dom.input_current_password.val(''); + ywl.dom.input_new_password.val(''); + ywl.dom.input_new_password_confirm.val(''); + }; + + ywl.dom.btn_modify_password.click(function () { + var old_pwd = ywl.dom.input_current_password.val(); + var new_pwd_1 = ywl.dom.input_new_password.val(); + var new_pwd_2 = ywl.dom.input_new_password_confirm.val(); + if (old_pwd.length === 0) { + ywl.notify_error('请输入当前密码!'); + ywl.dom.input_current_password.focus(); + return; + } + if (new_pwd_1.length === 0) { + ywl.notify_error('请设置新密码!'); + ywl.dom.input_new_password.focus(); + return; + } + if (new_pwd_1 !== new_pwd_2) { + ywl.notify_error('两次密码输入不一致!'); + ywl.dom.input_new_password_confirm.focus(); + return; + } + ywl.ajax_post_json('/auth/modify-pwd', {o_pwd: old_pwd, n_pwd: new_pwd_1, callback: 1}, + function (ret) { + if (ret.code === TPE_OK) { + ywl.notify_success('密码修改成功!'); + ywl.clear_password_input(); + } else if (ret.code === -101) { + ywl.notify_error('密码错误!'); + } else { + ywl.notify_error('密码修改失败:' + ret.message); + } + + }, + function () { + ywl.notify_error('密码修改失败!'); + } + ); + }); + + ywl.dom.btn_toggle_oath_download.click(function () { + if (ywl.dom.oath_app_download_box.is(':hidden')) { + ywl.dom.oath_app_download_box.slideDown('fast', function () { + ywl.dom.btn_toggle_oath_download.html('收起 '); + }); + } else { + ywl.dom.oath_app_download_box.slideUp('fast', function () { + ywl.dom.btn_toggle_oath_download.html('显示下载地址 '); + }); + } + }); + + ywl.dom.btn_verify_oath_code.click(function () { + var code = ywl.dom.input_oath_code.val().trim(); + if (code.length !== 6) { + ywl.notify_error('动态验证码错误:应该是6位数字!'); + ywl.dom.input_oath_code_verify.focus(); + return; + } + + ywl.ajax_post_json('/auth/oath-verify', {code: code}, + function (ret) { + if (ret.code === TPE_OK) { + ywl.notify_success('动态验证码验证成功!'); + } else if (ret.code === -3) { + ywl.notify_error('动态验证码验证失败!'); + } else { + ywl.notify_error('发生内部错误!' + ret.code + ret.message); + } + }, + function () { + ywl.notify_error('网路故障,无法连接到服务器!'); + } + ); + }); + + ywl.dom.btn_reset_oath_code.click(function () { + ywl.ajax_post_json('/auth/oath-secret-reset', {}, + function (ret) { + if (ret.code === TPE_OK) { + ywl.dom.oath_secret_image.attr('src', '/auth/oath-secret-qrcode?' + Math.random()); + ywl.dom.tmp_oath_secret.text(ret.data.tmp_oath_secret); + ywl.dom.dlg_reset_oath_code.modal({backdrop: 'static'}); + } else { + ywl.notify_error('发生内部错误!'); + } + }, + function () { + ywl.notify_error('网路故障,无法连接到服务器!'); + } + ); + }); + + ywl.dom.btn_verify_oath_code_and_save.click(function () { + var code = ywl.dom.input_oath_code_verify.val().trim(); + if (code.length !== 6) { + ywl.notify_error('动态验证码错误:应该是6位数字!'); + ywl.dom.input_oath_code_verify.focus(); + return; + } + + ywl.ajax_post_json('/auth/oath-update-secret', {code: code}, + function (ret) { + if (ret.code === TPE_OK) { + ywl.notify_success('身份验证器绑定成功!您可以用此身份验证器登录系统了!'); + ywl.dom.dlg_reset_oath_code.modal('hide'); + } else { + ywl.notify_error('发生内部错误!'); + } + }, + function () { + ywl.notify_error('网路故障,无法连接到服务器!'); + } + ); + }); + +}; \ No newline at end of file diff --git a/server/www/teleport/static/less/_base.less b/server/www/teleport/static/less/_base.less index 8a5f5d8..408f299 100644 --- a/server/www/teleport/static/less/_base.less +++ b/server/www/teleport/static/less/_base.less @@ -5,515 +5,535 @@ @font-family-mono: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace; body { - font-family: @font-family-normal; - //font-family: "微软雅黑", "Microsoft YaHei", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - background-color: @page-bg; - color: @page-color; + font-family: @font-family-normal; + //font-family: "微软雅黑", "Microsoft YaHei", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + background-color: @page-bg; + color: @page-color; } html, body { - height: 100%; + height: 100%; } #page-container { - min-width: 1260px; + min-width: 1260px; } a { - text-decoration: none; + text-decoration: none; } a:link { - text-decoration: none; + text-decoration: none; } a:hover { - text-decoration: none; + text-decoration: none; } a:active { - text-decoration: none; + text-decoration: none; } a:visited { - text-decoration: none; + text-decoration: none; } select { - outline: none; + outline: none; } label { - font-weight: normal; + font-weight: normal; } .clear-float { - clear: both; + clear: both; } .bigger { - font-size: 120%; + font-size: 120%; } .normal-text { - font-size: 13px; - color: @page-color; + font-size: 13px; + color: @page-color; } .mono { - font-family:@font-family-mono; + font-family: @font-family-mono; } hr.hr-sm { - margin-top: 5px; - margin-bottom: 5px; + margin-top: 5px; + margin-bottom: 5px; } //============================================== // 重载bootstrap的样式 //============================================== .btn-group-sm > .btn, .btn-sm { - padding: 2px 5px; - //margin-bottom: 2px; + padding: 2px 5px; + //margin-bottom: 2px; } .btn.btn-sm { - padding: 3px 8px; + padding: 3px 8px; } .btn.btn-icon { - padding: 3px 6px; - &.btn-sm { - //padding:1px 3px; - padding: 0; - font-size: 14px; - height: 24px; - width: 24px; - line-height: 24px; - border-radius: 0; - } + padding: 3px 6px; + &.btn-sm { + //padding:1px 3px; + padding: 0; + font-size: 14px; + height: 24px; + width: 24px; + line-height: 24px; + border-radius: 0; + } } .form-group-sm .input-group .input-group-btn > .btn { - height: 30px; - padding: 0 8px; + height: 30px; + padding: 0 8px; } .pop-menu-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; } //.modal { .form-group { - margin-bottom: 5px; + margin-bottom: 5px; } //} .badge { - display: inline-block; + display: inline-block; - min-width: 8px; - padding: 5px 10px; - border-radius: 10px; + min-width: 8px; + padding: 5px 10px; + border-radius: 10px; - text-align: center; - white-space: nowrap; - vertical-align: middle; + text-align: center; + white-space: nowrap; + vertical-align: middle; - font-size: 13px; - font-weight: 400; - line-height: 1em; + font-size: 13px; + font-weight: 400; + line-height: 1em; - background-color: @color-bg-default; - color: @color-text-on-dark-bg; - text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg; + background-color: @color-bg-default; + color: @color-text-on-dark-bg; + text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg; - &.badge-plain { - text-shadow: none; - } + &.badge-plain { + text-shadow: none; + } - &.badge-sm { - font-size: 11px; - padding: 3px 6px; - margin-top: 0; - border-radius: 8px; - text-shadow: none; - } + &.badge-sm { + font-size: 11px; + padding: 3px 6px; + margin-top: 0; + border-radius: 8px; + text-shadow: none; + } - &.badge-sup { - margin-left: -6px; - margin-top: -16px; - } + &.badge-sup { + margin-left: -6px; + margin-top: -16px; + } - &.badge-ignore { - background-color: @color-bg-ignore; - color: @color-text-ignore; - text-shadow: none; - } - &.badge-info { - background-color: @color-bg-info; - } - &.badge-primary { - background-color: @color-bg-primary; - } + &.badge-ignore { + background-color: @color-bg-ignore; + color: @color-text-ignore; + text-shadow: none; + } + &.badge-info { + background-color: @color-bg-info; + } + &.badge-primary { + background-color: @color-bg-primary; + } - &.badge-success { - background-color: @color-bg-success; - } - &.badge-warning { - background-color: @color-bg-warning; - } - &.badge-danger { - background-color: @color-bg-danger; - } + &.badge-success { + background-color: @color-bg-success; + } + &.badge-warning { + background-color: @color-bg-warning; + } + &.badge-danger { + background-color: @color-bg-danger; + } } .label { - display: inline-block; + display: inline-block; - min-width: 8px; - padding: 5px 10px; - border-radius: 5px; + min-width: 8px; + padding: 5px 10px; + border-radius: 5px; - text-align: center; - white-space: nowrap; - vertical-align: middle; + text-align: center; + white-space: nowrap; + vertical-align: middle; - font-size: 13px; - font-weight: 400; - line-height: 1em; + font-size: 13px; + font-weight: 400; + line-height: 1em; - background-color: @color-bg-default; - color: @color-text-on-dark-bg; - text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg; + background-color: @color-bg-default; + color: @color-text-on-dark-bg; + text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg; - &.label-plain { - text-shadow: none; - } + &.label-plain { + text-shadow: none; + } - &.label-sm { - font-size: 11px; - padding: 3px 8px; - margin-top: 0; - border-radius: 5px; - text-shadow: none; - } + &.label-sm { + font-size: 11px; + padding: 3px 8px; + margin-top: 0; + border-radius: 5px; + text-shadow: none; + } - &.label-ignore { - background-color: @color-bg-ignore; - color: @color-text-ignore; - text-shadow: none; - } - &.label-info { - background-color: @color-bg-info; - } - &.label-primary { - background-color: @color-bg-primary; - } + &.label-ignore { + background-color: @color-bg-ignore; + color: @color-text-ignore; + text-shadow: none; + } + &.label-info { + background-color: @color-bg-info; + } + &.label-primary { + background-color: @color-bg-primary; + } - &.label-success { - background-color: @color-bg-success; - } - &.label-warning { - background-color: @color-bg-warning; - } - &.label-danger { - background-color: @color-bg-danger; - } + &.label-success { + background-color: @color-bg-success; + } + &.label-warning { + background-color: @color-bg-warning; + } + &.label-danger { + background-color: @color-bg-danger; + } } // 表格页面中的一些小部件 .progress.progress-sm { - height: 18px; - margin-bottom: 2px; - background-color: #aaa; - &.button { - cursor: pointer; - } + height: 18px; + margin-bottom: 2px; + background-color: #aaa; + &.button { + cursor: pointer; + } - .progress-bar { - display: block; - font-size: 11px; - float: none; - } + .progress-bar { + display: block; + font-size: 11px; + float: none; + } } .alert-sm { - padding: 5px; - margin-bottom: 10px; + padding: 5px; + margin-bottom: 10px; } .modal-dialog-sm { - .modal-header { - padding: 10px; - } - .modal-body { - padding: 10px; - } - .modal-footer { - padding: 10px; - } + .modal-header { + padding: 10px; + } + .modal-body { + padding: 10px; + } + .modal-footer { + padding: 10px; + } - .form-horizontal .form-group { - margin-right: -5px; - margin-left: -5px; - } - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11 { - padding-right: 5px; - padding-left: 5px; - } + .form-horizontal .form-group { + margin-right: -5px; + margin-left: -5px; + } + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11 { + padding-right: 5px; + padding-left: 5px; + } } .btn-single-line { - white-space: nowrap; - .btn { - &:first-child { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - } - &:last-child { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - } - } + white-space: nowrap; + .btn { + &:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + } + &:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + } + } } .remote-action-group { - margin-bottom: 3px; - height: 28px; - min-width: 390px; + margin-bottom: 3px; + height: 28px; + min-width: 390px; - ul { - display: block; - height: 28px; - margin: 0; - padding: 0; + ul { + display: block; + height: 28px; + margin: 0; + padding: 0; - li { - float: left; - position: relative; - display: block; - height: 28px; - padding: 4px 5px; + li { + float: left; + position: relative; + display: block; + height: 28px; + padding: 4px 5px; - background-color: #eee; - border-top: 1px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; + background-color: #eee; + border-top: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; - &.remote-action-btn { - background: none; - padding: 0; - border: none; - } + &.remote-action-btn { + background: none; + padding: 0; + border: none; + } - &.remote-action-input { - background: none; - padding: 4px 0; + &.remote-action-input { + background: none; + padding: 4px 0; - select { - border: none; - } - } + select { + border: none; + } + } - &.remote-action-chk-protocol { - width: 86px; - } + &.remote-action-chk-protocol { + width: 86px; + } - &.remote-action-username, &.remote-action-name, &.remote-action-protocol { - width: 96px; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - &.remote-action-username { - font-size: 90%; - color: #999; - } - &.remote-action-name, &.remote-action-protocol, &.remote-action-chk-protocol { - color: #000; - } - &.remote-action-name, &.remote-action-chk-protocol { - font-weight:bold; - } + &.remote-action-username, &.remote-action-name, &.remote-action-protocol { + width: 96px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + &.remote-action-username { + font-size: 90%; + color: #999; + } + &.remote-action-name, &.remote-action-protocol, &.remote-action-chk-protocol { + color: #000; + } + &.remote-action-name, &.remote-action-chk-protocol { + font-weight: bold; + } - &.remote-action-password, &.remote-action-sshkey, &.remote-action-noauth { - text-align: center; - padding: 4px 8px; - width:45px; - } - &.remote-action-password { - background-color: #e3ffe3; - color: #999; - } - &.remote-action-sshkey { - background-color: #fbe9c8; - color: #666; - } - &.remote-action-noauth { - background-color: #e0e0e0; - color: #666; - } + &.remote-action-password, &.remote-action-sshkey, &.remote-action-noauth { + text-align: center; + padding: 4px 8px; + width: 45px; + } + &.remote-action-password { + background-color: #e3ffe3; + color: #999; + } + &.remote-action-sshkey { + background-color: #fbe9c8; + color: #666; + } + &.remote-action-noauth { + background-color: #e0e0e0; + color: #666; + } - .btn { - line-height: 1.5; - margin: 0; - padding: 4px 8px; - font-size: 12px; - border-radius: 0; - } + .btn { + line-height: 1.5; + margin: 0; + padding: 4px 8px; + font-size: 12px; + border-radius: 0; + } - label { - padding: 0; - display: block; - float: left; - margin-top: 1px; - cursor: pointer; - } - input[type=checkbox] { - display: block; - float: left; - margin: 3px 5px 0 0; - } - select { - margin-top: -3px; - } + label { + padding: 0; + display: block; + float: left; + margin-top: 1px; + cursor: pointer; + } + input[type=checkbox] { + display: block; + float: left; + margin: 3px 5px 0 0; + } + select { + margin-top: -3px; + } - &:first-child { - border-left: 1px solid #ccc; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; + &:first-child { + border-left: 1px solid #ccc; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; - .btn { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - } - } + .btn { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + } - &:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; + &:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; - .btn { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - } - } - } - } + .btn { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + } + } + } } hr.small { - margin: 5px 0; + margin: 5px 0; } .dlg-protocol-group { - margin-bottom: 3px; + margin-bottom: 3px; - ul { - display: block; - height: 28px; - margin: 0; - padding: 0; + ul { + display: block; + height: 28px; + margin: 0; + padding: 0; - li { - float: left; - position: relative; - display: block; - height: 28px; - padding: 4px 5px; + li { + float: left; + position: relative; + display: block; + height: 28px; + padding: 4px 5px; - background-color: #eee; - border-top: 1px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; + background-color: #eee; + border-top: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; - &.item-name { - width: 120px; - //font-size: 90%; - //color: #999; - //text-align: center; - //white-space: nowrap; - //overflow: hidden; - //text-overflow: ellipsis; - } + &.item-name { + width: 120px; + //font-size: 90%; + //color: #999; + //text-align: center; + //white-space: nowrap; + //overflow: hidden; + //text-overflow: ellipsis; + } - &.item-btn { - background: none; - padding: 0; - border: none; - } + &.item-btn { + background: none; + padding: 0; + border: none; + } - &.item-input { - background: none; - border: none; - padding: 0; - } + &.item-input { + background: none; + border: none; + padding: 0; + } - .form-control { - line-height: 1.5; - margin: 0; - padding: 4px 5px; - font-size: 12px; - height: 28px; - border-radius: 0; - border-left: none; - width: 100px; - } + .form-control { + line-height: 1.5; + margin: 0; + padding: 4px 5px; + font-size: 12px; + height: 28px; + border-radius: 0; + border-left: none; + width: 100px; + } - label { - padding: 0; - display: block; - float: left; - margin-top: 1px; - cursor: pointer; - } - input[type=checkbox] { - display: block; - float: left; - margin: 3px 5px 0 0; - } - //select { - // margin-top: -3px; - //} + label { + padding: 0; + display: block; + float: left; + margin-top: 1px; + cursor: pointer; + } + input[type=checkbox] { + display: block; + float: left; + margin: 3px 5px 0 0; + } + //select { + // margin-top: -3px; + //} - &:first-child { - border-left: 1px solid #ccc; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; + &:first-child { + border-left: 1px solid #ccc; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; - .btn, .form-control { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - } - } + .btn, .form-control { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + } - &:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; + &:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; - .btn, .form-control { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - } - } - } - } + .btn, .form-control { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + } + } + } } .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - padding-right: 5px; - padding-left: 5px; + padding-right: 5px; + padding-left: 5px; } .form-group-sm .form-control-static { - padding: 6px 0; + padding: 6px 0; } + +.input-group { + + :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: @color-placeholder; + } + + ::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: @color-placeholder; + } + + input:-ms-input-placeholder, + textarea:-ms-input-placeholder { + color: @color-placeholder; + } + + input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { + color: @color-placeholder; + } +} \ No newline at end of file diff --git a/server/www/teleport/static/less/_color.less b/server/www/teleport/static/less/_color.less index 725b8cf..2e7183e 100644 --- a/server/www/teleport/static/less/_color.less +++ b/server/www/teleport/static/less/_color.less @@ -27,6 +27,7 @@ @color-text-on-dark-bg: #fff; @color-text-shadow-on-dark-bg: #525252; +@color-placeholder: #d2d2d2; //.text-warning { // color: @color-text-warning !important; diff --git a/server/www/teleport/static/less/auth.less b/server/www/teleport/static/less/auth.less index 2fb559f..78b8184 100644 --- a/server/www/teleport/static/less/auth.less +++ b/server/www/teleport/static/less/auth.less @@ -1,246 +1,246 @@ -@charset "utf-8"; -body { - padding-top: 70px; - padding-bottom: 24px; - background-color: #ececed; -} - -#head nav.navbar { - height: 70px; - line-height: 70px; - background-color: #333; - color: #fff; -} - -#head .logo .desc { - display: block; - float: right; - color: #ccc; - margin-top: 10px; - font-size: 18px; -} - -#foot nav.navbar { - min-height: 24px; - height: 24px; - line-height: 24px; - background-color: #ddd; - color: #fff; - font-size: 12px; - border-top: 1px solid #ccc; -} - -#foot nav.navbar .container { - height: 24px; -} - -#foot nav.navbar p { - margin: 0 auto; - text-align: center; - color: #333; -} - -#content { - margin: 10px 0 50px 0; -} - -.auth-box { - margin-top: 30px; - min-height: 120px; - border: 1px solid #ccc; - border-radius: 8px; - background-color: rgba(255, 255, 255, 0.6); -} - -.auth-box .header { - min-height: 50px; - height: 50px; - border: none; - box-shadow: none; - border-bottom: 1px solid #ccc; -} - -.auth-box .header .title { - display: inline-block; - float: left; - margin-left: 60px; - height: 24px; - margin-top: 25px; - line-height: 16px; - font-size: 20px; - color: #999; -} - -.auth-box .header .selected { - border-bottom: 1px solid #6699cc; - color: #555; -} - -.auth-box .header .title:hover { - border-bottom: 1px solid #999; -} - -.auth-box .inputarea { - margin: 30px; -} - -.auth-box .inputarea .input-group-addon { - padding: 0 5px 0 5px; -} - -.auth-box .inputarea p.input-addon-desc { - text-align: right; - padding: 0 5px 0 5px; - color: #999; -} - -/*.auth-box .inputbox { - border:1px solid #6699cc; - border-radius:2px; - height:38px; - margin-bottom:20px; -} - -.auth-box .inputbox .input { - display: inline-block; - outline:none; - border-style:none; - position:relative; - width:360px; - padding:10px 10px 0 10px; - line-height: 18px; - font-family: Verdana, Tahoma, Ariall; - font-size:16px; -} - -.auth-box .inputbox .clean { - display:inline-block; - position:relative; - top:5px; - right:0px;height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat; -} -*/ - -#leftside { - width: 560px; - height: 560px; - padding-top: 60px; - background: url(../img/login/side-001.jpg) 0 0 no-repeat; -} - -@media screen and (max-width: 990px) { - #leftside { - display: none; - } -} - -#leftside h1 { - font-size: 24px; - color: #888; -} - -#leftside p { - font-size: 18px; - color: #888; - padding-left: 24px; -} - -.auth-box .inputbox { - margin-bottom: 10px; -} - -.auth-box-lg .inputbox { - margin-bottom: 20px; -} - -/*.auth-box .inputbox .clean { - display: block; - height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat; -}*/ - -/*.auth-box .inputarea label { - font-size:12px; - font-weight:400; - color:#999; - cursor:pointer; -}*/ - -.auth-box .op_box { - display: block; - padding: 5px; - border-radius: 3px; - text-align: center; - //margin-top: 5px; - margin: 5px 20px 10px 20px; -} - -.auth-box .op_error { - background: #fbb; -} - -.auth-box .op_wait { - background: #ccc; -} - -.auth-box { - - .quick-area { - padding: 80px 0 80px 0; - - .quick-disc { - //font-size:120%; - text-align: center; - //margin:auto; - margin-bottom: 20px; - - } - - .quick-no { - padding-top: 80px; - padding-bottom: 100px; - } - .quick-yes { - text-align: center; - //margin: auto; - - .quick-account { - display: inline-block; - margin: auto; - margin-bottom: 20px; - - //border:1px solid #888; - - &:hover { - .quick-image { - //background-color: #00bcee; - box-shadow: 0 0 8px rgb(0, 194, 246); - } - } - } - - .quick-image { - display: block; - width: 82px; - height: 82px; - line-height: 80px; - font-size: 64px; - margin: auto; - - //border-radius: 5px; - //color:#fff; - //background-color: #00acda; - //padding:3px; - border: 1px solid #a4cdf6; - box-shadow: 0 0 6px rgba(167, 209, 251, 1); - } - - .quick-name { - display: block; - margin-top: 5px; - } - } - - } -} - - +@charset "utf-8"; +body { + padding-top: 70px; + padding-bottom: 24px; + background-color: #ececed; +} + +#head nav.navbar { + height: 70px; + line-height: 70px; + background-color: #333; + color: #fff; +} + +#head .logo .desc { + display: block; + float: right; + color: #ccc; + margin-top: 10px; + font-size: 18px; +} + +#foot nav.navbar { + min-height: 24px; + height: 24px; + line-height: 24px; + background-color: #ddd; + color: #fff; + font-size: 12px; + border-top: 1px solid #ccc; +} + +#foot nav.navbar .container { + height: 24px; +} + +#foot nav.navbar p { + margin: 0 auto; + text-align: center; + color: #333; +} + +#content { + margin: 10px 0 50px 0; +} + +.auth-box { + margin-top: 30px; + min-height: 120px; + border: 1px solid #ccc; + border-radius: 8px; + background-color: rgba(255, 255, 255, 0.6); +} + +.auth-box .header { + min-height: 50px; + height: 50px; + border: none; + box-shadow: none; + border-bottom: 1px solid #ccc; +} + +.auth-box .header .title { + display: inline-block; + float: left; + margin-left: 60px; + height: 24px; + margin-top: 25px; + line-height: 16px; + font-size: 20px; + color: #999; +} + +.auth-box .header .selected { + border-bottom: 2px solid #4882cc; + color: #555; +} + +.auth-box .header .title:hover { + border-bottom: 2px solid #5396eb; +} + +.auth-box .inputarea { + margin: 30px; +} + +.auth-box .inputarea .input-group-addon { + padding: 0 5px 0 5px; +} + +.auth-box .inputarea p.input-addon-desc { + text-align: right; + padding: 0 5px 0 5px; + color: #999; +} + +/*.auth-box .inputbox { + border:1px solid #6699cc; + border-radius:2px; + height:38px; + margin-bottom:20px; +} + +.auth-box .inputbox .input { + display: inline-block; + outline:none; + border-style:none; + position:relative; + width:360px; + padding:10px 10px 0 10px; + line-height: 18px; + font-family: Verdana, Tahoma, Ariall; + font-size:16px; +} + +.auth-box .inputbox .clean { + display:inline-block; + position:relative; + top:5px; + right:0px;height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat; +} +*/ + +#leftside { + width: 560px; + height: 560px; + padding-top: 60px; + background: url(../img/login/side-001.jpg) 0 0 no-repeat; +} + +@media screen and (max-width: 990px) { + #leftside { + display: none; + } +} + +#leftside h1 { + font-size: 24px; + color: #888; +} + +#leftside p { + font-size: 18px; + color: #888; + padding-left: 24px; +} + +.auth-box .inputbox { + margin-bottom: 10px; +} + +.auth-box-lg .inputbox { + margin-bottom: 20px; +} + +/*.auth-box .inputbox .clean { + display: block; + height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat; +}*/ + +/*.auth-box .inputarea label { + font-size:12px; + font-weight:400; + color:#999; + cursor:pointer; +}*/ + +.auth-box .op_box { + display: block; + padding: 5px; + border-radius: 3px; + text-align: center; + //margin-top: 5px; + margin: 5px 20px 10px 20px; +} + +.auth-box .op_error { + background: #fbb; +} + +.auth-box .op_wait { + background: #ccc; +} + +.auth-box { + + .quick-area { + padding: 80px 0 80px 0; + + .quick-disc { + //font-size:120%; + text-align: center; + //margin:auto; + margin-bottom: 20px; + + } + + .quick-no { + padding-top: 80px; + padding-bottom: 100px; + } + .quick-yes { + text-align: center; + //margin: auto; + + .quick-account { + display: inline-block; + margin: auto; + margin-bottom: 20px; + + //border:1px solid #888; + + &:hover { + .quick-image { + //background-color: #00bcee; + box-shadow: 0 0 8px rgb(0, 194, 246); + } + } + } + + .quick-image { + display: block; + width: 82px; + height: 82px; + line-height: 80px; + font-size: 64px; + margin: auto; + + //border-radius: 5px; + //color:#fff; + //background-color: #00acda; + //padding:3px; + border: 1px solid #a4cdf6; + box-shadow: 0 0 6px rgba(167, 209, 251, 1); + } + + .quick-name { + display: block; + margin-top: 5px; + } + } + + } +} + + diff --git a/server/www/teleport/view/auth/login.mako b/server/www/teleport/view/auth/login.mako index 6516676..4e2212b 100644 --- a/server/www/teleport/view/auth/login.mako +++ b/server/www/teleport/view/auth/login.mako @@ -1,78 +1,89 @@ -<%! - # -*- coding: utf-8 -*- - page_title_ = '登录' - ## page_menu_ = ['host'] - ## page_id_ = 'host' -%> -<%inherit file="page_base.mako"/> - -<%block name="extend_js"> - - - -<%block name="embed_js" > - - - -
-
-
-
-
- - -
-
-
-
账号/密码 登录
-
- -
- - -
-
- - - -
-

验证码,点击图片可更换

-
- -
-
- -
-
- -
- -
- -
-
- -
- -
-
-
- +<%! + # -*- coding: utf-8 -*- + page_title_ = '登录' + ## page_menu_ = ['host'] + ## page_id_ = 'host' +%> +<%inherit file="page_base.mako"/> + +<%block name="extend_js"> + + + +<%block name="embed_js" > + + + +
+
+
+
+
+ + +
+
+ + +
+ + +
+
+ + + +
+

验证码,点击图片可更换

+
+ + + + +
+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ diff --git a/server/www/teleport/view/common/_sidebar_nav_menu.mako b/server/www/teleport/view/common/_sidebar_nav_menu.mako index 809cd2a..a2face9 100644 --- a/server/www/teleport/view/common/_sidebar_nav_menu.mako +++ b/server/www/teleport/view/common/_sidebar_nav_menu.mako @@ -101,18 +101,19 @@ diff --git a/server/www/teleport/view/maintenance/upgrade.mako b/server/www/teleport/view/maintenance/upgrade.mako index 061a068..41233ba 100644 --- a/server/www/teleport/view/maintenance/upgrade.mako +++ b/server/www/teleport/view/maintenance/upgrade.mako @@ -93,7 +93,7 @@ ywl.ajax_post_json('/maintenance/rpc', {cmd: 'upgrade_db'}, function (ret) { console.log('upgrade-db:', ret); - if (ret.code == 0) { + if (ret.code === 0) { var cb_stack = CALLBACK_STACK.create(); cb_stack @@ -112,7 +112,7 @@ ywl.get_task_ret = function (cb_stack, cb_args) { var task_id = cb_args.task_id || 0; - if (task_id == 0) { + if (task_id === 0) { console.log('task-id', task_id); return; } @@ -120,7 +120,7 @@ ywl.ajax_post_json('/maintenance/rpc', {cmd: 'get_task_ret', 'tid': task_id}, function (ret) { console.log('get_task_ret:', ret); - if (ret.code == 0) { + if (ret.code === 0) { // show step progress. var steps = ret.data.steps; @@ -130,14 +130,20 @@ var icon_class = ''; var err_class = ''; for(var i = 0; i < steps.length; ++i) { - if(steps[i].stat == 0) + if(steps[i].code !== 0) { + err_class = ' class="error"'; + icon_class = 'fa-times-circle'; + } + else { + err_class = ''; icon_class = 'fa-check'; + } + + if(steps[i].stat === 0) + ;//icon_class = 'fa-check'; else icon_class = 'fa-cog fa-spin'; - if(steps[i].code != 0) - err_class = ' class="error"'; - else - err_class = ''; + html.push(' +
+ +
+

这是一级标题,This is H1.

这是二级标题,This is H2.

diff --git a/server/www/teleport/view/user/personal.mako b/server/www/teleport/view/user/personal.mako new file mode 100644 index 0000000..4b6c8cd --- /dev/null +++ b/server/www/teleport/view/user/personal.mako @@ -0,0 +1,222 @@ +<%! + page_title_ = '个人中心' + page_menu_ = ['personal'] + page_id_ = 'personal' +%> +<%inherit file="../page_base.mako"/> + +<%block name="extend_js"> + + + + +<%block name="breadcrumb"> + + + +<%block name="extend_css"> + + + +## Begin Main Body. + +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
登录名:${user['name']}
姓名:${user['nick_name']}
邮箱:-
手机:-
注册时间:-
上次登录时间:-
+
+ +
+
+ 当前密码: + +
+
+ 新密码: + +
+
+ 重复新密码: + +
+ +
+ +
+

请在你的手机上安装身份验证器,然后在验证器中添加你的登录账号。

+ +

显示下载地址

+ +
+

要验证已经绑定的身份验证器,可在下面的输入框中输入验证器器上显示的动态验证码,然后点击验证。

+

如果验证失败,请注意检查您的身份验证器的时间与服务器时间是否一致,如果两者时间偏差超过两分钟则无法验证通过!

+
+
+ 动态验证码(6位数字): + + +
+ +
+
+
+ +
+

要将你的登录账号添加到身份验证器中,请点击下面的“绑定身份验证器”按钮。

+

+ +

+ +
+
+
+ +
+ + + + + + +<%block name="extend_content"> + +