mirror of https://github.com/tp4a/teleport
支持google身份验证器做双因子登录了。
parent
7881d0f1ee
commit
6ef17c79b1
|
@ -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:])
|
|
@ -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
|
|
@ -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()
|
|
@ -0,0 +1,5 @@
|
||||||
|
# QR error correct levels
|
||||||
|
ERROR_CORRECT_L = 1
|
||||||
|
ERROR_CORRECT_M = 0
|
||||||
|
ERROR_CORRECT_Q = 3
|
||||||
|
ERROR_CORRECT_H = 2
|
|
@ -0,0 +1,2 @@
|
||||||
|
class DataOverflowError(Exception):
|
||||||
|
pass
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 <path> 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'
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,8 @@
|
||||||
|
import string
|
||||||
|
import qrcode
|
||||||
|
|
||||||
|
qr = qrcode.QRCode()
|
||||||
|
|
||||||
|
qr.add_data(string.letters*13)
|
||||||
|
qr.make()
|
||||||
|
print(qr.version)
|
|
@ -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)
|
|
@ -24,7 +24,8 @@ def create_and_init(db, step_begin, step_end):
|
||||||
`account_pwd` varchar(128) DEFAULT NULL,
|
`account_pwd` varchar(128) DEFAULT NULL,
|
||||||
`account_status` int(11) DEFAULT 0,
|
`account_status` int(11) DEFAULT 0,
|
||||||
`account_lock` 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))
|
);""".format(db.table_prefix, db.auto_increment))
|
||||||
|
|
||||||
_db_exec(db, step_begin, step_end, '创建表 auth', """CREATE TABLE `{}auth`(
|
_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` (
|
_db_exec(db, step_begin, step_end, '创建表 group', """CREATE TABLE `{}group` (
|
||||||
`group_id` integer PRIMARY KEY {},
|
`group_id` integer PRIMARY KEY {},
|
||||||
`group_name` varchar(255) DEFAULT''
|
`group_name` varchar(255) DEFAULT ''
|
||||||
);""".format(db.table_prefix, db.auto_increment))
|
);""".format(db.table_prefix, db.auto_increment))
|
||||||
|
|
||||||
_db_exec(db, step_begin, step_end, '创建表 host_info', """CREATE TABLE `{}host_info`(
|
_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,
|
_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,
|
_db_exec(db, step_begin, step_end,
|
||||||
|
|
|
@ -48,7 +48,7 @@ def export_database(db):
|
||||||
else:
|
else:
|
||||||
return 'Unknown Database Type'
|
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))
|
ret.append(_export_table(db, 'account', _fields))
|
||||||
_fields = ['auth_id', 'account_name', 'host_id', 'host_auth_id']
|
_fields = ['auth_id', 'account_name', 'host_id', 'host_auth_id']
|
||||||
ret.append(_export_table(db, 'auth', _fields))
|
ret.append(_export_table(db, 'auth', _fields))
|
||||||
|
@ -66,101 +66,3 @@ def export_database(db):
|
||||||
ret.append(_export_table(db, 'log', _fields))
|
ret.append(_export_table(db, 'log', _fields))
|
||||||
|
|
||||||
return '\r\n'.join(ret)
|
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
|
|
||||||
|
|
|
@ -526,3 +526,38 @@ class DatabaseUpgrade:
|
||||||
log.e('failed.\n')
|
log.e('failed.\n')
|
||||||
self.step_end(_step, -1)
|
self.step_end(_step, -1)
|
||||||
return False
|
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
|
||||||
|
|
|
@ -19,7 +19,7 @@ __all__ = ['get_db', 'DbItem']
|
||||||
|
|
||||||
class TPDatabase:
|
class TPDatabase:
|
||||||
# 注意,每次调整数据库结构,必须增加版本号,并且在升级接口中编写对应的升级操作
|
# 注意,每次调整数据库结构,必须增加版本号,并且在升级接口中编写对应的升级操作
|
||||||
DB_VERSION = 5
|
DB_VERSION = 6
|
||||||
|
|
||||||
DB_TYPE_UNKNOWN = 0
|
DB_TYPE_UNKNOWN = 0
|
||||||
DB_TYPE_SQLITE = 1
|
DB_TYPE_SQLITE = 1
|
||||||
|
@ -185,7 +185,30 @@ class TPDatabase:
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return None
|
return None
|
||||||
if len(ret) == 0:
|
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
|
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:
|
else:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -312,8 +335,6 @@ class TPDatabase:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def export_to_sql(self):
|
def export_to_sql(self):
|
||||||
# TODO: not implement.
|
|
||||||
|
|
||||||
return export_database(self)
|
return export_database(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -40,6 +40,10 @@ controllers = [
|
||||||
(r'/auth/get-captcha', auth.GetCaptchaHandler),
|
(r'/auth/get-captcha', auth.GetCaptchaHandler),
|
||||||
(r'/auth/verify-captcha', auth.VerifyCaptchaHandler),
|
(r'/auth/verify-captcha', auth.VerifyCaptchaHandler),
|
||||||
(r'/auth/modify-pwd', auth.ModifyPwd),
|
(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/list', group.GetListHandler),
|
||||||
(r'/group/', group.IndexHandler),
|
(r'/group/', group.IndexHandler),
|
||||||
|
@ -49,9 +53,10 @@ controllers = [
|
||||||
(r'/cert/', cert.IndexHandler),
|
(r'/cert/', cert.IndexHandler),
|
||||||
(r'/cert', cert.IndexHandler),
|
(r'/cert', cert.IndexHandler),
|
||||||
|
|
||||||
(r'/pwd', pwd.IndexHandler),
|
# (r'/pwd', pwd.IndexHandler),
|
||||||
(r'/user', user.IndexHandler),
|
(r'/user', user.IndexHandler),
|
||||||
(r'/user/list', user.GetListHandler),
|
(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/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(cfg.data_path, 'replay')}),
|
||||||
(r"/log/replay/(.*)", record.ReplayStaticFileHandler, {"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', index.UIDesignHandler),
|
||||||
(r'/uidesign/without-sidebar', index.UIDesignWithoutSidebarHandler),
|
(r'/uidesign/without-sidebar', index.UIDesignWithoutSidebarHandler),
|
||||||
(r'/uidesign/table', index.UIDesignTableHandler)
|
(r'/uidesign/table', index.UIDesignTableHandler),
|
||||||
|
|
||||||
|
# (r'/test/oath-code', index.OathCodeHandler)
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,7 @@ from eom_app.module import user
|
||||||
from eom_common.eomcore.logger import *
|
from eom_common.eomcore.logger import *
|
||||||
from .base import TPBaseHandler, TPBaseUserAuthHandler, TPBaseJsonHandler, TPBaseUserAuthJsonHandler
|
from .base import TPBaseHandler, TPBaseUserAuthHandler, TPBaseJsonHandler, TPBaseUserAuthJsonHandler
|
||||||
from eom_app.app.util import gen_captcha
|
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):
|
class LoginHandler(TPBaseHandler):
|
||||||
|
@ -33,27 +34,40 @@ class LoginHandler(TPBaseHandler):
|
||||||
|
|
||||||
class VerifyUser(TPBaseJsonHandler):
|
class VerifyUser(TPBaseJsonHandler):
|
||||||
def post(self):
|
def post(self):
|
||||||
code = self.get_session('captcha')
|
# code = self.get_session('captcha')
|
||||||
if code is None:
|
# if code is None:
|
||||||
return self.write_json(-1, '验证码已失效')
|
# return self.write_json(-1, '验证码已失效')
|
||||||
|
#
|
||||||
self.del_session('captcha')
|
# self.del_session('captcha')
|
||||||
|
|
||||||
args = self.get_argument('args', None)
|
args = self.get_argument('args', None)
|
||||||
if args is not None:
|
if args is not None:
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
captcha = args['captcha']
|
login_type = args['type'].strip()
|
||||||
username = args['username']
|
captcha = args['captcha'].strip()
|
||||||
userpwd = args['userpwd']
|
username = args['username'].strip()
|
||||||
|
password = args['password'].strip()
|
||||||
|
oath = args['oath'].strip()
|
||||||
remember = args['remember']
|
remember = args['remember']
|
||||||
else:
|
else:
|
||||||
return self.write_json(-1, '参数错误')
|
return self.write_json(-1, '参数错误')
|
||||||
|
|
||||||
if code.lower() != captcha.lower():
|
if login_type == 'password':
|
||||||
return self.write_json(-1, '验证码错误')
|
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:
|
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:
|
if locked == 1:
|
||||||
return self.write_json(-1, '账号被锁定,请联系管理员!')
|
return self.write_json(-1, '账号被锁定,请联系管理员!')
|
||||||
if user_id == 0:
|
if user_id == 0:
|
||||||
|
@ -81,7 +95,7 @@ class VerifyUser(TPBaseJsonHandler):
|
||||||
_user['type'] = account_type
|
_user['type'] = account_type
|
||||||
|
|
||||||
if remember:
|
if remember:
|
||||||
self.set_session('user', _user, 12*60*60)
|
self.set_session('user', _user, 12 * 60 * 60)
|
||||||
else:
|
else:
|
||||||
self.set_session('user', _user)
|
self.set_session('user', _user)
|
||||||
return self.write_json(0)
|
return self.write_json(0)
|
||||||
|
@ -150,3 +164,104 @@ class ModifyPwd(TPBaseUserAuthJsonHandler):
|
||||||
except:
|
except:
|
||||||
log.e('modify password failed.')
|
log.e('modify password failed.')
|
||||||
return self.write_json(-4, '发生异常')
|
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, '验证失败!')
|
||||||
|
|
|
@ -27,4 +27,11 @@ class UIDesignWithoutSidebarHandler(TPBaseHandler):
|
||||||
|
|
||||||
class UIDesignTableHandler(TPBaseHandler):
|
class UIDesignTableHandler(TPBaseHandler):
|
||||||
def get(self):
|
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')
|
self.render('uidesign/table.mako')
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,12 @@ class IndexHandler(TPBaseAdminAuthHandler):
|
||||||
self.render('user/index.mako')
|
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):
|
class AuthHandler(TPBaseAdminAuthHandler):
|
||||||
def get(self, user_name):
|
def get(self, user_name):
|
||||||
group_list = host.get_group_list()
|
group_list = host.get_group_list()
|
||||||
|
|
|
@ -6,20 +6,26 @@ from eom_app.app.configs import app_cfg
|
||||||
from eom_app.app.const import *
|
from eom_app.app.const import *
|
||||||
from eom_app.app.db import get_db, DbItem
|
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.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()
|
cfg = app_cfg()
|
||||||
|
|
||||||
|
if cfg.app_mode == APP_MODE_MAINTENANCE:
|
||||||
|
if name == 'admin' and password == 'admin':
|
||||||
|
return 1, 100, '系统管理员', 0
|
||||||
|
|
||||||
db = get_db()
|
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)
|
db_ret = db.query(sql)
|
||||||
if db_ret is None:
|
if db_ret is None:
|
||||||
# 特别地,如果无法取得数据库连接,有可能是新安装的系统,尚未建立数据库,此时应该处于维护模式
|
# 特别地,如果无法取得数据库连接,有可能是新安装的系统,尚未建立数据库,此时应该处于维护模式
|
||||||
# 因此可以特别地处理用户验证:用户名admin,密码admin可以登录为管理员
|
# 因此可以特别地处理用户验证:用户名admin,密码admin可以登录为管理员
|
||||||
if cfg.app_mode == APP_MODE_MAINTENANCE:
|
if cfg.app_mode == APP_MODE_MAINTENANCE:
|
||||||
if name == 'admin' and password == 'admin':
|
if name == 'admin' and password == 'admin':
|
||||||
return 1, 100, 'admin', 0
|
return 1, 100, '系统管理员', 0
|
||||||
return 0, 0, '', 0
|
return 0, 0, '', 0
|
||||||
|
|
||||||
if len(db_ret) != 1:
|
if len(db_ret) != 1:
|
||||||
|
@ -27,8 +33,9 @@ def verify_user(name, password):
|
||||||
|
|
||||||
user_id = db_ret[0][0]
|
user_id = db_ret[0][0]
|
||||||
account_type = db_ret[0][1]
|
account_type = db_ret[0][1]
|
||||||
name = db_ret[0][2]
|
desc = db_ret[0][2]
|
||||||
locked = db_ret[0][4]
|
locked = db_ret[0][4]
|
||||||
|
oath_secret = db_ret[0][5]
|
||||||
if locked == 1:
|
if locked == 1:
|
||||||
return 0, 0, '', locked
|
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))
|
sql = 'UPDATE `{}account` SET `account_pwd`="{}" WHERE `account_id`={}'.format(db.table_prefix, _new_sec_password, int(user_id))
|
||||||
db.exec(sql)
|
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):
|
def modify_pwd(old_pwd, new_pwd, user_id):
|
||||||
|
@ -66,6 +93,16 @@ def modify_pwd(old_pwd, new_pwd, user_id):
|
||||||
return -102
|
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):
|
def get_user_list(with_admin=False):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
ret = list()
|
ret = list()
|
||||||
|
|
|
@ -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}
|
@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}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -1,151 +1,218 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
ywl.on_init = function (cb_stack, cb_args) {
|
var LOGIN_TYPE_PASSWORD = 1; // 使用用户名密码登录(额外需要验证码)
|
||||||
if (ywl.page_options.user_name.length > 0) {
|
var LOGIN_TYPE_OATH = 2; // 使用用户名密码登录(额外需要身份验证器的动态验证码)
|
||||||
$('#username_account').val(ywl.page_options.user_name);
|
|
||||||
}
|
ywl.on_init = function (cb_stack, cb_args) {
|
||||||
|
if (ywl.page_options.user_name.length > 0) {
|
||||||
$('#captcha_image').attr('src', '/auth/get-captcha?' + Math.random());
|
$('#username-account').val(ywl.page_options.user_name);
|
||||||
|
}
|
||||||
ywl.app = ywl.create_app();
|
|
||||||
cb_stack
|
$('#captcha-image').attr('src', '/auth/get-captcha?' + Math.random());
|
||||||
.add(ywl.app.init)
|
|
||||||
.exec();
|
ywl.app = ywl.create_app();
|
||||||
};
|
cb_stack
|
||||||
|
.add(ywl.app.init)
|
||||||
ywl.create_app = function () {
|
.exec();
|
||||||
var _app = {};
|
};
|
||||||
|
|
||||||
_app.dom_login_account = null;
|
ywl.create_app = function () {
|
||||||
|
var _app = {};
|
||||||
_app.init = function (cb_stack, cb_args) {
|
|
||||||
_app.dom_login_account = $('#login-type-account');
|
_app.login_type = LOGIN_TYPE_PASSWORD;
|
||||||
|
|
||||||
$('#btn-login-account').click(_app.login_account);
|
_app.dom = {
|
||||||
|
btn_login_type_password: $('#login-type-password'),
|
||||||
$('#captcha_image').click(function () {
|
btn_login_type_oath: $('#login-type-oath'),
|
||||||
$(this).attr('src', '/auth/get-captcha?' + Math.random());
|
area_captcha: $('#login-area-captcha'),
|
||||||
$('#captcha').focus().val('');
|
area_oath: $('#login-area-oath'),
|
||||||
});
|
captcha_image: $('#captcha-image'),
|
||||||
$('#username_account').keydown(function (event) {
|
|
||||||
$('[data-toggle="popover"]').popover('hide');
|
input_username: $('#username-account'),
|
||||||
if (event.which === 13) {
|
input_password: $('#password-account'),
|
||||||
$('#password_account').focus();
|
input_captcha: $('#captcha'),
|
||||||
}
|
input_oath: $('#oath-code'),
|
||||||
});
|
|
||||||
$('#password_account').keydown(function (event) {
|
remember: $('#remember-me'),
|
||||||
$('[data-toggle="popover"]').popover('hide');
|
btn_login: $('#btn-login'),
|
||||||
if (event.which === 13) {
|
|
||||||
$('#captcha').focus();
|
message: $('#message')
|
||||||
}
|
};
|
||||||
});
|
|
||||||
$('#captcha').keydown(function (event) {
|
|
||||||
$('[data-toggle="popover"]').popover('hide');
|
// _app.dom_login_account = null;
|
||||||
if (event.which === 13) {
|
// _app.dom_login_google = null;
|
||||||
_app.login_account();
|
|
||||||
}
|
_app.init = function (cb_stack, cb_args) {
|
||||||
});
|
// _app.dom_login_account = $('#login-type-account');
|
||||||
|
// _app.dom_login_google = $('#login-type-google');
|
||||||
cb_stack.exec();
|
|
||||||
};
|
_app.dom.btn_login_type_password.click(function () {
|
||||||
|
_app.login_type = LOGIN_TYPE_PASSWORD;
|
||||||
_app.login_account = function () {
|
_app.dom.btn_login_type_oath.removeClass('selected');
|
||||||
var str_username = '';
|
$(this).addClass('selected');
|
||||||
var str_password = '';
|
_app.dom.area_oath.slideUp(100);
|
||||||
var str_captcha = '';
|
_app.dom.area_captcha.slideDown(100);
|
||||||
var is_remember = false;
|
});
|
||||||
|
_app.dom.btn_login_type_oath.click(function () {
|
||||||
var dom_username = $('#username_account');
|
_app.login_type = LOGIN_TYPE_OATH;
|
||||||
var dom_password = $('#password_account');
|
_app.dom.btn_login_type_password.removeClass('selected');
|
||||||
var dom_captcha = $('#captcha');
|
$(this).addClass('selected');
|
||||||
var dom_remember = $('#remember-me');
|
_app.dom.area_oath.slideDown(100);
|
||||||
|
_app.dom.area_captcha.slideUp(100);
|
||||||
str_username = dom_username.val();
|
});
|
||||||
str_password = dom_password.val();
|
|
||||||
str_captcha = dom_captcha.val();
|
|
||||||
is_remember = dom_remember.is(':checked');
|
_app.dom.btn_login.click(_app.login_account);
|
||||||
|
|
||||||
if (str_username.length === 0) {
|
_app.dom.captcha_image.click(function () {
|
||||||
show_op_box('error', '缺少账号!');
|
$(this).attr('src', '/auth/get-captcha?' + Math.random());
|
||||||
dom_username.attr('data-content', "请填写您的账号!").popover('show');
|
_app.dom.input_captcha.focus().val('');
|
||||||
dom_username.focus();
|
});
|
||||||
return;
|
_app.dom.input_username.keydown(function (event) {
|
||||||
}
|
$('[data-toggle="popover"]').popover('hide');
|
||||||
|
if (event.which === 13) {
|
||||||
if (str_password.length === 0) {
|
_app.dom.input_password.focus();
|
||||||
show_op_box('error', '缺少密码!');
|
}
|
||||||
dom_password.attr('data-content', "请填写密码!").popover('show');
|
});
|
||||||
dom_password.focus();
|
_app.dom.input_password.keydown(function (event) {
|
||||||
return;
|
$('[data-toggle="popover"]').popover('hide');
|
||||||
}
|
if (event.which === 13) {
|
||||||
|
if(_app.login_type === LOGIN_TYPE_PASSWORD)
|
||||||
if (str_captcha.length !== 4) {
|
_app.dom.input_captcha.focus();
|
||||||
show_op_box('error', '验证码错误!');
|
else if(_app.login_type === LOGIN_TYPE_OATH)
|
||||||
dom_captcha.attr('data-content', "验证码为4位数字和字母的组合,请重新填写!").popover('show').focus();
|
_app.dom.input_oath.focus();
|
||||||
return;
|
}
|
||||||
}
|
});
|
||||||
|
_app.dom.input_captcha.keydown(function (event) {
|
||||||
$('#btn_login').attr('disabled', 'disabled');
|
$('[data-toggle="popover"]').popover('hide');
|
||||||
show_op_box('wait', '<i class="fa fa-circle-o-notch fa-spin"></i> 正在进行身份认证,请稍候...');
|
if (event.which === 13) {
|
||||||
|
_app.login_account();
|
||||||
// 先判断一下captcha是否正确,如果不正确,拒绝登录
|
}
|
||||||
ywl.ajax_post_json('/auth/verify-captcha', {captcha: str_captcha},
|
});
|
||||||
function (ret) {
|
|
||||||
if (ret.code === TPE_OK) {
|
cb_stack.exec();
|
||||||
// 验证成功
|
};
|
||||||
hide_op_box();
|
|
||||||
show_op_box('wait', '<i class="fa fa-circle-o-notch fa-spin"></i> 正在登录TELEPORT,请稍候...');
|
_app.login_account = function () {
|
||||||
_app.do_account_login(str_username, str_password, str_captcha, is_remember);
|
// var str_username = '';
|
||||||
}
|
// var str_password = '';
|
||||||
else {
|
// var str_captcha = '';
|
||||||
hide_op_box();
|
// var str_gcode = '';
|
||||||
show_op_box('error', '验证码错误!');
|
// var is_remember = false;
|
||||||
$('#captcha_image').attr('src', '/auth/get-captcha?' + Math.random());
|
//
|
||||||
$('#captcha').focus().val('');
|
// var dom_username = $('#username-account');
|
||||||
}
|
// var dom_password = $('#password-account');
|
||||||
|
// var dom_captcha = $('#captcha');
|
||||||
$('#btn_login').removeAttr('disabled');
|
// var dom_gcode = $('#oath-code');
|
||||||
},
|
// var dom_remember = $('#remember-me');
|
||||||
function () {
|
|
||||||
hide_op_box();
|
var str_username = _app.dom.input_username.val();
|
||||||
show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!');
|
var str_password = _app.dom.input_password.val();
|
||||||
$('#btn_login').removeAttr('disabled');
|
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) {
|
||||||
_app.do_account_login = function (username, userpwd, captcha, is_remember) {
|
show_op_box('error', '缺少账号!');
|
||||||
ywl.ajax_post_json('/auth/verify-user', {username: username, userpwd: userpwd, captcha: captcha, remember: is_remember},
|
_app.dom.input_username.attr('data-content', "请填写您的账号!").popover('show');
|
||||||
function (ret) {
|
_app.dom.input_username.focus();
|
||||||
if (ret.code === TPE_OK) {
|
return;
|
||||||
window.location.href = ywl.page_options.ref;
|
}
|
||||||
} else {
|
|
||||||
hide_op_box();
|
if (str_password.length === 0) {
|
||||||
show_op_box('error', '无法登录TELEPORT:' + ret.message);
|
show_op_box('error', '缺少密码!');
|
||||||
console.log(ret);
|
_app.dom.input_password.attr('data-content', "请填写密码!").popover('show');
|
||||||
}
|
_app.dom.input_password.focus();
|
||||||
|
return;
|
||||||
$('#btn_login').removeAttr('disabled');
|
}
|
||||||
},
|
|
||||||
function () {
|
if (_app.login_type === 'account') {
|
||||||
hide_op_box();
|
if (str_captcha.length !== 4) {
|
||||||
show_op_box('error', '很抱歉,无法连接服务器!请稍后再试一次!');
|
show_op_box('error', '验证码错误!');
|
||||||
$('#btn_login').removeAttr('disabled');
|
_app.dom.input_captcha.attr('data-content', "验证码为4位数字和字母的组合,请重新填写!").popover('show').focus();
|
||||||
}
|
return;
|
||||||
);
|
}
|
||||||
};
|
} else if (_app.login_type === 'google') {
|
||||||
|
if (str_oath.length !== 6) {
|
||||||
return _app;
|
show_op_box('error', '身份验证器动态验证码错误!');
|
||||||
};
|
_app.dom.input_oath.attr('data-content', "身份验证器动态验证码为6位数字,请重新填写!").popover('show').focus();
|
||||||
|
return;
|
||||||
function hide_op_box() {
|
}
|
||||||
$('#login_message').hide();
|
}
|
||||||
}
|
|
||||||
|
_app.dom.btn_login.attr('disabled', 'disabled');
|
||||||
function show_op_box(op_type, op_msg) {
|
_app.show_op_box('wait', '<i class="fa fa-circle-o-notch fa-spin"></i> 正在进行身份认证,请稍候...');
|
||||||
var obj_box = $('#login_message');
|
|
||||||
|
// 先判断一下captcha是否正确,如果不正确,拒绝登录
|
||||||
obj_box.html(op_msg);
|
if (_app.login_type === LOGIN_TYPE_PASSWORD) {
|
||||||
obj_box.removeClass().addClass('op_box op_' + op_type);
|
ywl.ajax_post_json('/auth/verify-captcha', {captcha: str_captcha},
|
||||||
obj_box.show();
|
function (ret) {
|
||||||
}
|
if (ret.code === TPE_OK) {
|
||||||
|
// 验证成功
|
||||||
|
_app.hide_op_box();
|
||||||
|
_app.show_op_box('wait', '<i class="fa fa-circle-o-notch fa-spin"></i> 正在登录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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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('收起 <i class="fa fa-angle-up"></i>');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ywl.dom.oath_app_download_box.slideUp('fast', function () {
|
||||||
|
ywl.dom.btn_toggle_oath_download.html('显示下载地址 <i class="fa fa-angle-down"></i>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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('网路故障,无法连接到服务器!');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
|
@ -5,515 +5,535 @@
|
||||||
@font-family-mono: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace;
|
@font-family-mono: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: @font-family-normal;
|
font-family: @font-family-normal;
|
||||||
//font-family: "微软雅黑", "Microsoft YaHei", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
//font-family: "微软雅黑", "Microsoft YaHei", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background-color: @page-bg;
|
background-color: @page-bg;
|
||||||
color: @page-color;
|
color: @page-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-container {
|
#page-container {
|
||||||
min-width: 1260px;
|
min-width: 1260px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:link {
|
a:link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:active {
|
a:active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:visited {
|
a:visited {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-float {
|
.clear-float {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bigger {
|
.bigger {
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal-text {
|
.normal-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: @page-color;
|
color: @page-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mono {
|
.mono {
|
||||||
font-family:@font-family-mono;
|
font-family: @font-family-mono;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr.hr-sm {
|
hr.hr-sm {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
// 重载bootstrap的样式
|
// 重载bootstrap的样式
|
||||||
//==============================================
|
//==============================================
|
||||||
.btn-group-sm > .btn, .btn-sm {
|
.btn-group-sm > .btn, .btn-sm {
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
//margin-bottom: 2px;
|
//margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-sm {
|
.btn.btn-sm {
|
||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.btn-icon {
|
.btn.btn-icon {
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
&.btn-sm {
|
&.btn-sm {
|
||||||
//padding:1px 3px;
|
//padding:1px 3px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group-sm .input-group .input-group-btn > .btn {
|
.form-group-sm .input-group .input-group-btn > .btn {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pop-menu-backdrop {
|
.pop-menu-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1040;
|
z-index: 1040;
|
||||||
}
|
}
|
||||||
|
|
||||||
//.modal {
|
//.modal {
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
//}
|
//}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
min-width: 8px;
|
min-width: 8px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
|
|
||||||
background-color: @color-bg-default;
|
background-color: @color-bg-default;
|
||||||
color: @color-text-on-dark-bg;
|
color: @color-text-on-dark-bg;
|
||||||
text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg;
|
text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg;
|
||||||
|
|
||||||
&.badge-plain {
|
&.badge-plain {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.badge-sm {
|
&.badge-sm {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.badge-sup {
|
&.badge-sup {
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.badge-ignore {
|
&.badge-ignore {
|
||||||
background-color: @color-bg-ignore;
|
background-color: @color-bg-ignore;
|
||||||
color: @color-text-ignore;
|
color: @color-text-ignore;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
&.badge-info {
|
&.badge-info {
|
||||||
background-color: @color-bg-info;
|
background-color: @color-bg-info;
|
||||||
}
|
}
|
||||||
&.badge-primary {
|
&.badge-primary {
|
||||||
background-color: @color-bg-primary;
|
background-color: @color-bg-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.badge-success {
|
&.badge-success {
|
||||||
background-color: @color-bg-success;
|
background-color: @color-bg-success;
|
||||||
}
|
}
|
||||||
&.badge-warning {
|
&.badge-warning {
|
||||||
background-color: @color-bg-warning;
|
background-color: @color-bg-warning;
|
||||||
}
|
}
|
||||||
&.badge-danger {
|
&.badge-danger {
|
||||||
background-color: @color-bg-danger;
|
background-color: @color-bg-danger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
min-width: 8px;
|
min-width: 8px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
|
|
||||||
background-color: @color-bg-default;
|
background-color: @color-bg-default;
|
||||||
color: @color-text-on-dark-bg;
|
color: @color-text-on-dark-bg;
|
||||||
text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg;
|
text-shadow: 1px 1px 0 @color-text-shadow-on-dark-bg;
|
||||||
|
|
||||||
&.label-plain {
|
&.label-plain {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.label-sm {
|
&.label-sm {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.label-ignore {
|
&.label-ignore {
|
||||||
background-color: @color-bg-ignore;
|
background-color: @color-bg-ignore;
|
||||||
color: @color-text-ignore;
|
color: @color-text-ignore;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
&.label-info {
|
&.label-info {
|
||||||
background-color: @color-bg-info;
|
background-color: @color-bg-info;
|
||||||
}
|
}
|
||||||
&.label-primary {
|
&.label-primary {
|
||||||
background-color: @color-bg-primary;
|
background-color: @color-bg-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.label-success {
|
&.label-success {
|
||||||
background-color: @color-bg-success;
|
background-color: @color-bg-success;
|
||||||
}
|
}
|
||||||
&.label-warning {
|
&.label-warning {
|
||||||
background-color: @color-bg-warning;
|
background-color: @color-bg-warning;
|
||||||
}
|
}
|
||||||
&.label-danger {
|
&.label-danger {
|
||||||
background-color: @color-bg-danger;
|
background-color: @color-bg-danger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格页面中的一些小部件
|
// 表格页面中的一些小部件
|
||||||
.progress.progress-sm {
|
.progress.progress-sm {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
background-color: #aaa;
|
background-color: #aaa;
|
||||||
&.button {
|
&.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
float: none;
|
float: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-sm {
|
.alert-sm {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog-sm {
|
.modal-dialog-sm {
|
||||||
.modal-header {
|
.modal-header {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-horizontal .form-group {
|
.form-horizontal .form-group {
|
||||||
margin-right: -5px;
|
margin-right: -5px;
|
||||||
margin-left: -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 {
|
.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-right: 5px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-single-line {
|
.btn-single-line {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
.btn {
|
.btn {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
}
|
}
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
border-bottom-right-radius: 3px;
|
border-bottom-right-radius: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.remote-action-group {
|
.remote-action-group {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
min-width: 390px;
|
min-width: 390px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: block;
|
display: block;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
float: left;
|
float: left;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 4px 5px;
|
padding: 4px 5px;
|
||||||
|
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
|
|
||||||
&.remote-action-btn {
|
&.remote-action-btn {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.remote-action-input {
|
&.remote-action-input {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
|
|
||||||
select {
|
select {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.remote-action-chk-protocol {
|
&.remote-action-chk-protocol {
|
||||||
width: 86px;
|
width: 86px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.remote-action-username, &.remote-action-name, &.remote-action-protocol {
|
&.remote-action-username, &.remote-action-name, &.remote-action-protocol {
|
||||||
width: 96px;
|
width: 96px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
&.remote-action-username {
|
&.remote-action-username {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
&.remote-action-name, &.remote-action-protocol, &.remote-action-chk-protocol {
|
&.remote-action-name, &.remote-action-protocol, &.remote-action-chk-protocol {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
&.remote-action-name, &.remote-action-chk-protocol {
|
&.remote-action-name, &.remote-action-chk-protocol {
|
||||||
font-weight:bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.remote-action-password, &.remote-action-sshkey, &.remote-action-noauth {
|
&.remote-action-password, &.remote-action-sshkey, &.remote-action-noauth {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
width:45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
&.remote-action-password {
|
&.remote-action-password {
|
||||||
background-color: #e3ffe3;
|
background-color: #e3ffe3;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
&.remote-action-sshkey {
|
&.remote-action-sshkey {
|
||||||
background-color: #fbe9c8;
|
background-color: #fbe9c8;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
&.remote-action-noauth {
|
&.remote-action-noauth {
|
||||||
background-color: #e0e0e0;
|
background-color: #e0e0e0;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
margin: 3px 5px 0 0;
|
margin: 3px 5px 0 0;
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid #ccc;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hr.small {
|
hr.small {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dlg-protocol-group {
|
.dlg-protocol-group {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: block;
|
display: block;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
float: left;
|
float: left;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 4px 5px;
|
padding: 4px 5px;
|
||||||
|
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
|
|
||||||
&.item-name {
|
&.item-name {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
//font-size: 90%;
|
//font-size: 90%;
|
||||||
//color: #999;
|
//color: #999;
|
||||||
//text-align: center;
|
//text-align: center;
|
||||||
//white-space: nowrap;
|
//white-space: nowrap;
|
||||||
//overflow: hidden;
|
//overflow: hidden;
|
||||||
//text-overflow: ellipsis;
|
//text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.item-btn {
|
&.item-btn {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.item-input {
|
&.item-input {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 5px;
|
padding: 4px 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
margin: 3px 5px 0 0;
|
margin: 3px 5px 0 0;
|
||||||
}
|
}
|
||||||
//select {
|
//select {
|
||||||
// margin-top: -3px;
|
// margin-top: -3px;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid #ccc;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
|
|
||||||
.btn, .form-control {
|
.btn, .form-control {
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
|
|
||||||
.btn, .form-control {
|
.btn, .form-control {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-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 {
|
.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-right: 5px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group-sm .form-control-static {
|
.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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
@color-text-on-dark-bg: #fff;
|
@color-text-on-dark-bg: #fff;
|
||||||
@color-text-shadow-on-dark-bg: #525252;
|
@color-text-shadow-on-dark-bg: #525252;
|
||||||
|
|
||||||
|
@color-placeholder: #d2d2d2;
|
||||||
|
|
||||||
//.text-warning {
|
//.text-warning {
|
||||||
// color: @color-text-warning !important;
|
// color: @color-text-warning !important;
|
||||||
|
|
|
@ -1,246 +1,246 @@
|
||||||
@charset "utf-8";
|
@charset "utf-8";
|
||||||
body {
|
body {
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
background-color: #ececed;
|
background-color: #ececed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#head nav.navbar {
|
#head nav.navbar {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
line-height: 70px;
|
line-height: 70px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#head .logo .desc {
|
#head .logo .desc {
|
||||||
display: block;
|
display: block;
|
||||||
float: right;
|
float: right;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#foot nav.navbar {
|
#foot nav.navbar {
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#foot nav.navbar .container {
|
#foot nav.navbar .container {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#foot nav.navbar p {
|
#foot nav.navbar p {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
margin: 10px 0 50px 0;
|
margin: 10px 0 50px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box {
|
.auth-box {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: rgba(255, 255, 255, 0.6);
|
background-color: rgba(255, 255, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .header {
|
.auth-box .header {
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .header .title {
|
.auth-box .header .title {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
float: left;
|
float: left;
|
||||||
margin-left: 60px;
|
margin-left: 60px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .header .selected {
|
.auth-box .header .selected {
|
||||||
border-bottom: 1px solid #6699cc;
|
border-bottom: 2px solid #4882cc;
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .header .title:hover {
|
.auth-box .header .title:hover {
|
||||||
border-bottom: 1px solid #999;
|
border-bottom: 2px solid #5396eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputarea {
|
.auth-box .inputarea {
|
||||||
margin: 30px;
|
margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputarea .input-group-addon {
|
.auth-box .inputarea .input-group-addon {
|
||||||
padding: 0 5px 0 5px;
|
padding: 0 5px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputarea p.input-addon-desc {
|
.auth-box .inputarea p.input-addon-desc {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: 0 5px 0 5px;
|
padding: 0 5px 0 5px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.auth-box .inputbox {
|
/*.auth-box .inputbox {
|
||||||
border:1px solid #6699cc;
|
border:1px solid #6699cc;
|
||||||
border-radius:2px;
|
border-radius:2px;
|
||||||
height:38px;
|
height:38px;
|
||||||
margin-bottom:20px;
|
margin-bottom:20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputbox .input {
|
.auth-box .inputbox .input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
outline:none;
|
outline:none;
|
||||||
border-style:none;
|
border-style:none;
|
||||||
position:relative;
|
position:relative;
|
||||||
width:360px;
|
width:360px;
|
||||||
padding:10px 10px 0 10px;
|
padding:10px 10px 0 10px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
font-family: Verdana, Tahoma, Ariall;
|
font-family: Verdana, Tahoma, Ariall;
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputbox .clean {
|
.auth-box .inputbox .clean {
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
position:relative;
|
position:relative;
|
||||||
top:5px;
|
top:5px;
|
||||||
right:0px;height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat;
|
right:0px;height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#leftside {
|
#leftside {
|
||||||
width: 560px;
|
width: 560px;
|
||||||
height: 560px;
|
height: 560px;
|
||||||
padding-top: 60px;
|
padding-top: 60px;
|
||||||
background: url(../img/login/side-001.jpg) 0 0 no-repeat;
|
background: url(../img/login/side-001.jpg) 0 0 no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 990px) {
|
@media screen and (max-width: 990px) {
|
||||||
#leftside {
|
#leftside {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#leftside h1 {
|
#leftside h1 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
#leftside p {
|
#leftside p {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #888;
|
color: #888;
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .inputbox {
|
.auth-box .inputbox {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box-lg .inputbox {
|
.auth-box-lg .inputbox {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.auth-box .inputbox .clean {
|
/*.auth-box .inputbox .clean {
|
||||||
display: block;
|
display: block;
|
||||||
height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat;
|
height:22px;width:22px;background:url(../img/login/input_right_clean.png) 0 0 no-repeat;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/*.auth-box .inputarea label {
|
/*.auth-box .inputarea label {
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
font-weight:400;
|
font-weight:400;
|
||||||
color:#999;
|
color:#999;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
.auth-box .op_box {
|
.auth-box .op_box {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
//margin-top: 5px;
|
//margin-top: 5px;
|
||||||
margin: 5px 20px 10px 20px;
|
margin: 5px 20px 10px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .op_error {
|
.auth-box .op_error {
|
||||||
background: #fbb;
|
background: #fbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box .op_wait {
|
.auth-box .op_wait {
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box {
|
.auth-box {
|
||||||
|
|
||||||
.quick-area {
|
.quick-area {
|
||||||
padding: 80px 0 80px 0;
|
padding: 80px 0 80px 0;
|
||||||
|
|
||||||
.quick-disc {
|
.quick-disc {
|
||||||
//font-size:120%;
|
//font-size:120%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
//margin:auto;
|
//margin:auto;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-no {
|
.quick-no {
|
||||||
padding-top: 80px;
|
padding-top: 80px;
|
||||||
padding-bottom: 100px;
|
padding-bottom: 100px;
|
||||||
}
|
}
|
||||||
.quick-yes {
|
.quick-yes {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
//margin: auto;
|
//margin: auto;
|
||||||
|
|
||||||
.quick-account {
|
.quick-account {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
//border:1px solid #888;
|
//border:1px solid #888;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.quick-image {
|
.quick-image {
|
||||||
//background-color: #00bcee;
|
//background-color: #00bcee;
|
||||||
box-shadow: 0 0 8px rgb(0, 194, 246);
|
box-shadow: 0 0 8px rgb(0, 194, 246);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-image {
|
.quick-image {
|
||||||
display: block;
|
display: block;
|
||||||
width: 82px;
|
width: 82px;
|
||||||
height: 82px;
|
height: 82px;
|
||||||
line-height: 80px;
|
line-height: 80px;
|
||||||
font-size: 64px;
|
font-size: 64px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
//border-radius: 5px;
|
//border-radius: 5px;
|
||||||
//color:#fff;
|
//color:#fff;
|
||||||
//background-color: #00acda;
|
//background-color: #00acda;
|
||||||
//padding:3px;
|
//padding:3px;
|
||||||
border: 1px solid #a4cdf6;
|
border: 1px solid #a4cdf6;
|
||||||
box-shadow: 0 0 6px rgba(167, 209, 251, 1);
|
box-shadow: 0 0 6px rgba(167, 209, 251, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-name {
|
.quick-name {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,78 +1,89 @@
|
||||||
<%!
|
<%!
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
page_title_ = '登录'
|
page_title_ = '登录'
|
||||||
## page_menu_ = ['host']
|
## page_menu_ = ['host']
|
||||||
## page_id_ = 'host'
|
## page_id_ = 'host'
|
||||||
%>
|
%>
|
||||||
<%inherit file="page_base.mako"/>
|
<%inherit file="page_base.mako"/>
|
||||||
|
|
||||||
<%block name="extend_js">
|
<%block name="extend_js">
|
||||||
<script type="text/javascript" src="${ static_url('js/ui/auth/login.js') }"></script>
|
<script type="text/javascript" src="${ static_url('js/ui/auth/login.js') }"></script>
|
||||||
</%block>
|
</%block>
|
||||||
|
|
||||||
<%block name="embed_js" >
|
<%block name="embed_js" >
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
ywl.add_page_options(${ page_param });
|
ywl.add_page_options(${ page_param });
|
||||||
</script>
|
</script>
|
||||||
</%block>
|
</%block>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div id="leftside">
|
<div id="leftside">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="auth-box auth-box-lg">
|
<div class="auth-box auth-box-lg">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div id="login-type-account" class="title selected">账号/密码 登录</div>
|
<a id="login-type-password" class="title selected" href="javascript:;">账号/密码 登录</a>
|
||||||
</div>
|
<a id="login-type-oath" class="title" href="javascript:;">身份验证器</a>
|
||||||
|
</div>
|
||||||
<div id="input-area-account" class="inputarea">
|
|
||||||
<div id="login_account" class="login-account">
|
<div class="inputarea">
|
||||||
<div class="inputbox">
|
<div id="login-area-account" class="login-account">
|
||||||
<div class="input-group input-group-lg">
|
<div class="inputbox">
|
||||||
<span class="input-group-addon"><i class="fa fa-user fa-fw"></i></span>
|
<div class="input-group input-group-lg">
|
||||||
<input id="username_account" type="text" class="form-control" placeholder="账号:邮箱地址或手机号" data-toggle="popover" data-trigger="manual" data-placement="top">
|
<span class="input-group-addon"><i class="fa fa-user fa-fw"></i></span>
|
||||||
</div>
|
<input id="username-account" type="text" class="form-control" placeholder="账号:邮箱地址或手机号" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="inputbox">
|
|
||||||
<div class="input-group input-group-lg">
|
<div class="inputbox">
|
||||||
<span class="input-group-addon"><i class="fa fa-key fa-fw"></i></span>
|
<div class="input-group input-group-lg">
|
||||||
<input id="password_account" type="password" class="form-control" placeholder="密码" data-toggle="popover" data-trigger="manual" data-placement="top">
|
<span class="input-group-addon"><i class="fa fa-key fa-fw"></i></span>
|
||||||
</div>
|
<input id="password-account" type="password" class="form-control" placeholder="密码" data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
</div>
|
||||||
<div class="inputbox">
|
|
||||||
<div class="input-group input-group-lg">
|
<div id="login-area-captcha" class="inputbox">
|
||||||
<span class="input-group-addon"><i class="fa fa-check-square-o fa-fw"></i></span>
|
<div class="input-group input-group-lg">
|
||||||
<input id="captcha" type="text" class="form-control" placeholder="验证码"
|
<span class="input-group-addon"><i class="fa fa-check-square-o fa-fw"></i></span>
|
||||||
data-toggle="popover" data-trigger="manual" data-placement="top">
|
<input id="captcha" type="text" class="form-control" placeholder="验证码"
|
||||||
<span class="input-group-addon"><a href="javascript:;"><img id="captcha_image" src=""></a></span>
|
data-toggle="popover" data-trigger="manual" data-placement="top">
|
||||||
</div>
|
<span class="input-group-addon"><a href="javascript:;"><img id="captcha-image" src=""></a></span>
|
||||||
<p class="input-addon-desc">验证码,点击图片可更换</p>
|
</div>
|
||||||
</div>
|
<p class="input-addon-desc">验证码,点击图片可更换</p>
|
||||||
|
</div>
|
||||||
<div class="inputbox">
|
|
||||||
<div class="checkbox">
|
<div id="login-area-oath" style="display:none;">
|
||||||
<label><input id="remember-me" type="checkbox" value=""> 记住我,12小时内无需重新登录。</label>
|
<div class="inputbox">
|
||||||
</div>
|
<div class="input-group input-group-lg">
|
||||||
</div>
|
<span class="input-group-addon"><i class="fa fa-google fa-fw"></i></span>
|
||||||
|
<input id="oath-code" type="text" class="form-control" placeholder="谷歌身份验证器动态验证码">
|
||||||
<div class="inputbox">
|
</div>
|
||||||
<button id="btn-login-account" class="btn btn-primary btn-lg btn-block">登 录</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div>
|
<div class="inputbox">
|
||||||
<p id="login_message" class="op_box" style="display:none;"></p>
|
<div class="checkbox">
|
||||||
</div>
|
<label><input id="remember-me" type="checkbox" value=""> 记住我,12小时内无需重新登录。</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="inputbox">
|
||||||
|
<button id="btn-login" class="btn btn-primary btn-lg btn-block">登 录</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p id="message" class="op_box" style="display:none;"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -101,18 +101,19 @@
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<a class="title" href="#" id="user-profile" data-target="#" data-toggle="dropdown" role="button"
|
<a class="title" href="#" id="user-profile" data-target="#" data-toggle="dropdown" role="button"
|
||||||
aria-haspopup="true" aria-expanded="false">
|
aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="name">${ current_user['nick_name'] }</span>
|
<span class="name">${ current_user['name'] }</span>
|
||||||
<span class="role">
|
<span class="role">
|
||||||
%if current_user['type'] == 100:
|
%if current_user['type'] == 100:
|
||||||
平台管理员
|
系统管理员
|
||||||
%else:
|
%else:
|
||||||
普通用户
|
运维人员
|
||||||
%endif
|
%endif
|
||||||
<i class="fa fa-caret-right"></i></span>
|
<i class="fa fa-caret-right"></i></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-right">
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
<li><a href="/pwd" id="btn-logout">修改密码</a></li>
|
## <li><a href="/pwd" id="btn-logout">修改密码</a></li>
|
||||||
<li><a href="/auth/logout" id="btn-logout">安全退出</a></li>
|
<li><a href="/user/personal">个人中心</a></li>
|
||||||
|
<li><a href="/auth/logout">安全退出</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
ywl.ajax_post_json('/maintenance/rpc', {cmd: 'upgrade_db'},
|
ywl.ajax_post_json('/maintenance/rpc', {cmd: 'upgrade_db'},
|
||||||
function (ret) {
|
function (ret) {
|
||||||
console.log('upgrade-db:', ret);
|
console.log('upgrade-db:', ret);
|
||||||
if (ret.code == 0) {
|
if (ret.code === 0) {
|
||||||
|
|
||||||
var cb_stack = CALLBACK_STACK.create();
|
var cb_stack = CALLBACK_STACK.create();
|
||||||
cb_stack
|
cb_stack
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
|
|
||||||
ywl.get_task_ret = function (cb_stack, cb_args) {
|
ywl.get_task_ret = function (cb_stack, cb_args) {
|
||||||
var task_id = cb_args.task_id || 0;
|
var task_id = cb_args.task_id || 0;
|
||||||
if (task_id == 0) {
|
if (task_id === 0) {
|
||||||
console.log('task-id', task_id);
|
console.log('task-id', task_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
ywl.ajax_post_json('/maintenance/rpc', {cmd: 'get_task_ret', 'tid': task_id},
|
ywl.ajax_post_json('/maintenance/rpc', {cmd: 'get_task_ret', 'tid': task_id},
|
||||||
function (ret) {
|
function (ret) {
|
||||||
console.log('get_task_ret:', ret);
|
console.log('get_task_ret:', ret);
|
||||||
if (ret.code == 0) {
|
if (ret.code === 0) {
|
||||||
|
|
||||||
// show step progress.
|
// show step progress.
|
||||||
var steps = ret.data.steps;
|
var steps = ret.data.steps;
|
||||||
|
@ -130,14 +130,20 @@
|
||||||
var icon_class = '';
|
var icon_class = '';
|
||||||
var err_class = '';
|
var err_class = '';
|
||||||
for(var i = 0; i < steps.length; ++i) {
|
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';
|
icon_class = 'fa-check';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(steps[i].stat === 0)
|
||||||
|
;//icon_class = 'fa-check';
|
||||||
else
|
else
|
||||||
icon_class = 'fa-cog fa-spin';
|
icon_class = 'fa-cog fa-spin';
|
||||||
if(steps[i].code != 0)
|
|
||||||
err_class = ' class="error"';
|
|
||||||
else
|
|
||||||
err_class = '';
|
|
||||||
html.push('<p');
|
html.push('<p');
|
||||||
html.push(err_class);
|
html.push(err_class);
|
||||||
html.push('><i class="fa ');
|
html.push('><i class="fa ');
|
||||||
|
|
|
@ -33,6 +33,10 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
|
<div class="box">
|
||||||
|
<img src="/test/oath-code?">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h1>这是一级标题,This is H1.</h1>
|
<h1>这是一级标题,This is H1.</h1>
|
||||||
<h2>这是二级标题,This is H2.</h2>
|
<h2>这是二级标题,This is H2.</h2>
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
<%!
|
||||||
|
page_title_ = '个人中心'
|
||||||
|
page_menu_ = ['personal']
|
||||||
|
page_id_ = 'personal'
|
||||||
|
%>
|
||||||
|
<%inherit file="../page_base.mako"/>
|
||||||
|
|
||||||
|
<%block name="extend_js">
|
||||||
|
<script type="text/javascript" src="${ static_url('js/ui/teleport.js') }"></script>
|
||||||
|
<script type="text/javascript" src="${ static_url('js/ui/user/personal.js') }"></script>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><i class="fa fa-pencil-square-o fa-fw"></i> ${self.attr.page_title_}</li>
|
||||||
|
</ol>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
<%block name="extend_css">
|
||||||
|
<style type="text/css">
|
||||||
|
.table {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table .key {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table .value {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oath-code {
|
||||||
|
font-family: Consolas, Lucida Console, Monaco, Courier, 'Courier New', monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #559f47;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
## Begin Main Body.
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<!-- Nav tabs -->
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="active"><a href="#info" data-toggle="tab">个人信息</a></li>
|
||||||
|
<li><a href="#password" data-toggle="tab">修改密码</a></li>
|
||||||
|
<li><a href="#oath" data-toggle="tab">身份认证器</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab panes -->
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="info">
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td class="key">登录名:</td>
|
||||||
|
<td class="value">${user['name']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="key">姓名:</td>
|
||||||
|
<td class="value">${user['nick_name']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="key">邮箱:</td>
|
||||||
|
<td class="value">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="key">手机:</td>
|
||||||
|
<td class="value">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="key">注册时间:</td>
|
||||||
|
<td class="value">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="key">上次登录时间:</td>
|
||||||
|
<td class="value">-</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane" id="password">
|
||||||
|
<div class="input-group input-group-sm" style="margin-top: 10px">
|
||||||
|
<span class="input-group-addon" style="width:90px;">当前密码:</span>
|
||||||
|
<input type="password" class="form-control" id="current-password">
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm" style="margin-top: 10px">
|
||||||
|
<span class="input-group-addon" style="width:90px;">新密码:</span>
|
||||||
|
<input type="password" class="form-control" id="new-password-1">
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm" style="margin-top: 10px">
|
||||||
|
<span class="input-group-addon" style="width:90px;">重复新密码:</span>
|
||||||
|
<input type="password" class="form-control" id="new-password-2">
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:20px;">
|
||||||
|
<a href="javascript:;" id="btn-modify-password" class="btn btn-sm btn-primary"><i class="fa fa-check fa-fw"></i> 确认修改</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane" id="oath">
|
||||||
|
<p>请在你的手机上安装身份验证器,然后在验证器中添加你的登录账号。</p>
|
||||||
|
<div id="oath-app-download-box" style="display:none;">
|
||||||
|
<p>选择你喜欢的身份验证器,扫描二维码进行安装:</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<i class="fa fa-apple"></i> iOS(Apple Store)<br/>
|
||||||
|
<img src="${ static_url('img/qrcode/google-oath-appstore.png') }"><br/>
|
||||||
|
Google身份验证器
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<i class="fa fa-android"></i> Android(百度手机助手)<br/>
|
||||||
|
<img src="${ static_url('img/qrcode/google-oath-baidu.png') }"><br/>
|
||||||
|
Google身份验证器
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<i class="fa fa-android"></i> Android(Google Play)<br/>
|
||||||
|
<img src="${ static_url('img/qrcode/google-oath-googleplay.png') }"><br/>
|
||||||
|
Google身份验证器
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<i class="fa fa-apple"></i> iOS(Apple Store)<br/>
|
||||||
|
<img src="${ static_url('img/qrcode/xiaomi-oath-appstore.png') }"><br/>
|
||||||
|
小米安全令牌
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<i class="fa fa-android"></i> Android(小米应用商店)<br/>
|
||||||
|
<img src="${ static_url('img/qrcode/xiaomi-oath-xiaomi.png') }"><br/>
|
||||||
|
小米安全令牌
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="margin-top:5px;"><a href="javascript:;" id="toggle-oath-download">显示下载地址 <i class="fa fa-angle-down"></i></a></p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<p>要验证已经绑定的身份验证器,可在下面的输入框中输入验证器器上显示的动态验证码,然后点击验证。</p>
|
||||||
|
<p>如果验证失败,请注意检查您的身份验证器的时间与服务器时间是否一致,如果两者时间偏差超过两分钟则无法验证通过!</p>
|
||||||
|
<div style="width:360px;">
|
||||||
|
<div class="input-group input-group-sm" style="margin-top: 10px">
|
||||||
|
<span class="input-group-addon">动态验证码(6位数字):</span>
|
||||||
|
<input type="text" class="form-control" id="oath-code">
|
||||||
|
|
||||||
|
<div class="input-group-btn">
|
||||||
|
<button id="btn-verify-oath-code" class="btn btn-primary"><i class="fa fa-check"></i> 验证</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<p>要将你的登录账号添加到身份验证器中,请点击下面的“绑定身份验证器”按钮。</p>
|
||||||
|
<p>
|
||||||
|
<button id="btn-reset-oath-code" class="btn btn-sm btn-success"><i class="fa fa-refresh"></i> 绑定身份验证器</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<%block name="extend_content">
|
||||||
|
<div class="modal fade" id="dialog-reset-oath-code" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">绑定身份验证器</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<p>请在手机上打开身份验证器,点击增加账号按钮,然后选择“扫描条形码”并扫描下面的二维码来完成账号绑定。</p>
|
||||||
|
<p style="text-align: center;"><img id="oath-secret-qrcode" src=""></p>
|
||||||
|
<p>如果无法扫描二维码,则可以选择“手动输入验证码”,设置一个容易记忆的账号名称,并确保“基于时间”一项是选中的,然后在“密钥”一项中输入下列密钥:</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4" style="text-align:right;">
|
||||||
|
<span style="line-height:25px;">密钥:</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<span class="oath-code"><span id="tmp-oath-secret"></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<p>然后请在下面的动态验证码输入框中输入身份验证器提供的6位数字:</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4" style="text-align:right;">
|
||||||
|
<span style="line-height:34px;">动态验证码:</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input type="text" class="form-control" id="oath-code-verify">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" id="btn-verify-oath-and-save"><i class="fa fa-check fa-fw"></i> 验证动态验证码并完成绑定</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><i class="fa fa-close fa-fw"></i> 取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%block>
|
Loading…
Reference in New Issue