新增工具‘HTTPCookie管理器’,支持设置HTTPCookie数据
This commit is contained in:
parent
793d8c7bc3
commit
a73e9bca96
|
@ -69,19 +69,19 @@ class HTTPCookiePoolManager:
|
|||
if old_rcj is None:
|
||||
__class__.ScheduleCookiePool[project_id] = rcj
|
||||
else:
|
||||
cls._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
|
||||
cls.update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
|
||||
else:
|
||||
session_id = session_id_manager.get_session_id()
|
||||
old_rcj = __class__.CookiePool[session_id][type]
|
||||
if old_rcj is None:
|
||||
__class__.CookiePool[session_id][type] = rcj
|
||||
else:
|
||||
cls._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
|
||||
cls.update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
|
||||
else:
|
||||
raise ValueError('不支持传入type值为%s,只支持type=single_case 或 type=build_case' % type)
|
||||
|
||||
@classmethod
|
||||
def _update_request_cookie_jar(cls, old_rcj: RequestsCookieJar, new_rcj: RequestsCookieJar):
|
||||
def update_request_cookie_jar(cls, old_rcj: RequestsCookieJar, new_rcj: RequestsCookieJar):
|
||||
"""将新的rcj更新到老的rcj上"""
|
||||
old_rcj.update(other=new_rcj)
|
||||
|
||||
|
@ -107,7 +107,7 @@ class HTTPCookiePoolManager:
|
|||
})
|
||||
|
||||
@classmethod
|
||||
def get_request_cookie_jar(cls, type: str) -> Optional[RequestsCookieJar]:
|
||||
def get_request_cookie_jar(cls, type: str) -> RequestsCookieJar:
|
||||
"""
|
||||
获取当前会话中指定类型的cookie数据
|
||||
:param type: cookie类型 DISPATCHER_TYPE.BUILD或DISPATCHER_TYPE.DEBUG
|
||||
|
@ -117,12 +117,12 @@ class HTTPCookiePoolManager:
|
|||
project_id = session.get('project_id')
|
||||
if project_id:
|
||||
if project_id not in __class__.ScheduleCookiePool:
|
||||
return
|
||||
cls._add_empty_to_cookie_pool()
|
||||
return __class__.ScheduleCookiePool[project_id]
|
||||
else:
|
||||
session_id = session_id_manager.get_session_id()
|
||||
if session_id not in __class__.CookiePool:
|
||||
return
|
||||
cls._add_empty_to_cookie_pool()
|
||||
return __class__.CookiePool[session_id][type]
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -102,6 +102,7 @@ class TOOL_TYPE:
|
|||
VARIABLE_DEFINITION = 'VARIABLE_DEFINITION'
|
||||
SCRIPT = 'SCRIPT'
|
||||
HTTP_REQUEST_HEADER_MANAGER = 'HTTP_REQUEST_HEADER_MANAGER'
|
||||
HTTP_COOKIE_MANAGER = 'HTTP_COOKIE_MANAGER'
|
||||
|
||||
|
||||
class CONTENT_TYPE:
|
||||
|
|
|
@ -868,7 +868,8 @@ class SceneDispatcher(AbstractSceneDispatcher):
|
|||
if tool.status in [STATUS.NORMAL] and tool.tool_type in [TOOL_TYPE.TIMER,
|
||||
TOOL_TYPE.SCRIPT,
|
||||
TOOL_TYPE.VARIABLE_DEFINITION,
|
||||
TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER]:
|
||||
TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER,
|
||||
TOOL_TYPE.HTTP_COOKIE_MANAGER]:
|
||||
# ReportToolData表数据字段
|
||||
report_id = self.dispatcher.report.id
|
||||
try:
|
||||
|
@ -890,6 +891,11 @@ class SceneDispatcher(AbstractSceneDispatcher):
|
|||
HTTPRequestHeaderManagerToolDispatcher(tool=tool, dispatcher_type=DISPATCHER_TYPE.BUILD,
|
||||
logger=self.dispatcher_logger,
|
||||
dispatcher=self.dispatcher).run()
|
||||
elif tool.tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
from app.cores.tool.dispatcher import HTTPCookieManagerToolDispatcher
|
||||
HTTPCookieManagerToolDispatcher(tool=tool, dispatcher_type=DISPATCHER_TYPE.BUILD,
|
||||
logger=self.dispatcher_logger,
|
||||
dispatcher=self.dispatcher).run()
|
||||
finally:
|
||||
# 工具增加调度子数据
|
||||
dispatcher_detail_id = DispatcherDetail.add(element_type=ELEMENT_TYPE.TOOL, element_id=tool.id,
|
||||
|
|
|
@ -6,6 +6,7 @@ from app.cores.tool.script import Script
|
|||
from app.cores.tool.timer import Timer
|
||||
from app.cores.tool.variable_definition import VariableDefinition
|
||||
from app.cores.tool.http_request_header_manager import HTTPRequestHeaderManager
|
||||
from app.cores.tool.http_cookie_manager import HTTPCookieManager
|
||||
|
||||
|
||||
class ScriptToolDispatcher(AbstractToolDispatcher):
|
||||
|
@ -127,3 +128,31 @@ class HTTPRequestHeaderManagerToolDispatcher(AbstractToolDispatcher):
|
|||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
||||
|
||||
class HTTPCookieManagerToolDispatcher(AbstractToolDispatcher):
|
||||
def __init__(self, tool, dispatcher_type=DISPATCHER_TYPE.BUILD, logger=None, dispatcher=None):
|
||||
"""
|
||||
:param tool: 单个HTTPCookieManager Tool工具组件
|
||||
:type tool: Tool
|
||||
:param dispatcher_type: 标识构建是通过单独组件调试(DISPATCHER_TYPE.DEBUG)还是通过模块/项目构建测试(DISPATCHER_TYPE.BUILD)
|
||||
:type dispatcher_type: str
|
||||
:param logger: 当dispatcher_type为DISPATCHER_TYPE.BUILD时,需要传入调度日志
|
||||
:type logger: DispatcherLogger
|
||||
:param dispatcher: 当dispatcher_type为DISPATCHER_TYPE.BUILD时,需要传入调度数据
|
||||
:type dispatcher: Dispatcher
|
||||
"""
|
||||
super().__init__(tool=tool, dispatcher_type=dispatcher_type, logger=logger, dispatcher=dispatcher)
|
||||
|
||||
def set_up(self):
|
||||
super().set_up()
|
||||
|
||||
def execute(self):
|
||||
super().execute()
|
||||
HTTPCookieManager(tool=self.tool, dispatcher_logger=self.dispatcher_logger).exec_tool()
|
||||
|
||||
def tear_down(self):
|
||||
super().tear_down()
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# coding=utf-8
|
||||
from requests.cookies import RequestsCookieJar
|
||||
|
||||
from app.cores.case.http.http_cookie_pool_manager import HTTPCookiePoolManager
|
||||
from app.cores.dictionaries import DISPATCHER_TYPE
|
||||
|
||||
|
||||
class HTTPCookieManager:
|
||||
|
||||
def __init__(self, tool, dispatcher_logger):
|
||||
"""
|
||||
初始化HTTPCookieManager工具对象
|
||||
:param tool: 工具
|
||||
:type tool: Tool
|
||||
:param dispatcher_logger: 日志
|
||||
:type dispatcher_logger: DispatcherLogger
|
||||
"""
|
||||
self.http_cookie_manager_tool = tool.specific_tool
|
||||
self.dispatcher_logger = dispatcher_logger
|
||||
|
||||
def exec_tool(self):
|
||||
self.dispatcher_logger.logger.info('[HTTPCookie管理器][执行]')
|
||||
rcj = HTTPCookiePoolManager.get_request_cookie_jar(type=DISPATCHER_TYPE.BUILD)
|
||||
new_rcj = RequestsCookieJar()
|
||||
for row in self.http_cookie_manager_tool.http_cookie_manager_list:
|
||||
new_rcj.set(name=row.name_,
|
||||
value=row.value_,
|
||||
domain=row.domain_,
|
||||
path=row.path_,
|
||||
secure=row.secure)
|
||||
HTTPCookiePoolManager.update_request_cookie_jar(old_rcj=rcj, new_rcj=new_rcj)
|
|
@ -1004,6 +1004,8 @@ class Tool(db.Model):
|
|||
return VariableDefinitionTool.query.filter_by(tool_id=self.id).first()
|
||||
if self.tool_type == TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER:
|
||||
return HTTPRequestHeaderManagerTool.query.filter_by(tool_id=self.id).first()
|
||||
if self.tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
return HTTPCookieManagerTool.query.filter_by(tool_id=self.id).first()
|
||||
|
||||
@classmethod
|
||||
def exist_and_status_not_delete(cls, tool_id) -> bool:
|
||||
|
@ -1051,6 +1053,9 @@ class Tool(db.Model):
|
|||
elif tool_type == TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER:
|
||||
specific_tool = HTTPRequestHeaderManagerTool.new_tool()
|
||||
specific_tool.tool = tool
|
||||
elif tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
specific_tool = HTTPCookieManagerTool.new_tool()
|
||||
specific_tool.tool = tool
|
||||
else:
|
||||
raise ValueError('不支持当前tool_type类型, tool_type=%s' % tool_type)
|
||||
db.session.add(specific_tool)
|
||||
|
@ -1557,6 +1562,172 @@ class HTTPRequestHeaderManagerList(db.Model):
|
|||
return _p(self.value)
|
||||
|
||||
|
||||
class HTTPCookieManagerTool(db.Model):
|
||||
"""
|
||||
关系说明
|
||||
tool: HTTPCookieManagerTool对应的工具对象 HTTPCookieManagerTool:Tool is 1:1
|
||||
http_cookie_manager_list: HTTPCookieManagerTool工具所包含的所有变量与值数据 HTTPCookieManagerTool:HTTPCookieManagerList is 1:N
|
||||
"""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# relationship
|
||||
tool_id = db.Column(db.Integer, db.ForeignKey('tool.id'))
|
||||
tool = db.relationship('Tool')
|
||||
http_cookie_manager_list = db.relationship('HTTPCookieManagerList',
|
||||
back_populates='http_cookie_manager_tool',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return [{
|
||||
'name': row.name,
|
||||
'value': row.value,
|
||||
'domain': row.domain,
|
||||
'path': row.path,
|
||||
'secure': row.secure,
|
||||
} for row in self.http_cookie_manager_list]
|
||||
|
||||
@classmethod
|
||||
def new_tool(cls):
|
||||
"""新增默认"""
|
||||
http_cookie_manager_tool = cls()
|
||||
http_cookie_manager_tool.http_cookie_manager_list = []
|
||||
http_cookie_manager_tool.http_cookie_manager_list.append(
|
||||
HTTPCookieManagerList(name='cookie_name',
|
||||
value='cookie_value',
|
||||
domain='localhost',
|
||||
path='/',
|
||||
secure=False)
|
||||
)
|
||||
db.session.add(http_cookie_manager_tool)
|
||||
db.session.commit()
|
||||
return http_cookie_manager_tool
|
||||
|
||||
@classmethod
|
||||
def update(cls, name, description, tool_id, attributes):
|
||||
"""
|
||||
更新工具数据
|
||||
:param tool_id: 工具编号
|
||||
:type tool_id: int
|
||||
:param name: 名称
|
||||
:type name: str
|
||||
:param description: 注释
|
||||
:type description: str
|
||||
:param attributes: HTTPCookieManager数据列表
|
||||
:type attributes: List[Mapping[str, str]]
|
||||
"""
|
||||
tool = Tool.query.filter_by(id=tool_id).first()
|
||||
if tool:
|
||||
tool.name = name
|
||||
tool.description = description
|
||||
http_cookie_manager_tool = tool.specific_tool
|
||||
http_cookie_manager_tool.http_cookie_manager_list = []
|
||||
for attr in attributes:
|
||||
http_cookie_manager_tool.http_cookie_manager_list.append(
|
||||
HTTPCookieManagerList(name=attr.get('name', ''),
|
||||
value=attr.get('value', ''),
|
||||
domain=attr.get('domain', ''),
|
||||
path=attr.get('path', ''),
|
||||
secure=attr.get('secure', False))
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@classmethod
|
||||
def add(cls, name, description, attributes, status=STATUS.NORMAL):
|
||||
"""
|
||||
新增一条工具数据
|
||||
:param name: 名称
|
||||
:type name: str
|
||||
:param description: 注释
|
||||
:type description: str
|
||||
:param attributes: HTTPCookieManager数据列表
|
||||
:type attributes: List[Mapping[str, str]]
|
||||
:param status: 案例状态(删除 正常)
|
||||
:type status: str
|
||||
"""
|
||||
tool = Tool(
|
||||
name=name,
|
||||
description=description,
|
||||
status=status,
|
||||
tool_type=TOOL_TYPE.HTTP_COOKIE_MANAGER,
|
||||
)
|
||||
http_cookie_manager_tool = cls()
|
||||
http_cookie_manager_tool.http_cookie_manager_list = []
|
||||
for attr in attributes:
|
||||
http_cookie_manager_tool.http_cookie_manager_list.append(
|
||||
HTTPCookieManagerList(name=attr.get('name', ''),
|
||||
value=attr.get('value', ''),
|
||||
domain=attr.get('domain', ''),
|
||||
path=attr.get('path', ''),
|
||||
secure=attr.get('secure', False))
|
||||
)
|
||||
http_cookie_manager_tool.tool = tool
|
||||
db.session.add(tool)
|
||||
db.session.add(http_cookie_manager_tool)
|
||||
db.session.commit()
|
||||
return http_cookie_manager_tool
|
||||
|
||||
def copy_from_self(self):
|
||||
"""
|
||||
复制一个新的工具
|
||||
:return: 新的工具
|
||||
:rtype: Tool
|
||||
"""
|
||||
# 先获取变量键值对数据
|
||||
copy_tool = self.add(
|
||||
# Tool
|
||||
name='Copy' + self.tool.name,
|
||||
description=self.tool.description,
|
||||
status=self.tool.status,
|
||||
# HTTPCookieManagerTool
|
||||
attributes=self.attributes,
|
||||
)
|
||||
return copy_tool
|
||||
|
||||
|
||||
class HTTPCookieManagerList(db.Model):
|
||||
"""
|
||||
字段说明
|
||||
name: 名称
|
||||
value: 值
|
||||
domain: 域
|
||||
path: 路径
|
||||
secure: 安全
|
||||
关系说明
|
||||
http_cookie_manager_tool: HTTPCookieManager数据列表对应的HTTPCookieManager工具对象 HTTPCookieManagerList:HTTPCookieManagerTool is N:1
|
||||
"""
|
||||
# 需要转换为json数据格式的字段
|
||||
render_field_list = ['name', 'value', 'domain', 'path', 'secure']
|
||||
# filed
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
value = db.Column(db.Text, nullable=False, default='', server_default='')
|
||||
domain = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
path = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
secure = db.Column(db.Boolean, nullable=False, default=False, server_default='0')
|
||||
|
||||
# relationship
|
||||
http_cookie_manager_tool_id = db.Column(db.Integer, db.ForeignKey('http_cookie_manager_tool.id'))
|
||||
http_cookie_manager_tool = db.relationship('HTTPCookieManagerTool',
|
||||
back_populates='http_cookie_manager_list')
|
||||
|
||||
@property
|
||||
def name_(self):
|
||||
return _p(self.name)
|
||||
|
||||
@property
|
||||
def value_(self):
|
||||
return _p(self.value)
|
||||
|
||||
@property
|
||||
def domain_(self):
|
||||
return _p(self.domain)
|
||||
|
||||
@property
|
||||
def path_(self):
|
||||
return _p(self.path)
|
||||
|
||||
|
||||
class Case(db.Model):
|
||||
"""
|
||||
字段说明
|
||||
|
@ -3225,6 +3396,10 @@ class ReportToolData(db.Model):
|
|||
back_populates='report_tool_data',
|
||||
uselist=False,
|
||||
cascade='all, delete-orphan')
|
||||
report_http_cookie_manager_tool_data = db.relationship('ReportHTTPCookieManagerToolData',
|
||||
back_populates='report_tool_data',
|
||||
uselist=False,
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
@classmethod
|
||||
def add(cls, tool, report_id, dispatcher_detail_id):
|
||||
|
@ -3241,6 +3416,9 @@ class ReportToolData(db.Model):
|
|||
elif tool.tool_type == TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER:
|
||||
ReportHTTPRequestHeaderManagerToolData.add(variables=tool.specific_tool.variables,
|
||||
report_tool_data_id=report_tool_data.id)
|
||||
elif tool.tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
ReportHTTPCookieManagerToolData.add(attributes=tool.specific_tool.attributes,
|
||||
report_tool_data_id=report_tool_data.id)
|
||||
return report_tool_data
|
||||
|
||||
|
||||
|
@ -3368,6 +3546,59 @@ class ReportHTTPRequestHeaderManagerToolListData(db.Model):
|
|||
back_populates='report_http_request_header_manager_tool_list_data')
|
||||
|
||||
|
||||
class ReportHTTPCookieManagerToolData(db.Model):
|
||||
# field
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# relationship
|
||||
report_tool_data_id = db.Column(db.Integer, db.ForeignKey('report_tool_data.id'))
|
||||
report_tool_data = db.relationship('ReportToolData', back_populates='report_http_cookie_manager_tool_data')
|
||||
report_http_cookie_manager_tool_list_data = db.relationship('ReportHTTPCookieManagerToolListData',
|
||||
back_populates='report_http_cookie_manager_tool_data',
|
||||
cascade='all, delete-orphan')
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return [{
|
||||
'name': row.name,
|
||||
'value': row.value,
|
||||
'domain': row.domain,
|
||||
'path': row.path,
|
||||
'secure': row.secure,
|
||||
} for row in self.report_http_cookie_manager_tool_list_data]
|
||||
|
||||
@classmethod
|
||||
def add(cls, attributes, report_tool_data_id):
|
||||
report_http_cookie_manager_tool_data = ReportHTTPCookieManagerToolData(report_tool_data_id=report_tool_data_id)
|
||||
db.session.add(report_http_cookie_manager_tool_data)
|
||||
db.session.commit() # commit后report_http_cookie_manager_tool_data.id才有值,否则为None
|
||||
for attr in attributes:
|
||||
db.session.add(ReportHTTPCookieManagerToolListData(
|
||||
name=attr.get('name', ''),
|
||||
value=attr.get('value', ''),
|
||||
domain=attr.get('domain', ''),
|
||||
path=attr.get('path', ''),
|
||||
secure=attr.get('secure', False),
|
||||
report_http_cookie_manager_tool_data_id=report_http_cookie_manager_tool_data.id,
|
||||
))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
class ReportHTTPCookieManagerToolListData(db.Model):
|
||||
# field
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
value = db.Column(db.Text, nullable=False, default='', server_default='')
|
||||
domain = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
path = db.Column(db.String(512), nullable=False, default='', server_default='')
|
||||
secure = db.Column(db.Boolean, nullable=False, default=False, server_default='0')
|
||||
|
||||
# relationship
|
||||
report_http_cookie_manager_tool_data_id = db.Column(db.Integer, db.ForeignKey('report_http_cookie_manager_tool_data.id'))
|
||||
report_http_cookie_manager_tool_data = db.relationship('ReportHTTPCookieManagerToolData',
|
||||
back_populates='report_http_cookie_manager_tool_list_data')
|
||||
|
||||
|
||||
class Scheduler(db.Model):
|
||||
"""
|
||||
调度定时任务
|
||||
|
|
|
@ -20,7 +20,8 @@ from app.utils.util import get_form_from_request, exception_handle, get_project_
|
|||
from app.models import Case, Project, Module, Scene, HTTPCase, SSHCase, SQLCase, Dispatcher, DispatcherDetail, Report, \
|
||||
SubElementInLogicController, LogicController, IfController, WhileController, LoopController, SimpleController, \
|
||||
Scheduler, DingTalkRobotSetting, DingTalkRobotSettingAssociationProject, DebugCase, Tool, TimerTool, ScriptTool, \
|
||||
VariableDefinitionTool, EmailReceiverSetting, EmailReceiverSettingAssociationProject, HTTPRequestHeaderManagerTool
|
||||
VariableDefinitionTool, EmailReceiverSetting, EmailReceiverSettingAssociationProject, HTTPRequestHeaderManagerTool,\
|
||||
HTTPCookieManagerTool
|
||||
from app.template_global import render_to_json
|
||||
from app.extensions import dispatcher_scheduler
|
||||
from app.template_global import sort_by_order_in_project, sort_by_order_in_module, sort_by_order_in_logic_controller
|
||||
|
@ -799,6 +800,39 @@ def save_http_request_header_manager_tool():
|
|||
})
|
||||
|
||||
|
||||
@bp.route('/tool/http_cookie_manager/save', methods=['POST'])
|
||||
@login_required
|
||||
def save_http_cookie_manager_tool():
|
||||
# 参数校验
|
||||
exist, form_tool_id = get_form_from_request(request, 'tool_id')
|
||||
if not exist: return form_tool_id
|
||||
exist, form_tool_name = get_form_from_request(request, 'tool_name')
|
||||
if not exist: return form_tool_name
|
||||
exist, form_tool_description = get_form_from_request(request, 'tool_description')
|
||||
if not exist: return form_tool_description
|
||||
exist, form_attrs = get_form_from_request(request, 'attrs')
|
||||
if not exist: return form_attrs
|
||||
try:
|
||||
attributes = json.loads(form_attrs)
|
||||
HTTPCookieManagerTool.update(
|
||||
tool_id=form_tool_id,
|
||||
name=form_tool_name,
|
||||
description=form_tool_description,
|
||||
attributes=attributes,
|
||||
)
|
||||
except Exception as e:
|
||||
exception_handle(current_app, e)
|
||||
return jsonify({
|
||||
'error_no': -1,
|
||||
'error_msg': '错误信息 [' + repr(e) + ']',
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'error_no': 0,
|
||||
'error_msg': '',
|
||||
})
|
||||
|
||||
|
||||
@bp.route('/tool/script/run', methods=['POST'])
|
||||
@login_required
|
||||
def run_script_tool():
|
||||
|
@ -2290,6 +2324,7 @@ def get_report_detail_tool():
|
|||
})
|
||||
report_tool_data = dispatcher_detail.specific_element_report_data
|
||||
variables = []
|
||||
attributes = []
|
||||
if report_tool_data.tool_type == TOOL_TYPE.TIMER:
|
||||
html_text = render_template('report/_report_detail_timer_tool.html',
|
||||
dispatcher_detail=dispatcher_detail,
|
||||
|
@ -2308,6 +2343,11 @@ def get_report_detail_tool():
|
|||
dispatcher_detail=dispatcher_detail,
|
||||
report_tool_data=report_tool_data)
|
||||
variables = report_tool_data.report_http_request_header_manager_tool_data.variables
|
||||
elif report_tool_data.tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
html_text = render_template('report/_report_detail_http_cookie_manager_tool.html',
|
||||
dispatcher_detail=dispatcher_detail,
|
||||
report_tool_data=report_tool_data)
|
||||
attributes = report_tool_data.report_http_cookie_manager_tool_data.attributes
|
||||
else:
|
||||
html_text = ''
|
||||
except Exception as e:
|
||||
|
@ -2323,6 +2363,7 @@ def get_report_detail_tool():
|
|||
'html_text': html_text,
|
||||
'tool_type': report_tool_data.tool_type,
|
||||
'variables': variables,
|
||||
'attributes': attributes,
|
||||
})
|
||||
|
||||
|
||||
|
@ -2548,6 +2589,7 @@ def tool():
|
|||
'error_msg': '错误信息 [未找工具数据tool_id=%s]' % form_tool_id,
|
||||
})
|
||||
data_variables = []
|
||||
data_attributes = []
|
||||
if tool.tool_type == TOOL_TYPE.TIMER:
|
||||
html_text = render_template('tool/timer.html', tool=tool)
|
||||
elif tool.tool_type == TOOL_TYPE.SCRIPT:
|
||||
|
@ -2558,6 +2600,9 @@ def tool():
|
|||
elif tool.tool_type == TOOL_TYPE.HTTP_REQUEST_HEADER_MANAGER:
|
||||
html_text = render_template('tool/http_request_header_manager.html', tool=tool)
|
||||
data_variables = json.loads(render_to_json(tool.specific_tool.http_request_header_manager_list))
|
||||
elif tool.tool_type == TOOL_TYPE.HTTP_COOKIE_MANAGER:
|
||||
html_text = render_template('tool/http_cookie_manager.html', tool=tool)
|
||||
data_attributes = json.loads(render_to_json(tool.specific_tool.http_cookie_manager_list))
|
||||
else:
|
||||
return jsonify({
|
||||
'error_no': -1,
|
||||
|
@ -2577,6 +2622,7 @@ def tool():
|
|||
'tool_type': tool.tool_type,
|
||||
'html_text': html_text,
|
||||
'data_variables': data_variables,
|
||||
'data_attributes': data_attributes,
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -199,6 +199,9 @@ body {
|
|||
.icon-tool-http-request-header-manager{
|
||||
background-image: url(/static/icon/http-request-header.svg);
|
||||
}
|
||||
.icon-tool-http-cookie-manager{
|
||||
background-image: url(/static/icon/cookie.svg);
|
||||
}
|
||||
|
||||
/*div边框样式*/
|
||||
.border-left-primary {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M512 938.666667c-235.52-0.256-426.410667-191.146667-426.666667-426.666667 16.810667 4.736 34.133333 7.168 51.626667 7.210667a175.36 175.36 0 0 0 145.066667-74.965334c29.568-42.666667 35.84-97.322667 16.64-145.578666 12.245333 2.176 24.661333 3.285333 37.12 3.328A182.058667 182.058667 0 0 0 478.506667 234.666667a177.536 177.536 0 0 0 36.266666-149.333334c235.648 0.768 426.026667 192.426667 425.301334 428.074667C939.306667 749.013333 747.648 939.392 512 938.666667z m32.554667-146.432a53.376 53.376 0 1 0 20.778666-102.4 53.333333 53.333333 0 0 0-20.778666 102.4zM295.253333 720a71.125333 71.125333 0 1 0-0.853333-0.256h0.682667l0.170666 0.256z m402.389334-106.965333a71.082667 71.082667 0 1 0-43.605334-63.573334c0.682667 26.666667 16.256 50.645333 40.277334 62.208h-0.512l1.28 0.554667 0.853333 0.341333h-0.256l1.792 0.682667 0.170667-0.213333zM512 440.874667a35.584 35.584 0 1 0 13.824 2.816l-0.768-0.341334-1.152-0.426666a35.157333 35.157333 0 0 0-11.904-2.048z m160-177.749334a53.162667 53.162667 0 1 0 20.736 4.266667h-0.426667a53.12 53.12 0 0 0-20.309333-4.266667zM192 405.333333a35.541333 35.541333 0 1 1 0-71.082666 35.541333 35.541333 0 0 1 0 71.082666zM138.666667 298.666667a53.333333 53.333333 0 1 1 0-106.666667 53.333333 53.333333 0 0 1 0 106.666667z m213.333333-71.125334a53.333333 53.333333 0 1 1 0-106.752 53.333333 53.333333 0 0 1 0 106.794667v-0.042667zM227.584 156.458667a35.541333 35.541333 0 1 1 0-71.082667 35.541333 35.541333 0 0 1 0 71.082667z" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -236,6 +236,9 @@ function renderToolReportDetail(node){
|
|||
}else if(data.tool_type === 'HTTP_REQUEST_HEADER_MANAGER'){
|
||||
// 渲染HTTPRequestHeaderManagerTool工具组件页面
|
||||
renderHTTPRequestHeaderManagerToolPage(node.data.dispatcher_detail_id, data.variables);
|
||||
}else if(data.tool_type === 'HTTP_COOKIE_MANAGER'){
|
||||
// 渲染HTTPCookieManagerTool工具组件页面
|
||||
renderHTTPCookieManagerToolPage(node.data.dispatcher_detail_id, data.attributes);
|
||||
}
|
||||
resolve(['成功了', 'success']); // 也可以传递其他类型参数
|
||||
}else{
|
||||
|
@ -721,6 +724,92 @@ function renderHTTPRequestHeaderManagerToolPage(dispatcher_detail_id, data_varia
|
|||
table_http_request_header_manager = $container.handsontable(option_table_http_request_header_manager).handsontable('getInstance');
|
||||
}
|
||||
}
|
||||
// 渲染HTTPCookieManagerTool工具组件页面
|
||||
function renderHTTPCookieManagerToolPage(dispatcher_detail_id, data_attributes) {
|
||||
// HTTPCookieManager表格对象
|
||||
let table_http_cookie_manager = null;
|
||||
|
||||
// 渲染HTTPCookieManager表格
|
||||
renderHTTPCookieManagerTable();
|
||||
|
||||
// 渲染HTTPCookieManager表格
|
||||
function renderHTTPCookieManagerTable() {
|
||||
const option_table_http_request_header_manager = {
|
||||
data: data_attributes,
|
||||
rowHeaders: true,
|
||||
// colHeaders: true,
|
||||
licenseKey: 'non-commercial-and-evaluation',
|
||||
// 表格宽度
|
||||
// width: '95vw', // 不指定宽度使其自适应容器
|
||||
// 拉伸方式
|
||||
stretchH: 'all',
|
||||
// tab键自动换行切换
|
||||
autoWrapRow: true,
|
||||
height: '55vh',
|
||||
// 最大行数
|
||||
maxRows: 999,
|
||||
// 允许手工移动行或列
|
||||
manualRowResize: true,
|
||||
manualColumnResize: true,
|
||||
// 列名
|
||||
colHeaders: [
|
||||
'名称',
|
||||
'值',
|
||||
'域',
|
||||
'路径',
|
||||
'安全',
|
||||
],
|
||||
// 为列设置默认值
|
||||
dataSchema: {
|
||||
name: '',
|
||||
value: '',
|
||||
url_encode: false,
|
||||
content_type: 'text/plain',
|
||||
include_equals: true,
|
||||
},
|
||||
// 设置列数据类型
|
||||
columns: [
|
||||
{
|
||||
data: 'name',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
data: 'value',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
data: 'domain',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
data: 'path',
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
data: 'secure',
|
||||
type: 'checkbox',
|
||||
readOnly: true,
|
||||
},
|
||||
],
|
||||
// 列宽比例
|
||||
colWidths: [2, 5, 5, 3, 1],
|
||||
manualRowMove: true,
|
||||
manualColumnMove: false,
|
||||
// 右键菜单
|
||||
// contextMenu: ['row_above', 'row_below', '---------', 'remove_row', '---------', 'undo', 'redo', '---------', 'alignment', '---------', 'copy', 'cut'],
|
||||
// 列是否支持过滤
|
||||
filters: false,
|
||||
// 下拉菜单
|
||||
// dropdownMenu: ['make_read_only', '---------', 'alignment'],
|
||||
// 语言
|
||||
language: 'zh-CN',
|
||||
// 是否允许无效数据 默认 true
|
||||
allowInvalid: false,
|
||||
};
|
||||
let $container = $(`#table-http-cookie-manager-${dispatcher_detail_id}`);
|
||||
table_http_cookie_manager = $container.handsontable(option_table_http_request_header_manager).handsontable('getInstance');
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染报告图表
|
||||
function renderEcharts() {
|
||||
|
@ -872,6 +961,8 @@ function renderReportTree() {
|
|||
$(`#table-variable-definition-${dispatcher_detail_id}`).handsontable('getInstance').render();
|
||||
}else if(element_type === 'TOOL' && node.data.tool_type === 'HTTP_REQUEST_HEADER_MANAGER'){
|
||||
$(`#table-http-request-header-manager-${dispatcher_detail_id}`).handsontable('getInstance').render();
|
||||
}else if(element_type === 'TOOL' && node.data.tool_type === 'HTTP_COOKIE_MANAGER'){
|
||||
$(`#table-http-cookie-manager-${dispatcher_detail_id}`).handsontable('getInstance').render();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
|
@ -955,6 +1046,8 @@ function renderReportTree() {
|
|||
return 'icon-tool-variable-definition';
|
||||
}else if(tool_type === 'HTTP_REQUEST_HEADER_MANAGER'){
|
||||
return 'icon-tool-http-request-header-manager';
|
||||
}else if(tool_type === 'HTTP_COOKIE_MANAGER'){
|
||||
return 'icon-tool-http-cookie-manager';
|
||||
}
|
||||
}else if(element_type === undefined) {
|
||||
return true;
|
||||
|
|
|
@ -478,6 +478,8 @@ function renderElementCopyTree() {
|
|||
return 'icon-tool-variable-definition';
|
||||
}else if(tool_type === 'HTTP_REQUEST_HEADER_MANAGER'){
|
||||
return 'icon-tool-http-request-header-manager';
|
||||
}else if(tool_type === 'HTTP_COOKIE_MANAGER'){
|
||||
return 'icon-tool-http-cookie-manager';
|
||||
}
|
||||
}else if(element_type === undefined) {
|
||||
return true;
|
||||
|
@ -1516,6 +1518,8 @@ function showTool(tool_id, tool_type) {
|
|||
$(`#table-variable-definition-${tool_id}`).handsontable('getInstance').render();
|
||||
}else if(tool_type === 'HTTP_REQUEST_HEADER_MANAGER'){
|
||||
$(`#table-http-request-header-manager-${tool_id}`).handsontable('getInstance').render();
|
||||
}else if(tool_type === 'HTTP_COOKIE_MANAGER'){
|
||||
$(`#table-http-cookie-manager-${tool_id}`).handsontable('getInstance').render();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
|
@ -1899,6 +1903,10 @@ function addToolElement(tool_id) {
|
|||
let element = getHTTPRequestHeaderManagerToolElement(data.tool_id);
|
||||
element.init(data.data_variables);
|
||||
element_tool_dict[data.tool_id] = element;
|
||||
}else if(data.tool_type === 'HTTP_COOKIE_MANAGER') {
|
||||
let element = getHTTPCookieManagerToolElement(data.tool_id);
|
||||
element.init(data.data_attributes);
|
||||
element_tool_dict[data.tool_id] = element;
|
||||
}
|
||||
resolve(['成功了', 'success']); // 也可以传递其他类型参数
|
||||
}else{
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
function getHTTPCookieManagerToolElement(tool_id) {
|
||||
let element = new Object;
|
||||
element.dom = new Object;
|
||||
element.obj = new Object;
|
||||
|
||||
// 案例页面元素
|
||||
element.dom.$input_tool_name = $(`#input-tool-name-${tool_id}`);
|
||||
element.dom.$input_tool_description = $(`#input-tool-description-${tool_id}`);
|
||||
element.dom.$btn_tool_save = $(`#btn-tool-save-${tool_id}`);
|
||||
element.dom.$div_navigation_tool_name = $(`.tool-name[data-tool-id=${tool_id}]`);
|
||||
|
||||
// 对象
|
||||
element.obj.table_http_cookie_manager = null; // 变量定义表格
|
||||
|
||||
// 初始化
|
||||
element.init = function(data_variables) {
|
||||
// 事件绑定
|
||||
eventBinding();
|
||||
// 渲染HTTPCookie管理器表格
|
||||
renderHTTPCookieManagerTable(data_variables);
|
||||
};
|
||||
|
||||
// 事件绑定
|
||||
function eventBinding() {
|
||||
element.dom.$input_tool_name.on('change keyup', handleKeyUpAndChange);
|
||||
element.dom.$btn_tool_save.on('click', saveTool);
|
||||
|
||||
function handleKeyUpAndChange(){
|
||||
let tool_name = element.dom.$input_tool_name.val();
|
||||
element.dom.$div_navigation_tool_name.children().text(tool_name);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染HTTPCookie管理器表格
|
||||
function renderHTTPCookieManagerTable(data_attributes) {
|
||||
const option_table_http_cookie_manager = {
|
||||
data: data_attributes,
|
||||
rowHeaders: true,
|
||||
// colHeaders: true,
|
||||
licenseKey: 'non-commercial-and-evaluation',
|
||||
// 表格宽度
|
||||
// width: '95vw', // 不指定宽度使其自适应容器
|
||||
// 拉伸方式
|
||||
stretchH: 'all',
|
||||
// tab键自动换行切换
|
||||
autoWrapRow: true,
|
||||
height: '65vh',
|
||||
// 最大行数
|
||||
maxRows: 999,
|
||||
// 允许手工移动行或列
|
||||
manualRowResize: true,
|
||||
manualColumnResize: true,
|
||||
// 列名
|
||||
colHeaders: [
|
||||
'名称',
|
||||
'值',
|
||||
'域',
|
||||
'路径',
|
||||
'安全',
|
||||
],
|
||||
// 为列设置默认值
|
||||
dataSchema: {
|
||||
name: '',
|
||||
value: '',
|
||||
url_encode: false,
|
||||
content_type: 'text/plain',
|
||||
include_equals: true,
|
||||
},
|
||||
// 设置列数据类型
|
||||
columns: [
|
||||
{
|
||||
data: 'name'
|
||||
},
|
||||
{
|
||||
data: 'value',
|
||||
},
|
||||
{
|
||||
data: 'domain',
|
||||
},
|
||||
{
|
||||
data: 'path',
|
||||
},
|
||||
{
|
||||
data: 'secure',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
// 列宽比例
|
||||
colWidths: [2, 5, 5, 3, 1],
|
||||
manualRowMove: true,
|
||||
manualColumnMove: false,
|
||||
// 右键菜单
|
||||
contextMenu: ['row_above', 'row_below', '---------', 'remove_row', '---------', 'undo', 'redo', '---------', 'alignment', '---------', 'copy', 'cut'],
|
||||
// 列是否支持过滤
|
||||
filters: false,
|
||||
// 下拉菜单
|
||||
dropdownMenu: ['make_read_only', '---------', 'alignment'],
|
||||
// 语言
|
||||
language: 'zh-CN',
|
||||
// 是否允许无效数据 默认 true
|
||||
allowInvalid: false,
|
||||
};
|
||||
let $container = $(`#table-http-cookie-manager-${tool_id}`);
|
||||
element.obj.table_http_cookie_manager = $container.handsontable(option_table_http_cookie_manager).handsontable('getInstance');
|
||||
}
|
||||
|
||||
function saveTool() {
|
||||
let tool_name = element.dom.$input_tool_name.val();
|
||||
let tool_description = element.dom.$input_tool_description.val();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/ajax/tool/http_cookie_manager/save',
|
||||
data: {
|
||||
tool_id: tool_id,
|
||||
tool_name: tool_name,
|
||||
tool_description: tool_description,
|
||||
attrs: JSON.stringify(element.obj.table_http_cookie_manager.getSourceData()),
|
||||
},
|
||||
success: function (data, status, xhr) {
|
||||
if (data.error_no === 0){
|
||||
message('HTTPCookie保存成功', 'success');
|
||||
}else{
|
||||
message("HTTPCookie保存失败: " + data.error_msg, "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关键词查找
|
||||
element.mark = function (text) {
|
||||
// 匹配到则返回true
|
||||
let pat = new RegExp(text);
|
||||
let value = '';
|
||||
let markFlag = false;
|
||||
// 名称
|
||||
value = element.dom.$input_tool_name.val();
|
||||
if (pat.test(value)) return true;
|
||||
// 注释
|
||||
value = element.dom.$input_tool_description.val();
|
||||
if (pat.test(value)) return true;
|
||||
// 期望表格
|
||||
markFlag = false;
|
||||
value = element.obj.table_http_cookie_manager.getSourceData();
|
||||
$.each(value, function (index, row) {
|
||||
$.each(row, function (column, data) {
|
||||
if (pat.test(data)){
|
||||
markFlag = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (markFlag) return true;
|
||||
|
||||
// 未匹配到返回false
|
||||
return false;
|
||||
};
|
||||
|
||||
return element;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<div class="container-fluid report-detail-data" id="dispatcher-detail-{{ dispatcher_detail.id }}" data-element-type="TOOL" style="display: none;">
|
||||
<h4>工具名称: {{ dispatcher_detail.element_name }}</h4>
|
||||
<h6>开始时间: {{ dispatcher_detail.start_time }}</h6>
|
||||
<h6>调度编号: {{ dispatcher_detail.dispatcher_id }}</h6>
|
||||
<h6>元素编号: {{ dispatcher_detail.element_id }}</h6>
|
||||
<h6>HTTPCookie管理器:</h6>
|
||||
<div class="container-fluid p-0 mt-2">
|
||||
<div id="table-http-cookie-manager-{{ dispatcher_detail.id }}"></div>
|
||||
</div>
|
||||
</div>
|
|
@ -100,6 +100,15 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="v-pills-tool" role="tabpanel">
|
||||
<div class="btn btn-outline-primary" data-scene-id="{{ scene.id }}" onclick="addTool({{ scene.scene_controller.logic_controller.id }}, 'HTTP_COOKIE_MANAGER')">
|
||||
<h5 class="text-left">
|
||||
HTTPCookieManager
|
||||
<svg class="icon float-right" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M512 938.666667c-235.52-0.256-426.410667-191.146667-426.666667-426.666667 16.810667 4.736 34.133333 7.168 51.626667 7.210667a175.36 175.36 0 0 0 145.066667-74.965334c29.568-42.666667 35.84-97.322667 16.64-145.578666 12.245333 2.176 24.661333 3.285333 37.12 3.328A182.058667 182.058667 0 0 0 478.506667 234.666667a177.536 177.536 0 0 0 36.266666-149.333334c235.648 0.768 426.026667 192.426667 425.301334 428.074667C939.306667 749.013333 747.648 939.392 512 938.666667z m32.554667-146.432a53.376 53.376 0 1 0 20.778666-102.4 53.333333 53.333333 0 0 0-20.778666 102.4zM295.253333 720a71.125333 71.125333 0 1 0-0.853333-0.256h0.682667l0.170666 0.256z m402.389334-106.965333a71.082667 71.082667 0 1 0-43.605334-63.573334c0.682667 26.666667 16.256 50.645333 40.277334 62.208h-0.512l1.28 0.554667 0.853333 0.341333h-0.256l1.792 0.682667 0.170667-0.213333zM512 440.874667a35.584 35.584 0 1 0 13.824 2.816l-0.768-0.341334-1.152-0.426666a35.157333 35.157333 0 0 0-11.904-2.048z m160-177.749334a53.162667 53.162667 0 1 0 20.736 4.266667h-0.426667a53.12 53.12 0 0 0-20.309333-4.266667zM192 405.333333a35.541333 35.541333 0 1 1 0-71.082666 35.541333 35.541333 0 0 1 0 71.082666zM138.666667 298.666667a53.333333 53.333333 0 1 1 0-106.666667 53.333333 53.333333 0 0 1 0 106.666667z m213.333333-71.125334a53.333333 53.333333 0 1 1 0-106.752 53.333333 53.333333 0 0 1 0 106.794667v-0.042667zM227.584 156.458667a35.541333 35.541333 0 1 1 0-71.082667 35.541333 35.541333 0 0 1 0 71.082667z"></path>
|
||||
</svg>
|
||||
</h5>
|
||||
<p class="text-left">定义HTTPCookie.</p>
|
||||
</div>
|
||||
<div class="btn btn-outline-primary" data-scene-id="{{ scene.id }}" onclick="addTool({{ scene.scene_controller.logic_controller.id }}, 'HTTP_REQUEST_HEADER_MANAGER')">
|
||||
<h5 class="text-left">
|
||||
HTTPRequestHeaderManager
|
||||
|
|
|
@ -146,6 +146,7 @@ class="bg-gradient-grey"
|
|||
<script src="{{ url_for('static', filename='js/tool/script.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/tool/variable_definition.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/tool/http_request_header_manager.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/tool/http_cookie_manager.js') }}"></script>
|
||||
<script>
|
||||
let project_id = {{ project.id }};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<div class="row container-fluid element-data element-tool mt-2 ml-0" data-tool-id="{{ tool.id }}" data-tool-type="{{ tool.tool_type }}" style="display: none">
|
||||
<h4>HTTPCookie管理器</h4>
|
||||
<div class="input-group input-group-sm">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="input-tool-name-{{ tool.id }}">名称</label>
|
||||
</div>
|
||||
<input id="input-tool-name-{{ tool.id }}" type="text" class="form-control" placeholder="这里填写案例名称" value="{{ tool.name }}">
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="input-tool-description-{{ tool.id }}">注释</label>
|
||||
</div>
|
||||
<input id="input-tool-description-{{ tool.id }}" type="text" class="form-control" placeholder="这里填写注释说明" value="{{ tool.description }}">
|
||||
</div>
|
||||
<div class="container-fluid d-flex align-items-center justify-content-between pr-0 mr-0 pl-0 ml-0 mt-2">
|
||||
<label class=" control-label">存储在Cookie管理器中的Cookie:</label>
|
||||
<div class="btn-toolbar pr-0 mr-2" role="toolbar">
|
||||
<div class="btn-group" role="group">
|
||||
<button id="btn-tool-save-{{ tool.id }}" type="button" class="btn btn-primary">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid p-0 mt-2">
|
||||
<div id="table-http-cookie-manager-{{ tool.id }}"></div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue