pull/32/merge
Apex Liu 2017-01-16 21:28:13 +08:00
parent 3ec1bea6ea
commit b6e3448792
22 changed files with 515 additions and 385 deletions

2
.gitignore vendored
View File

@ -38,6 +38,7 @@ __pycache__
/external/openssl /external/openssl
/external/python /external/python
/build/config.py
# for dist folder # for dist folder
/dist/*.zip /dist/*.zip
@ -60,3 +61,4 @@ __pycache__
/client/tp_rdp /client/tp_rdp
/external/libssh-win-static/lib /external/libssh-win-static/lib
/server/share/log /server/share/log
/config.ini

5
build.bat.in Normal file
View File

@ -0,0 +1,5 @@
@echo off
SET PYEXEC=C:\Python\Python34-x86\python.exe
%PYEXEC% -B build/build.py %1 %2 %3 %4 %5

1
build/.gitignore vendored
View File

@ -1 +0,0 @@
/config.py

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/builder" /> <content url="file://$MODULE_DIR$/builder" />
<orderEntry type="jdk" jdkName="3.4 x64" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="py34-x86" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TestRunnerService"> <component name="TestRunnerService">

View File

@ -8,6 +8,7 @@ import platform
import sys import sys
THIS_PATH = os.path.abspath(os.path.dirname(__file__)) THIS_PATH = os.path.abspath(os.path.dirname(__file__))
ROOT_PATH = os.path.abspath(os.path.join(THIS_PATH, '..'))
BUILDER_PATH = os.path.join(THIS_PATH, 'builder') BUILDER_PATH = os.path.join(THIS_PATH, 'builder')
sys.path.append(os.path.join(BUILDER_PATH)) sys.path.append(os.path.join(BUILDER_PATH))
@ -139,16 +140,12 @@ def do_opt(opt):
# arg = 'installer' # arg = 'installer'
arg = '%s %s installer' % (ctx.dist, opt['bits']) arg = '%s %s installer' % (ctx.dist, opt['bits'])
elif 'installer-ubuntu' == opt['name']:
script = 'build-installer.py'
arg = '%s %s installer' % ('ubuntu', opt['bits'])
elif 'assist-exe' == opt['name']: elif 'assist-exe' == opt['name']:
script = 'build-assist.py' script = 'build-assist.py'
arg = '%s %s exe' % (ctx.target_path, opt['bits']) arg = '%s %s exe' % (ctx.target_path, opt['bits'])
elif 'assist-rdp' == opt['name']: # elif 'assist-rdp' == opt['name']:
script = 'build-assist.py' # script = 'build-assist.py'
arg = '%s rdp' % (opt['bits']) # arg = '%s rdp' % (opt['bits'])
elif 'assist-installer' == opt['name']: elif 'assist-installer' == opt['name']:
script = 'build-assist.py' script = 'build-assist.py'
arg = '%s %s installer' % (ctx.dist, opt['bits']) arg = '%s %s installer' % (ctx.dist, opt['bits'])
@ -157,9 +154,8 @@ def do_opt(opt):
cc.e('unknown option: ', opt['name']) cc.e('unknown option: ', opt['name'])
return return
cmd = '"%s" -B "%s/%s" %s' % (utils.cfg.py_exec, BUILDER_PATH, script, arg) # cmd = '"%s" -B "%s" %s' % (utils.cfg.py_exec, os.path.join(BUILDER_PATH, script), arg)
cc.i(cmd) cmd = '%s -B %s %s' % (utils.cfg.py_exec, os.path.join(BUILDER_PATH, script), arg)
cc.v('')
os.system(cmd) os.system(cmd)
@ -176,13 +172,13 @@ def select_option_by_name(name):
return None return None
def select_option_by_id(id): def select_option_by_id(_id):
global options global options
for o in range(len(options)): for o in range(len(options)):
if options[o] is None: if options[o] is None:
continue continue
if options[o]['id'] == id: if options[o]['id'] == _id:
return options[o] return options[o]
return None return None

View File

@ -31,8 +31,8 @@ class BuilderWin(BuilderBase):
def build_exe(self): def build_exe(self):
cc.n('build tp_assist...') cc.n('build tp_assist...')
sln_file = os.path.join(ROOT_PATH, 'tp_assist', 'tp_assist.vs2015.sln') sln_file = os.path.join(ROOT_PATH, 'client', 'tp_assist', 'tp_assist.vs2015.sln')
out_file = os.path.join(ROOT_PATH, 'out', 'tp_assist', ctx.bits_path, ctx.target_path, 'tp_assist.exe') out_file = os.path.join(ROOT_PATH, 'out', 'client', ctx.bits_path, ctx.target_path, 'tp_assist.exe')
if os.path.exists(out_file): if os.path.exists(out_file):
utils.remove(out_file) utils.remove(out_file)
utils.msvc_build(sln_file, 'tp_assist', ctx.target_path, ctx.bits_path, False) utils.msvc_build(sln_file, 'tp_assist', ctx.target_path, ctx.bits_path, False)

View File

@ -27,13 +27,30 @@ class BuilderWin(BuilderBase):
super().__init__() super().__init__()
def build_server(self): def build_server(self):
cc.n('build eom_ts...') cc.n('build web server ...')
sln_file = os.path.join(ROOT_PATH, 'teleport-server', 'src', 'eom_ts.vs2015.sln') sln_file = os.path.join(ROOT_PATH, 'server', 'tp_web', 'src', 'tp_web.vs2015.sln')
out_file = os.path.join(ROOT_PATH, 'out', 'eom_ts', ctx.bits_path, ctx.target_path, 'eom_ts.exe') out_file = os.path.join(ROOT_PATH, 'out', 'server', ctx.bits_path, ctx.target_path, 'tp_web.exe')
if os.path.exists(out_file): if os.path.exists(out_file):
utils.remove(out_file) utils.remove(out_file)
utils.msvc_build(sln_file, 'eom_ts', ctx.target_path, ctx.bits_path, False) utils.msvc_build(sln_file, 'tp_web', ctx.target_path, ctx.bits_path, False)
utils.ensure_file_exists(out_file) utils.ensure_file_exists(out_file)
cc.n('build core server ...')
sln_file = os.path.join(ROOT_PATH, 'server', 'tp_core', 'core', 'tp_core.vs2015.sln')
out_file = os.path.join(ROOT_PATH, 'out', 'server', ctx.bits_path, ctx.target_path, 'tp_core.exe')
if os.path.exists(out_file):
utils.remove(out_file)
utils.msvc_build(sln_file, 'tp_core', ctx.target_path, ctx.bits_path, False)
utils.ensure_file_exists(out_file)
cc.n('build SSH protocol ...')
sln_file = os.path.join(ROOT_PATH, 'server', 'tp_core', 'protocol', 'ssh', 'tpssh.vs2015.sln')
out_file = os.path.join(ROOT_PATH, 'out', 'server', ctx.bits_path, ctx.target_path, 'tpssh.dll')
if os.path.exists(out_file):
utils.remove(out_file)
utils.msvc_build(sln_file, 'tpssh', ctx.target_path, ctx.bits_path, False)
utils.ensure_file_exists(out_file)
# #
# s = os.path.join(ROOT_PATH, 'out', 'console', ctx.bits_path, ctx.target_path, 'console.exe') # s = os.path.join(ROOT_PATH, 'out', 'console', ctx.bits_path, ctx.target_path, 'console.exe')
# t = os.path.join(ROOT_PATH, 'out', 'eom_agent', ctx.target_path, ctx.dist_path, 'eom_agent.com') # t = os.path.join(ROOT_PATH, 'out', 'eom_agent', ctx.target_path, ctx.dist_path, 'eom_agent.com')

View File

@ -3,12 +3,13 @@
import os import os
import sys import sys
import platform import platform
import configparser
from . import colorconsole as cc from . import colorconsole as cc
__all__ = ['cfg'] __all__ = ['cfg']
class TpDict(dict): class AttrDict(dict):
""" """
可以像属性一样访问字典的 Keyvar.key 等同于 var['key'] 可以像属性一样访问字典的 Keyvar.key 等同于 var['key']
""" """
@ -24,17 +25,11 @@ class TpDict(dict):
self[name] = val self[name] = val
class ConfigFile(TpDict): class ConfigFile(AttrDict):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# self.__file_name = None
# self.__save_indent = 0
# self.__loaded = False
def init(self, cfg_file): def init(self, cfg_file):
if not self.load(cfg_file, True):
return False
self['ROOT_PATH'] = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) self['ROOT_PATH'] = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
self['py_exec'] = sys.executable self['py_exec'] = sys.executable
@ -55,70 +50,47 @@ class ConfigFile(TpDict):
_os = platform.system().lower() _os = platform.system().lower()
self['is_win'] = False
self['is_linux'] = False
self['is_macos'] = False
self['dist'] = '' self['dist'] = ''
if _os == 'windows': if _os == 'windows':
self['is_win'] = True
self['dist'] = 'windows' self['dist'] = 'windows'
elif _os == 'linux': elif _os == 'linux':
self['is_linux'] = True
self['dist'] = 'linux' self['dist'] = 'linux'
elif _os == 'darwin': elif _os == 'darwin':
self['is_macos'] = True
self['dist'] = 'macos' self['dist'] = 'macos'
else: else:
cc.e('not support this OS: {}'.format(platform.system())) cc.e('not support this OS: {}'.format(platform.system()))
return False return False
return True _cfg = configparser.ConfigParser()
_cfg.read(cfg_file)
def load_str(self, module, code): if 'external_ver' not in _cfg.sections() or 'toolchain' not in _cfg.sections():
m = type(sys)(module) cc.e('invalid configuration file: need `external_ver` and `toolchain` section.')
m.__module_class__ = type(sys)
m.__file__ = module
try:
exec(compile(code, module, 'exec'), m.__dict__)
except Exception as e:
cc.e('%s\n' % str(e))
# print(str(e))
# if eom_dev_conf.debug:
# raise
return False return False
for y in m.__dict__: _tmp = _cfg['external_ver']
if '__' == y[:2]: if 'libuv' not in _tmp or 'mbedtls' not in _tmp or 'sqlite' not in _tmp:
continue cc.e('invalid configuration file: external version not set.')
if isinstance(m.__dict__[y], dict): return False
self[y] = TpDict()
self._assign_dict(m.__dict__[y], self[y]) self['ver'] = AttrDict()
for k in _tmp:
self['ver'][k] = _tmp[k]
_tmp = _cfg['toolchain']
if self.is_win:
self['nsis'] = _tmp.get('nsis', None)
self['msbuild'] = None # msbuild always read from register.
else: else:
self[y] = m.__dict__[y] self['cmake'] = _tmp.get('cmake', '/usr/bin/cmake')
return True return True
def load(self, full_path, must_exists=True):
try:
f = open(full_path, encoding='utf8')
code = f.read()
f.close()
self.__loaded = True
except IOError:
if must_exists:
cc.e('Can not load config file: %s\n' % full_path)
return False
module = os.path.basename(full_path)
if not self.load_str(module, code):
return False
self.__file_name = full_path
return True
def _assign_dict(self, _from, _to):
for y in _from:
if isinstance(_from[y], dict):
_to[y] = TpDict()
self._assign_dict(_from[y], _to[y])
else:
_to[y] = _from[y]
cfg = ConfigFile() cfg = ConfigFile()
del ConfigFile del ConfigFile

View File

@ -11,18 +11,15 @@ import time
from . import colorconsole as cc from . import colorconsole as cc
from .configs import cfg from .configs import cfg
try: try:
CONFIG_FILE = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')), 'config.py') CONFIG_FILE = os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')), 'config.ini')
if not cfg.init(CONFIG_FILE): if not cfg.init(CONFIG_FILE):
sys.exit(1) sys.exit(1)
except: except:
cc.e('can not load configuration.\n\nplease copy `config.py.in` into `config.py` and modify it to fit your condition and try again.') cc.e('can not load configuration.\n\nplease copy `config.py.in` into `config.py` and modify it to fit your condition and try again.')
sys.exit(1) sys.exit(1)
# PY_VER = platform.python_version_tuple()
#IS_PY2 = sys.version_info[0] == 2
#IS_PY3 = sys.version_info[0] == 3
if cfg.is_py2: if cfg.is_py2:
import imp import imp
elif cfg.is_py3: elif cfg.is_py3:
@ -222,13 +219,9 @@ def python_exec():
return sys.executable return sys.executable
g_msbuild_path = None
def msbuild_path(): def msbuild_path():
global g_msbuild_path if cfg.msbuild is not None:
if g_msbuild_path is not None: return cfg.msbuild
return g_msbuild_path
# 14.0 = VS2015 # 14.0 = VS2015
# 12.0 = VS2012 # 12.0 = VS2012
@ -248,21 +241,13 @@ def msbuild_path():
if not os.path.exists(msb): if not os.path.exists(msb):
raise RuntimeError('Can not locate MSBuild at {}'.format(msp)) raise RuntimeError('Can not locate MSBuild at {}'.format(msp))
g_msbuild_path = msb cfg.msbuild = msb
return msb return msb
g_nsis_path = None
def nsis_path(): def nsis_path():
global g_nsis_path if cfg.nsis is not None:
if g_nsis_path is not None: return cfg.nsis
return g_nsis_path
if 'nsis' in cfg:
g_nsis_path = cfg['nsis']
return g_nsis_path
p = winreg_read_wow64_32(r'SOFTWARE\NSIS\Unicode', '') p = winreg_read_wow64_32(r'SOFTWARE\NSIS\Unicode', '')
if p is None: if p is None:
@ -272,7 +257,7 @@ def nsis_path():
if not os.path.exists(p): if not os.path.exists(p):
raise RuntimeError('Can not locate NSIS at {}'.format(p)) raise RuntimeError('Can not locate NSIS at {}'.format(p))
g_nsis_path = p cfg.nsis = p
return p return p
@ -355,7 +340,7 @@ def nsis_build(nsi_file, _define=''):
def cmake(work_path, target, force_rebuild, cmake_define=''): def cmake(work_path, target, force_rebuild, cmake_define=''):
# because cmake v2.8 shipped with Ubuntu 14.04LTS, but we need 3.5. # because cmake v2.8 shipped with Ubuntu 14.04LTS, but we need 3.5.
# so we copy a v3.5 cmake from CLion and put to $WORK/eomsoft/toolchain/cmake. # so we copy a v3.5 cmake from CLion and put to $WORK/eomsoft/toolchain/cmake.
#CMAKE = os.path.abspath(os.path.join(root_path(), 'toolchain', 'cmake', 'bin', 'cmake')) # CMAKE = os.path.abspath(os.path.join(root_path(), 'toolchain', 'cmake', 'bin', 'cmake'))
if 'cmake' not in cfg: if 'cmake' not in cfg:
raise RuntimeError('please set `cmake` path.') raise RuntimeError('please set `cmake` path.')
if not os.path.exists(cfg['cmake']): if not os.path.exists(cfg['cmake']):

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
OPENSSL_VER = '1.0.2h'
LIBUV_VER = '1.9.1'
MBEDTLS_VER = '2.3.0'
SQLITE_VER = '3160200'
# ============================================
# for windows
# ============================================
# if not set nsis path, builder will try to get it by read register.
nsis = 'C:\\Program Files (x86)\\NSIS\\Unicode\\makensis.exe'
# ============================================
# for linux
# ============================================
cmake = '/opt/cmake/bin/cmake'
# pyexec = os.path.join(ROOT_PATH, 'external', 'linux', 'release', 'bin', 'python3.4')

26
config.ini.in Normal file
View File

@ -0,0 +1,26 @@
[toolchain]
#============================================
# for windows
#============================================
# if not set nsis path, default to get it by register.
#nsis = "C:\Program Files (x86)\NSIS\Unicode\makensis.exe"
# if not set msbuild path, default to get it by register.
#msbuild = "C:\Program Files (x86)\MSBuild\14.0\bin\MSBuild.exe"
# ============================================
# for linux
# ============================================
# if not set cmake path, default to '/usr/bin/cmake'
cmake = "/opt/cmake/bin/cmake"
[external_ver]
# openssl = 1.0.2h
libuv = 1.9.1
mbedtls = 2.3.0
sqlite = 3160200

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.23107.0 VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tpssh", "tpssh.vcxproj", "{FDA16D20-09B7-45AF-ADF1-DAF3EF2C0531}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tpssh", "tpssh.vs2015.vcxproj", "{FDA16D20-09B7-45AF-ADF1-DAF3EF2C0531}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -23,6 +23,7 @@
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
<RootNamespace>tpssh</RootNamespace> <RootNamespace>tpssh</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
<ProjectName>tpssh</ProjectName>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

View File

@ -40,6 +40,10 @@ static ex_u8 g_run_type = RUN_UNKNOWN;
static bool _run_daemon(void); static bool _run_daemon(void);
// 导出函数给Python脚本使用主要是为了记录日志
// Windows平台上tp_web程序打开日志文件写之后Python脚本尝试写入方式打开此日志文件时会失败。
PyObject* init_web_builtin_module(void);
#ifdef EX_OS_WIN32 #ifdef EX_OS_WIN32
static int service_install() static int service_install()
{ {
@ -227,6 +231,13 @@ static int _main_loop(void)
pys_add_arg(pysh, it->c_str()); pys_add_arg(pysh, it->c_str());
} }
if (!pys_add_builtin_module(pysh, "tpweb", init_web_builtin_module))
{
EXLOGE("[tpweb] can not add builtin module for python script.\n");
return 1;
}
return pys_run(pysh); return pys_run(pysh);
} }
@ -417,9 +428,9 @@ VOID WINAPI service_main(DWORD argc, wchar_t** argv)
} }
} }
#else //======================================================
// not EX_OS_WIN32 #else // Linux or MacOS
//#include "ts_util.h"
#include <fcntl.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -500,3 +511,130 @@ static bool _run_daemon(void)
} }
#endif #endif
//===============================================================
// 演示如何加入内建模块供脚本调用
//===============================================================
PyObject* _py_log_output(PyObject* self, PyObject* args)
{
// UNUSED(self);
// UNUSED(args);
int level = 0;
const char* msg = NULL;
if (!pylib_PyArg_ParseTuple(args, "is", &level, &msg))
{
EXLOGE("invalid args for _py_log_output().\n");
PYLIB_RETURN_FALSE;
}
ex_wstr tmp;
ex_astr2wstr(msg, tmp, EX_CODEPAGE_UTF8);
//EXLOGV(msg);
switch (level)
{
case EX_LOG_LEVEL_DEBUG:
ex_printf_d(tmp.c_str());
break;
case EX_LOG_LEVEL_VERBOSE:
ex_printf_v(tmp.c_str());
break;
case EX_LOG_LEVEL_INFO:
ex_printf_i(tmp.c_str());
break;
case EX_LOG_LEVEL_WARN:
ex_printf_w(tmp.c_str());
break;
case EX_LOG_LEVEL_ERROR:
ex_printf_e(tmp.c_str());
break;
default:
PYLIB_RETURN_FALSE;
break;
}
//return pylib_PyLong_FromLong(0x010001);
PYLIB_RETURN_TRUE;
}
PyObject* _py_log_level(PyObject* self, PyObject* args)
{
int level = 0;
if (!pylib_PyArg_ParseTuple(args, "i", &level))
{
EXLOGE("invalid args for _py_log_level().\n");
PYLIB_RETURN_FALSE;
}
EXLOG_LEVEL(level);
PYLIB_RETURN_TRUE;
}
PyObject* _py_log_console(PyObject* self, PyObject* args)
{
bool to_console = false;
if (!pylib_PyArg_ParseTuple(args, "p", &to_console))
{
EXLOGE("invalid args for _py_log_console().\n");
PYLIB_RETURN_FALSE;
}
EXLOG_CONSOLE(to_console);
PYLIB_RETURN_TRUE;
}
PYS_BUILTIN_FUNC _demo_funcs[] = {
{
"log_output", // 脚本函数名,在脚本中使用
_py_log_output, // 对应的C代码函数名
PYS_TRUE, // 函数的基本信息(是否需要参数,等等)
"write log." // 函数的说明文档,可选(可以是空字符串)
},
{
"log_level",
_py_log_level,
PYS_TRUE,
"set log level."
},
{
"log_console",
_py_log_console,
PYS_TRUE,
"set log to console or not."
},
// 最后一组,第一个成员为空指针,表示结束
{ NULL, NULL, 0, NULL }
};
PyObject* init_web_builtin_module(void)
{
PyObject* mod = NULL;
mod = pys_create_module("_tpweb", _demo_funcs);
pys_builtin_const_long(mod, "EX_LOG_LEVEL_DEBUG", EX_LOG_LEVEL_DEBUG);
pys_builtin_const_long(mod, "EX_LOG_LEVEL_VERBOSE", EX_LOG_LEVEL_VERBOSE);
pys_builtin_const_long(mod, "EX_LOG_LEVEL_INFO", EX_LOG_LEVEL_INFO);
pys_builtin_const_long(mod, "EX_LOG_LEVEL_WARN", EX_LOG_LEVEL_WARN);
pys_builtin_const_long(mod, "EX_LOG_LEVEL_ERROR", EX_LOG_LEVEL_ERROR);
// pys_builtin_const_bool(mod, "DEMO_CONST_2", PYS_TRUE);
// //pys_builtin_const_wcs(mod, "DEMO_CONST_3", L"STRING 中文测试 this is string.");
// pys_builtin_const_wcs(mod, "DEMO_CONST_3", L"STRING this is string.");
// pys_builtin_const_utf8(mod, "DEMO_CONST_4", "this is string.");
//
// ex_u8 test_buf[12] = { 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, 0x12, 0x34, 0xab, 0xcd };
// pys_builtin_const_bin(mod, "DEMO_CONST_5", test_buf, 12);
return mod;
}

View File

@ -69,7 +69,6 @@
<ClInclude Include="ts_env.h"> <ClInclude Include="ts_env.h">
<Filter>main app</Filter> <Filter>main app</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="resource.h" />
<ClInclude Include="..\..\..\common\libex\include\ex\ex_const.h"> <ClInclude Include="..\..\..\common\libex\include\ex\ex_const.h">
<Filter>libex\header</Filter> <Filter>libex\header</Filter>
</ClInclude> </ClInclude>
@ -115,6 +114,9 @@
<ClInclude Include="..\..\..\common\pyshell\include\pys.h"> <ClInclude Include="..\..\..\common\pyshell\include\pys.h">
<Filter>pyshell\header</Filter> <Filter>pyshell\header</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="resource.h">
<Filter>Resource Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="tp_web.rc"> <ResourceCompile Include="tp_web.rc">

View File

@ -24,7 +24,7 @@ bool TsEnv::init(void)
ex_path_join(base_path, true, L"..", NULL); ex_path_join(base_path, true, L"..", NULL);
ex_wstr conf_file = base_path; ex_wstr conf_file = base_path;
ex_path_join(conf_file, false, L"etc", L"web.conf", NULL); ex_path_join(conf_file, false, L"etc", L"web.ini", NULL);
if (ex_is_file_exists(conf_file.c_str())) if (ex_is_file_exists(conf_file.c_str()))
{ {
@ -38,7 +38,7 @@ bool TsEnv::init(void)
ex_path_join(base_path, true, L"..", L"..", L"..", L"..", L"server", L"share", NULL); ex_path_join(base_path, true, L"..", L"..", L"..", L"..", L"server", L"share", NULL);
conf_file = base_path; conf_file = base_path;
ex_path_join(conf_file, false, L"etc", L"web.conf", NULL); ex_path_join(conf_file, false, L"etc", L"web.ini", NULL);
m_www_path = m_exec_path; m_www_path = m_exec_path;
ex_path_join(m_www_path, true, L"..", L"..", L"..", L"..", L"server", L"www", NULL); ex_path_join(m_www_path, true, L"..", L"..", L"..", L"..", L"server", L"www", NULL);
@ -53,7 +53,7 @@ bool TsEnv::init(void)
ExIniFile cfg; ExIniFile cfg;
if (!cfg.LoadFromFile(conf_file)) if (!cfg.LoadFromFile(conf_file))
{ {
EXLOGE("[tpweb] can not load web.conf.\n"); EXLOGE("[tpweb] can not load web.ini.\n");
return false; return false;
} }

View File

@ -11,7 +11,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="jdk" jdkName="py34-x86" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

View File

@ -2,13 +2,14 @@
import os import os
import sys import sys
import configparser
from eom_common.eomcore.logger import log from eom_common.eomcore.logger import log
__all__ = ['app_cfg'] __all__ = ['app_cfg']
class SwxDict(dict): class AttrDict(dict):
""" """
可以像属性一样访问字典的 Keyvar.key 等同于 var['key'] 可以像属性一样访问字典的 Keyvar.key 等同于 var['key']
""" """
@ -24,154 +25,112 @@ class SwxDict(dict):
self[name] = val self[name] = val
def swx_dict(obj): # def attr_dict(obj):
""" # """
将一个对象中的dict转变为EomDict类型 # 将一个对象中的dict转变为AttrDict类型
""" # """
if isinstance(obj, dict): # if isinstance(obj, dict):
ret = SwxDict() # ret = AttrDict()
for k in obj: # for k in obj:
# ret[k] = obj[k] # # ret[k] = obj[k]
if isinstance(obj[k], dict): # if isinstance(obj[k], dict):
ret[k] = swx_dict(obj[k]) # ret[k] = attr_dict(obj[k])
else: # else:
ret[k] = obj[k] # ret[k] = obj[k]
else: # else:
ret = obj # ret = obj
return ret # return ret
class ConfigFile(SwxDict): class ConfigFile(AttrDict):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# self.__file_name = None # self.__file_name = None
# self.__save_indent = 0 # self.__save_indent = 0
# self.__loaded = False # self.__loaded = False
def load_str(self, module, code): def load(self, cfg_file):
m = type(sys)(module) if not os.path.exists(cfg_file):
m.__module_class__ = type(sys) log.e('configuration file does not exists.')
m.__file__ = module return False
try: try:
exec(compile(code, module, 'exec'), m.__dict__) _cfg = configparser.ConfigParser()
except Exception as e: _cfg.read(cfg_file)
log.e('%s\n' % str(e)) except:
# print(str(e)) log.e('can not load configuration file.')
# if eom_dev_conf.debug:
# raise
return False return False
for y in m.__dict__: if 'common' not in _cfg:
if '__' == y[:2]: log.e('invalid configuration file.')
continue return False
if isinstance(m.__dict__[y], dict):
self[y] = SwxDict() _comm = _cfg['common']
self._assign_dict(m.__dict__[y], self[y]) self['server_port'] = _comm.getint('port', 7190)
else: self['log_file'] = _comm.get('log-file', None)
self[y] = m.__dict__[y] if self['log_file'] is not None:
self['log_path'] = os.path.dirname(self['log_file'])
return True return True
def load(self, full_path, must_exists=True): # def load_str(self, module, code):
try: # m = type(sys)(module)
f = open(full_path, encoding='utf8') # m.__module_class__ = type(sys)
code = f.read() # m.__file__ = module
f.close() #
self.__loaded = True # try:
except IOError: # exec(compile(code, module, 'exec'), m.__dict__)
if must_exists: # except Exception as e:
log.e('Can not load config file: %s\n' % full_path) # log.e('%s\n' % str(e))
return False # # print(str(e))
# # if eom_dev_conf.debug:
# # raise
# return False
#
# for y in m.__dict__:
# if '__' == y[:2]:
# continue
# if isinstance(m.__dict__[y], dict):
# self[y] = AttrDict()
# self._assign_dict(m.__dict__[y], self[y])
# else:
# self[y] = m.__dict__[y]
#
# return True
#
# def _load(self, full_path, must_exists=True):
# try:
# f = open(full_path, encoding='utf8')
# code = f.read()
# f.close()
# self.__loaded = True
# except IOError:
# if must_exists:
# log.e('Can not load config file: %s\n' % full_path)
# return False
#
# module = os.path.basename(full_path)
# if not self.load_str(module, code):
# return False
#
# self.__file_name = full_path
# return True
#
# def _assign_dict(self, _from, _to):
# for y in _from:
# if isinstance(_from[y], dict):
# _to[y] = AttrDict()
# self._assign_dict(_from[y], _to[y])
# else:
# _to[y] = _from[y]
#
module = os.path.basename(full_path) _g_cfg = ConfigFile()
if not self.load_str(module, code):
return False
self.__file_name = full_path
return True
"""
def save(self, filename=None):
if filename is None and not self.__loaded:
# log.w('Can not save config file without file name.\n')
return False
_file_name = filename
if _file_name is None:
_file_name = self.__file_name
if _file_name is None:
log.e('Do not known which file to save to.\n')
return False
# 排序后保存
m = [(k, self[k]) for k in sorted(self.keys())]
self.__save_indent = 0
s = self._save(m)
# 尝试加载生成的要保存的配置字符串,如果加载成功,则保存到文件,否则报错
x = ConfigFile()
if not x.load_str('_eom_tmp_cfg_data_', s):
log.e('Cannot generate config for save.\n')
return False
f = open(_file_name, 'w')
f.write('# -*- coding: utf-8 -*-\n\n')
f.write(s)
f.close()
return True
def _save(self, var):
s = ''
for (k, v) in var:
if self.__save_indent == 0 and k.find('_ConfigFile__') == 0:
# 本类的成员变量不用保存
continue
if isinstance(v, dict):
if self.__save_indent > 0:
s += "\n%s'%s' : {\n" % ('\t' * self.__save_indent, k)
else:
s += "\n%s%s = {\n" % ('\t' * self.__save_indent, k)
self.__save_indent += 1
m = [(x, v[x]) for x in sorted(v.keys())]
s += self._save(m)
self.__save_indent -= 1
if self.__save_indent > 0:
s += "%s},\n\n" % ('\t' * self.__save_indent)
else:
s += "%s}\n\n" % ('\t' * self.__save_indent)
else:
if isinstance(v, str):
val = "'%s'" % v.replace("'", "\\'")
else:
val = v
if self.__save_indent > 0:
s += "%s'%s' : %s,\n" % ('\t' * self.__save_indent, k, val)
else:
s += "%s%s = %s\n" % ('\t' * self.__save_indent, k, val)
return s
"""
def _assign_dict(self, _from, _to):
for y in _from:
if isinstance(_from[y], dict):
_to[y] = SwxDict()
self._assign_dict(_from[y], _to[y])
else:
_to[y] = _from[y]
_cfg = ConfigFile()
del ConfigFile del ConfigFile
def app_cfg(): def app_cfg():
global _cfg global _g_cfg
return _cfg return _g_cfg
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -7,7 +7,6 @@ import tornado.ioloop
import tornado.netutil import tornado.netutil
import tornado.process import tornado.process
import tornado.web import tornado.web
# from eom_app.controller import controllers
# from eom_common.eomcore.eom_mysql import get_mysql_pool # from eom_common.eomcore.eom_mysql import get_mysql_pool
from eom_common.eomcore.eom_sqlite import get_sqlite_pool from eom_common.eomcore.eom_sqlite import get_sqlite_pool
@ -15,6 +14,7 @@ import eom_common.eomcore.utils as utils
from eom_common.eomcore.logger import log from eom_common.eomcore.logger import log
from .configs import app_cfg from .configs import app_cfg
from .session import swx_session from .session import swx_session
cfg = app_cfg() cfg = app_cfg()
@ -28,25 +28,30 @@ class SwxCore:
cfg.dev_mode = options['dev_mode'] cfg.dev_mode = options['dev_mode']
if not self._load_config(options):
return False
if cfg.log_file is None:
if 'log_path' not in options: if 'log_path' not in options:
return False return False
else: else:
cfg.log_path = options['log_path'] cfg.log_path = options['log_path']
cfg.log_file = os.path.join(cfg.log_path, 'tpweb.log')
if not os.path.exists(cfg.log_path): if not os.path.exists(cfg.log_path):
utils.make_dir(cfg.log_path) utils.make_dir(cfg.log_path)
if not os.path.exists(cfg.log_path): if not os.path.exists(cfg.log_path):
log.e('Can not create log path.\n') log.e('Can not create log path.\n')
return False return False
log.set_attribute(filename=cfg.log_file)
if 'app_path' not in options: if 'app_path' not in options:
return False return False
else: else:
cfg.app_path = options['app_path'] cfg.app_path = options['app_path']
if not self._load_config(options):
return False
if 'static_path' in options: if 'static_path' in options:
cfg.static_path = options['static_path'] cfg.static_path = options['static_path']
else: else:
@ -92,7 +97,7 @@ class SwxCore:
else: else:
_cfg_path = os.path.join(options['app_path'], 'conf') _cfg_path = os.path.join(options['app_path'], 'conf')
_cfg_file = os.path.join(_cfg_path, 'web.conf') _cfg_file = os.path.join(_cfg_path, 'web.ini')
if not cfg.load(_cfg_file): if not cfg.load(_cfg_file):
return False return False
@ -238,9 +243,9 @@ class SwxCore:
log.e('Can not listen on port {}, maybe it been used by another application.\n'.format(cfg.server_port)) log.e('Can not listen on port {}, maybe it been used by another application.\n'.format(cfg.server_port))
return 0 return 0
if not cfg.dev_mode: # if not cfg.dev_mode:
log_file = os.path.join(cfg.log_path, 'ts-web.log') # log_file = os.path.join(cfg.log_path, 'ts-web.log')
log.set_attribute(console=False, filename=log_file) # log.set_attribute(console=False, filename=log_file)
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()
return 0 return 0

View File

@ -10,11 +10,25 @@ __all__ = ['log',
'CR_DEBUG', 'CR_VERBOSE', 'CR_INFO', 'CR_WARN', 'CR_ERROR', 'CR_DEBUG', 'CR_VERBOSE', 'CR_INFO', 'CR_WARN', 'CR_ERROR',
'LOG_DEBUG', 'LOG_VERBOSE', 'LOG_INFO', 'LOG_WARN', 'LOG_ERROR', 'TRACE_ERROR_NONE', 'TRACE_ERROR_FULL'] 'LOG_DEBUG', 'LOG_VERBOSE', 'LOG_INFO', 'LOG_WARN', 'LOG_ERROR', 'TRACE_ERROR_NONE', 'TRACE_ERROR_FULL']
LOG_DEBUG = 1 LOG_DEBUG = 0
LOG_VERBOSE = 10 LOG_VERBOSE = 1
LOG_INFO = 20 LOG_INFO = 2
LOG_WARN = 30 LOG_WARN = 3
LOG_ERROR = 99 LOG_ERROR = 4
USE_TPWEB_LOG = True
try:
import tpweb
LOG_DEBUG = tpweb.EX_LOG_LEVEL_DEBUG
LOG_VERBOSE = tpweb.EX_LOG_LEVEL_VERBOSE
LOG_INFO = tpweb.EX_LOG_LEVEL_INFO
LOG_WARN = tpweb.EX_LOG_LEVEL_WARN
LOG_ERROR = tpweb.EX_LOG_LEVEL_ERROR
except ImportError:
print('can not import tpweb.')
USE_TPWEB_LOG = False
TRACE_ERROR_NONE = 0 TRACE_ERROR_NONE = 0
TRACE_ERROR_FULL = 999999 TRACE_ERROR_FULL = 999999
@ -71,9 +85,6 @@ COLORS = {
} }
# env = eomcore.env.get_env()
class EomLogger: class EomLogger:
""" """
日志记录模块支持输出到控制台及文件 日志记录模块支持输出到控制台及文件
@ -83,8 +94,13 @@ class EomLogger:
""" """
def __init__(self): def __init__(self):
atexit.register(self.finalize)
self._locker = threading.RLock() self._locker = threading.RLock()
# self._sep = ' '
# self._end = '\n'
self._min_level = LOG_INFO # 大于等于此值的日志信息才会记录 self._min_level = LOG_INFO # 大于等于此值的日志信息才会记录
self._trace_error = TRACE_ERROR_NONE # 记录错误信息时,是否追加记录调用栈 self._trace_error = TRACE_ERROR_NONE # 记录错误信息时,是否追加记录调用栈
self._log_datetime = True # 是否记录日志时间 self._log_datetime = True # 是否记录日志时间
@ -93,17 +109,20 @@ class EomLogger:
self._win_color = None self._win_color = None
self.d = self._func_debug if USE_TPWEB_LOG:
self.v = self._func_verbose self._do_log = self._do_log_tpweb
self.i = self._func_info else:
self.w = self._func_warn self._do_log = self._do_log_local
self.e = self._func_error
# self.d = self._log_debug
# self.v = self._log_verbose
# self.i = self._log_info
# self.w = self._log_warn
# self.e = self._log_error
self._set_console(True) self._set_console(True)
self._set_level(self._min_level) self._set_level(self._min_level)
atexit.register(self.finalize)
def initialize(self): def initialize(self):
pass pass
@ -138,28 +157,28 @@ class EomLogger:
return True return True
def _set_level(self, level): def _set_level(self, level):
self.d = self._func_debug self.d = self._log_debug
self.v = self._func_verbose self.v = self._log_verbose
self.i = self._func_info self.i = self._log_info
self.w = self._func_warn self.w = self._log_warn
# self.e = self._func_error self.e = self._log_error
if LOG_DEBUG == level: if LOG_DEBUG == level:
pass pass
elif LOG_VERBOSE == level: elif LOG_VERBOSE == level:
self.d = self._func_pass self.d = self._log_pass
elif LOG_INFO == level: elif LOG_INFO == level:
self.d = self._func_pass self.d = self._log_pass
self.v = self._func_pass self.v = self._log_pass
elif LOG_WARN == level: elif LOG_WARN == level:
self.d = self._func_pass self.d = self._log_pass
self.v = self._func_pass self.v = self._log_pass
self.i = self._func_pass self.i = self._log_pass
elif LOG_ERROR == level: elif LOG_ERROR == level:
self.d = self._func_pass self.d = self._log_pass
self.v = self._func_pass self.v = self._log_pass
self.i = self._func_pass self.i = self._log_pass
self.w = self._func_pass self.w = self._log_pass
pass pass
else: else:
pass pass
@ -218,33 +237,92 @@ class EomLogger:
return True return True
def _log_pass(self, *args, **kwargs):
pass
def _log_debug(self, *args, **kwargs):
self._do_log(LOG_DEBUG, *args, **kwargs)
def _log_verbose(self, *args, **kwargs):
self._do_log(LOG_VERBOSE, *args, **kwargs)
def _log_info(self, *args, **kwargs):
self._do_log(LOG_INFO, *args, **kwargs)
def _log_warn(self, *args, **kwargs):
self._do_log(LOG_WARN, *args, **kwargs)
def _log_error(self, *args, **kwargs):
self._do_log(LOG_ERROR, *args, **kwargs)
def _do_log_tpweb(self, level, *args, **kwargs):
# sep = kwargs['sep'] if 'sep' in kwargs else self._sep
# end = kwargs['end'] if 'end' in kwargs else self._end
# first = True
for x in args:
# if not first:
# tpweb.log_output(level, sep)
first = False
if isinstance(x, str):
tpweb.log_output(level, x)
continue
else:
tpweb.log_output(level, x.__str__())
# tpweb.log_output(level, end)
def _do_log_local(self, level, *args, **kwargs):
if level < self._min_level:
return
# sep = kwargs['sep'] if 'sep' in kwargs else self._sep
# end = kwargs['end'] if 'end' in kwargs else self._end
# first = True
for x in args:
# if not first:
# sys.stdout.writelines(sep)
first = False
if isinstance(x, str):
sys.stdout.writelines(x)
continue
else:
sys.stdout.writelines(x.__str__())
# sys.stdout.writelines(end)
sys.stdout.flush()
def log(self, msg, color=None): def log(self, msg, color=None):
""" """
自行指定颜色输出到控制台不会输出到日志文件且输出时不含时间信息 自行指定颜色输出到控制台不会输出到日志文件且输出时不含时间信息
""" """
self._do_log(msg, color=color, show_datetime=False) self._do_log(msg, color=color, show_datetime=False)
def _func_pass(self, msg, color=None): # def _func_pass(self, msg, color=None):
# do nothing. # # do nothing.
pass
def _func_debug(self, msg):
# 调试输出的数据,在正常运行中不会输出
self._do_log(msg, CR_DEBUG)
# 普通的日志数据
def _func_verbose(self, msg):
# pass # pass
self._do_log(msg, None) #
# def _func_debug(self, msg):
# 重要信息 # # 调试输出的数据,在正常运行中不会输出
def _func_info(self, msg): # self._do_log(msg, CR_DEBUG)
self._do_log(msg, CR_INFO)
# 警告
def _func_warn(self, msg):
self._do_log(msg, CR_WARN)
# # 普通的日志数据
# def _func_verbose(self, msg):
# # pass
# self._do_log(msg, None)
#
# # 重要信息
# def _func_info(self, msg):
# self._do_log(msg, CR_INFO)
#
# # 警告
# def _func_warn(self, msg):
# self._do_log(msg, CR_WARN)
#
def _func_error(self, msg): def _func_error(self, msg):
"""错误 """错误
""" """
@ -315,7 +393,7 @@ class EomLogger:
m += '.' m += '.'
m += '\n' m += '\n'
self.log(m, CR_DEBUG) self._log_debug(m)
if loop > 0: if loop > 0:
x += 1 x += 1
@ -342,25 +420,25 @@ class EomLogger:
m += '.' m += '.'
m += '\n' m += '\n'
self.log(m, CR_DEBUG) self._log_debug(m)
def _do_log(self, msg, color=None, show_datetime=True): # def _do_log(self, msg, color=None, show_datetime=True):
with self._locker: # with self._locker:
now = time.localtime(time.time()) # now = time.localtime(time.time())
_log_time = '[{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}] '.format(now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec) # _log_time = '[{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}] '.format(now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)
#
try: # try:
if show_datetime and self._log_datetime: # if show_datetime and self._log_datetime:
msg = '{}{}'.format(_log_time, msg) # msg = '{}{}'.format(_log_time, msg)
self._log_console(msg, color) # self._log_console(msg, color)
else: # else:
self._log_console(msg, color) # self._log_console(msg, color)
msg = '{}{}'.format(_log_time, msg) # msg = '{}{}'.format(_log_time, msg)
#
self._log_file(msg) # self._log_file(msg)
#
except IOError: # except IOError:
pass # pass
def _console_default(self, msg, color=None): def _console_default(self, msg, color=None):
""" """
@ -482,46 +560,6 @@ class EomLogger:
self._set_level(LOG_DEBUG) self._set_level(LOG_DEBUG)
self._trace_error = TRACE_ERROR_FULL self._trace_error = TRACE_ERROR_FULL
self.log('###################', CR_NORMAL)
self.log(' CR_NORMAL\n')
self.log('###################', CR_BLACK)
self.log(' CR_BLACK\n')
self.log('###################', CR_LIGHT_GRAY)
self.log(' CR_LIGHT_GRAY\n')
self.log('###################', CR_GRAY)
self.log(' CR_GRAY\n')
self.log('###################', CR_WHITE)
self.log(' CR_WHITE\n')
self.log('###################', CR_RED)
self.log(' CR_RED\n')
self.log('###################', CR_GREEN)
self.log(' CR_GREEN\n')
self.log('###################', CR_YELLOW)
self.log(' CR_YELLOW\n')
self.log('###################', CR_BLUE)
self.log(' CR_BLUE\n')
self.log('###################', CR_MAGENTA)
self.log(' CR_MAGENTA\n')
self.log('###################', CR_CYAN)
self.log(' CR_CYAN\n')
self.log('###################', CR_LIGHT_RED)
self.log(' CR_LIGHT_RED\n')
self.log('###################', CR_LIGHT_GREEN)
self.log(' CR_LIGHT_GREEN\n')
self.log('###################', CR_LIGHT_YELLOW)
self.log(' CR_LIGHT_YELLOW\n')
self.log('###################', CR_LIGHT_BLUE)
self.log(' CR_LIGHT_BLUE\n')
self.log('###################', CR_LIGHT_MAGENTA)
self.log(' CR_LIGHT_MAGENTA\n')
self.log('###################', CR_LIGHT_CYAN)
self.log(' CR_LIGHT_CYAN\n')
data = b'This is a test string and you can see binary format data here.'
self.bin('Binary Data:\n', data)
data = b''
self.bin('Empty binary\n', data)
self.bin('This is string\n\n', 'data')
self.d('This is DEBUG message.\n') self.d('This is DEBUG message.\n')
self.v('This is VERBOSE message.\n') self.v('This is VERBOSE message.\n')
self.i('This is INFORMATION message.\n') self.i('This is INFORMATION message.\n')
@ -530,6 +568,12 @@ class EomLogger:
self.v('test auto\nsplited lines.\nYou should see\nmulti-lines.\n') self.v('test auto\nsplited lines.\nYou should see\nmulti-lines.\n')
data = b'This is a test string and you can see binary format data here.'
self.bin('Binary Data:\n', data)
data = b''
self.bin('Empty binary\n', data)
self.bin('This is string\n\n', 'data')
class Win32DebugView: class Win32DebugView:
def __init__(self): def __init__(self):
@ -615,6 +659,8 @@ class Win32ColorConsole:
log = EomLogger() log = EomLogger()
del EomLogger del EomLogger
log._test()
import builtins import builtins
builtins.__dict__['print'] = log._log_print builtins.__dict__['print'] = log._log_print

View File

@ -10,18 +10,18 @@
<%block name="breadcrumb"> <%block name="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><i class="fa fa-server fa-fw"></i> 录像回放</li> <li><i class="fa fa-server"></i> 录像回放</li>
</ol> </ol>
</%block> </%block>
<div class="page-content"> <div class="page-content">
<div ng-controller="TerminalRecordCtrl"> <div ng-controller="TerminalRecordCtrl">
<button id="btn-play" type="button" class="btn btn-primary btn-sm"><i class="fa fa-pause"> 暂停</i></button> <button id="btn-play" type="button" class="btn btn-primary btn-sm" style="width:80px;"><i class="fa fa-pause"> 暂停</i></button>
<button id="btn-restart" type="button" class="btn btn-success btn-sm"><i class="fa fa-refresh"> 重新播放</i></button> <button id="btn-restart" type="button" class="btn btn-success btn-sm"><i class="fa fa-refresh"></i> 重新播放</button>
<button id="btn-speed" type="button" class="btn btn-warning btn-sm">正常速度</button> <button id="btn-speed" type="button" class="btn btn-warning btn-sm" style="width:80px;">正常速度</button>
<span id="play-status" class="badge badge-danger" style="margin-left:5px;">状态:正在获取数据</span> <span id="play-status" class="badge badge-normal" style="margin-left:5px;">状态:正在获取数据</span>
<span id="play-time" class="badge badge-success" style="margin-left:5px;">总时长:未知</span> <span id="play-time" class="badge badge-success" style="margin-left:5px;">总时长:未知</span>
<input id="progress" type="range" value="0" min=0 max=100 style="margin-top: 10px;"/> <input id="progress" type="range" value="0" min=0 max=100 style="margin-top: 10px;"/>
<div id="terminal" style="margin-top: 10px;"></div> <div id="terminal" style="margin-top: 10px;"></div>