Compare commits

...

2 Commits

  1. 6
      DBManager.py
  2. 151
      TaskManager.py
  3. 2
      config.yaml
  4. 53
      main.py
  5. 56
      mycode/AttackMap.py
  6. 174
      mycode/ControlCenter.py
  7. 3
      mycode/DBManager.py
  8. 0
      mycode/InstructionManager.py
  9. 135
      mycode/LLMManager.py
  10. 90
      mycode/Result_merge.py
  11. 0
      mycode/TargetManager.py
  12. 1
      payload/test.txt
  13. 22
      pipfile
  14. 1
      test
  15. 43
      test.py
  16. 136
      tools/CurlTool.py
  17. 17
      tools/DirbTool.py
  18. 2
      tools/EchoTool.py
  19. 13
      tools/Enum4linuxTool.py
  20. 12
      tools/FtpTool.py
  21. 48
      tools/HydraTool.py
  22. 11
      tools/MkdirTool.py
  23. 61
      tools/MysqlTool.py
  24. 2
      tools/NcTool.py
  25. 69
      tools/NiktoTool.py
  26. 3
      tools/NmapTool.py
  27. 11
      tools/PrintfTool.py
  28. 84
      tools/PsqlTool.py
  29. 25
      tools/PythoncodeTool.py
  30. 8
      tools/SearchsploitTool.py
  31. 11
      tools/ShowmountTool.py
  32. 24
      tools/SmtpuserenumTool.py
  33. 11
      tools/SwaksTool.py
  34. 86
      tools/TelnetTool.py
  35. 68
      tools/ToolBase.py
  36. 11
      tools/TouchTool.py
  37. 4
      web/API/__init__.py
  38. 143
      web/API/user.py
  39. 65
      web/__init__.py
  40. 0
      web/common/__init__.py
  41. 4
      web/common/errors.py
  42. 10
      web/common/models.py
  43. 43
      web/common/utils.py
  44. 5
      web/main/__init__.py
  45. 83
      web/main/routes.py
  46. 7
      web/main/static/data/document.js
  47. 105
      web/main/static/data/styles.css
  48. BIN
      web/main/static/favicon.ico
  49. BIN
      web/main/static/favicon_bak.ico
  50. 26
      web/main/static/images/login/zf.svg
  51. 171
      web/main/static/plugins/debug/debug.js
  52. 265
      web/main/static/plugins/debug/styles/debug.css
  53. 3
      web/main/static/plugins/debug/styles/images/console_panel_off.svg
  54. 3
      web/main/static/plugins/debug/styles/images/console_panel_on.svg
  55. 474
      web/main/static/plugins/page_notes/page_notes.js
  56. 3
      web/main/static/plugins/page_notes/styles/images/notes_panel_off.svg
  57. 3
      web/main/static/plugins/page_notes/styles/images/notes_panel_on.svg
  58. 209
      web/main/static/plugins/page_notes/styles/page_notes.css
  59. 479
      web/main/static/plugins/recordplay/recordplay.js
  60. 90
      web/main/static/plugins/recordplay/styles/recordplay.css
  61. 562
      web/main/static/plugins/sitemap/sitemap.js
  62. 7
      web/main/static/plugins/sitemap/styles/images/back_keys.svg
  63. 14
      web/main/static/plugins/sitemap/styles/images/closed_item.svg
  64. 15
      web/main/static/plugins/sitemap/styles/images/flow.svg
  65. 6
      web/main/static/plugins/sitemap/styles/images/folder_closed_blue.svg
  66. 10
      web/main/static/plugins/sitemap/styles/images/forward_keys.svg
  67. 3
      web/main/static/plugins/sitemap/styles/images/left_arrow.svg
  68. 12
      web/main/static/plugins/sitemap/styles/images/open_item.svg
  69. 6
      web/main/static/plugins/sitemap/styles/images/page_lt_grey.svg
  70. 3
      web/main/static/plugins/sitemap/styles/images/right_arrow.svg
  71. 6
      web/main/static/plugins/sitemap/styles/images/search_off.svg
  72. 6
      web/main/static/plugins/sitemap/styles/images/search_on.svg
  73. 3
      web/main/static/plugins/sitemap/styles/images/sitemap_panel_off.svg
  74. 3
      web/main/static/plugins/sitemap/styles/images/sitemap_panel_on.svg
  75. 384
      web/main/static/plugins/sitemap/styles/sitemap.css
  76. 35
      web/main/static/resources/Other.html
  77. 292
      web/main/static/resources/css/axure_rp_page.css
  78. 6
      web/main/static/resources/css/bootstrap.min.css
  79. 1052
      web/main/static/resources/css/default.css
  80. 15
      web/main/static/resources/css/headers.css
  81. 25
      web/main/static/resources/css/images/images.html
  82. BIN
      web/main/static/resources/css/images/newwindow.gif
  83. BIN
      web/main/static/resources/css/images/note.gif
  84. BIN
      web/main/static/resources/css/images/touch.cur
  85. 32
      web/main/static/resources/css/images/touch.svg
  86. BIN
      web/main/static/resources/css/images/ui-bg_flat_0_aaaaaa_40x100.png
  87. BIN
      web/main/static/resources/css/images/ui-bg_glass_55_fbf9ee_1x400.png
  88. BIN
      web/main/static/resources/css/images/ui-bg_glass_65_ffffff_1x400.png
  89. BIN
      web/main/static/resources/css/images/ui-bg_glass_75_dadada_1x400.png
  90. BIN
      web/main/static/resources/css/images/ui-bg_glass_75_e6e6e6_1x400.png
  91. BIN
      web/main/static/resources/css/images/ui-bg_glass_75_ffffff_1x400.png
  92. BIN
      web/main/static/resources/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png
  93. BIN
      web/main/static/resources/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png
  94. BIN
      web/main/static/resources/css/images/ui-icons_222222_256x240.png
  95. BIN
      web/main/static/resources/css/images/ui-icons_2e83ff_256x240.png
  96. BIN
      web/main/static/resources/css/images/ui-icons_454545_256x240.png
  97. BIN
      web/main/static/resources/css/images/ui-icons_888888_256x240.png
  98. BIN
      web/main/static/resources/css/images/ui-icons_cd0a0a_256x240.png
  99. 412
      web/main/static/resources/css/jquery-ui-themes.css
  100. 12
      web/main/static/resources/css/previewfonts.css

6
DBManager.py

@ -1,6 +0,0 @@
'''
数据库管理功能类
'''
class DBManager:
def __init__(self):
pass

151
TaskManager.py

@ -1,11 +1,11 @@
'''
渗透测试任务管理类 一次任务的闭合性要检查2025-3-10 一次任务后要清理LLM和InstrM的数据
'''
from TargetManager import TargetManager # 从模块导入类
from mycode.TargetManager import TargetManager # 从模块导入类
#from LLMManager import LLMManager # 同理修正其他导入
from mycode.ControlCenter import ControlCenter #控制中心替代LLM--控制中心要实现一定的基础逻辑和渗透测试树的维护。
from myutils.FileManager import FileManager
from InstructionManager import InstructionManager
from mycode.InstructionManager import InstructionManager
from mycode.DBManager import DBManager
from myutils.MyTime import get_local_timestr
from myutils.MyLogger_logger import LogHandler
@ -38,6 +38,7 @@ class TaskManager:
self.lock = threading.Lock() #线程锁
self.node_num = 0 #在处理Node线程的处理
self.brun = True
self.cookie = "" #cookie参数
def res_in_quere(self,bres,instr,reslut,start_time,end_time,th_DBM,source_result,ext_params,work_node):
'''
@ -57,8 +58,9 @@ class TaskManager:
#结果入队列---2025-3-18所有的指令均需返回给LLM便于节点状态的更新,所以bres作用要调整。
res = {'执行指令':instr,'结果':reslut}
str_res = json.dumps(res,ensure_ascii=False) #直接字符串组合也可以-待验证
work_node.llm_type = 1
work_node.add_res(res) #入节点结果队列
work_node.add_res(str_res) #入节点结果队列
def do_worker_th(self):
#线程的dbm需要一个线程一个
@ -87,14 +89,20 @@ class TaskManager:
with self.lock:
self.node_num -= 1
if self.node_num == 0 and self.node_queue.empty(): #
self.logger.debug("此批次指令执行完成!")
with open("attack_tree", 'wb') as f:
pickle.dump(TM.CCM.attack_tree, f)
pickle.dump(self.CCM.attack_tree, f)
except queue.Empty:
self.logger.debug("暂无需要执行指令的节点!")
time.sleep(20)
def start_task(self,target_name,target_in):
'''
:param target_name: 任务目标名字
:param target_in: 任务目标访问地址
:return:
'''
#判断目标合法性
bok,target,type = self.TargetM.validate_and_extract(target_in)
if bok:
@ -134,11 +142,13 @@ if __name__ == "__main__":
current_path = os.path.dirname(os.path.realpath(__file__))
strMsg = FM.read_file("test",1)
test_type = 5
iput_index = 6 # 0是根节点
test_type = 2
instr_index = 19
iput_index = -1 # 0是根节点
indexs = []
if test_type == 0: #新目标测试
# 启动--初始化指令
node_list = TM.CCM.start_do("192.168.204.137", 0)
node_list = TM.CCM.start_do("58.216.217.70", 1)
#异步处理,需要等待线程结束了
for th in TM.CCM.llmth_list:
th.join()
@ -147,10 +157,16 @@ if __name__ == "__main__":
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
# 遍历node,查看有instr的ndoe
nodes = TM.CCM.attack_tree.traverse_bfs()
for node in nodes:
nodes = TM.CCM.attack_tree.traverse_dfs()
if indexs:
for index in indexs:
node = nodes[index]
if node.instr_queue: # list
TM.node_queue.put(node)
else:
for node in nodes:
if node.instr_queue:
TM.node_queue.put(node)
#创建线程执行指令
for i in range(TM.max_thread_num):
@ -165,21 +181,18 @@ if __name__ == "__main__":
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
#遍历node,查看有res的数据
iput_max_num = 1
iput_max_num = 0
iput_num = 0
nodes = TM.CCM.attack_tree.traverse_bfs()
if iput_index != -1:#index 不为-1就是指定节点返回,人为保障不越界
node = nodes[iput_index]
nodes = TM.CCM.attack_tree.traverse_dfs()
if indexs:
for index in indexs:
node = nodes[index]
if node.res_quere:
TM.CCM.llm_quere.put(node)
else:
for node in nodes:
if node.res_quere: #有结果需要提交LLM
if node.res_quere:
TM.CCM.llm_quere.put(node)
iput_num += 1
if iput_max_num > 0: #0是有多少提交多少
if iput_num == iput_max_num:
break
#创建llm工作线程
TM.CCM.brun = True
@ -191,25 +204,28 @@ if __name__ == "__main__":
for t in TM.CCM.llmth_list:
t.join()
elif test_type ==3: #执行指定指令
instrlist=[
"msfconsole -q -x \"use auxiliary/scanner/smb/smb_version; set RHOSTS 192.168.204.137; run; exit\""]
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
# 遍历node,查看有instr的ndoe
nodes = TM.CCM.attack_tree.traverse_dfs()
instrlist = nodes[instr_index].instr_queue
instrlist = ['''
mkdir -p /tmp/nfs_test && mount -t nfs -o nolock 192.168.204.137:/ /tmp/nfs_test && ls /tmp/nfs_test
''']
for instr in instrlist:
start_time = get_local_timestr() # 指令执行开始时间
bres, instr, reslut, source_result, ext_params = TM.InstrM.execute_instruction(instr)
end_time = get_local_timestr() # 指令执行结束时间
# 入数据库 -- bres True和False 都入数据库2025-3-10---加node_path(2025-3-18)#?
if TM.DBM.ok:
TM.DBM.insetr_result(0, instr, reslut, 0, start_time, end_time, source_result,
ext_params, "独立命令执行")
else:
TM.logger.error("数据库连接失败!!")
res = {'执行结果': reslut}
str_res = json.dumps(res,ensure_ascii=False) # 直接字符串组合也可以-待验证
print(str_res)
elif test_type == 4: #修改Message
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
#创建一个新的节点
from mycode.AttackMap import TreeNode
testnode = TreeNode("test",0)
testnode = TreeNode("test",0,0)
TM.CCM.LLM.build_initial_prompt(testnode)#新的Message
systems = testnode.messages[0]["content"]
#print(systems)
@ -222,9 +238,84 @@ if __name__ == "__main__":
elif test_type ==5: #显示指令和结果list
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
nodes = TM.CCM.attack_tree.traverse_bfs()
nodes = TM.CCM.attack_tree.traverse_dfs()
if iput_index == -1:
for node in nodes:
print(f"----{node.path}-{node.status}----\n****instr_quere")
print(f"{','.join(node.instr_queue)}\n****res_quere")
try:
print(f"{','.join(node.res_quere)}")
except:
print(f"{json.dumps(node.res_quere)}")
elif iput_index == -2:#只输出有instruction的数据
index = 0
for node in nodes:
if node.instr_queue:
print(f"----{index}--{node.path}--{node.status}----")
print(f"{','.join(node.instr_queue)}")
index += 1
else:
print(f"********\n{','.join(nodes[iput_index].instr_queue)}\n********")
print(f"&&&&&&&&\n{','.join(nodes[iput_index].res_quere)}\n&&&&&&&&")
elif test_type == 6: #给指定节点添加测试指令
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
nodes = TM.CCM.attack_tree.traverse_dfs()
str_instr = "nmap -sV -p- 192.168.204.137 -T4 -oN nmap_full_scan.txt"
index = 9
nodes[index].instr_queue.append(str_instr)
nodes[index].res_quere = []
with open("attack_tree", 'wb') as f:
pickle.dump(TM.CCM.attack_tree, f)
elif test_type == 7: #给指定节点修改指令的执行结果
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
nodes = TM.CCM.attack_tree.traverse_dfs()
str_instr = "psql -h 192.168.204.137 -U postgres -c '\l'"
start_time = get_local_timestr() # 指令执行开始时间
bres, instr, reslut, source_result, ext_params = TM.InstrM.execute_instruction(str_instr)
end_time = get_local_timestr() # 指令执行结束时间
# 入数据库 -- bres True和False 都入数据库2025-3-10---加node_path(2025-3-18)#?
if TM.DBM.ok:
TM.DBM.insetr_result(0, instr, reslut, 0, start_time, end_time, source_result,
ext_params, "独立命令执行")
index = 9
nodes[index].res_quere.clear()
nodes[index].res_quere.append(reslut)
with open("attack_tree", 'wb') as f:
pickle.dump(TM.CCM.attack_tree, f)
elif test_type ==8: #显示有漏洞信息的数据
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
nodes = TM.CCM.attack_tree.traverse_dfs()
if nodes:
for node in nodes:
if node.vul_type != "未发现":
print(f"{node.path}----{node.vul_type}")
elif test_type == 9: #处理自定义llm回复内容
with open("attack_tree", "rb") as f:
TM.CCM.attack_tree = pickle.load(f)
nodes = TM.CCM.attack_tree.traverse_dfs()
node = nodes[5]
strconent = '''
{'role': 'assistant', 'content': '{"action":"update_status", "node": "25端口", "status": "已完成", "vulnerability": {"name":"SMTP用户枚举漏洞","risk":"中危","info":"VRFY命令可验证有效用户"}}\n\n```bash-[目标系统->192.168.204.137->25端口]\nsmtp-user-enum -M VRFY -U /usr/share/wordlists/metasploit/unix_users.txt -t 192.168.204.137\n```\n\n```bash-[目标系统->192.168.204.137->25端口]\nnc -nv 192.168.204.137 25 << EOF\nEXPN root\nMAIL FROM: attacker@example.com\nRCPT TO: external@example.com\nDATA\nTest open relay\n.\nQUIT\nEOF\n```'}
'''
strjson = json.loads(strconent)
node_cmds,commands = TM.CCM.LLM.fetch_instruction(strjson["content"])
TM.CCM.tree_manager(node_cmds)
# 更新tree
bok, new_commands = TM.CCM.tree_manager(node_cmds, node, commands, TM.DBM)
# 分析指令入对应节点
if bok: # 节点指令若存在错误,测试指令都不处理,需要LLM重新生成
node_list = TM.CCM.instr_in_node(new_commands, node)
#报不保存待定--
with open("attack_tree", 'wb') as f:
pickle.dump(TM.CCM.attack_tree, f)
else:
#完整过程测试---要设定终止条件
pass

2
config.yaml

@ -14,7 +14,7 @@ mysql:
database: zfsafe
#LLM-Type
LLM_type: 1 #0-腾讯云,1-DS,2-GPT
LLM_type: 2 #0-腾讯云,1-DS,2-2233ai,3-GPT
LLM_max_chain_count: 10 #为了避免推理链过长,造成推理效果变差,应该控制一个推理链的长度上限
#用户初始密码

53
main.py

@ -1,40 +1,16 @@
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your mycode.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
import os
from openai import OpenAI
import asyncio
import uvicorn
import TaskManager
import os
from web import create_app
from hypercorn.asyncio import serve
from hypercorn.config import Config
def LLMtest():
client = OpenAI(
# 请用知识引擎原子能力API Key将下行替换为:api_key="sk-xxx",
api_key="sk-fGBYaQLHykBOQsFwVrQdIFTsYr8YDtDVDQWFU41mFsmvfNPc",
base_url="https://api.lkeap.cloud.tencent.com/v1",
)
completion = client.chat.completions.create(
model="deepseek-v3", # 此处以 deepseek-r1 为例,可按需更换模型名称。
messages=[
{'role': 'user', 'content': '你是一个渗透测试专家,正在对IP58.216.217.70进行渗透测试。当前阶段是[信息收集]。请生成下一步的指令。'}
],
)
# 通过reasoning_content字段打印思考过程
# print("思考过程:")
# print(completion.choices[0].message.reasoning_content)
# 通过content字段打印最终答案
print("最终答案:")
print(completion.choices[0].message.content)
print("\n" + "=" * 20 + "Token 使用情况" + "=" * 20 + "\n")
print(completion.usage)
def print_hi(name):
# Use a breakpoint in the mycode line below to debug your script.
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
async def run_quart_app():
app = create_app()
config = Config()
config.bind = ["0.0.0.0:5001"]
await serve(app, config)
#对某个目标进行测试
def startWork(targets):
@ -46,7 +22,8 @@ def startWork(targets):
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
LLMtest()
print(f"Current working directory (run.py): {os.getcwd()}")
#启动web项目
asyncio.run(run_quart_app())
uvicorn.run("run:app", host="0.0.0.0", port=5001, workers=4, reload=True)
# See PyCharm help at https://www.jetbrains.com/help/pycharm/

56
mycode/AttackMap.py

@ -1,4 +1,7 @@
import queue
import copy
import re
#渗透测试树结构维护类
class AttackTree:
def __init__(self,root_node):
@ -130,12 +133,17 @@ class AttackTree:
class TreeNode:
def __init__(self, name,task_id,status="未完成", vul_type="未发现"):
self.task_id = task_id #任务id
self.name = name # 节点名称
self.status = status # 节点状态
self.vul_type = vul_type # 漏洞类型
self.vul_name = ""
self.vul_grade = ""
self.vul_info = ""
self.children = [] # 子节点列表
self.parent = None # 父节点引用
self.path = "" #当前节点的路径
self.bwork = True #当前节点是否工作,默认True
self.messages = [] # 针对当前节点积累的messages -- 针对不同节点提交不同的messages
self.llm_type = 0 #llm提交类型 0--初始状态无任务状态,1--指令结果反馈,2--llm错误反馈
@ -143,12 +151,58 @@ class TreeNode:
self.do_sn = 0 #针对该节点instr执行次数
self.instr_queue = [] # queue.Queue() #针对当前节点的执行指令----重要约束:一个节点只能有一个线程在执行指令
self.res_quere = [] # queue.Queue() #指令执行的结果,一批一批
#用户补充信息
self.cookie = ""
self.ext_info = ""
#设置用户信息
def set_user_info(self,cookie,ext_info):
self.cookie = cookie
self.ext_info = ext_info
def copy_messages(self,childe_node):
'''
子节点继承父节点的messages目前规则保留上两层节点的message信息
:param childe_node:
:return:
'''
tmp_messages = copy.deepcopy(self.messages)
if not self.parent:
childe_node.messages = tmp_messages
else:
parent_path = self.parent.path
bfind = False
for msg in tmp_messages:
if msg["role"] == "system":
childe_node.messages.append(msg)
elif msg["role"] == "user":
if not bfind:
#获取user的node_path
content = msg["content"]
pattern = r"当前分支路径:(.+?)\n"
match = re.search(pattern, content)
if match:
path = match.group(1)
if parent_path in path:#当前节点的父节点路径在存入子节点messages
childe_node.messages.append(msg)
bfind = True #后续messages都保留
else:
print("提交的用户提示词结构有问题!")
else:
childe_node.messages.append(msg)
elif msg["role"] == "assistant":
if bfind:
childe_node.messages.append(msg)
else:
print("非法的信息体类型!")
def add_child(self, child_node):
child_node.parent = self
child_node.path = self.path + f"->{child_node.name}" #子节点的路径赋值
child_node.messages = self.messages #传递messages #给什么时候的messages待验证#?
#child_node.messages = copy.deepcopy(self.messages) #传递messages #给什么时候的messages待验证#?
self.copy_messages(child_node) #传递messages--只保留两层
self.children.append(child_node)
def add_instr(self,instr):

174
mycode/ControlCenter.py

@ -40,7 +40,11 @@ class ControlCenter:
pass
def get_user_init_info(self):
'''开始任务初,获取用户设定的基础信息'''
'''开始任务初,获取用户设定的基础信息,初始信息可以分为两块:
1.提交llm的补充信息 保留在本地的信息如工具补充参数等cookie
2.用户可以设置全局和指定节点端口
3.补充测试节点
'''
# ?包括是否对目标进行初始化的信息收集
return {"已知信息":""}
@ -82,10 +86,14 @@ class ControlCenter:
'''获取该节点的llm提交数据,会清空type和res_quere'''
llm_type = node.llm_type
node.llm_type = 0 #回归0
res_list = node.res_quere[:] #复制独立副本
node.res_quere.clear() #清空待处理数据
res_list = node.res_quere[:] #浅拷贝,复制第一层
node.res_quere.clear() #清空待处理数据,相当于把原应用关系接触
return llm_type,res_list
def restore_one_llm_work(self,node,llm_type,res_list):
node.llm_type = llm_type
node.res_quere = res_list
#llm请求提交线程
def th_llm_worker(self):#LLM没有修改的全局变量,应该可以共用一个client
'''
@ -104,7 +112,7 @@ class ControlCenter:
self.get_llm_instruction(node,th_DBM)
#释放锁
# 暂存状态--测试时使用
# 暂存状态--测试时使用--限制条件llm工作线程只能未1个
with open("attack_tree", 'wb') as f:
pickle.dump(self.attack_tree, f)
@ -137,8 +145,7 @@ class ControlCenter:
# 构造本次提交的prompt
ext_Prompt = f'''
上一步结果{res_str}
任务生成下一步渗透测试指令或判断是否完成该节点测试
请确保生成的指令满足以下
任务生成下一步渗透测试指令或判断是否完成该节点测试
'''
elif llm_type ==2: #llm返回的指令存在问题,需要再次请求返回
ext_Prompt = f'''
@ -146,25 +153,17 @@ class ControlCenter:
错误信息{res_str}
任务请按格式要求重新生成该节点上一次返回中生成的所有指令
'''
elif llm_type ==3: #已生成节点,但未生成测试指令
ext_Prompt = f'''
反馈类型需要继续补充信息
缺失信息{res_str}
任务
1.请生成这些节点的测试指令
2.这些节点的父节点为当前节点请正确生成这些节点的节点路径
3.若还有节点未能生成测试指令必须返回未生成指令的节点列表
'''
elif llm_type ==4: #未生成节点列表
ext_Prompt = f'''
反馈类型需要继续补充信息
缺失信息{res_str}
任务
1.请生成这些节点的新增节点指令并生成对应的测试指令
2.这些节点的父节点为当前节点请正确生成这些节点的节点路径
3.若节点未能全部新增必须返回未新增的节点列表
4.若有未生成指令的节点必须返回未生成指令的节点列表
'''
# '''
# elif llm_type ==4: #未生成节点列表
# ext_Prompt = f'''
# 反馈类型:需要继续补充信息
# 缺失信息:{res_str}
# 任务:
# 1.请生成这些节点的新增节点指令,并生成对应的测试指令;
# 2.这些节点的父节点为当前节点,请正确生成这些节点的节点路径;
# 3.若节点未能全部新增,必须返回未新增的节点列表
# 4.若有未生成指令的节点,必须返回未生成指令的节点列表。
# '''
elif llm_type ==5:
ext_Prompt = f'''
反馈类型测试指令格式错误
@ -186,10 +185,10 @@ class ControlCenter:
2.LLM的回复开始反复时有点难判断
'''
# 更新tree
bok = self.tree_manager(node_cmds, node,commands)
bok,new_commands = self.tree_manager(node_cmds, node,commands,DBM)
# 分析指令入对应节点
if bok: #节点指令若存在错误,测试指令都不处理,需要LLM重新生成
node_list = self.instr_in_node(commands, node)
node_list = self.instr_in_node(new_commands, node)
# 插入TM的node_queue中,交TM线程处理---除了LLM在不同的请求返回针对同一节点的测试指令,正常业务不会产生两次进队列
for node in node_list:
self.TM.node_queue.put(node)
@ -229,27 +228,29 @@ class ControlCenter:
self.put_one_llm_work(strerror,node,2)
return False
def tree_manager(self,node_cmds,node,commands):
def tree_manager(self,node_cmds,node,commands,DBM):
'''更新渗透测试树
node_cmds是json-list
2025-03-22添加commands参数用于处理LLM对同一个节点返回了测试指令但还返回了no_instruction节点指令
'''
if not node_cmds: # or len(node_cmds)==0: 正常not判断就可以有没有节点指令
return True
return True,commands
#对节点指令进行校验
if not self.verify_node_cmds(node_cmds,node):
return False #节点指令存在问题,终止执行
#执行节点操作
return False,commands #节点指令存在问题,终止执行
#执行节点操作---先执行add_node,怕返回顺序不一直
residue_node_cmds = []
for node_json in node_cmds:
action = node_json["action"]
if action == "add_node": # 新增节点
parent_node_name = node_json["parent"]
# 新增节点原则上应该都是当前节点增加子节点
if node.name == parent_node_name:
status = node_json["status"]
node_names = node_json["nodes"].split(',')
# 新增节点原则上应该都是当前节点增加子节点
if node.name == parent_node_name or parent_node_name.endswith(node.name):
for node_name in node_names:
#判重
# 判重---遇到过补充未生成指令的节点时,返回了新增这些节点的指令
bfind = False
for node_child in node.children:
if node_child.name == node_name:
@ -259,19 +260,47 @@ class ControlCenter:
# 添加节点
new_node = TreeNode(node_name, node.task_id, status)
node.add_child(new_node) # message的传递待验证
elif node.parent.name == parent_node_name or parent_node_name.endswith(node.parent.name):
#是添加当前节点的平级节点(当前节点的父节点下添加子节点) --使用2233ai-o3时遇到的情况
for node_name in node_names:
# 判重---遇到过补充未生成指令的节点时,返回了新增这些节点的指令
bfind = False
for node_child in node.parent.children:
if node_child.name == node_name:
bfind = True
break
if not bfind:
# 添加节点
new_node = TreeNode(node_name, node.task_id, status)
node.parent.add_child(new_node)
else:
self.logger.error(f"添加子节点时,遇到父节点名称不一致的,需要介入!!{node_json}") # 丢弃该节点
elif action == "update_status":
else:#其他指令添加到list
residue_node_cmds.append(node_json)
#执行剩余的节点指令--不分先后
for node_json in residue_node_cmds:
action = node_json["action"]
if action == "update_status":
node_name = node_json["node"]
status = node_json["status"]
vul_type = "未发现"
if "vulnerability" in node_json:
vul_type = json.dumps(node_json["vulnerability"])
if node.name == node_name:
if node.name == node_name or node_name.endswith(node_name):
node.status = status
if "vulnerability" in node_json:
#{\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}};
vul_type = json.dumps(node_json["vulnerability"],ensure_ascii=False) #json转字符串
try:
node.name = node_json["vulnerability"]["name"]
node.vul_grade = node_json["vulnerability"]["risk"]
node.vul_info = node_json["vulnerability"]["info"]
except:
self.logger.error("漏洞信息错误")
node.vul_type = vul_type
else:
self.logger.error(f"遇到不是修改本节点状态的,需要介入!!{node_json}")
str_user = f"遇到不是修改本节点状态的,需要介入!!{node_json}"
self.logger.error(str_user)
self.need_user_know(str_user,node)
elif action == "no_instruction":
#返回的未生成指令的数据进行校验:1.要有数据;2.节点不是当前节点就是子节点
nodes = []
@ -285,29 +314,68 @@ class ControlCenter:
break
if bcommand: #如果存在测试指令,则不把该节点放入补充信息llm任务
continue
#验证对应节点是否已经创建---本节点或子节点,其他节点不处理(更狠一点就是本节点都不行)
if node_name == node.name:
nodes.append(node_name)
# str_add = "请生成测试指令"
# self.put_one_llm_work(str_add,node,1)
else:
for child_node in node.children:
if child_node.name == node_name:
nodes.append(node_name)
# str_add = "无"
# self.put_one_llm_work(str_add, child_node, 1)
break
if nodes: #找到对应的节点才返回
str_nodes = ",".join(nodes)
str_add = {"已新增但未生成测试指令的节点":str_nodes}
# 提交一个错误反馈任务--但继续后续工作
self.put_one_llm_work(str_add, node, 3)
self.logger.debug(f"已新增但未生成指令的节点有:{nodes}")
elif action == "no_create":
if nodes: #阻塞式,在当前节点提交补充信息,完善节点指令 -- 优势是省token
new_commands = self.get_other_instruction(nodes,DBM,node)
commands.extend(new_commands)
elif action == "no_create": #提交人工确认
nodes = node_json["nodes"]
if nodes:
str_add = {"未新增的节点": nodes}
# 提交一个错误反馈任务--但继续后续工作
self.put_one_llm_work(str_add, node, 4)
self.logger.debug(f"未新增的节点有:{nodes}")
self.logger.debug(str_add)
# 提交一个继续反馈任务--继续后续工作 2025-3-25不自动处理
# self.put_one_llm_work(str_add, node, 4)
# self.logger.debug(f"未新增的节点有:{nodes}")
else:
self.logger.error("****不应该执行到这!程序逻辑存在问题!")
return True
return True,commands
#阻塞轮询补充指令
def get_other_instruction(self,nodes,DBM,cur_node):
res_str = ','.join(nodes)
new_commands = []
while res_str:
self.logger.debug(f"开始针对f{res_str}这些节点请求测试指令")
user_Prompt = f'''
当前分支路径{cur_node.path}
当前节点信息
- 节点名称{cur_node.name}
- 节点状态{cur_node.status}
- 漏洞类型{cur_node.vul_type}
反馈类型需要补充信息
缺失信息针对{res_str}的测试指令
任务
1.请生成这些节点的测试指令
2.这些节点的父节点为当前节点请正确生成这些节点的节点路径
3.若还有节点未能生成测试指令必须返回未生成指令的节点列表
'''
res_str = ""
node_cmds, commands = self.LLM.get_llm_instruction(user_Prompt, DBM, cur_node) # message要更新
#把返回的测试指令进行追加
new_commands.extend(commands)
#判断是否还有未添加指令的节点
for node_json in node_cmds: #正常应该只有一条no_instruction
if "no_instruction" in node_json and "nodes" in node_json:
tmp_nodes = []
node_names = node_json["nodes"].split(',')
for node_name in node_names:
if node_name in nodes:
tmp_nodes.append(node_name)
res_str = ','.join(tmp_nodes)
break
self.logger.debug("为添加指令的节点,都已完成指令的添加!")
return new_commands
def instr_in_node(self,commands,node):
node_list = [] #一次返回的测试指令
@ -334,6 +402,10 @@ class ControlCenter:
# 3.独立队列处理
return node_list
#需要用户确认的信息--待完善
def need_user_know(self,strinfo,node):
pass
#待修改
def is_user_instr(self,instr):
'''
@ -380,3 +452,5 @@ class ControlCenter:
self.brun =False
for th in self.llmth_list:
th.jion()

3
mycode/DBManager.py

@ -246,6 +246,9 @@ class DBManager:
cursor.close()
conn.close()
#全局的单一实例
app_DBM = DBManager()
app_DBM.connect()
if __name__ == "__main__":
mDBM = DBManager()

0
InstructionManager.py → mycode/InstructionManager.py

135
mycode/LLMManager.py

@ -7,12 +7,14 @@ import openai
import json
import threading
import re
import os
from openai import OpenAI
from mycode.DBManager import DBManager
from myutils.MyTime import get_local_timestr
from myutils.MyLogger_logger import LogHandler
class LLMManager:
def __init__(self,illm_type=0):
def __init__(self,illm_type=3):
self.logger = LogHandler().get_logger("LLMManager")
self.api_key = None
self.api_url = None
@ -30,11 +32,24 @@ class LLMManager:
self.model = "deepseek-reasoner" #model=deepseek-reasoner -- R1 model=deepseek-chat --V3
# 创建会话对象 -- 一个任务的LLM必须唯一
self.client = OpenAI(api_key=self.api_key, base_url=self.api_url)
elif illm_type == 2: #2233.ai
self.api_key = "sk-J3562ad9aece8fd2855bb495bfa1a852a4e8de8a2a1IOchD"
self.api_url = "https://api.gptsapi.net/v1"
self.model = "o3-mini-2025-01-31"
self.client = OpenAI(api_key=self.api_key,base_url=self.api_url)
elif illm_type ==3: #GPT
self.api_key =""
self.api_url = ""
self.model = ""
self.client = OpenAI()
# 定义代理服务器地址
proxy_url = "http://192.168.3.102:3128"
os.environ["HTTP_PROXY"] = proxy_url
os.environ["HTTPS_PROXY"] = proxy_url
self.api_key ="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"
self.api_url = "https://api.openai.com/v1"
self.model = "o3-mini-2025-01-31"
openai.proxy = proxy_url
openai.api_key = self.api_key
#self.client = openai
self.client = OpenAI(api_key=self.api_key,base_url=self.api_url)
#******测试使用,设置slef.message的值
def test_old_message(self,strMessage):
@ -45,62 +60,55 @@ class LLMManager:
except Exception as e:
print(f"错误: {str(e)}")
'''
**决策原则**
- 根据节点类型和状态优先执行基础测试如端口扫描服务扫描
- 仅在发现新信息或漏洞时新增子节点
- 确保每个新增节点匹配测试指令
'''
# 初始化messages
def build_initial_prompt(self,node):
def build_initial_prompt(self,node,str_ip=""):
if not node:
return
'''暂时未添加到提示词
**核心要求**
- 每次新增节点时必须在同一响应中为该节点提供测试指令
- 分批新增节点每次响应中新增节点不超过3个确保指令完整
'''
#根节点初始化message
node.messages = [{"role": "system",
"content":'''
你是一位渗透测试专家基于你的专业知识和经验负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试本地程序会执行你的指令并返回结果
你是一位渗透测试专家基于你的专业知识和经验负责动态控制整个渗透测试过程根据当前测试状态和返回结果决定下一步测试指令推动测试前进直至完成渗透测试本地程序会执行你的指令并返回结果
**总体要求**
1.以目标系统IP为根节点每个渗透测试点如端口服务漏洞点作为子节点形成树型结构测试树
2.测试树整体数据由本地程序存储你只需要关注当前节点的测试推进状态更新(未完成/已完成)及完整新增子节点
3.返回两类指令节点指令和测试指令以空行间隔不要包含注释和说明
4.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增节点的完整性若有未生成指令的节点必须返回已新增但未生成指令的节点列表如果节点无法全部新增必须返回未新增的节点列表
5.若无节点修改,新增,未生成指令等数据节点指令可以为空但测试指令必须对应已有节点
4.若一次性新增的节点过多无法为每个节点都匹配测试指令请优先保障新增中高危节点的完整性若有未生成测试指令的节点必须返回未生成指令节点列表
5.若无需要处理的节点数据节点指令可以不生成但测试指令必须对应已有节点
**决策流程**
1. 若当前节点是IP且未进行端口扫描则执行端口扫描
1. 若当前节点是IP且未进行端口扫描对当前节点执行端口扫描
2. 若端口扫描发现开放端口对可能存在中高危以上风险的端口新增节点并提供测试指令
3. 若当前节点是端口且未进行服务扫描则执行服务扫描
4. 若服务扫描发现服务版本或漏洞则新增漏洞测试节点并提供测试指令
5. 若漏洞验证成功则根据结果决定是否需要进一步测试若需要进一步测试则新增子节点并提供测试指令
6. 若节点测试无新信息和测试指令则更新状态为已完成
**测试指令生成要求**
1.明确每个测试指令的测试目标并优先尝试最简单最直接的办法
2.对于复杂的测试点使用递进逻辑组织指令先尝试基础测试方法根据执行结果决定是否进行更深入的测试
5. 若漏洞验证成功则根据结果决定是否需要进一步测试若需要进一步测试则为测试内容新增子节点并提供测试指令
6. 当当前节点执行完成所有可能的测试指令更新状态为已完成
**测试指令生成准则**
1.明确每个测试指令的测试目标并优先尝试最简单最直接的办法不要在同一个请求生成测试效果覆盖的指令
2.使用递进逻辑组织指令先尝试基础测试方法根据执行结果决定是否进行更深入的测试;
3.本地的IP地址为192.168.204.135
**节点指令格式**
- 新增节点{\"action\":\"add_node\", \"parent\": \"父节点\", \"nodes\": \"子节点1,子节点2\", \"status\": \"未完成\"};
- 已新增但未生成指令的节点列表{\"action\": \"no_instruction\", \"nodes\": \"3306端口,1000端口\"};
- 未新增的节点列表{\"action\": \"no_create\", \"nodes\": \"8080端口,8081端口,9000端口\"};
- 节点完成测试未发现漏洞{\"action\": \"update_status\", \"node\": \"21端口\", \"status\": \"已完成\"};
- 节点完成测试发现漏洞{\"action\": \"update_status\", \"node\": \"21端口\", \"status\": \"已完成\"\"vulnerability\": {\"name\":\"ftp匿名登录\",\"risk\":\"\"}};
- 新增节点{\"action\":\"add_node\", \"parent\": \"父节点\", \"nodes\": \"节点1,节点2\", \"status\": \"未完成\"};
- 未生成指令节点列表{\"action\": \"no_instruction\", \"nodes\": \"节点1,节点2\"};
- 完成测试未发现漏洞{\"action\": \"update_status\", \"node\": \"节点\", \"status\": \"已完成\"};
- 完成测试且发现漏洞{\"action\": \"update_status\", \"node\": \"节点\", \"status\": \"已完成\"\"vulnerability\": {\"name\":\"漏洞名称\",\"risk\":\"风险等级(低危/中危/高危)\",\"info\":\"补充信息(没有可为空)\"}};
**测试指令格式**
- shell指令```bash-[节点路径](.*?)```包裹需要避免用户交互
- python指令```python-[节点路径](.*?)```包裹主函数名为dynamic_fun需包含错误处理执行结束后必须返回一个tuple (status, output)其中status为'success''failure'output为补充输出信息
- shell指令```bash-[节点路径]指令内容```包裹需要避免用户交互,若涉及到多步指令请生成python代码
- python指令```python-[节点路径]指令内容```包裹主函数名为dynamic_fun需包含错误处理必须返回一个tuple(status, output)
- [节点路径]为从根节点到目标节点的完整层级描述
**核心要求**
- 优先保障新增中高危测试节点的完整性
- 指令之间必须要有一个空行
**响应示例**
{\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"node\": \"3306端口\", \"status\": \"未完成\"}
{\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"nodes\": \"3306端口,22端口\", \"status\": \"未完成\"}
```bash-[目标系统->192.168.1.100->3306端口]
mysql -u root -p 192.168.1.100
```
{\"action\":\"add_node\", \"parent\": \"192.168.1.100\", \"node\": \"22端口\", \"status\": \"未完成\"}
```python-[目标系统->192.168.1.100->22端口]
def dynamic_fun():
try:
result = "扫描完成"
return ("success", result)
except Exception as e:
return ("failure", str(e))
```
'''}] # 一个messages
def init_data(self,task_id=0):
@ -122,11 +130,12 @@ def dynamic_fun():
#提交LLM
post_time = get_local_timestr()
response = self.client.chat.completions.create(
model=self.model,
reasoning_effort="high",
messages = node.messages
)
#LLM返回结果处理
reasoning_content = ""
content = ""
@ -143,6 +152,12 @@ def dynamic_fun():
content = response.choices[0].message
# 记录llm历史信息
node.messages.append(content)
elif self.model == "o3-mini-2025-01-31":
reasoning_content = "" #gpt不返回推理内容
content = response.choices[0].message.content
print(content)
# 记录llm历史信息
node.messages.append({'role': 'assistant', 'content': content})
else:
self.logger.error("处理到未预设的模型!")
return None
@ -202,11 +217,12 @@ def dynamic_fun():
commands.append(shell_blocks[shell_index])
shell_index +=1
else:#其他的认为是节点操作指令--指令格式还存在不确定性,需要正则匹配,要求是JSON
pattern = re.compile(r'\{.*\}', re.DOTALL) #贪婪模式会匹配到最后一个},能使用嵌套的JSON
pattern = re.compile(r'\{(?:[^{}]|\{[^{}]*\})*\}')
# 遍历所有匹配到的 JSON 结构
strlines = part.strip('\n') #按行拆分,避免贪婪模式下,匹配到多行的最后一个}
for strline in strlines:
for match in pattern.findall(strline): #正常只能有一个
# strlines = part.strip('\n') #按行拆分,避免贪婪模式下,匹配到多行的最后一个}
# for strline in strlines:
for match in pattern.findall(part): #正常只能有一个
try:
node_cmds.append(json.loads(match)) # 解析 JSON 并添加到列表
except json.JSONDecodeError as e:#解析不了的不入队列
@ -214,25 +230,20 @@ def dynamic_fun():
return node_cmds,commands
def test_llm(self):
with open("../test", "r", encoding="utf-8") as f:
messages = json.load(f)
text = messages[-1]["content"]
list = self.fetch_instruction(text)
for itme in list:
print("***********")
print(itme)
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "讲个笑话吧。"}
]
response = self.client.chat.completions.create(
model=self.model,
reasoning_effort="medium",
messages=messages
)
print(response)
if __name__ == "__main__":
# LM = LLMManager(1)
# LM.test_llm()
tlist1 = []
tlist2 = []
tlist2.append(1)
if not tlist1:
print("list1空")
if not tlist2:
print("list2空")
if tlist2:
print("list2不为空")
llm = LLMManager(3)
llm.test_llm()

90
mycode/Result_merge.py

@ -0,0 +1,90 @@
#结果合并功能函数模块
import re
def my_merge(fun_name,result):
if fun_name == "enum4linux":
result = enum4linux_merge(result)
else:
pass
return result
#--------------------enum4linux--------------------
def enum4linux_merge(result):
print("enum4linux")
# 1.用户列表(用于密码爆破)
users = extract_users(result)
# 2. 共享目录(用于未授权访问/文件泄露)
shares = extract_shares(result)
# 3. 密码策略(指导爆破规则)
policy = extract_password_policy(result)
# 4. 操作系统信息(用于漏洞匹配)
os_info = extract_os_info(result)
# 整合输出
result = f"Users:{users}\nShares:{shares}\nPolicy:{policy}\nOS Info:{os_info}\n"
print(result)
return result
def extract_users(data):
"""提取所有用户列表(含 RID)"""
users = {}
pattern = re.compile(r"user:\[(.*?)\] rid:\[(0x[a-fA-F0-9]+)\]")
matches = pattern.findall(data)
for user, rid in matches:
users[user] = rid
return users
def extract_shares(data):
"""提取共享目录并清理 ANSI 转义码"""
shares = []
share_block = re.search(r"Share Enumeration.*?=\n(.*?)\n\n", data, re.DOTALL)
if share_block:
lines = share_block.group(1).split('\n')
for line in lines:
# 清理 ANSI 转义码(如 \x1b[35m)
line_clean = re.sub(r'\x1b\[[0-9;]*m', '', line)
if 'Disk' in line_clean or 'IPC' in line_clean:
parts = list(filter(None, line_clean.split()))
if len(parts) >= 3:
share = {
"name": parts[0],
"type": parts[1],
"access": "Unknown"
}
# 提取清理后的访问权限
access_line = re.search(rf"//.*{re.escape(parts[0])}.*Mapping: (.*?) ", data)
if access_line:
access_clean = re.sub(r'\x1b\[[0-9;]*m', '', access_line.group(1))
share["access"] = access_clean
shares.append(share)
return shares
def extract_password_policy(data):
"""提取密码策略"""
policy = {}
policy_block = re.search(r"Password Policy Information.*?=\n(.*?)\n\n", data, re.DOTALL)
if not policy_block:
return policy
policy_text = policy_block.group(1)
# 提取最小密码长度(处理未匹配情况)
min_length_match = re.search(r"Minimum password length: (\d+)", policy_text)
policy["min_length"] = min_length_match.group(1) if min_length_match else "未知"
# 提取密码复杂性要求
complexity_match = re.search(r"Password Complexity: (Enabled|Disabled)", policy_text)
policy["complexity"] = complexity_match.group(1) if complexity_match else "未知"
# 提取账户锁定阈值
lockout_match = re.search(r"Account Lockout Threshold: (\d+|None)", policy_text)
policy["lockout_threshold"] = lockout_match.group(1) if lockout_match else "未知"
return policy
def extract_os_info(data):
"""提取操作系统信息"""
os_info = {}
match = re.search(r"server \(([^)]+)\)", data)
if match:
os_info["samba_version"] = match.group(1)
return os_info
#------------------------------------------------

0
TargetManager.py → mycode/TargetManager.py

1
payload/test.txt

@ -0,0 +1 @@
测试文件

22
pipfile

@ -14,5 +14,27 @@ pip install loguru -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install paramiko -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install impacket -i https://pypi.tuna.tsinghua.edu.cn/simple/
sudo apt-get install libpq-dev python3-dev
pip install psycopg2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
cd /usr/share/wordlists/
gzip -d rockyou.txt.gz
#searchsploit -u 更新漏洞信息
#-----------------web相关-------------------
pip install quart -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install hypercorn -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install uvicorn[standard] -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install quart-session -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install quart-cors -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pymemcache -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install quart-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple
---arial.ttf字体---
sudo apt-get install ttf-mscorefonts-installer
sudo fc-cache -fv #更新字体缓存
fc-match Arial #验证安装

1
test

@ -1 +0,0 @@
[{"role": "system", "content": "你是一位资深的渗透测试专家,需要动态控制整个渗透测试过程,包括信息收集、漏洞扫描、漏洞利用等阶段,最终生成渗透测试报告。由你规划执行的指令,我会根据你的指令执行并提交结果,你再对结果进行分析,规划并生成下一步指令,直到完成渗透测试,生成测试报告。生成的指令需满足如下约束:1.只返回具体的shell指令或Python代码,不要包含注释和说明;2.shell指令以```bash(.*?)```包裹,python代码以```python(.*?)```包裹;3.针对提供的Python代码,需要包含错误处理,并将执行结果保存到文件中(为每个任务指定唯一文件名),执行结束返回success或failure和该文件名;4.如果认为渗透测试已完成,请生成生成报告的python代码,并返回success和complete"}, {"role": "user", "content": "现在开始对目标192.168.204.137进行渗透测试,已知信息无,请提供下一步执行的指令。"}, {"role": "assistant", "content": "```bash\nnmap -Pn -sS -sV -O -T4 -p- 192.168.204.137 -oN nmap_initial_scan.txt\n```"}]

43
test.py

@ -3,6 +3,10 @@ import subprocess
import tempfile
import os
import pexpect
import struct
import sys
import mysql.connector
import requests
def do_worker(str_instruction):
@ -51,22 +55,27 @@ def do_worker_ftp_script(str_instruction):
os.remove(output_file)
return output
import socket
import ssl
def dynamic_fun():
try:
host = "58.216.217.70"
port = 992
# Create a socket and wrap it with SSL context since service is SSL/Telnet
context = ssl.create_default_context()
sock = socket.create_connection((host, port), timeout=5)
ssl_sock = context.wrap_socket(sock, server_hostname=host)
# Attempt to receive banner
banner = ssl_sock.recv(1024)
ssl_sock.close()
return (True, banner.decode("utf-8", errors="ignore"))
except Exception as e:
return (False, str(e))
if __name__ == "__main__":
# 示例使用
str_instruction = """
ftp -n 192.168.204.137 << EOF
user anonymous anonymous@example.com
ls
bye
EOF
"""
output = do_worker(str_instruction)
print(f"*****\n{output}\n*****")
output = do_worker_ftp_script(str_instruction)
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
strout = '\n'.join(ftp_output)
print("111111111")
print(strout)
bok,res = dynamic_fun()
print(bok,res)

136
tools/CurlTool.py

@ -56,6 +56,7 @@ class CurlTool(ToolBase):
parts = instruction_old.split()
if 'base64 -d' in instruction_old:
return instruction_old
if "-I" not in parts:#-I 仅输出响应头信息
if '-i' not in parts and '--include' not in parts:
url_index = next((i for i, p in enumerate(parts) if p.startswith(('http://', 'https://'))), None)
if url_index is not None:
@ -66,33 +67,6 @@ class CurlTool(ToolBase):
parts.append('-i')
return ' '.join(parts),timeout
# def execute_instruction(self, instruction_old):
# '''
# 执行指令:验证合法性 -> 执行 -> 分析结果
# :param instruction_old:
# :return:
# bool:true-正常返回给大模型,false-结果不返回给大模型
# str:执行的指令
# str:执行指令的结果
# '''
#
# # 第一步:验证指令合法性
# instruction = self.validate_instruction(instruction_old)
# if not instruction:
# return False,instruction_old,"该指令暂不执行!"
#
# # 第二步:执行指令 --- 基于request使用
# #print(f"执行指令:{instruction}")
# output = ""
#
# # 第三步:分析执行结果
# analysis = self.analyze_result(output,instruction)
# #指令和结果入数据库
# #?
# if not analysis: #analysis为“” 不提交LLM
# return False,instruction,analysis
# return True,instruction, analysis
def get_ssl_info(self,stderr,stdout):
# --------------------------
# 解释信息的安全意义:
@ -148,42 +122,23 @@ class CurlTool(ToolBase):
result = f"HTTP 状态行:{http_status},Content-Type:{content_type},HTML Title:{html_title},TLS 连接信息:{tls_info},证书 Common Name:{cert_cn},证书 Issuer:{issuer_info}"
return result
def get_info_xpost(self,stdout,stderr):
"""
subprocess.run 执行 curl 后的结果中提取关键信息
- HTTP 状态码
- 常见响应头Content-Type, Content-Length
- HTML 页面标题如果内容为 HTML
- 返回正文的前200字符body_snippet
- TLS/证书相关信息从详细调试信息 stderr 中提取
对于未匹配到的信息返回Not found或空字符串
"""
def get_info_curl(self,instruction,stdout,stderr):
info = {}
# 处理 stdout: 拆分响应头与正文(假设用空行分隔)
parts = re.split(r'\r?\n\r?\n', stdout, maxsplit=1)
#***************解析方式一
# headers_str = parts[0] if parts else ""
# body = parts[1] if len(parts) > 1 else ""
#
# # 提取 HTTP 状态码(从响应头第一行中获取,例如 "HTTP/1.1 202 OK")
# header_lines = headers_str.splitlines()
# ***************解析方式二
if len(parts) == 2:
headers_str, body = parts
else:
# 如果没有拆分成功,可能 stdout 中只有正文,则从 stderr 尝试提取 HTTP 状态行
headers_str = ""
body = stdout
# 如果没有在 stdout 中找到头信息,则尝试从 stderr 中提取(部分信息可能在 stderr 中)
if not headers_str:
header_lines = stderr.splitlines()
else:
header_lines = headers_str.splitlines()
#**************************
#status_code
if header_lines:
status_line = header_lines[0]
status_match = re.search(r'HTTP/\d+\.\d+\s+(\d+)', status_line)
@ -191,20 +146,24 @@ class CurlTool(ToolBase):
else:
info['status_code'] = "No headers found"
# 提取常见响应头
#Server
m = re.search(r'^Server:\s*(.+)$', headers_str, re.MULTILINE)
if m:
info["server"] = m.group(1).strip()
#content-type,content-length
content_type = "Not found"
content_length = "Not found"
for line in header_lines:
if line.lower().startswith("content-type:"):
info['content_type'] = line.split(":", 1)[1].strip()
info['content-type'] = line.split(":", 1)[1].strip()
elif line.lower().startswith("content-length:"):
info['content_length'] = line.split(":", 1)[1].strip()
# 如果未匹配到,则设置默认值
info.setdefault('content_type', "Not found")
info.setdefault('content_length', "Not found")
info['content-length'] = line.split(":", 1)[1].strip()
info.setdefault('content-type', "Not found")
info.setdefault('content-length', "Not found")
# 如果内容为 HTML,则使用 BeautifulSoup 提取 <title> 标签内容
if "html" in info['content_type'].lower():
if "html" in info['content-type'].lower():
try:
soup = BeautifulSoup(body, "html.parser")
if soup.title and soup.title.string:
@ -216,9 +175,61 @@ class CurlTool(ToolBase):
else:
info['html_title'] = "N/A"
# 保存部分正文内容,便于后续分析
info['body_snippet'] = body[:200] # 前500字符
#------------正文部分解析------------
if "phpinfo.php" in instruction:
info["configurations"] = {}
info["sensitive_info"] = {}
# 提取PHP版本信息,可以尝试从phpinfo表格中提取
m = re.search(r'PHP Version\s*</th>\s*<td[^>]*>\s*([\d.]+)\s*</td>', body, re.IGNORECASE)
if m:
info["php_version"] = m.group(1).strip()
else:
# 备用方案:在页面中查找 "PHP Version" 后面的数字
m = re.search(r'PHP\s*Version\s*([\d.]+)', body, re.IGNORECASE)
if m:
info["php_version"] = m.group(1).strip()
# 提取配置信息(如allow_url_include, display_errors, file_uploads, open_basedir)
configs = ["allow_url_include", "display_errors", "file_uploads", "open_basedir"]
for key in configs:
# 尝试匹配HTML表格形式:<td>key</td><td>value</td>
regex = re.compile(r'<td[^>]*>\s*' + re.escape(key) + r'\s*</td>\s*<td[^>]*>\s*([^<]+?)\s*</td>',
re.IGNORECASE)
m = regex.search(body)
if m:
info["configurations"][key] = m.group(1).strip()
# 提取敏感信息,这里以MYSQL_PASSWORD为例
sensitive_keys = ["MYSQL_PASSWORD"]
for key in sensitive_keys:
regex = re.compile(r'<td[^>]*>\s*' + re.escape(key) + r'\s*</td>\s*<td[^>]*>\s*([^<]+?)\s*</td>',
re.IGNORECASE)
m = regex.search(body)
if m:
info["sensitive_info"][key] = m.group(1).strip()
elif "phpMyAdmin" in instruction:
info["security_info"] = {}
info["login_info"] = {}
# 查找登录表单中用户名、密码字段(例如 name="pma_username" 和 name="pma_password")
m = re.search(r'<input[^>]+name=["\'](pma_username)["\']', body, re.IGNORECASE)
if m:
info["login_info"]["username_field"] = m.group(1).strip()
m = re.search(r'<input[^>]+name=["\'](pma_password)["\']', body, re.IGNORECASE)
if m:
info["login_info"]["password_field"] = m.group(1).strip()
#安全信息
# csrf_protection:尝试查找隐藏域中是否存在 csrf token(例如 name="csrf_token" 或 "token")
m = re.search(r'<input[^>]+name=["\'](csrf_token|token)["\']', stdout, re.IGNORECASE)
info["security_info"]["csrf_protection"] = True if m else False
# httponly_cookie:从响应头中查找 Set-Cookie 行中是否包含 HttpOnly
m = re.search(r'Set-Cookie:.*HttpOnly', stdout, re.IGNORECASE)
info["security_info"]["httponly_cookie"] = True if m else False
# secure_cookie:从响应头中查找 Set-Cookie 行中是否包含 Secure
m = re.search(r'Set-Cookie:.*Secure', stdout, re.IGNORECASE)
info["security_info"]["secure_cookie"] = True if m else False
else: #
#info['body_snippet'] = body[:200] # 前500字符
if stderr:
# 处理 stderr 中的 TLS/证书信息:只提取包含关键字的行
tls_info_lines = []
cert_info_lines = []
@ -231,10 +242,9 @@ class CurlTool(ToolBase):
info['tls_info'] = tls_info_lines if tls_info_lines else "Not found"
info['certificate_info'] = cert_info_lines if cert_info_lines else "Not found"
# 可选:保留完整的 verbose 信息以便后续分析
#info['verbose'] = stderr
#转换成字符串
result = json.dumps(info,ensure_ascii=False)
#print(result)
return result
def analyze_result(self, result,instruction,stderr,stdout):
@ -268,11 +278,11 @@ class CurlTool(ToolBase):
elif("-kv https://" in instruction or "-vk https://" in instruction):
result = self.get_ssl_info(stderr,stdout)
elif("-X POST " in instruction):
result = self.get_info_xpost(stdout,stderr)
result = self.get_info_curl(instruction,stdout,stderr)
elif("-v " in instruction): #curl -v http://192.168.204.137:8180/manager/html --user admin:admin 常规解析curl返回内容
result = self.get_info_xpost(stdout,stderr)
else: #非处理命令的结果,暂时不提交LLM
result =""
result = self.get_info_curl(instruction,stdout,stderr)
else:
result = self.get_info_curl(instruction,stdout,stderr)
return result
if __name__ =="__main__":

17
tools/DirbTool.py

@ -0,0 +1,17 @@
import subprocess
import tempfile
import os
from tools.ToolBase import ToolBase
class DirbTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
instruction = instruction.strip()
if " -o" not in instruction:
instruction += " -o dirout.txt"
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

2
tools/EchoTool.py

@ -16,6 +16,6 @@ class EchoTool(ToolBase):
else:
result ="不存在安全问题"
else:#未预处理的情况,暂时不返回LLM
result = ""
pass
return result

13
tools/Enum4linuxTool.py

@ -0,0 +1,13 @@
from tools.ToolBase import ToolBase
from mycode.Result_merge import my_merge
class Enum4linuxTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
result = my_merge("enum4linux", result)
return result

12
tools/FtpTool.py

@ -46,8 +46,10 @@ class FtpTool(ToolBase):
def validate_instruction(self, instruction):
timeout = 30
#modified_code = "ftp匿名登录测试"
return instruction,timeout
#若有put文件,则替换为payload文件
new_file = "payload/test.txt"
new_instr = re.sub(r'(put\s+)\S+', r'\1' + new_file, instruction)
return new_instr,timeout
def do_worker_subprocess(self,str_instruction,timeout,ext_params):
output = ""
@ -123,11 +125,11 @@ class FtpTool(ToolBase):
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令---需要对ftp指令进行区分判断
pattern = re.compile(r'ftp\s+-n\s+\S+\s+<< EOF')
pattern = re.compile(r'ftp\s+-n\s+\S+(\s+\d+)?\s+<<\s*EOF')
match = pattern.search(instruction)
if bool(match): #如果是 ftp -n 192.168.204.137 <<EOF 开头
output = self.do_worker_subprocess(instruction,time_out,ext_params)
if not output:
# output = self.do_worker_subprocess(instruction,time_out,ext_params)
# if not output:
output = self.do_worker_script(instruction,time_out,ext_params)
else: #最后使用ftp匿名登陆验证代码

48
tools/HydraTool.py

@ -1,6 +1,7 @@
import os
import shlex
import re
from collections import OrderedDict
from tools.ToolBase import ToolBase
class HydraTool(ToolBase):
@ -13,22 +14,63 @@ class HydraTool(ToolBase):
if match_p:
str_p = match_p.group(1)
#判断文件是否存在
if not os.path.exists(str_p): #文件不存在要替换
#if not os.path.exists(str_p): #文件不存在要替换
new_pass_path = os.path.join(current_path, "../payload", "passwords")
instruction = instruction.replace(str_p,new_pass_path)
if match_l:
str_l = match_l.group(1)
#判断文件是否存在
if not os.path.exists(str_l):
#if not os.path.exists(str_l):
new_user_path = os.path.join(current_path, "../payload", "users")
instruction = instruction.replace(str_l, new_user_path)
#不是双字典的情况加-f
if "-l" in instruction or "-p" in instruction:
if "-f" not in instruction:
instruction = instruction + " -f" #当是单密码,或单用户名时,使用成功即停止模式
instruction = instruction.strip() + " -f" #当是单密码,或单用户名时,使用成功即停止模式
#取消-v -V
instruction = instruction.replace(" -V "," ")
instruction = instruction.replace(" -v "," ")
instruction = instruction.replace(" -vV","")
#加-o 存在个不确定项是:若没有匹配到,输出文件里面是只有一行执行的命令,没有结果描述
if " -o" not in instruction:
instruction = instruction + " -o hydra_result.txt"
# # 加 -q
# if " -q" not in instruction:
# instruction = instruction + " -q"
return instruction,timeout
def merge_info(self,result):
try:
# 按行分割输出,保留非空行
lines = [line.strip() for line in result.splitlines() if line.strip() != ""]
# 使用有序字典统计相同行的出现次数,保持原始顺序
counts = OrderedDict()
for line in lines:
if line in counts:
counts[line] += 1
else:
counts[line] = 1
# 生成整合后的输出,重复的行后面跟上*次数标记
output_lines = []
for line, count in counts.items():
if count > 1:
output_lines.append(f"{line} *{count}")
else:
output_lines.append(line)
consolidated = "\n".join(output_lines)
return consolidated
except Exception as e:
return result
def analyze_result(self, result,instruction,stderr,stdout):
#返回结果
# result = self.merge_info(result)
# print(result)
#加文件后缀了
lines = result.splitlines()
if len(lines) == 1:
result = "没有匹配到成功的结果"
return result

11
tools/MkdirTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class MkdirTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

61
tools/MysqlTool.py

@ -1,5 +1,6 @@
#mysql
#pip install mysql-connector-python
import subprocess
import mysql.connector
from mysql.connector import Error
from tools.ToolBase import ToolBase
@ -31,32 +32,60 @@ class MysqlTool(ToolBase):
return res
def validate_instruction(self, instruction):
#mysql暂只执行空密码攻击
timeout = 0
modified_code = "mysql空密码登录测试"
return modified_code,0
timeout = 30
#modified_code = "mysql空密码登录测试"
instr = instruction.replace("--ssl-mode=DISABLED","--ssl=0") #mariaDB 没有ssl-mode参数
# if "--ssl=0" not in instr:
# instr = instr + " --ssl=0"
return instr,timeout
#对于非sh命令调用的工具,自己实现命令执行的内容
def execute_instruction(self, instruction_old):
#对于非sh命令调用的工具,自己实现命令执行的内容 --#2025-3-24暂时不使用
def execute_instruction_old(self, instruction_old):
ext_params = self.create_extparams()
# 第一步:验证指令合法性
instruction,time_out = self.validate_instruction(instruction_old)
instruction,timeout = self.validate_instruction(instruction_old)
if not instruction:
return False, instruction_old, "该指令暂不执行!","",ext_params
# 过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
target = ""
parts = instruction_old.split()
for i, part in enumerate(parts):
if part == "-h" and i + 1 < len(parts):
target = parts[i + 1]
output = self.test_empty_password_mysql_connection(target)#弱密码攻击如何处理?
# target = ""
# parts = instruction_old.split()
# for i, part in enumerate(parts):
# if part == "-h" and i + 1 < len(parts):
# target = parts[i + 1]
# output = self.test_empty_password_mysql_connection(target)#弱密码攻击如何处理?
output = ""
stdout = ""
stderr = ""
try:
if timeout == 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True)
elif timeout > 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True, timeout=timeout)
else:
print("timeout参数错误,需要自查程序逻辑!")
stderr = result.stderr
stdout = result.stdout
except subprocess.TimeoutExpired as e:
stdout = e.stdout if e.stdout is not None else ""
stderr = e.stderr if e.stderr is not None else ""
ext_params.is_user = True # 对于超时的也需要人工进行确认,是否是预期的超时
except Exception as e:
ext_params.is_user = True
return False, instruction, f"执行失败:{str(e)}", "", ext_params # 执行失败,提交给人工确认指令的正确性
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
# 指令和结果入数据库
# ?
output = stdout
if stderr:
output += stderr
if isinstance(output, bytes): # 若是bytes则转成str
output = output.decode('utf-8', errors='ignore')
analysis = self.analyze_result(output, instruction, stderr, stdout)
if not analysis: # analysis为“” 不提交LLM
ext_params.is_user = True
return False, instruction, analysis, output, ext_params
return True, instruction, analysis, output, ext_params
def analyze_result(self, result,instruction,stderr,stdout):

2
tools/NcTool.py

@ -2,7 +2,7 @@ from tools.ToolBase import ToolBase
class NcTool(ToolBase):
def validate_instruction(self, instruction):
timeout = 30
timeout = 60
#指令过滤
if "<<<" in instruction:
instruction = f"bash -c \"{instruction}\""

69
tools/NiktoTool.py

@ -10,13 +10,78 @@ class NiktoTool(ToolBase):
:param instruction:
:return:
'''
timeout = 0
timeout = 60*30 #30分钟
# 使用正则表达式匹配 -ssl 参数及其相邻的空格
cleaned_command = re.sub(r'\s+-ssl\b|\b-ssl\b', '', instruction, flags=re.IGNORECASE)
# 处理可能残留的多余空格
return re.sub(r'\s+', ' ', cleaned_command).strip(),timeout
command = re.sub(r'\s+', ' ', cleaned_command).strip()
command = command.replace("-p80","-p 80")
#添加输出文件
if "-output" not in instruction:
instruction += " -output nikto_out.txt"
return command,timeout
def analyze_result(self, result,instruction,stderr,stdout):
# 检查结果
if stderr:
result = stderr
# else:
# result = self.parse_nikto_full_info(result)
return result
def parse_nikto_full_info(self,nikto_output: str) -> dict:
"""
Nikto 扫描结果中提取尽可能多的漏洞信息和对后续渗透测试有用的信息
解析思路
1. 通过正则表达式提取以+开头的行Nikto 输出通常以+开头
2. 针对常见漏洞格式 OSVDB-xxxx进行匹配
3. 针对可能包含vulnerabilitywarningpotential等关键字的行进行提取
4. 针对配置或其他信息 "Allowed HTTP Methods", "Cookie", "Directory indexing" 也进行匹配
5. 对于未知格式尽量保留原始提示以供后续人工分析
返回的字典包含以下键
- vulnerabilities: 漏洞相关信息 OSVDB 编号潜在漏洞提示等
- configuration_issues: 与服务器配置HTTP 方法Cookie安全策略等相关的提示
- misc_info: 其他可能对渗透测试有帮助的信息
- error: 如解析过程中发生异常则记录异常信息
"""
result = {
"vulnerabilities": [],
"configuration_issues": [],
"misc_info": []
}
try:
# 获取所有以 + 开头的行
plus_lines = re.findall(r'^\+\s*(.+)$', nikto_output, re.MULTILINE)
# 定义一些常见关键词,出现这些关键词的行我们认为是漏洞或安全问题
vuln_keywords = [
"OSVDB", "vulnerab", "potential", "insecure", "misconfig", "expos",
"Directory indexing", "Outdated", "Deprecated", "SSL", "Cookie", "error"
]
config_keywords = [
"Allowed HTTP Methods", "Server:", "Banner", "HTTP Options", "Headers", "trace", "PUT", "DELETE"
]
for line in plus_lines:
# 先将行统一为小写做匹配,但保留原始行内容
low_line = line.lower()
# 判断是否属于漏洞信息
if re.search(r'osvdb-\d+', line):
result["vulnerabilities"].append(line.strip())
elif any(kw.lower() in low_line for kw in vuln_keywords):
result["vulnerabilities"].append(line.strip())
# 判断是否属于配置问题(例如HTTP方法、Cookie安全、服务器banner异常等)
elif any(kw.lower() in low_line for kw in config_keywords):
result["configuration_issues"].append(line.strip())
else:
# 其他信息,可能对后续渗透测试有启示
result["misc_info"].append(line.strip())
except Exception as e:
result["error"] = f"解析过程中出现异常: {str(e)}"
return result

3
tools/NmapTool.py

@ -92,6 +92,9 @@ class NmapTool(ToolBase):
def analyze_result(self, result,instruction,stderr,stdout):
# 检查结果
if len(result) < 5120:
return result
else:
start_index = result.find("If you know the service/version")
if start_index != -1:
return result[:start_index]

11
tools/PrintfTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class PrintfTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

84
tools/PsqlTool.py

@ -0,0 +1,84 @@
import subprocess
import tempfile
import os
from tools.ToolBase import ToolBase
class PsqlTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 60
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result
def do_worker_script(self,str_instruction,timeout,ext_params):
# 创建临时文件保存输出
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
output_file = tmpfile.name
# 构建并执行 script 命令
script_cmd = f"script -c '{str_instruction}' {output_file}"
try:
result = subprocess.run(script_cmd, shell=True, text=True,timeout=timeout)
# 读取输出文件内容
with open(output_file, 'r') as f:
output = f.read()
lines = output.splitlines()
# 跳过第一行(Script started)和最后一行(Script done)
ftp_output = lines[1:-1]
output = '\n'.join(ftp_output)
except subprocess.TimeoutExpired:
output = "命令超时返回"
try:
with open(output_file, 'r') as f:
partial_output = f.read()
if partial_output:
output += f"\n部分输出:\n{partial_output}"
except FileNotFoundError:
pass # 文件可能未创建
except subprocess.CalledProcessError as e:
output = f"错误: {e}"
finally:
# 删除临时文件
try:
os.remove(output_file)
except FileNotFoundError:
pass # 文件可能未创建
return output
def execute_instruction1(self, instruction_old):
'''
执行指令验证合法性 -> 执行 -> 分析结果
*****如果指令要做验证只做白名单所有逻辑不是全放开就是白名单*****
:param instruction_old:
:return:
bool:true-正常返回给大模型处理下一步false-结果不返回给大模型,2--需要人工确认的指令
str:执行的指令
str:执行指令的结果-解析过滤后的结果--也是提交给LLM的结果
str:执行指令的结果-原结果
object:补充参数-封装一个对象 0-不知是否攻击成功1-明确存在漏洞2-明确不存在漏洞
'''
ext_params = self.create_extparams()
# 第一步:验证指令合法性
instruction,timeout = self.validate_instruction(instruction_old)
if not instruction:
ext_params.is_user= True
return False,instruction_old,"该指令暂不执行!由用户确认是否要兼容支持","",ext_params #未
#过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
output = self.do_worker_script(instruction,timeout,ext_params)
# 第三步:分析执行结果
if isinstance(output,bytes):#若是bytes则转成str
output = output.decode('utf-8', errors='ignore')
analysis = self.analyze_result(output,instruction,"","")
if not analysis: #analysis为“” 不提交LLM
ext_params.is_user = True
return False,instruction,analysis,output,ext_params
return True,instruction, analysis,output,ext_params

25
tools/PythoncodeTool.py

@ -6,7 +6,15 @@ import builtins
import re
import paramiko
import impacket
import psycopg2
import socket
import struct
import sys
import requests
import ssl
import mysql.connector
from tools.ToolBase import ToolBase
from mycode.Result_merge import my_merge
class PythoncodeTool(ToolBase):
@ -56,6 +64,7 @@ class PythoncodeTool(ToolBase):
# 定义允许的内置函数集合 --白名单
allowed_builtins = {
'__name__': __name__,
'__import__': builtins.__import__,
"abs": abs,
"all": all,
@ -96,7 +105,15 @@ class PythoncodeTool(ToolBase):
'json':json,
're':re,
'paramiko':paramiko,
'impacket':impacket}
'impacket':impacket,
'psycopg2':psycopg2,
'socket':socket,
'mysql':mysql,
'mysql.connector':mysql.connector,
'struct':struct,
'sys':sys,
'requests':requests,
'ssl':ssl}
safe_locals = {} #不需要预设局部参数
# 在限制环境中执行代码
exec(instruction, safe_globals,safe_locals)
@ -107,7 +124,7 @@ class PythoncodeTool(ToolBase):
return True,instruction,analysis,analysis,ext_params
# Get the function and call it
dynamic_fun = safe_locals['dynamic_fun']
status, tmpout = dynamic_fun()
status, tmpout = dynamic_fun() #LLM存在status定义错误的情况(执行成功,却返回的是False) #重点要处理
output = f"status:{status},output:{tmpout}"
except Exception as e:
analysis = f"执行动态代码时出错: {str(e)}"
@ -124,7 +141,9 @@ class PythoncodeTool(ToolBase):
return True, instruction, analysis,"",ext_params
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
#指令结果分析 --- 要不要限定一个max_len?
if "enum4linux " in instruction: #存在指令包装成Python代码返回的情况
result = my_merge("enum4linux",result)
return result
if __name__ == "__main__":

8
tools/SearchsploitTool.py

@ -103,9 +103,9 @@ class SearchsploitTool(ToolBase):
instruction = f"searchsploit -m {filepath}"
try:
subprocess.run(instruction, shell=True, check=True,timeout=60)
print("命令执行成功,文件下载完成。")
#print("命令执行成功,文件下载完成。")
except subprocess.CalledProcessError as e: #超时暂不处理--后续补充
print(f"命令执行失败: {e}")
#print(f"命令执行失败: {e}")
return False
if not os.path.exists(filename):
@ -113,10 +113,10 @@ class SearchsploitTool(ToolBase):
# 移动文件到目标目录
try:
shutil.move(filename, os.path.join(dirpath, filename))
print(f"文件已成功移动到 {dirpath}")
#print(f"文件已成功移动到 {dirpath}")
return True
except Exception as e:
print(f"移动文件失败: {e}")
#print(f"移动文件失败: {e}")
return False
else: #暂时只利用python脚本
return False

11
tools/ShowmountTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class ShowmountTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

24
tools/SmtpuserenumTool.py

@ -1,29 +1,25 @@
import os
import shlex
import re
from tools.ToolBase import ToolBase
class SmtpuserenumTool(ToolBase):
def validate_instruction(self, instruction):
timeout = 0
# 分割指令为参数列表
cmd_parts = shlex.split(instruction)
new_cmd = []
# 获取当前程序所在目录
current_path = os.path.dirname(os.path.realpath(__file__))
new_user_path = os.path.join(current_path, "../payload", "users")
i = 0
while i < len(cmd_parts):
part = cmd_parts[i]
new_cmd.append(part)
# 检测到-P参数
if part == "-U" and i + 1 < len(cmd_parts): # 用户名
# 替换下一参数为指定路径
new_cmd.append(new_user_path)
i += 1 # 跳过原路径参数
i += 1
return " ".join(shlex.quote(p) for p in new_cmd),timeout
match = re.search(r'-U\s+(\S+)', instruction)
if match:
file_path = match.group(1)
# 检查该文件是否存在
if not os.path.isfile(file_path):
# 替换原文件路径为新的路径
instruction = instruction.replace(file_path, new_user_path)
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析

11
tools/SwaksTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class SwaksTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

86
tools/TelnetTool.py

@ -6,7 +6,7 @@ MAIL FROM: <admin@haitutech.cn>
RCPT TO: <target@example.com> NOTIFY=SUCCESS,FAILURE
'''
import socket
import subprocess
from tools.ToolBase import ToolBase
class TelnetTool(ToolBase):
@ -19,10 +19,9 @@ class TelnetTool(ToolBase):
#指令结果分析
return result
def smtp_injection_test(self,cmd_str: str):
def smtp_injection_test(self,cmd_str: str,host,port):
"""
测试 SMTP 命令注入漏洞
参数:
cmd_str: 多行字符串其中第一行为类似 "telnet haitutech.cn 25"
后续行为具体的 SMTP 命令例如
@ -35,26 +34,8 @@ class TelnetTool(ToolBase):
如果 RCPT 命令响应以 250 开头认为可能存在漏洞返回 True
否则认为安全或不支持该注入返回 False
"""
lines = cmd_str.strip().splitlines()
if not lines:
print("未提供命令")
return ""
# 解析第一行,格式应为:telnet host port
parts = lines[0].strip().split()
if len(parts) < 3:
print("第一行格式错误,预期格式: 'telnet host port'")
return ""
host = parts[1]
try:
port = int(parts[2])
except ValueError:
print("端口转换失败")
return ""
last_response = ""
if port == 25:
lines = cmd_str.strip().splitlines()
try:
# 建立 TCP 连接
s = socket.create_connection((host, port), timeout=10)
@ -65,7 +46,6 @@ class TelnetTool(ToolBase):
# 读取 SMTP Banner
try:
banner = s.recv(1024).decode('utf-8', errors='ignore')
print("Banner:", banner.strip())
except Exception as e:
#print("读取 Banner 失败:", e)
s.close()
@ -76,26 +56,20 @@ class TelnetTool(ToolBase):
cmd = line.strip()
if not cmd:
continue
print("发送命令:", cmd)
try:
s.send((cmd + "\r\n").encode())
except Exception as e:
print("发送命令失败:", e)
s.close()
return ""
return f"发送命令失败:{e}"
try:
response = s.recv(1024).decode('utf-8', errors='ignore')
print("收到响应:", response.strip())
#print("收到响应:", response.strip())
last_response = response.strip()
except Exception as e:
print("读取响应失败:", e)
#print("读取响应失败:", e)
s.close()
return f"读取响应失败:{e}"
s.close()
else:
return ""
# 以 RCPT 命令的响应作为判断依据
# 注意:对于支持 DSN 的服务器,250 可能是合法的响应,
# 但在不支持的情况下,若返回 250 则可能说明命令注入导致了不正常的处理。
@ -115,19 +89,59 @@ class TelnetTool(ToolBase):
'''
ext_params = self.create_extparams()
# 第一步:验证指令合法性
instruction,time_out = self.validate_instruction(instruction_old)
instruction,timeout = self.validate_instruction(instruction_old)
if not instruction:
return False,instruction_old,"该指令暂不执行!","",ext_params
#过滤修改后的指令是否需要判重?同样指令再执行结果一致?待定---#?
# 第二步:执行指令
output = self.smtp_injection_test(instruction)
lines = instruction.strip().splitlines()
# 解析第一行,格式应为:telnet host port
parts = lines[0].strip().split()
if len(parts) < 3:
output = "第一行格式错误,预期格式: 'telnet host port'"
ext_params["is_user"] = True
return True,instruction,output,output,ext_params
host = parts[1]
try:
port = int(parts[2])
except ValueError:
output = "端口转换失败"
ext_params["is_user"] = True
return True, instruction, output, output, ext_params
if port == 25:
output = self.smtp_injection_test(instruction,host,port)
else:#其他默认subprocess执行
try:
# 指令过滤
if "<<<" in instruction:
instruction = f"bash -c \"{instruction}\""
if timeout == 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True)
elif timeout > 0:
result = subprocess.run(instruction, shell=True, capture_output=True, text=True, timeout=timeout)
else:
print("timeout参数错误,需要自查程序逻辑!")
stderr = result.stderr
stdout = result.stdout
except subprocess.TimeoutExpired as e:
stdout = e.stdout if e.stdout is not None else ""
stderr = e.stderr if e.stderr is not None else ""
ext_params.is_user = True # 对于超时的也需要人工进行确认,是否是预期的超时
except Exception as e:
ext_params.is_user = True
return False, instruction, f"执行失败:{str(e)}", "", ext_params # 执行失败,提交给人工确认指令的正确性
output = stdout
if stderr:
output += stderr
if isinstance(output, bytes): # 若是bytes则转成str
output = output.decode('utf-8', errors='ignore')
# 第三步:分析执行结果
analysis = self.analyze_result(output,instruction,"","")
#指令和结果入数据库
#?
if not analysis: #analysis为“” 不提交LLM
ext_params.is_user = True
return False,instruction,analysis,output,ext_params
return True,instruction, analysis,output,ext_params

68
tools/ToolBase.py

@ -9,6 +9,7 @@
import abc
import subprocess
import argparse
import shlex
import sys
from myutils.ReturnParams import ReturnParams
@ -26,7 +27,7 @@ class ToolBase(abc.ABC):
return ext_params
def parse_sublist3r_command(self,command):
parser = argparse.ArgumentParser(add_help=False) #创建命令行参数解析器对象‌
parser = argparse.ArgumentParser(add_help=False,allow_abbrev=False) #创建命令行参数解析器对象‌
parser.add_argument("-o", "--output", type=str) #添加需要解析的参数规则‌
parser.add_argument("-oN", "--Noutput", type=str) #nmap
parser.add_argument("-oG","--NMG",type=str) #nmap
@ -34,18 +35,57 @@ class ToolBase(abc.ABC):
args, _ = parser.parse_known_args(command.split()[1:])
return args
def parse_output_params(self,command, valid_flags=None):
"""
解析 shell 命令中用于输出文件的参数
只检查完全匹配的 token忽略诸如 "-oKexAlgorithms" 这样的参数
:param command: 完整的命令字符串
:param valid_flags: 合法的输出参数列表默认支持 -o, -oN, -oG, -output
:return: dict键为输出参数值为对应的文件路径如果有的话
"""
if command.strip().startswith("mkdir"):
return ""
if valid_flags is None:
valid_flags = ['-o', '-oN', '-oG', '-output']
tokens = shlex.split(command)
output_params = {}
output_file = ""
i = 0
while i < len(tokens):
token = tokens[i]
# 如果 token 完全匹配有效的参数,则认为它是输出参数
if token in valid_flags:
# 如果紧跟着有值,则把它作为输出文件路径,否则为 None
if i + 1 < len(tokens):
#output_params[token] = tokens[i + 1]
#i += 2
output_file = tokens[i+1]
# else:
# output_params[token] = None
# i += 1
break # 一般只有一个输出参数
else:
i += 1
return output_file
def read_output_file(self,filename):
"""
读取 -o 参数指定的文件内容
"""
content = ""
try:
with open(filename, "r") as f:
return f.read()
content = f.read()
with open(filename,'w') as f:
f.write("")
except FileNotFoundError:
print(f"错误: 文件 {filename} 不存在")
except PermissionError:
print(f"错误: 无权限读取文件 {filename}")
return ""
return content
def execute_instruction(self, instruction_old):
'''
@ -82,23 +122,11 @@ class ToolBase(abc.ABC):
print("timeout参数错误,需要自查程序逻辑!")
#output = result.stdout if result.stdout else result.stderr
#-o 的命令需要处理
parsed_arg = self.parse_sublist3r_command(instruction)
if parsed_arg.output:
file_out = self.read_output_file(parsed_arg.output)
stderr = ""
stdout = file_out
elif parsed_arg.Noutput:
file_out = self.read_output_file(parsed_arg.Noutput)
stderr = ""
stdout = file_out
elif parsed_arg.nikto:
file_out = self.read_output_file(parsed_arg.nikto)
stderr = ""
stdout = file_out
elif parsed_arg.NMG:
file_out = self.read_output_file(parsed_arg.NMG)
parsed_arg = self.parse_output_params(instruction)
if parsed_arg:
fole_out = self.read_output_file(parsed_arg)
stderr =""
stdout = file_out
stdout = fole_out
else:
stderr = result.stderr
stdout = result.stdout
@ -121,7 +149,7 @@ class ToolBase(abc.ABC):
if not analysis: #analysis为“” 不提交LLM
ext_params.is_user = True
return False,instruction,analysis,output,ext_params
#return False,instruction,analysis,output,ext_params -- 单节点后都要提交结果
return True,instruction, analysis,output,ext_params
@abc.abstractmethod

11
tools/TouchTool.py

@ -0,0 +1,11 @@
from tools.ToolBase import ToolBase
class TouchTool(ToolBase):
def validate_instruction(self, instruction):
#指令过滤
timeout = 0
return instruction,timeout
def analyze_result(self, result,instruction,stderr,stdout):
#指令结果分析
return result

4
web/API/__init__.py

@ -0,0 +1,4 @@
from quart import Blueprint
#定义模块
api = Blueprint('api',__name__)
from . import user

143
web/API/user.py

@ -0,0 +1,143 @@
import os
import hashlib
from quart import Quart, render_template, request, session, redirect, url_for,jsonify,send_file,flash
from quart_sqlalchemy import SQLAlchemy
from quart_session import Session
from web.common.utils import generate_captcha,login_required
from myutils.ConfigManager import myCongif
from . import api
from web.common.errors import handle_error
@api.route('/user/code',methods=['GET'])
async def user_get_code(): #获取验证码
captcha_text, buffer = generate_captcha()
print(captcha_text)
session['captcha'] = captcha_text # 记录验证码?
return await send_file(buffer, mimetype='image/png')
@api.route('/user/login',methods=['POST'])
async def user_login(): #用户登录
try:
form = await request.form
username = form['username']
password = form['password']
captcha = form['captcha']
except Exception as e:
await flash('请求数据格式错误', 'error')
return redirect(url_for('main.login'))
#return jsonify({'error': '请求数据格式错误'}), 400
if captcha != session.get('captcha'):
# 验证码验证过后,需要失效
session.pop('captcha', None)
await flash('验证码错误', 'error')
return redirect(url_for('main.login'))
#return jsonify({'error': '验证码错误'}), 400
#return 'captcha error!', 400
#比对用户名和密码
strsql = f"select password from user where username = '{username}'"
db_password = mDBM.do_select(strsql,1)
passwd_md5 = get_md5(password)
if db_password:
if db_password[0] == passwd_md5: #后续需要对密码进行MD5加默
print("登录成功")
session['user'] = username
return redirect(url_for('main.get_html', html='view_main.html'))
await flash('用户名或密码错误', 'error')
return redirect(url_for('main.login'))
@api.route('/user/userinfo',methods=['GET'])
@login_required
async def user_info(): #获取用户列表
strsql = "select username,status,people,tellnum from user;";
data = mDBM.do_select(strsql)
if data:
user_list = [{"username": user[0], "status": user[1],
"people":user[2],"tellnum":user[3]} for user in data]
return jsonify(user_list)
else:
return jsonify(0)
@api.route('/user/adduser',methods=['POST'])
@login_required
async def user_adduser(): #新增用户
username = (await request.form)['username']
people = (await request.form)['people']
tellnum = (await request.form)['tellnum']
strsql = f"select username from user where username = '{username}';"
password = myCongif.get_data('pw')
data = mDBM.do_select(strsql)
if data:
reStatus = 0
reMsg = '用户名重复,请重新输入!'
else:
strsql = (f"INSERT INTO user (username ,password ,status,people,tellnum ) VALUES "
f"('{username}','{password}',1,'{people}','{tellnum}');")
ret = mDBM.do_sql(strsql)
if ret == True:
reStatus = 1
reMsg = '添加用户成功'
else:
reStatus = 0
reMsg = '添加用户异常,请联系管理员处理!'
return jsonify({'status':reStatus,'msg':reMsg})
@api.route('/user/passwd',methods=['POST'])
@login_required
async def user_change_passwd(): #修改密码
json_data = await request.get_json()
oldpasswd = json_data.get('oldpasswd')
newpasswd = json_data.get('newpasswd')
old_md5= get_md5(oldpasswd)
print(old_md5)
strsql = f"select id from user where password='{old_md5}';"
data = mDBM.do_select(strsql,1)
reStatus = 0
if data:
new_md5 = get_md5(newpasswd)
strsql = f"update user set password = '{new_md5}' where password = '{old_md5}';"
ret = mDBM.do_sql(strsql)
if ret:
reStatus = 1
reMsg = '修改密码成功'
else:
reMsg = '修改密码失败,请联系技术支持!'
else:
reMsg = '原密码错误,请确认!'
return jsonify({'status':reStatus,'msg':reMsg})
@api.route('/user/changeuser',methods=['POST'])
@login_required
async def user_change_user_info(): #修改用户信息
username = (await request.form)['username']
people = (await request.form)['people']
tellnum = (await request.form)['tellnum']
strsql = f"update user set people='{people}',tellnum='{tellnum}' where username='{username}';"
ret = mDBM.do_sql(strsql)
if ret == True:
reStatus = 1
reMsg = '修改用户信息成功'
else:
reStatus = 0
reMsg = '修改失败,请联系管理员处理!'
return jsonify({'status': reStatus, 'msg': reMsg})
@api.route('/user/<int:user_id>', methods=['GET'])
async def get_user(user_id):
try:
user = user_id
if user:
return jsonify(user)
else:
return jsonify({'error': 'User not found'}), 404
except Exception as e:
return handle_error(e)
def get_md5(value):
md5 = hashlib.md5() # 创建一个md5对象
md5.update(value.encode('utf-8')) # 使用utf-8编码更新待计算的字符串
return md5.hexdigest() # 返回十六进制的MD5值

65
web/__init__.py

@ -0,0 +1,65 @@
from quart import Quart,session,redirect, url_for
from quart_session import Session
from quart_cors import cors
from pymemcache.client import base
from .main import main
from .API import api
from functools import wraps
from myutils.ConfigManager import myCongif
# from quart_sqlalchemy import SQLAlchemy
# from flask_migrate import Migrate
#app.config['SECRET_KEY'] = 'mysecret' #密钥 --需要放配置文件
#socketio = SocketIO(app)
# Create the custom backend for quart-session
class MemcachedSessionInterface: #只是能用,不明所以
def __init__(self, client):
self.client = client
async def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
val = await self.client.get(self.key_prefix + sid)
if val is not None:
return self._deserialize(val)
return self._get_default_session()
async def save_session(self, app, session, response):
val = self._serialize(session)
await self.client.set(self.key_prefix + session.sid, val, self.expire)
def create_app():
app = Quart(__name__)
app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#'
if myCongif.get_data("model_platform") == "acl":
app.config['SESSION_TYPE'] = 'memcached' # session类型
elif myCongif.get_data("model_platform") =="cpu":
app.config['SESSION_TYPE'] = 'redis' # session类型
#app.config['SESSION_FILE_DIR'] = './sessions' # session保存路径
#app.config['SESSION_MEMCACHED'] = base.Client(('localhost', 11211))
app.config['SESSION_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密
memcached_client = base.Client(('localhost', 11211))
app.session_interface = MemcachedSessionInterface(memcached_client)
Session(app)
# 注册main
app.register_blueprint(main)
#注册API模块
app.register_blueprint(api,url_prefix = '/api')
return app
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('main.login'))
return await f(*args, **kwargs)
return decorated_function

0
web/common/__init__.py

4
web/common/errors.py

@ -0,0 +1,4 @@
from quart import jsonify
def handle_error(e):
return jsonify({'error':str(e)}),500

10
web/common/models.py

@ -0,0 +1,10 @@
from app import db
import datetime
class Captcha(db.Model):
id = db.Column(db.Integer, primary_key=True)
captcha_text = db.Column(db.String(10), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
def __init__(self, captcha_text):
self.captcha_text = captcha_text

43
web/common/utils.py

@ -0,0 +1,43 @@
from PIL import Image, ImageDraw, ImageFont,ImageFilter
import random
import string
import io
from functools import wraps
from quart import session, redirect, url_for
def generate_captcha():
characters = string.ascii_uppercase + string.digits
captcha_text = ''.join(random.choices(characters, k=6))
font = ImageFont.truetype("arial.ttf", 36)
image = Image.new('RGB', (200, 60), color=(255, 255, 255))
draw = ImageDraw.Draw(image)
for i in range(6):
draw.text((10 + i * 30, 10), captcha_text[i], font=font,
fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
# 模糊化处理
image = image.filter(ImageFilter.BLUR)
# 将图片保存到BytesIO流中
buffer = io.BytesIO()
image.save(buffer, 'jpeg')
buffer.seek(0)
# captcha_path = os.path.join('static', 'captcha', f'{captcha_text}.png')
# image.save(captcha_path)
# return captcha_text, f'static/captcha/{captcha_text}.png'
return captcha_text, buffer
def verify_captcha(user_input, actual_captcha):
return user_input == actual_captcha
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('main.index',error='未登录,请重新登录'))
return await f(*args, **kwargs)
return decorated_function

5
web/main/__init__.py

@ -0,0 +1,5 @@
from quart import Blueprint
main = Blueprint('main', __name__,static_folder='static/resources',template_folder='templates')
from . import routes

83
web/main/routes.py

@ -0,0 +1,83 @@
import os
from web.main import main
from quart import render_template, send_from_directory,request
from quart import session, redirect, url_for,flash
from functools import wraps
from myutils.ConfigManager import myCongif
from werkzeug.utils import secure_filename
'''
页面路由
'''
def login_required(f):
@wraps(f)
async def decorated_function(*args, **kwargs):
if 'user' not in session:
return redirect(url_for('main.index',error='未登录,请重新登录'))
return await f(*args, **kwargs)
return decorated_function
@main.route('/')
async def index():
#return await render_template('实时预览.html')
return await render_template('login.html')
#return await render_template('index_webrtc.html')
@main.route('/login', methods=['GET', 'POST'])
async def login():
if request.method == 'POST':
form = await request.form
username = form.get('username')
password = form.get('password')
# Add your login logic here
if username == 'admin' and password == 'password':
return redirect(url_for('main.dashboard')) # Assuming you have a dashboard route
else:
return "Invalid credentials", 401
return await render_template('login.html')
@main.route('/dashboard')
async def dashboard():
return "Welcome to the dashboard!"
@main.route('/favicon.ico')
async def favicon():
return await send_from_directory('web/main/static', 'favicon.ico')
@main.route('/<html>')
@login_required
async def get_html(html):
return await render_template(html)
'''
各种配置文件路由
'''
@main.route('/data/<file>')
async def data(file):
return await send_from_directory('web/main/static/data', file)
@main.route('/files/<path:subdir>/<file>')
async def files(subdir,file):
return await send_from_directory(os.path.join('web/main/static/files', subdir), file)
@main.route('/images/<path:subdir>/<file>')
async def images(subdir,file):
return await send_from_directory(os.path.join('web/main/static/images', subdir), file)
@main.route('/resources/<file>')
async def resources(file):
return await send_from_directory('web/main/static/resources', file)
@main.route('/resources/<path:subdir>/<file>')
async def resources_dir(subdir,file):
return await send_from_directory(os.path.join('web/main/static/resources', subdir), file)
@main.route('/resources/css/<path:subdir>/<file>')
async def resources_css_dir(subdir,file):
return await send_from_directory(os.path.join('web/main/static/resources/css', subdir), file)
@main.route('/resources/scripts/<path:subdir>/<file>')
async def resources_scripts_dir(subdir,file):
return await send_from_directory(os.path.join('web/main/static/resources/scripts', subdir), file)

7
web/main/static/data/document.js

@ -0,0 +1,7 @@
$axure.loadDocument(
(function() {
var _ = function() { var r={},a=arguments; for(var i=0; i<a.length; i+=2) r[a[i]]=a[i+1]; return r; }
var _creator = function() { return _(b,_(c,d,e,f,g,d,h,d,i,d,j,k,l,d,m,f,n,f,o,d,p,f),q,_(r,[_(s,t,u,v,w,x,y,z),_(s,A,u,B,w,x,y,C),_(s,D,u,E,w,x,y,F),_(s,G,u,H,w,x,y,I),_(s,J,u,K,w,x,y,L),_(s,M,u,N,w,x,y,O)]),P,[Q,R,S],T,[U,V,W],X,_(Y,Z),ba,_(bb,_(s,bc,bd,be,bf,bg,bh,bi,bj,bk,bl,_(bm,bn,bo,bp,bq,br),bs,bt,bu,f,bv,bw,bx,bi,by,bi,bz,bA,bB,f,bC,_(bD,bE,bF,bE),bG,_(bH,bE,bI,bE),bJ,d,bK,f,bL,bc,bM,_(bm,bn,bo,bN),bO,_(bm,bn,bo,bP),bQ,bR,bS,bn,bq,bR,bT,bU,bV,bW,bX,bY,bZ,ca,cb,ca,cc,ca,cd,ca,ce,_(),cf,null,cg,null,ch,bU,ci,_(cj,f,ck,cl,cm,cl,cn,cl,co,bE,bo,_(cp,cq,cr,cq,cs,cq,ct,cu)),cv,_(cj,f,ck,bE,cm,cl,cn,cl,co,bE,bo,_(cp,cq,cr,cq,cs,cq,ct,cu)),cw,_(cj,f,ck,br,cm,br,cn,cl,co,bE,bo,_(cp,cq,cr,cq,cs,cq,ct,cx)),cy,cz),cA,_(cB,_(s,cC),cD,_(s,cE),cf,_(s,cF,bQ,bU),cG,_(s,cH,bT,bk),cI,_(s,cJ,bl,_(bm,bn,bo,bN,bq,br),bQ,bU,bT,bk,bM,_(bm,bn,bo,cK)),cL,_(s,cM,bs,cN,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),cS,_(s,cT,bs,cU,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),cV,_(s,cW,bs,cX,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),cY,_(s,cZ,bs,da,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),db,_(s,dc,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),dd,_(s,de,bs,df,bf,cO,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),dg,_(s,dh,bs,da,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),di,_(s,dj,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,cR,bZ,bU,cb,bU,cc,bU,cd,bU),dk,_(s,dl,bl,_(bm,bn,bo,dm,bq,br),bv,cQ,bX,bY),dn,_(s,dp,bl,_(bm,bn,bo,dm,bq,br),bv,cQ,bX,cR),dq,_(s,dr,bl,_(bm,bn,bo,dm,bq,br),bv,cQ,bX,cR),ds,_(s,dt,bv,cQ,bX,cR),du,_(s,dv,bQ,bU,bM,_(bm,bn,bo,cP),bv,cQ,bX,bY),dw,_(s,dx),dy,_(s,dz,bM,_(bm,bn,bo,cP)),dA,_(s,dB,bl,_(bm,bn,bo,dC,bq,br)),dD,_(s,dE,bM,_(bm,bn,bo,dF)),dG,_(s,dH,bM,_(bm,bn,bo,bN)),dI,_(s,dJ,bQ,bU,bM,_(bm,bn,bo,bp)),dK,_(s,dL)),dM,_(dN,cE,dO,cW,dP,dh)));};
var b="configuration",c="showPageNotes",d=true,e="showPageNoteNames",f=false,g="showAnnotations",h="showAnnotationsSidebar",i="showConsole",j="linkStyle",k="displayMultipleTargetsOnly",l="linkFlowsToPages",m="linkFlowsToPagesNewWindow",n="useLabels",o="useViews",p="loadFeedbackPlugin",q="sitemap",r="rootNodes",s="id",t="f8hzm0",u="pageName",v="登录",w="type",x="Wireframe",y="url",z="登录.html",A="tm5q63",B="实时预览",C="实时预览.html",D="20xynf",E="通道管理",F="通道管理.html",G="ey1jts",H="算法管理",I="算法管理.html",J="idexti",K="系统管理",L="系统管理.html",M="su6kdn",N="用户管理",O="用户管理.html",P="additionalJs",Q="plugins/sitemap/sitemap.js",R="plugins/page_notes/page_notes.js",S="plugins/debug/debug.js",T="additionalCss",U="plugins/sitemap/styles/sitemap.css",V="plugins/page_notes/styles/page_notes.css",W="plugins/debug/styles/debug.css",X="globalVariables",Y="onloadvariable",Z="",ba="stylesheet",bb="defaultStyle",bc="627587b6038d43cca051c114ac41ad32",bd="fontName",be="'Arial Normal', 'Arial', sans-serif",bf="fontWeight",bg="400",bh="fontStyle",bi="normal",bj="fontStretch",bk="5",bl="foreGroundFill",bm="fillType",bn="solid",bo="color",bp=0xFF333333,bq="opacity",br=1,bs="fontSize",bt="13px",bu="underline",bv="horizontalAlignment",bw="center",bx="lineSpacing",by="characterSpacing",bz="letterCase",bA="none",bB="strikethrough",bC="location",bD="x",bE=0,bF="y",bG="size",bH="width",bI="height",bJ="visible",bK="limbo",bL="baseStyle",bM="fill",bN=0xFFFFFFFF,bO="borderFill",bP=0xFF797979,bQ="borderWidth",bR="1",bS="linePattern",bT="cornerRadius",bU="0",bV="borderVisibility",bW="all",bX="verticalAlignment",bY="middle",bZ="paddingLeft",ca="2",cb="paddingTop",cc="paddingRight",cd="paddingBottom",ce="stateStyles",cf="image",cg="imageFilter",ch="rotation",ci="outerShadow",cj="on",ck="offsetX",cl=5,cm="offsetY",cn="blurRadius",co="spread",cp="r",cq=0,cr="g",cs="b",ct="a",cu=0.349019607843137,cv="innerShadow",cw="textShadow",cx=0.647058823529412,cy="viewOverride",cz="19e82109f102476f933582835c373474",cA="customStyles",cB="box_1",cC="4b7bfc596114427989e10bb0b557d0ce",cD="shape",cE="40519e9ec4264601bfb12c514e4f4867",cF="75a91ee5b9d042cfa01b8d565fe289c0",cG="button",cH="c9f35713a1cf4e91a0f2dbac65e6fb5c",cI="primary_button",cJ="cd64754845384de3872fb4a066432c1f",cK=0xFF169BD5,cL="heading_1",cM="1111111151944dfba49f67fd55eb1f88",cN="32px",cO="bold",cP=0xFFFFFF,cQ="left",cR="top",cS="heading_2",cT="b3a15c9ddde04520be40f94c8168891e",cU="24px",cV="heading_3",cW="8c7a4c5ad69a4369a5f7788171ac0b32",cX="18px",cY="heading_4",cZ="e995c891077945c89c0b5fe110d15a0b",da="14px",db="heading_5",dc="386b19ef4be143bd9b6c392ded969f89",dd="heading_6",de="fc3b9a13b5574fa098ef0a1db9aac861",df="10px",dg="label",dh="2285372321d148ec80932747449c36c9",di="paragraph",dj="4988d43d80b44008a4a415096f1632af",dk="text_field",dl="44157808f2934100b68f2394a66b2bba",dm=0xFF000000,dn="droplist",dp="85f724022aae41c594175ddac9c289eb",dq="list_box",dr="d5a74867db1f49ceb7c59e94129aa67a",ds="radio_button",dt="4eb5516f311c4bdfa0cb11d7ea75084e",du="tree_node",dv="93a4c3353b6f4562af635b7116d6bf94",dw="table_cell",dx="33ea2511485c479dbf973af3302f2352",dy="menu_item",dz="2036b2baccbc41f0b9263a6981a11a42",dA="form_hint",dB="4889d666e8ad4c5e81e59863039a5cc0",dC=0xFF999999,dD="form_disabled",dE="9bd0236217a94d89b0314c8c7fc75f16",dF=0xFFF0F0F0,dG="flow_shape",dH="caddf88798f04a469d3bb16589ed2a5d",dI="icon",dJ="26c731cb771b44a88eb8b6e97e78c80e",dK="ellipse",dL="78a26aa073ac4ed2b3c192ce4be8b862",dM="duplicateStyles",dN="55037c00beca4ab981fb8ff744aa5f75",dO="aa017f6a23d447e8a77c4c2eea3d335c",dP="4eea517f5eec41269a0db429802a7adf";
return _creator();
})());

105
web/main/static/data/styles.css

@ -0,0 +1,105 @@
.ax_default {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:400;
font-style:normal;
font-size:13px;
letter-spacing:normal;
color:#333333;
vertical-align:none;
text-align:center;
line-height:normal;
text-transform:none;
}
.box_1 {
}
.shape {
}
.image {
}
.button {
}
.primary_button {
color:#FFFFFF;
}
.heading_1 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
font-size:32px;
text-align:left;
}
.heading_2 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
font-size:24px;
text-align:left;
}
.heading_3 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
font-size:18px;
text-align:left;
}
.heading_4 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
font-size:14px;
text-align:left;
}
.heading_5 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
text-align:left;
}
.heading_6 {
font-family:'Arial Normal', 'Arial', sans-serif;
font-weight:bold;
font-style:normal;
font-size:10px;
text-align:left;
}
.label {
font-size:14px;
text-align:left;
}
.paragraph {
text-align:left;
}
.text_field {
color:#000000;
text-align:left;
}
.droplist {
color:#000000;
text-align:left;
}
.list_box {
color:#000000;
text-align:left;
}
.radio_button {
text-align:left;
}
.tree_node {
text-align:left;
}
.table_cell {
}
.menu_item {
}
.form_hint {
color:#999999;
}
.form_disabled {
}
.flow_shape {
}
.icon {
}
.ellipse {
}
textarea, select, input, button { outline: none; }

BIN
web/main/static/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
web/main/static/favicon_bak.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

26
web/main/static/images/login/zf.svg

@ -0,0 +1,26 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="410.000000pt" height="410.000000pt" viewBox="0 0 410.000000 410.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,410.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1815 3864 c-254 -30 -508 -114 -724 -241 -103 -60 -231 -153 -296
-215 -22 -21 -43 -38 -47 -38 -13 0 -187 -201 -240 -278 -160 -230 -258 -471
-309 -762 -18 -102 -18 -500 -1 -600 35 -194 84 -354 156 -506 254 -534 768
-909 1376 -1004 142 -23 426 -27 557 -9 598 81 1142 456 1394 959 152 304 207
565 196 920 -23 713 -407 1305 -1042 1605 -264 125 -502 176 -815 173 -96 0
-188 -3 -205 -4z m1320 -1023 c-11 -9 -236 -182 -356 -273 -42 -32 -103 -90
-135 -130 -32 -40 -90 -107 -129 -148 -38 -42 -137 -150 -220 -240 -82 -91
-186 -203 -231 -249 l-82 -85 -121 -4 -121 -4 -78 -89 c-78 -90 -142 -145
-352 -298 -169 -124 -451 -313 -457 -307 -7 6 27 55 91 131 71 83 378 419 500
545 311 324 728 767 742 791 12 19 253 137 397 193 131 51 325 115 452 150
102 28 113 30 100 17z m-1065 -89 c44 -22 62 -56 56 -106 -6 -47 -40 -85 -87
-94 -19 -4 -201 -7 -406 -7 l-372 0 -30 29 c-57 55 -50 128 18 173 34 23 35
23 410 23 338 0 380 -2 411 -18z m402 -1202 l330 0 34 -34 c29 -29 34 -41 34
-81 0 -42 -5 -52 -36 -81 l-35 -34 -385 0 -384 0 -38 34 c-32 30 -37 40 -37
80 0 38 5 51 31 77 34 34 82 50 128 43 16 -2 177 -4 358 -4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

171
web/main/static/plugins/debug/debug.js

@ -0,0 +1,171 @@
// use this to isolate the scope
(function () {
if(!$axure.document.configuration.showConsole) { return; }
$(document).ready(function () {
$axure.player.createPluginHost({
id: 'debugHost',
context: 'inspect',
title: 'Console',
gid: 3
});
generateDebug();
$('#variablesClearLink').click(clearvars_click);
$('#traceClear').click(cleartrace_click);
$('#traceToggle').click(stoptrace_click);
$('#traceStart').click(starttrace_click);
$('#traceClear').hide();
$('#traceToggle').hide();
$('#closeConsole').click(close);
var currentStack= [];
var finishedStack = [];
$axure.messageCenter.addMessageListener(function (message, data) {
if(message == 'axCompositeEventMessage') {
for(var i = 0; i < data.length; i++) {
processMessages(data[i].message, data[i].data);
}
} else processMessages(message, data);
});
var processMessages = function(message, data) {
if(message == 'globalVariableValues') {
$('#variablesDiv').empty();
for(var key in data) {
var value = data[key] == '' ? '(blank)' : data[key];
$('#variablesDiv').append('<div class="variableList"><div class="variableName">' + key + '</div><div class="variableValue">' + value + '</div></div>');
}
} else if(message == 'axEvent') {
var addToStack = "<div class='axEventBlock'>";
addToStack += "<div class='axEventContainer'>";
addToStack += " <div class='axTime'>" + new Date().toLocaleTimeString() + "</div>";
addToStack += " <div class='axEvent'>" + data.event.description + ": </div>";
addToStack += " <div class='axLabel'>" + data.label + " (" + data.type + ")</div>";
addToStack += "</div>";
currentStack.push(addToStack);
} else if (message == 'axEventComplete') {
currentStack[currentStack.length - 1] += "</div>";
finishedStack.push(currentStack.pop());
if(currentStack.length == 0) {
$('#traceEmptyState').hide();
$('#traceClear').show();
$('#traceToggle').show();
for(var i = finishedStack.length - 1; i >= 0; i--) {
if($('#traceDiv').children().length > 99) $('#traceDiv').children().last().remove();
$('#traceDiv').prepend(finishedStack[i]);
}
finishedStack = [];
}
} else if (message == 'axCase') {
//var addToStack = "<div class='axCaseContainer' style='background-color: #" + data.color + "'>";
var addToStack = "<div class='axCaseContainer'>";
addToStack += " <div class='axCaseItem'>" + data.item + "</div>";
if (data.description) { addToStack += " <div class='axCaseDescription' title='" + data.description + "'>" + data.description + "</div>" };
addToStack += "</div>";
currentStack[currentStack.length - 1] += addToStack;
} else if (message == 'axAction') {
var addToStack = "<div class='axActionContainer'>";
addToStack += " <div class='axActionItem'>" + data.name + "</div>";
//addToStack += " <div class='axActionItem'>" + data.item + "</div>";
//if (data.description) { addToStack += " <div class='axActionDescription' title='" + data.description + "'>" + data.description + "</div>" };
addToStack += "</div>";
currentStack[currentStack.length - 1] += addToStack;
} else if (message == 'axInfo') {
var addToStack = "<div class='axInfoContainer'>";
addToStack += " <div class='axInfoItem'>" + data.item + "</div>";
if (data.description) { addToStack += " <div class='axInfoDescription' title='" + data.longDescription + "'>" + data.description + "</div>" };
addToStack += "</div>";
currentStack[currentStack.length - 1] += addToStack;
}
}
// bind to the page load
$axure.page.bind('load.debug', function () {
var traceStr = $axure.player.getHashStringVar(TRACE_VAR_NAME);
if (traceStr.length > 0) $axure.messageCenter.setState("isTracing", true);
else $axure.messageCenter.setState("isTracing", false);
$axure.messageCenter.postMessage('getGlobalVariables', '');
return false;
});
function clearvars_click(event) {
$axure.messageCenter.postMessage('resetGlobalVariables', '');
}
function close() {
$axure.player.pluginClose("debugHost");
}
function cleartrace_click(event) {
$('#traceDiv').html('');
}
function starttrace_click(event) {
$axure.messageCenter.setState("isTracing", true);
//$('#traceDiv').html('');
$('#traceEmptyState').hide();
$('#traceClear').show();
$('#traceToggle').text('Stop Trace');
$('#traceToggle').off("click");
$('#traceToggle').click(stoptrace_click);
$('#traceToggle').show();
console.log("starting trace");
$axure.player.setVarInCurrentUrlHash(TRACE_VAR_NAME, 1);
}
function stoptrace_click(event) {
$axure.messageCenter.setState("isTracing", false);
$('#traceDiv').prepend('<div class="tracePausedNotification">Trace Paused<div>');
$('#traceToggle').text('Restart Trace');
$('#traceToggle').off("click");
$('#traceToggle').click(starttrace_click);
console.log("stopping trace");
$axure.player.deleteVarFromCurrentUrlHash(TRACE_VAR_NAME);
}
});
function generateDebug() {
var pageNotesUi = "<div id='debugHeader'>";
pageNotesUi += "<div id='debugToolbar'>";
pageNotesUi += "<div id='consoleTitle' class='pluginNameHeader'>Console</div>";
pageNotesUi += "</div>";
pageNotesUi += "</div>";
pageNotesUi += "<div id='variablesContainer' style='max-height:300px; overflow-y:auto'>";
pageNotesUi += "<div id='variablesTitle' class='sectionTitle'>Variables</div>";
pageNotesUi += "<a id='variablesClearLink' class='traceOption'>Reset Variables</a>";
pageNotesUi += "<div id='variablesDiv'></div></div>";
pageNotesUi += "<div id='traceContainer'>";
pageNotesUi += "<div id='traceHeader'>";
pageNotesUi += "<span class='sectionTitle'>Trace</span><a id='traceClear' class='traceOption'>Clear Trace</a><a id='traceToggle' class='traceOption'>Stop Trace</a>";
pageNotesUi += "</div>";
pageNotesUi += "</div>";
pageNotesUi += "<div id='debugScrollContainer'>";
pageNotesUi += "<div id='debugContainer'>";
pageNotesUi += "<div id='traceEmptyState'>";
pageNotesUi += "<div class='startInstructions'>Click the button below to start recording interactions as you click through the prototype.</div>";
pageNotesUi += "<div id='traceStart' class='startButton'>Start Trace</div>";
pageNotesUi += "</div>";
pageNotesUi += "<div id='traceDiv'></div></div>";
pageNotesUi += "</div></div>";
$('#debugHost').html(pageNotesUi);
$('#traceEmptyState').show();
}
})();

265
web/main/static/plugins/debug/styles/debug.css

@ -0,0 +1,265 @@
#debugHost {
display: flex;
flex-direction: column;
font-size: 13px;
color: #4a4a4a;
height: 100%;
}
#debugHostBtn {
order: 4;
}
#debugHostBtn a {
background: url('images/console_panel_on.svg') no-repeat center center, linear-gradient(transparent, transparent);
}
#debugHostBtn a.selected, #debugHostBtn a.selected:hover {
background: url('images/console_panel_off.svg') no-repeat center center, linear-gradient(transparent, transparent);
}
#debugToolbar {
margin-left: 8px;
}
#variablesClearLink {
display: inline-block;
margin-bottom: 15px;
}
#variablesClearLink:hover {
color: #0a6cd6;
}
#traceClearLink {
display: inline-block;
margin-bottom: 15px;
}
#traceClearLink:hover {
color: #0a6cd6;
}
#debugScrollContainer
{
overflow: auto;
width: 100%;
-webkit-overflow-scrolling: touch;
flex: 1;
}
#debugContainer {
padding: 10px 0px 10px 0px;
}
#consoleTitle {
clear: right;
margin: 12px 0px;
}
.variableName
{
font-weight: bold;
}
.variableDiv
{
margin-bottom: 20px;
line-height: 16px;
}
#variablesDiv
{
clear: right;
}
#variablesContainer {
border-bottom: solid 1px #e7e7e7;
padding: 0px 10px 12px 10px;
}
#traceContainer {
margin-bottom: 5px;
padding: 15px 10px 0px 10px;
}
#variablesTitle {
margin-bottom: 9px;
}
.sectionTitle {
font-size: 11px;
color: #2c2c2c;
display: inline-block;
}
.debugToolbarButton
{
font-size: 1em;
color: #069;
}
.axEventBlock {
display: inline-block;
width: 100%;
margin: 5px 0px 5px 0px;
line-height: 21px;
border-bottom: solid 5px #e7e7e7;
}
.axEventContainer {
background-color: #e7e7e7;
padding: 0px 10px 0px 10px;
}
.axTime {
margin: 0px 0px 0px 5px;
font-size: 10px;
color: #575757;
display: inline-block;
float: right;
}
.axLabel {
display: inline-block;
}
.axEvent {
margin: 0px 0px 2px 0px;
font-size: 15px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
.axCaseContainer, .axActionContainer, .axInfoContainer {
justify-content: space-between;
padding: 0px 10px 0px 10px;
}
.axCaseContainer {
border-top: solid 2px #e7e7e7;
/*background-color: #47b6b5;*/
background-color: #e7e7e7;
/*color: #ffffff;*/
}
.axActionContainer {
border-top: solid 3px #e7e7e7;
}
.axInfoContainer {
border-top: solid 1px #e7e7e7;
}
.axCaseItem, .axActionItem, .axInfoItem {
overflow: hidden;
text-overflow: ellipsis;
}
.axCaseItem {
font-size: 15px;
font-weight: bold;
}
.axActionItem {
font-weight: bold;
}
.axInfoItem {
color: #8c8c8c;
}
.axCaseDescription {
flex: 5 0 33%;
margin-left: 10px;
text-align: right;
}
/*.axActionDescription, .axInfoDescription {
flex: 5 0 33%;
margin-left: 10px;
text-align: right;
}*/
.axCaseDescription, .axActionDescription {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.axInfoDescription, .axActionDescription {
color: #8c8c8c;
font-size: 11px;
}
.variableName {
width: 55%;
line-height: 0.92;
text-align: left;
color: #0891b3;
display: inline-block;
word-wrap: break-word;
vertical-align: top;
}
.variableValue {
width: 45%;
line-height: 0.92;
text-align: right;
color: #373d48;
display: inline-block;
word-wrap: break-word;
}
.traceEvent {
border-bottom: solid 1px #e7e7e7;
}
.tracePausedNotification {
height: 25px;
/*background-color: #e7e7e7;*/
border-radius: 5px;
line-height: 25px;
margin: 5px 10px;
text-align: center
}
#traceEmptyState.emptyStateContainer {
margin-top: 0px;
}
.variableList{
width: 100%;
margin-bottom: 4px;
}
.traceOption {
margin-left: 11px;
height: 16px;
float: right;
font-size: 12px;
font-style: italic;
line-height: 1.45;
text-align: right;
color: #8c8c8c;
text-decoration: underline;
display: inline-block;
}
.startInstructions {
margin: auto;
width: 179px;
font-size: 11px;
text-align: center;
color: #666666;
}
.startButton {
margin: auto;
margin-top: 10px;
width: 181px;
height: 24px;
border-radius: 2px;
border: solid 1px #008fe0;
text-align: center;
line-height: 24px;
color: #008fe0;
cursor: pointer;
}
.debugLinksContainer {
text-align: right;
}

3
web/main/static/plugins/debug/styles/images/console_panel_off.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#008DCB" fill-rule="evenodd" d="M14 2.5l-2 1V2H2v12h12v1a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v1.5zm-2.981 3.702c.78-1.06 1.407-1.803 1.882-2.23.475-.428.938-.641 1.389-.641.54 0 .913.184 1.118.553.11.192.164.424.164.698 0 .28-.113.536-.339.769a1.1 1.1 0 0 1-.82.348c-.198 0-.422-.075-.672-.225-.25-.15-.439-.226-.569-.226-.253 0-.494.13-.723.39-.229.26-.623.81-1.184 1.65l.195 1.026c.102.526.188.959.256 1.297.069.338.144.651.226.938.11.397.219.684.328.862.11.177.27.266.482.266.191 0 .424-.14.697-.42.15-.15.38-.427.687-.83l.43.297a8.113 8.113 0 0 1-1.409 1.733c-.578.546-1.143.82-1.697.82-.465 0-.848-.192-1.148-.574-.171-.205-.322-.486-.452-.841a11.32 11.32 0 0 1-.282-.98 24.82 24.82 0 0 0-.23-.866l-.144.246c-.677 1.162-1.172 1.918-1.487 2.266-.471.52-1.018.78-1.64.78-.356 0-.665-.122-.928-.364a1.172 1.172 0 0 1-.395-.898c0-.294.097-.565.292-.815.195-.25.467-.374.815-.374.212 0 .474.075.785.226.31.15.514.225.61.225.212 0 .395-.094.548-.282.154-.188.457-.654.908-1.4l.41-.676c-.068-.287-.142-.64-.22-1.056-.079-.417-.16-.845-.241-1.282l-.164-.872c-.117-.629-.301-1.042-.554-1.24-.144-.117-.38-.175-.708-.175a14.992 14.992 0 0 0-.636.051v-.564c.616-.075 1.29-.17 2.026-.287a52.738 52.738 0 0 0 1.471-.246c.205.274.374.605.508.995.133.39.234.803.302 1.24l.113.688z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

3
web/main/static/plugins/debug/styles/images/console_panel_on.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#6D6D6D" fill-rule="evenodd" d="M14 2.5l-2 1V2H2v12h12v1a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v1.5zm-2.981 3.702c.78-1.06 1.407-1.803 1.882-2.23.475-.428.938-.641 1.389-.641.54 0 .913.184 1.118.553.11.192.164.424.164.698 0 .28-.113.536-.339.769a1.1 1.1 0 0 1-.82.348c-.198 0-.422-.075-.672-.225-.25-.15-.439-.226-.569-.226-.253 0-.494.13-.723.39-.229.26-.623.81-1.184 1.65l.195 1.026c.102.526.188.959.256 1.297.069.338.144.651.226.938.11.397.219.684.328.862.11.177.27.266.482.266.191 0 .424-.14.697-.42.15-.15.38-.427.687-.83l.43.297a8.113 8.113 0 0 1-1.409 1.733c-.578.546-1.143.82-1.697.82-.465 0-.848-.192-1.148-.574-.171-.205-.322-.486-.452-.841a11.32 11.32 0 0 1-.282-.98 24.82 24.82 0 0 0-.23-.866l-.144.246c-.677 1.162-1.172 1.918-1.487 2.266-.471.52-1.018.78-1.64.78-.356 0-.665-.122-.928-.364a1.172 1.172 0 0 1-.395-.898c0-.294.097-.565.292-.815.195-.25.467-.374.815-.374.212 0 .474.075.785.226.31.15.514.225.61.225.212 0 .395-.094.548-.282.154-.188.457-.654.908-1.4l.41-.676c-.068-.287-.142-.64-.22-1.056-.079-.417-.16-.845-.241-1.282l-.164-.872c-.117-.629-.301-1.042-.554-1.24-.144-.117-.38-.175-.708-.175a14.992 14.992 0 0 0-.636.051v-.564c.616-.075 1.29-.17 2.026-.287a52.738 52.738 0 0 0 1.471-.246c.205.274.374.605.508.995.133.39.234.803.302 1.24l.113.688z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

474
web/main/static/plugins/page_notes/page_notes.js

@ -0,0 +1,474 @@
// use this to isolate the scope
(function () {
// No notes shown specified by generation config
if (!$axure.document.configuration.showPageNotes && !$axure.document.configuration.showAnnotationsSidebar && !$axure.document.configuration.showAnnotations) { return; }
$(window.document).ready(function () {
// Load right panel for Page Notes
if ($axure.document.configuration.showPageNotes || $axure.document.configuration.showAnnotationsSidebar) {
$axure.player.createPluginHost({
id: 'pageNotesHost',
context: 'inspect',
title: 'Documentation',
gid: 2,
});
}
// Load footnotes on widgets
if ($axure.document.configuration.showAnnotations) {
$('#overflowMenuContainer').prepend('<div id="showNotesOption" class="showOption" style="order: 3"><div class="overflowOptionCheckbox"></div>Show Note Markers</div>');
}
createNotesOverlay();
generatePageNotes();
if ($axure.player.isMobileMode()) {
$('#showNotesOption').hide();
} else {
$('#showNotesOption').click(footnotes_click);
$('#showNotesOption').find('.overflowOptionCheckbox').addClass('selected');
}
function populateNotes(pageForNotes) {
var hasNotes = false;
if ($axure.document.configuration.showPageNotes) {
var pageNoteUi = '';
function populatePageNotes(pageOrMaster) {
//populate the page notes
var notes = pageOrMaster.notes;
if (notes && !$.isEmptyObject(notes)) {
pageNoteUi += "<div class='notesPageNameHeader'>" + pageOrMaster.pageName + "</div>";
var showNames = $axure.document.configuration.showPageNoteNames;
for(var noteName in notes) {
pageNoteUi += "<div class='pageNoteContainer'>";
if(showNames) {
pageNoteUi += "<div class='pageNoteName'>" + noteName + "</div>";
}
pageNoteUi += "<div class='pageNote'>" + linkify(notes[noteName]) + "</div>";
pageNoteUi += "</div>";
//$('#pageNotesContent').append(pageNoteUi);
hasNotes = true;
}
}
}
populatePageNotes(pageForNotes);
if (pageForNotes.masterNotes) {
for (var i = 0; i < pageForNotes.masterNotes.length; i++) {
populatePageNotes(pageForNotes.masterNotes[i]);
}
}
if (pageNoteUi.length > 0) {
pageNoteUi += "<div class='lineDivider'></div>";
var pageNotesHeader = "<div id='pageNotesSectionHeader' class='notesSectionHeader pluginNameHeader'>Page Notes</div>";
$('#pageNotesContent').append(pageNotesHeader + pageNoteUi);
}
}
if ($axure.document.configuration.showAnnotationsSidebar) {
var widgetNoteUi = '';
//var widgetNotes = pageForNotes.widgetNotes;
function populateWidgetNotes(widgetNotes){
if (widgetNotes) {
for (var i = 0; i < widgetNotes.length; i++) {
var widgetNote = widgetNotes[i];
widgetNoteUi += "<div class='widgetNoteContainer' data-id='" + widgetNote["ownerId"] + "'>";
widgetNoteUi += "<div class='widgetNoteFootnote'>" + widgetNote["fn"] + "</div>";
widgetNoteUi += "<div class='widgetNoteLabel'>" + widgetNote["label"] + "</div>";
for (var widgetNoteName in widgetNote) {
if (widgetNoteName != "label" && widgetNoteName != "fn" && widgetNoteName != "ownerId") {
widgetNoteUi += "<div class='pageNoteName'>" + widgetNoteName + "</div>";
widgetNoteUi += "<div class='pageNote'>" + linkify(widgetNote[widgetNoteName]) + "</div>";
//widgetNoteUi += "<div class='nondottedDivider'></div>";
}
}
widgetNoteUi += "</div>";
//widgetNoteUi += "<div class='nondottedDivider'></div>";
//$('#pageNotesContent').append(widgetNoteUi);
hasNotes = true;
}
}
}
populateWidgetNotes(pageForNotes.widgetNotes);
if (pageForNotes.masterNotes) {
for (var i = 0; i < pageForNotes.masterNotes.length; i++) {
populateWidgetNotes(pageForNotes.masterNotes[i].widgetNotes);
}
}
if (widgetNoteUi.length > 0) {
var widgetNotesHeader = "<div id='widgetNotesSectionHeader' class='notesSectionHeader pluginNameHeader'>Widget Notes</div>";
$('#pageNotesContent').append(widgetNotesHeader + widgetNoteUi);
//$('.widgetNoteContainer').children(':last-child').remove();
//$('.widgetNoteFootnote').append("<div class='annnoteline'></div><div class='annnoteline'></div><div class='annnoteline'></div>");
$('.widgetNoteContainer').click(function () {
var wasSelected = $(this).hasClass('widgetNoteContainerSelected');
$('.widgetNoteContainerSelected').removeClass('widgetNoteContainerSelected');
if (!wasSelected) $(this).addClass('widgetNoteContainerSelected');
var dimStr = $('.currentAdaptiveView').attr('data-dim');
var h = dimStr ? dimStr.split('x')[1] : '0';
var $leftPanel = $('.leftPanel:visible');
var leftPanelOffset = (!$axure.player.isMobileMode() && $leftPanel.length > 0) ? $leftPanel.width() : 0;
var $rightPanel = $('.rightPanel:visible');
var rightPanelOffset = (!$axure.player.isMobileMode() && $rightPanel.length > 0) ? $rightPanel.width() : 0;
var viewDimensions = {
h: h != '0' ? h : '',
scaleVal: $('.vpScaleOption').find('.selectedRadioButton').parent().attr('val'),
height: $('.rightPanel').height(),
panelWidthOffset: leftPanelOffset + rightPanelOffset
};
$axure.messageCenter.postMessage('toggleSelectWidgetNote', { id: this.getAttribute('data-id'), value: !wasSelected, view: viewDimensions});
});
}
//if (pageForNotes.masterNotes) {
// for (var i = 0; i < pageForNotes.masterNotes.length; i++) {
// var master = pageForNotes.masterNotes[i];
// hasNotes = populateNotes(master) || hasNotes;
// }
//}
}
return hasNotes;
}
// bind to the page load
$axure.page.bind('load.page_notes', function () {
closeAllDialogs();
var hasNotes = false;
$('#pageNotesContent').html("");
hasNotes = populateNotes($axure.page);
if(hasNotes) $('#pageNotesEmptyState').hide();
else $('#pageNotesEmptyState').show();
//If footnotes enabled for this prototype...
if ($axure.player.isMobileMode()) {
$axure.messageCenter.postMessage('annotationToggle', false);
} else if($axure.document.configuration.showAnnotations == true) {
//If the fn var is defined and set to 0, hide footnotes
//else if hide-footnotes button selected, hide them
var fnVal = $axure.player.getHashStringVar(FOOTNOTES_VAR_NAME);
if(fnVal.length > 0 && fnVal == 0) {
$('#showNotesOption').find('.overflowOptionCheckbox').removeClass('selected');
$axure.messageCenter.postMessage('annotationToggle', false);
} else if(!$('#showNotesOption').find('.overflowOptionCheckbox').hasClass('selected')) {
//If the footnotes button isn't selected, hide them on this loaded page
$axure.messageCenter.postMessage('annotationToggle', false);
}
}
// Get multiple click call if not removing beforehand
$('#notesOverlay').off('click');
$('#notesOverlay').on('click', '.closeNotesDialog', function () {
var ownerId = $(this).attr("data-ownerid");
_toggleAnnDialog(ownerId);
});
$axure.player.updatePlugins();
return false;
});
$axure.messageCenter.addMessageListener(function (message, data) {
//var messageData = { id: elementId, x: event.pageX, y: event.pageY }
if (message == 'toggleAnnDialog') {
_toggleAnnDialog(data.id, data.x, data.y, data.page);
}
});
});
function linkify(text) {
var urlRegex = /(\b(((https?|ftp|file):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
return text.replace(urlRegex, function (url, b, c) {
var url2 = (c == 'www.') ? 'http://' + url : url;
return '<a href="' + url2 + '" target="_blank" class="noteLink">' + url + '</a>';
});
}
function getWidgetNotesHtml(ownerId, page) {
var pageForNotes = page || $axure.page;
var widgetNoteUi = '';
widgetNoteUi += "<div data-ownerid='" + ownerId + "' class='closeNotesDialog'></div>";
widgetNoteUi += "<div class='notesDialogScroll'>";
function getNotesForPage(widgetNotes) {
for (var i = 0; i < widgetNotes.length; i++) {
var widgetNote = widgetNotes[i];
if (widgetNote["ownerId"] == ownerId) {
widgetNoteUi += "<div class='widgetNoteContainer' data-id='" + widgetNote["ownerId"] + "'>";
widgetNoteUi += "<div class='widgetNoteFootnote'>" + widgetNote["fn"] + "</div>";
widgetNoteUi += "<div class='widgetNoteLabel'>" + widgetNote["label"] + "</div>";
for (var widgetNoteName in widgetNote) {
if (widgetNoteName != "label" && widgetNoteName != "fn" && widgetNoteName != "ownerId") {
widgetNoteUi += "<div class='pageNoteName'>" + widgetNoteName + "</div>";
widgetNoteUi += "<div class='pageNote'>" + linkify(widgetNote[widgetNoteName]) + "</div>";
}
}
widgetNoteUi += "</div>";
}
}
}
getNotesForPage(pageForNotes.widgetNotes);
if (pageForNotes.masterNotes) {
for (var i = 0; i < pageForNotes.masterNotes.length; i++) {
getNotesForPage(pageForNotes.masterNotes[i].widgetNotes);
}
}
widgetNoteUi += "</div>";
widgetNoteUi += "<div class='resizeNotesDialog'></div>";
return widgetNoteUi;
}
var maxZIndex = 1;
var dialogs = {};
var _toggleAnnDialog = function (id, srcLeft, srcTop, page) {
if(dialogs[id]) {
var $dialog = dialogs[id];
// reset the dialog
dialogs[id] = undefined;
$dialog.find('.notesDialogScroll').getNiceScroll().remove();
$dialog.remove();
return;
}
var bufferH = 10;
var bufferV = 10;
var blnLeft = false;
var blnAbove = false;
var mfPos = $('#mainPanelContainer').position();
var viewablePanelLeftMargin = parseInt($('#mainPanelContainer').css('margin-left'));
var sourceTop = srcTop + mfPos.top;
var sourceLeft = srcLeft + viewablePanelLeftMargin;
var width = 300;
var height = 300;
if(sourceLeft > width + bufferH) {
blnLeft = true;
}
if(sourceTop > height + bufferV) {
blnAbove = true;
}
var top = 0;
var left = 0;
if(blnAbove) top = sourceTop - height - 20;
else top = sourceTop + 10;
if(blnLeft) left = sourceLeft - width - 4;
else left = sourceLeft - 6;
//need to set the zindex
maxZIndex = maxZIndex + 1;
var $dialog = $('<div class="notesDialog"></div>')
.appendTo('#notesOverlay')
.html(getWidgetNotesHtml(id, page));
$dialog.css({ 'left': left, 'top': top, 'z-index': maxZIndex });
$dialog.find('.notesDialogScroll').niceScroll({ cursorcolor: "#8c8c8c", cursorborder: "0px solid #fff" });
$dialog.find('.notesDialogScroll').on($axure.eventNames.mouseDownName, function(event) {
event.stopPropagation();
});
$dialog.find('.closeNotesDialog').on($axure.eventNames.mouseDownName, function (event) {
event.stopPropagation();
});
$dialog.on($axure.eventNames.mouseDownName, startDialogMove);
var startMouseX;
var startMouseY;
var startDialogX;
var startDialogY;
function startDialogMove() {
startMouseX = window.event.pageX;
startMouseY = window.event.pageY;
var position = $dialog.position();
startDialogX = position.left;
startDialogY = position.top;
$dialog.addClass('active');
$('<div class="splitterMask"></div>').insertAfter($('#notesOverlay'));
$(document).bind($axure.eventNames.mouseMoveName, doDialogMove).bind($axure.eventNames.mouseUpName, endDialogMove);
$dialog.find('.notesDialogScroll').getNiceScroll().hide();
}
function doDialogMove() {
var currentX = window.event.pageX;
var currentY = window.event.pageY;
$dialog.css({ 'left': startDialogX + currentX - startMouseX, 'top': startDialogY + currentY - startMouseY });
}
function endDialogMove() {
$('div.splitterMask').remove();
$dialog.removeClass('active');
$(document).unbind($axure.eventNames.mouseMoveName, doDialogMove).unbind($axure.eventNames.mouseUpName, endDialogMove);
$dialog.find('.notesDialogScroll').getNiceScroll().resize();
$dialog.find('.notesDialogScroll').getNiceScroll().show();
}
$dialog.find('.resizeNotesDialog').on($axure.eventNames.mouseDownName, startDialogResize);
var startDialogW;
var startDialogH;
function startDialogResize() {
event.stopPropagation();
startMouseX = window.event.pageX;
startMouseY = window.event.pageY;
startDialogW = Number($dialog.css('width').replace('px',''));
startDialogH = Number($dialog.css('height').replace('px', ''));
$dialog.addClass('active');
$('<div class="splitterMask"></div>').insertAfter($('#notesOverlay'));
$(document).bind($axure.eventNames.mouseMoveName, doDialogResize).bind($axure.eventNames.mouseUpName, endDialogResize);
$dialog.find('.notesDialogScroll').getNiceScroll().hide();
}
function doDialogResize() {
var currentX = window.event.pageX;
var currentY = window.event.pageY;
var newWidth = Math.max(200, startDialogW + currentX - startMouseX);
var newHeight = Math.max(200, startDialogH + currentY - startMouseY);
$dialog.css({ 'width': newWidth, 'height': newHeight });
}
function endDialogResize() {
$('div.splitterMask').remove();
$dialog.removeClass('active');
$(document).unbind($axure.eventNames.mouseMoveName, doDialogResize).unbind($axure.eventNames.mouseUpName, endDialogResize);
$dialog.find('.notesDialogScroll').getNiceScroll().resize();
$dialog.find('.notesDialogScroll').getNiceScroll().show();
}
dialogs[id] = $dialog;
// scroll ... just for IE
//window.scrollTo(scrollX, scrollY);
};
$(document).on('sidebarCollapse', function (event, data) {
clearSelection();
});
$(document).on('pluginShown', function (event, data) {
if(data != 2) {
clearSelection();
}
});
function clearSelection() {
var selectedNote = $('#pageNotesContainer').find('.widgetNoteContainerSelected');
if(selectedNote.length > 0) {
selectedNote.removeClass('widgetNoteContainerSelected');
//var dimStr = $('.currentAdaptiveView').attr('data-dim');
//var h = dimStr ? dimStr.split('x')[1] : '0';
//var $leftPanel = $('.leftPanel:visible');
//var leftPanelOffset = (!$axure.player.isMobileMode() && $leftPanel.length > 0) ? $leftPanel.width() : 0;
//var $rightPanel = $('.rightPanel:visible');
//var rightPanelOffset = (!$axure.player.isMobileMode() && $rightPanel.length > 0) ? $rightPanel.width() : 0;
//var viewDimensions = {
// h: h != '0' ? h : '',
// scaleVal: $('.vpScaleOption').find('.selectedRadioButton').parent().attr('val'),
// scrollLeft: $('#clipFrameScroll').scrollLeft(),
// scrollTop: $('#clipFrameScroll').scrollTop(),
// height: $('.rightPanel').height(),
// panelWidthOffset: leftPanelOffset + rightPanelOffset
//};
//$axure.messageCenter.postMessage('toggleSelectWidgetNote', { id: '', value: false, view: viewDimensions });
$axure.messageCenter.postMessage('toggleSelectWidgetNote', { id: '', value: false });
//$axure.messageCenter.postMessage('toggleSelectWidgetNote', '');
}
}
function closeAllDialogs() {
for (var id in dialogs) {
var $dialog = dialogs[id];
if ($dialog !== undefined) _toggleAnnDialog(id);
}
}
$axure.player.toggleFootnotes = function(val) {
var scaleCheckDiv = $('#showNotesOption').find('.overflowOptionCheckbox');
if (scaleCheckDiv.hasClass('selected')) {
if (!val) $('#showNotesOption').click();
} else {
if (val) $('#showNotesOption').click();
}
}
function footnotes_click(event) {
var scaleCheckDiv = $('#showNotesOption').find('.overflowOptionCheckbox');
if (scaleCheckDiv.hasClass('selected')) {
closeAllDialogs();
scaleCheckDiv.removeClass('selected');
$axure.messageCenter.postMessage('annotationToggle', false);
//Add 'fn' hash string var so that footnotes stay hidden across reloads
$axure.player.setVarInCurrentUrlHash(FOOTNOTES_VAR_NAME, 0);
} else {
scaleCheckDiv.addClass('selected');
$axure.messageCenter.postMessage('annotationToggle', true);
//Delete 'fn' hash string var if it exists since default is visible
$axure.player.deleteVarFromCurrentUrlHash(FOOTNOTES_VAR_NAME);
}
}
function createNotesOverlay() {
var $targetPanel = $('#clippingBounds');
if (!$('#notesOverlay').length) {
var notesOverlay = document.createElement('div');
notesOverlay.setAttribute('id', 'notesOverlay');
$targetPanel.prepend(notesOverlay);
$(notesOverlay).append('&nbsp;');
}
}
function generatePageNotes() {
var pageNotesUi = "<div id='pageNotesHeader'>";
pageNotesUi += "<div id='pageNotesToolbar' style='height: 12px;'>";
pageNotesUi += "</div>";
pageNotesUi += "</div>";
pageNotesUi += "<div id='pageNotesScrollContainer'>";
pageNotesUi += "<div id='pageNotesContainer'>";
pageNotesUi += "<div id='pageNotesEmptyState' class='emptyStateContainer'><div class='emptyStateTitle'>No notes for this page.</div><div class='emptyStateContent'>Notes added in Axure RP will appear here.</div><div class='dottedDivider'></div></div>";
pageNotesUi += "<span id='pageNotesContent'></span>";
pageNotesUi += "</div></div>";
$('#pageNotesHost').html(pageNotesUi);
if(!$axure.document.configuration.showAnnotations) {
$('#pageNotesHost .pageNameHeader').css('padding-right', '55px');
}
}
})();

3
web/main/static/plugins/page_notes/styles/images/notes_panel_off.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16">
<path fill="#008DCB" fill-rule="evenodd" d="M1 0h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm1 2v12h10V2H2zm2 2h6a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 3h6a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 3h6a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 340 B

3
web/main/static/plugins/page_notes/styles/images/notes_panel_on.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16">
<path fill="#6D6D6D" fill-rule="evenodd" d="M1 0h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1zm1 2v12h10V2H2zm2 2h6a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 3h6a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 3h6a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 340 B

209
web/main/static/plugins/page_notes/styles/page_notes.css

@ -0,0 +1,209 @@
#pageNotesHost {
display: flex;
flex-direction: column;
height: 100%;
}
#pageNotesHostBtn {
order: 2;
}
#pageNotesHostBtn a {
background: url('images/notes_panel_on.svg') no-repeat center center,linear-gradient(transparent, transparent);
}
#pageNotesHostBtn a.selected, #pageNotesHostBtn a.selected:hover {
background: url('images/notes_panel_off.svg') no-repeat center center,linear-gradient(transparent, transparent);
}
#pageNotesScrollContainer {
overflow: auto;
width: 100%;
flex: 1;
-webkit-overflow-scrolling: touch;
}
#pageNotesContent {
overflow: visible;
}
.pageNoteContainer {
padding: 0px 12px 8px 12px;
}
.mobileMode .pageNoteContainer {
padding: 0px 16px 8px 17px;
}
.pageNoteName {
font-size: 13px;
font-weight: bold;
color: #2c2c2c;
margin: 15px 0px 5px 0px;
white-space: nowrap;
}
.pageNote {
font-size: 13px;
color: #2a2e38;
line-height: 1.67;
word-wrap: break-word;
}
.pageNote ul {
list-style: disc;
padding: 0px 0px 0px 40px;
}
.pageNote ul ul{
list-style: circle;
}
.pageNote ul ul ul{
list-style: square;
}
.pageNote ul ul ul ul {
list-style: disc;
}
.pageNote ul ul ul ul ul {
list-style: circle;
}
.pageNote ul ul ul ul ul ul {
list-style: square;
}
.widgetNoteContainer {
padding: 12px;
border-bottom: 1px solid transparent;
border-top: 1px solid transparent;
cursor: pointer;
}
.mobileMode .widgetNoteContainer {
padding: 12px 16px 12px 17px;
}
.widgetNoteContainerSelected {
background-color: white;
border-bottom: 1px solid #c2c2c2;
border-top: 1px solid #c2c2c2;
}
.widgetNoteFootnote {
display: inline-block;
padding-top: 1px;
background-color: #fff849;
font-size: 11px;
font-weight: bold;
line-height: 16px;
margin-right: 8px;
padding: 0px 5px;
color: #000;
}
div.annnoteline {
display: inline-block;
width: 9px;
height: 1px;
border-bottom: 1px solid white;
margin-top: 1px;
}
.widgetNoteLabel {
font-size: 13px;
font-weight: 600;
color: #58167d;
margin-top: 4px;
float: right;
}
.noteLink {
text-decoration: inherit;
color: inherit;
}
.noteLink:hover {
background-color: white;
}
.notesSectionHeader {
margin: 0px 8px 0px 12px;
}
.notesPageNameHeader {
margin: 8px 8px 15px 12px;
}
.mobileMode .notesPageNameHeader {
margin: 18px 14px 5px 16px;
}
#notesOverlay {
width: 0;
height: 0;
position: absolute;
overflow: visible;
z-index: 1;
}
div.closeNotesDialog {
position: absolute;
top: 6px;
right: 6px;
width: 11px;
height: 10px;
object-fit: contain;
background: url(../../../resources/images/close_x.svg) no-repeat center center, linear-gradient(transparent, transparent);
margin-left: auto;
cursor: pointer;
}
div.resizeNotesDialog {
position: absolute;
bottom: 2px;
right: 2px;
width: 11px;
height: 10px;
object-fit: contain;
background: url(../../../resources/images/resize.svg) no-repeat center center, linear-gradient(transparent, transparent);
margin-left: auto;
cursor: nwse-resize;
}
div.notesDialog {
position: absolute;
padding: 16px 3px 10px 3px;
background-color: #efefef;
width: 300px;
height: 300px;
line-height: normal;
border: #8F949A solid 1px;
box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.4);
cursor: move;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
div.notesDialog.active {
user-select: none;
}
div.notesDialog .widgetNoteContainer {
cursor: auto;
padding: 2px 26px 16px 14px;
}
div.notesDialogScroll {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
cursor: auto;
}
.mobileMode .pageNoteName, .mobileMode #pageNotesToolbar, .mobileMode .dottedDivider {
display: none;
}

479
web/main/static/plugins/recordplay/recordplay.js

@ -0,0 +1,479 @@
// use this to isolate the scope
(function() {
if(!$axure.document.configuration.showRecordPlay) { return; }
$(window.document).ready(function() {
$axure.player.createPluginHost({
id: 'recordPlayHost',
context: 'interface',
title: 'Recording'
});
_generateRecordPlay();
$('#recordButton').click(_recordClick);
$('#playButton').click(_playClick);
$('#stopButton').click(_stopClick);
$('#deleteButton').click(_deleteClick);
// bind to the page load
$axure.page.bind('load.page_notes', function() {
$.ajax({
type: "POST",
url: '/RecordController/ListRecordings',
success: function(response) {
$('#recordNameHeader').html("");
$('#recordPlayContent').html("");
//populate the notes
axRecordingList = [];
if(!eventList) {
recordingIndex = 0;
eventList = [];
recordingStartTime = 0;
bulkEventElement = "";
lastBulkEvent = {};
}
for(var idx in response.recordingList) {
getOneRecording(response.recordingList[idx]);
}
return false;
},
// dataType: 'json'
});
});
});
var nameMatcher = new RegExp("^axRecording[0-9]{4}$", "i");
var indexMatcher = new RegExp("[0-9]{4}$", "i");
var convertFromJson = function(oneRecording) {
if(nameMatcher.exec(oneRecording.recordingName)) {
var myArray = indexMatcher.exec(oneRecording.recordingName);
var currIdx = parseInt(myArray);
if(recordingIndex < currIdx) {
recordingIndex = currIdx;
}
}
for(var idx in oneRecording.eventList) {
var thisEvent = oneRecording.eventList[idx];
thisEvent.eventInfo = {};
thisEvent.eventInfo.srcElement = thisEvent.elementID;
// TODO: check that this is correct.
if(isBulkMouse(thisEvent.eventType)) {
thisEvent.eventInfo.mousePositions = [];
thisEvent.eventInfo.mousePositions = thisEvent.mousePositions;
thisEvent.timeStamp = thisEvent.mousePositions[0].timeStamp;
}
if(isSingleMouse(thisEvent.eventType)) {
thisEvent.eventInfo.cursor = {};
thisEvent.eventInfo.cursor = thisEvent.cursor;
}
if(thisEvent.eventType === 'OnDrag') {
thisEvent.eventInfo.dragInfo = {};
thisEvent.eventInfo.dragInfo = thisEvent.dragInfo;
thisEvent.timeStamp = thisEvent.dragInfo.startTime;
}
}
return oneRecording;
};
var getOneRecording = function(recordingItem) {
$.ajax({
type: "POST",
url: '/RecordController/GetRecording',
data: { 'recordingId': recordingItem.recordingId },
success: function(response) {
axRecordingList[axRecordingList.length] = convertFromJson(response);
var axRecordingContainer = $('#recordingContainer').find('li').filter('.recordingRootNode');
axRecordingContainer.append(_formAxRecordingBranch(response));
_attachEventTriggers(response);
}, // dataType: 'json'
});
};
var axRecordingList;
var eventList;
var recordingIndex;
var recordingStartTime;
var recordingId;
var recordingName;
var leadingZeros = function(number, digits) { // because this thing doesn't have string.format (or does it?)
var recurseLeadingZeros = function(number, digitsLeft) {
if(digitsLeft > 0) {
return recurseLeadingZeros("0" + number, digitsLeft - 1);
} else {
return number;
}
};
return recurseLeadingZeros(number, digits - String(number).length);
};
var generateRecordingName = function() {
return "axRecording" + leadingZeros(recordingIndex, 4);
};
var isSingleMouse = function(eventType) {
return (eventType === 'OnClick' ||
eventType === 'OnMouseUp' ||
eventType === 'OnMouseDown' ||
eventType === 'OnMouseOver' ||
eventType === 'OnKeyUp' ||
eventType === 'OnSelectedChange' ||
eventType === 'OnSelect' ||
eventType === 'OnUnselect' ||
eventType === 'OnTextChange' ||
eventType === 'OnMouseOut');
};
var isBulkMouse = function(eventType) {
return (eventType === 'OnMouseHover' ||
eventType === 'OnMouseMove');
};
var bulkEventElement;
var lastBulkEvent;
$axure.messageCenter.addMessageListener(function(message, eventData) {
var lastEvent, lastBulkData;
if(message === 'logEvent') {
if(bulkEventElement !== eventData.elementID) {
lastBulkEvent = {};
bulkEventElement = eventData.elementID;
}
if(isBulkMouse(eventData.eventType)) {
lastEvent = lastBulkEvent[eventData.eventType];
if(lastEvent) {
// this is the second or third or whatever onmousemove in a row
lastBulkData = lastEvent.eventInfo.mousePositions;
lastBulkData[lastBulkData.length] = {
cursor: eventData.eventInfo.cursor,
timeStamp: eventData.timeStamp
};
} else {
eventData.eventInfo.mousePositions = [];
eventData.eventInfo.mousePositions[0] = {
cursor: eventData.eventInfo.cursor,
timeStamp: eventData.timeStamp
};
eventList[eventList.length] = eventData;
lastBulkEvent[eventData.eventType] = eventData;
}
} else {
var z = true;
}
if(isSingleMouse(eventData.eventType) ) {
eventList[eventList.length] = eventData;
lastBulkEvent = {};
bulkEventElement = eventData.elementID;
}
if(eventData.eventType === 'OnDrag') {
lastEvent = lastBulkEvent[eventData.eventType];
if (lastEvent) {
// this is the second or third or whatever onmousemove in a row
lastBulkData = lastEvent.eventInfo.mousePositions;
lastBulkData[lastBulkData.length] = {
dragInfo: eventData.eventInfo.dragInfo,
timeStamp: eventData.timeStamp
};
} else {
eventData.eventInfo.mousePositions = [];
eventData.eventInfo.mousePositions[0] = {
dragInfo: eventData.eventInfo.dragInfo,
timeStamp: eventData.timeStamp
};
eventList[eventList.length] = eventData;
lastBulkEvent[eventData.eventType] = eventData;
}
}
// if(eventData.eventType === 'OnKeyUp') {
// transmissionFields.eventInfo = eventData.eventInfo;
// $.ajax({
// type: "POST",
// url: '/RecordController/LogMouseClick',
// data: transmissionFields,
// });
// }
}
});
var _recordClick = function(event) {
$('#recordButton').addClass('recordPlayButtonSelected');
recordingIndex++;
// $axure.recording.startRecord();
recordingStartTime = new Date().getTime();
$.ajax({
type: "POST",
url: '/RecordController/CreateRecording',
data: {
'recordingName': generateRecordingName(),
timeStamp: recordingStartTime
},
success: function(response) {
recordingId = response.recordingId;
recordingName = response.recordingName;
$axure.messageCenter.postMessage('startRecording', {'recordingId' : recordingId, 'recordingName': recordingName});
},
// dataType: 'json'
});
};
var _playClick = function(event) {
$('#playButton').addClass('recordPlayButtonSelected');
};
var _stopClick = function(event) {
var axRecording, axObjectDictionary, axRecordingContainer, transmissionFields;
$('#sitemapLinksContainer').toggle();
if($('#recordButton').is('.recordPlayButtonSelected')) {
$('#recordButton').removeClass('recordPlayButtonSelected');
// $axure.recording.stopRecord();
axRecording = {
'recordingId' : recordingId,
'recordingName': recordingName,
'eventList': eventList
};
axRecordingList[axRecordingList.length] = axRecording;
axRecordingContainer = $('#recordingContainer').find('li').filter('.recordingRootNode');
axRecordingContainer.append(_formAxRecordingBranch(axRecording));
_attachEventTriggers(axRecording);
lastBulkEvent = {};
var recordingStepList = [];
for(var eventListIdx in eventList) {
var eventListItem = eventList[eventListIdx];
if(eventListItem.eventType === 'OnDrag') {
var lastDrag = eventListItem.eventInfo.mousePositions[eventListItem.eventInfo.mousePositions.length - 1].dragInfo;
eventListItem.eventInfo.dragInfo.currentX = lastDrag.currentX;
eventListItem.eventInfo.dragInfo.currentY = lastDrag.currentY;
eventListItem.eventInfo.dragInfo.currentTime = lastDrag.currentTime;
eventListItem.eventInfo.dragInfo.xDelta = eventListItem.eventInfo.dragInfo.currentX - eventListItem.eventInfo.dragInfo.lastX;
eventListItem.eventInfo.dragInfo.yDelta = eventListItem.eventInfo.dragInfo.currentY - eventListItem.eventInfo.dragInfo.lastY;
transmissionFields = {};
transmissionFields = tackItOn(transmissionFields, eventListItem, ['eventType', 'elementID', 'path']);
transmissionFields = tackItOn(transmissionFields, eventListItem.eventInfo, ['dragInfo']);
transmissionFields.recordingId = recordingId;
}
if(isSingleMouse(eventListItem.eventType)) {
transmissionFields = {};
transmissionFields = tackItOn(transmissionFields, eventListItem, ['timeStamp', 'eventType', 'elementID', 'path']);
transmissionFields = tackItOn(transmissionFields, eventListItem.eventInfo, ['cursor']);
transmissionFields.recordingId = recordingId;
}
if(isBulkMouse(eventListItem.eventType)) {
transmissionFields = {};
transmissionFields = tackItOn(transmissionFields, eventListItem, ['eventType', 'elementID', 'path']);
transmissionFields = tackItOn(transmissionFields, eventListItem.eventInfo, ['mousePositions']);
transmissionFields.recordingId = recordingId;
}
recordingStepList[recordingStepList.length] = transmissionFields;
}
eventList = [];
$axure.messageCenter.postMessage('stopRecording', axObjectDictionary);
var jsonText = {
'recordingName': recordingName,
'recordingId': recordingId,
recordingStart: new Date().getTime(),
recordingEnd: recordingStartTime,
'eventList': recordingStepList
};
$.ajax({
type: "POST",
url: '/RecordController/StopRecording',
data: { 'jsonText': JSON.stringify(jsonText) }
});
}
if($('#playButton').is('.recordPlayButtonSelected')) {
$('#playButton').removeClass('recordPlayButtonSelected');
}
};
var _deleteClick = function(event) {
$.ajax({
type: "POST",
url: '/RecordController/DeleteRecordings',
success: function(response) {
var x = true;
}, // dataType: 'json'
});
};
var tackItOn = function(destination, source, fields) {
for(var idx in fields) {
destination[fields[idx]] = source[fields[idx]];
}
return destination;
};
var makeFirstLetterLower = function(eventName) {
return eventName.substr(0, 1).toLowerCase() + eventName.substr(1);
};
var _attachEventTriggers = function(axRecording) {
for(var eventIdx in axRecording.eventList) {
var eventObject = axRecording.eventList[eventIdx];
var eventID = axRecording['recordingId'] + '_' + eventObject.timeStamp;
currentEvent = eventID;
$('#' + eventID).click(_triggerEvent(axRecording['recordingId'], eventObject.timeStamp));
// $('#' + eventID).click(event.trigger);
}
};
var _formAxRecordingBranch = function(axRecording) {
var eventObject, eventID, RDOID;
var recordPlayUi = '<ul class="recordingTree">';
recordPlayUi += "<li class='recordingNode recordingExpandableNode'>";
recordPlayUi += '<div class="recordingContainer" style="margin-left:15px">';
recordPlayUi += '<a class="recordingPlusMinusLink"><span class="recordingMinus"></span></a>';
recordPlayUi += '<a class="recordingPageLink" nodeurl="home.html">';
recordPlayUi += '<span class="recordingPageIcon"></span>';
recordPlayUi += '<span class="recordingPageName">' + axRecording['recordingName'] + '</span>';
recordPlayUi += '</a>';
recordPlayUi += '<ul>';
for(eventID in axRecording.eventList) {
eventObject = axRecording.eventList[eventID];
recordPlayUi += '<li class="recordingNode recordingLeafNode">';
recordPlayUi += '<div class="recordingEventContainer" style="margin-left:44px">';
var eventID = axRecording['recordingId'] + '_' + eventObject.timeStamp;
recordPlayUi += '<a id="' + eventID + '" class="sitemapPageLink">';
recordPlayUi += 'Event ID: ' + eventID + '<br/>';
recordPlayUi += '<span class="sitemapPageIcon"></span>';
recordPlayUi += '<span class="sitemapPageName">';
recordPlayUi += 'elementID: ' + eventObject.elementID + '<br/>';
recordPlayUi += 'eventType: ' + eventObject.eventType + '<br/>';
// recordPlayUi += 'cursor: ' + eventObject.eventInfo.cursor.x + ',' + eventObject.eventInfo.cursor.y + '<br/>';
for(RDOID in eventObject.path) {
recordPlayUi += '/' + eventObject.path[RDOID];
}
recordPlayUi += '<br/>';
recordPlayUi += '</span>';
recordPlayUi += '</a>';
recordPlayUi += '</div>';
recordPlayUi += '</li>';
}
recordPlayUi += '</ul>';
recordPlayUi += '</div>';
recordPlayUi += "</li>";
recordPlayUi += "</ul>";
return recordPlayUi;
};
var currentEvent = '';
var _triggerEvent = function(axRecording, timeStamp) {
// $axure.messageCenter.postMessage('triggerEvent', false);
for(var axRecordingIdx in axRecordingList) {
if(axRecordingList[axRecordingIdx].recordingId === axRecording) {
for(var eventIdx in axRecordingList[axRecordingIdx].eventList) {
if(axRecordingList[axRecordingIdx].eventList[eventIdx].timeStamp === timeStamp) {
var thisEvent = axRecordingList[axRecordingIdx].eventList[eventIdx];
// thisEvent.trigger();
var thisEventInfo, lowerEventType;
lowerEventType = thisEvent.eventType.toLowerCase();
if(lowerEventType === 'onclick' || lowerEventType === 'onmousein') {
thisEventInfo = {};
thisEventInfo = tackItOn(thisEventInfo, thisEvent.eventInfo, ['cursor', 'timeStamp', 'srcElement']);
if(thisEvent.eventInfo.inputType) {
thisEventInfo = tackItOn(thisEventInfo, thisEvent.eventInfo, ['inputType', 'inputValue']);
}
} else {
thisEventInfo = thisEvent.eventInfo;
}
var thisParameters = {
'element': thisEvent.elementID,
'eventInfo': thisEventInfo,
// 'axEventObject': thisEvent.eventObject,
'eventType': thisEvent.eventType
};
return function() {
$axure.messageCenter.postMessage('playEvent', thisParameters);
};
}
}
}
}
};
var _generateRecordPlay = function() {
var recordPlayUi = "<div id='recordPlayContainer'>";
recordPlayUi += "<div id='recordPlayToolbar'>";
recordPlayUi += "<div style='height:30px;'>";
recordPlayUi += "<a id='recordButton' title='Start a Recording' class='recordPlayButton'></a>";
recordPlayUi += "<a id='playButton' title='Play Back a Recording' class='recordPlayButton'></a>";
recordPlayUi += "<a id='stopButton' title='Stop' class='recordPlayButton'></a>";
recordPlayUi += "<a id='deleteButton' title='Delete All Recordings' class='recordPlayButton'></a>";
recordPlayUi += "</div>";
recordPlayUi += "<div id='recordingContainer'><li class='recordingNode recordingRootNode'></li></div>";
recordPlayUi += "</div>";
$('#recordPlayHost').html(recordPlayUi);
};
})();

90
web/main/static/plugins/recordplay/styles/recordplay.css

@ -0,0 +1,90 @@
#recordPlayHost {
font-size: 12px;
color:#333;
height: 100%;
}
#recordPlayContainer
{
overflow: auto;
width: 100%;
height: 100%;
padding: 10px 10px 10px 10px;
}
#recordPlayToolbar
{
margin: 5px 5px 5px 5px;
height: 22px;
}
#recordPlayToolbar .recordPlayButton
{
float: left;
width: 22px;
height: 22px;
border: 1px solid transparent;
}
#recordPlayToolbar .recordPlayButton:hover
{
border: 1px solid rgb(0,157,217);
background-color : rgb(166,221,242);
}
#recordPlayToolbar .recordPlayButton:active
{
border: 1px solid rgb(0,157,217);
background-color : rgb(204,235,248);
}
#recordPlayToolbar .recordPlayButtonSelected {
border: 1px solid rgb(0,157,217);
background-color : rgb(204,235,248);
}
/* removed images */
/*#recordButton {
background: url('../../sitemap/styles/images/233_hyperlink_16.png') no-repeat center center;
}
#playButton {
background: url('../../sitemap/styles/images/225_responsive_16.png') no-repeat center center;
}
#stopButton {
background: url('../../sitemap/styles/images/228_togglenotes_16.png') no-repeat center center;
}
#deleteButton {
background: url('../../sitemap/styles/images/231_event_16.png') no-repeat center center;
}*/
#recordNameHeader
{
/* yeah??*/
font-size: 13px;
font-weight: bold;
height: 23px;
white-space: nowrap;
}
#recordPlayContent
{
/* yeah??*/
overflow: visible;
}
.recordPlayName
{
font-size: 12px;
margin-bottom: 5px;
text-decoration: underline;
white-space: nowrap;
}
.recordPlay
{
margin-bottom: 10px;
}

562
web/main/static/plugins/sitemap/sitemap.js

@ -0,0 +1,562 @@
var currentNodeUrl = '';
var allNodeUrls = [];
var openNextPage = $axure.player.openNextPage = function () {
var index = allNodeUrls.indexOf(currentNodeUrl) + 1;
if(index >= allNodeUrls.length) return;
var nextNodeUrl = allNodeUrls[index];
currentNodeUrl = nextNodeUrl;
$('.sitemapPageLink[nodeUrl="' + nextNodeUrl + '"]').parent().mousedown();
};
var openPreviousPage = $axure.player.openPreviousPage = function () {
var index = allNodeUrls.indexOf(currentNodeUrl) - 1;
if(index < 0) return;
var nextNodeUrl = allNodeUrls[index];
currentNodeUrl = nextNodeUrl;
$('.sitemapPageLink[nodeUrl="' + nextNodeUrl + '"]').parent().mousedown();
};
// use this to isolate the scope
(function() {
var SHOW_HIDE_ANIMATION_DURATION = 0;
var HIGHLIGHT_INTERACTIVE_VAR_NAME = 'hi';
var currentPageLoc = '';
var currentPlayerLoc = '';
var currentPageHashString = '';
$(window.document).ready(function() {
$axure.player.createPluginHost({
id: 'sitemapHost',
context: 'project',
title: 'Project Pages',
gid: 1,
});
$(window.document).bind('keyup', function (e) {
if (e.target.localName == "textarea" || e.target.localName == "input" || event.target.isContentEditable) return;
switch(e.which) {
case 188:
openPreviousPage();
break;
case 190:
openNextPage();
break;
default: return; // exit this handler for other keys
}
});
generateSitemap();
var pageCount = $('.sitemapPageLink').length;
$('.leftArrow').click(openPreviousPage);
$('.rightArrow').click(openNextPage);
$('.sitemapPlusMinusLink').click(collapse_click);
$('.sitemapPageLink').parent().mousedown(node_click);
$('#interfaceAdaptiveViewsListContainer').hide();
$('#projectOptionsShowHotspots').click(showHotspots_click);
$('#searchIcon').click(searchBoxClose_click);
$('#searchDiv').click(searchBoxExpand_click);
$('#searchBox').keyup(search_input_keyup);
// bind to the page load
$axure.page.bind('load.sitemap', function() {
currentPageLoc = $axure.page.location.split("#")[0];
var decodedPageLoc = decodeURI(currentPageLoc);
currentNodeUrl = decodedPageLoc.substr(decodedPageLoc.lastIndexOf('/') ? decodedPageLoc.lastIndexOf('/') + 1 : 0);
currentPlayerLoc = $(location).attr('href').split("#")[0].split("?")[0];
currentPageHashString = '#p=' + currentNodeUrl.substr(0, currentNodeUrl.lastIndexOf('.'));
$axure.player.setVarInCurrentUrlHash(PAGE_ID_NAME, $axure.player.getPageIdByUrl(currentNodeUrl));
$axure.player.setVarInCurrentUrlHash(PAGE_URL_NAME, currentNodeUrl.substring(0, currentNodeUrl.lastIndexOf('.html')));
$('#sitemapTreeContainer').find('.sitemapHighlight').removeClass('sitemapHighlight');
var $currentNode = $('.sitemapPageLink[nodeUrl="' + currentNodeUrl + '"]');
$currentNode.parent().parent().addClass('sitemapHighlight');
var pageName = $axure.page.pageName;
$('.pageNameHeader').html(pageName);
if ($currentNode.length > 0 && pageCount > 1) {
var currentNode = $currentNode[0];
var currentNum = $('.sitemapPageLink').index(currentNode) + 1;
$('.pageCountHeader').html('(' + currentNum + ' of ' + pageCount + ')');
} else $('.pageCountHeader').html('');
//If highlight var is present and set to 1 or else if
//sitemap highlight button is selected then highlight interactive elements
var hiVal = $axure.player.getHashStringVar(HIGHLIGHT_INTERACTIVE_VAR_NAME);
if(hiVal.length > 0 && hiVal == 1) {
$('#showHotspotsOption').find('.overflowOptionCheckbox').addClass('selected');
if ($('#projectOptionsHotspotsCheckbox').length > 0) $('#projectOptionsHotspotsCheckbox').addClass('selected');
$axure.messageCenter.postMessage('highlightInteractive', true);
} else if ($('#showHotspotsOption').find('.overflowOptionCheckbox').hasClass('selected')) {
$axure.messageCenter.postMessage('highlightInteractive', true);
}
generateAdaptiveViews(false);
if (MOBILE_DEVICE) generateAdaptiveViews(true);
$axure.player.suspendRefreshViewPort = true;
//Set the current view if it is defined in the hash string
//If the view is invalid, set it to 'auto' in the string
//ELSE set the view based on the currently selected view in the toolbar menu
var viewStr = $axure.player.getHashStringVar(ADAPTIVE_VIEW_VAR_NAME);
if(viewStr.length > 0) {
var $view = $('.adaptiveViewOption[val="' + viewStr + '"]');
if($view.length > 0) $view.click();
else $('.adaptiveViewOption[val="auto"]').click();
} else if($('.selectedRadioButton').length > 0) {
var $viewOption = $('.selectedRadioButton').parents('.adaptiveViewOption');
$viewOption.click();
}
updateAdaptiveViewHeader();
function setDefaultScaleForDevice() {
if(MOBILE_DEVICE && $axure.player.isMobileMode()) {
$('.projectOptionsScaleRow[val="0"]').click();
} else {
$('.vpScaleOption[val="0"]').click();
}
}
var scaleStr = $axure.player.getHashStringVar(SCALE_VAR_NAME);
if(scaleStr.length > 0) {
var $scale = $('.vpScaleOption[val="' + scaleStr + '"]');
if($scale.length > 0) $scale.click();
else setDefaultScaleForDevice();
} else {
setDefaultScaleForDevice();
}
var rotateStr = $axure.player.getHashStringVar(ROT_VAR_NAME);
if(rotateStr.length > 0) {
$('#vpRotate').prop('checked', true);
}
$axure.player.suspendRefreshViewPort = false;
if (!$axure.player.isViewOverridden()) $axure.messageCenter.postMessage('setAdaptiveViewForSize', { 'width': $('#mainPanel').width(), 'height': $('#mainPanel').height() });
$axure.player.refreshViewPort();
$axure.messageCenter.postMessage('finishInit');
showMainPanel();
return false;
});
var $vpContainer = $('#interfaceScaleListContainer');
var scaleOptions = '<div class="vpScaleOption" val="0"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Default Scale</div>';
scaleOptions += '<div class="vpScaleOption" val="1"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Scale to Width</div>';
scaleOptions += '<div class="vpScaleOption" val="2"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Scale to Fit</div>';
$(scaleOptions).appendTo($vpContainer);
$('#overflowMenuContainer').append('<div id="showHotspotsOption" class="showOption" style="order: 1"><div class="overflowOptionCheckbox"></div>Show Hotspots</div>');
$('#overflowMenuContainer').append($vpContainer);
$vpContainer.show();
$('#showHotspotsOption').click(showHotspots_click);
$('.vpScaleOption').click(vpScaleOption_click);
$('.vpScaleOption').mouseup(function (event) {
event.stopPropagation();
});
if (MOBILE_DEVICE) {
var scaleOptions = '<div class="projectOptionsScaleRow" val="1"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Scale to fit width</div>';
scaleOptions += '<div class="projectOptionsScaleRow" val="0"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Original size (100%)</div>';
scaleOptions += '<div class="projectOptionsScaleRow" val="2" style="border-bottom: solid 1px #c7c7c7"><div class="scaleRadioButton"><div class="selectedRadioButtonFill"></div></div>Fit all to screen</div>';
$(scaleOptions).appendTo($('#projectOptionsScaleContainer'));
$('.projectOptionsScaleRow').click(vpScaleOption_click);
}
$('#searchBox').focusin(function() {
if($(this).is('.searchBoxHint')) {
$(this).val('');
$(this).removeClass('searchBoxHint');
}
}).focusout(function() {
if($(this).val() == '') {
$(this).addClass('searchBoxHint');
}
});
$('#searchBox').focusout();
});
var _formatViewDimension = function(dim) {
if(dim == 0) return 'any';
if(dim.toString().includes('.')) return dim.toFixed(2);
return dim;
};
function generateAdaptiveViews(forProjectOptions) {
var $container = forProjectOptions ? $('#projectOptionsAdaptiveViewsContainer') : $('#interfaceAdaptiveViewsListContainer');
var $viewSelect = forProjectOptions ? $('#projectOptionsViewSelect') : $('#viewSelect');
var adaptiveViewOptionClass = forProjectOptions ? 'projectOptionsAdaptiveViewRow' : 'adaptiveViewOption';
var currentViewClass = forProjectOptions ? '' : 'currentAdaptiveView';
$container.empty();
$viewSelect.empty();
//Fill out adaptive view container with prototype's defined adaptive views, as well as the default, and Auto
var viewsList = '<div class="' + adaptiveViewOptionClass + '" val="auto"><div class="adapViewRadioButton selectedRadioButton"><div class="selectedRadioButtonFill"></div></div>Adaptive</div>';
var viewSelect = '<option value="auto">Adaptive</option>';
if (typeof $axure.page.defaultAdaptiveView.name != 'undefined') {
//If the name is a blank string, make the view name the width if non-zero, else 'any'
var defaultView = $axure.page.defaultAdaptiveView;
var defaultViewName = defaultView.name;
var widthString = _formatViewDimension(defaultView.size.width);
var heightString = _formatViewDimension(defaultView.size.height);
var viewString = defaultViewName + ' (' + widthString + ' x ' + heightString + ')';
viewsList += '<div class="' + adaptiveViewOptionClass + ' ' + currentViewClass + '" val="default"data-dim="' + defaultView.size.width + 'x' + defaultView.size.height + '">' +
'<div class="adapViewRadioButton"><div class="selectedRadioButtonFill"></div></div>' + viewString + '</div>';
viewSelect += '<option value="default">' + viewString + '</option>';
}
var useViews = $axure.document.configuration.useViews;
var hasViews = false;
if(useViews) {
for(var viewIndex = 0; viewIndex < $axure.page.adaptiveViews.length; viewIndex++) {
var currView = $axure.page.adaptiveViews[viewIndex];
var widthString = _formatViewDimension(currView.size.width);
var heightString = _formatViewDimension(currView.size.height);
var viewString = currView.name + ' (' + widthString + ' x ' + heightString + ')';
viewsList += '<div class="' + adaptiveViewOptionClass +
((forProjectOptions && (viewIndex == $axure.page.adaptiveViews.length - 1)) ? '" style="border-bottom: solid 1px #c7c7c7; margin-bottom: 15px;' : '') +
'" val="' +
currView.id +
'" data-dim="' +
currView.size.width +
'x' +
currView.size.height +
'"><div class="adapViewRadioButton"><div class="selectedRadioButtonFill"></div></div>' +
viewString +
'</div>';
viewSelect += '<option value="' + currView.id + '">' + viewString + '</option>';
hasViews = true;
}
}
$container.append(viewsList);
$viewSelect.append(viewSelect);
if (!hasViews) {
if (forProjectOptions) {
$('#projectOptionsAdaptiveViewsHeader').hide();
$('#projectOptionsAdaptiveViewsContainer').hide();
} else $('#interfaceAdaptiveViewsContainer').hide();
} else {
if (forProjectOptions) {
$('#projectOptionsAdaptiveViewsHeader').show();
$('#projectOptionsAdaptiveViewsContainer').show();
} else $('#interfaceAdaptiveViewsContainer').show();
}
$(('.' + adaptiveViewOptionClass)).click(adaptiveViewOption_click);
if (!forProjectOptions) {
$(('.' + adaptiveViewOptionClass)).mouseup(function (event) {
event.stopPropagation();
});
}
}
function collapse_click(event) {
if($(this).children('.sitemapPlus').length > 0) {
expand_click($(this));
} else {
$(this)
.children('.sitemapMinus').removeClass('sitemapMinus').addClass('sitemapPlus').end()
.closest('li').children('ul').hide(SHOW_HIDE_ANIMATION_DURATION);
}
event.stopPropagation();
}
function expand_click($this) {
$this
.children('.sitemapPlus').removeClass('sitemapPlus').addClass('sitemapMinus').end()
.closest('li').children('ul').show(SHOW_HIDE_ANIMATION_DURATION);
}
function searchBoxExpand_click(event) {
if (!$('#searchIcon').hasClass('sitemapToolbarButtonSelected')) {
$('#searchIcon').addClass('sitemapToolbarButtonSelected')
$('#searchBox').width(0);
$('#searchBox').show();
$('#searchBox').animate({ width: '95%' }, { duration: 200, complete: function () { $('#searchBox').focus(); } });
}
}
function searchBoxClose_click(event) {
if ($('#searchIcon').hasClass('sitemapToolbarButtonSelected')) {
$('#searchBox').animate({ width: '0%' }, { duration: 200,
complete: function () {
$('#searchBox').hide();
$('#searchIcon').removeClass('sitemapToolbarButtonSelected')
}});
$('#searchBox').val('');
$('#searchBox').keyup();
}
}
function node_click(event) {
hideMainPanel();
$('#sitemapTreeContainer').find('.sitemapHighlight').removeClass('sitemapHighlight');
$(this).parent().addClass('sitemapHighlight');
$axure.page.navigate($(this).children('.sitemapPageLink')[0].getAttribute('nodeUrl'), true);
}
function hideMainPanel() {
$('#mainPanel').css('opacity', '0');
$('#clippingBounds').css('opacity', '0');
}
function showMainPanel() {
$('#mainPanel').animate({ opacity: 1 }, 10);
$('#clippingBounds').animate({ opacity: 1 }, 10);
}
$axure.messageCenter.addMessageListener(function(message, data) {
if(message == 'adaptiveViewChange') {
$('.adaptiveViewOption').removeClass('currentAdaptiveView');
if(data.viewId) {$('.adaptiveViewOption[val="' + data.viewId + '"]').addClass('currentAdaptiveView');}
else $('.adaptiveViewOption[val="default"]').addClass('currentAdaptiveView');
//when we set adaptive view through user event, we want to update the checkmark on sitemap
if(data.forceSwitchTo) {
$('.adapViewRadioButton').find('.selectedRadioButtonFill').hide();
$('.adapViewRadioButton').removeClass('selectedRadioButton');
$('div[val="' + data.forceSwitchTo + '"]').find('.adapViewRadioButton').addClass('selectedRadioButton');
$('div[val="' + data.forceSwitchTo + '"]').find('.selectedRadioButtonFill').show();
}
updateAdaptiveViewHeader();
$axure.player.refreshViewPort();
} else if(message == 'previousPage') {
openPreviousPage();
} else if(message == 'nextPage') {
openNextPage();
}
});
$axure.player.toggleHotspots = function (val) {
var overflowMenuCheckbox = $('#showHotspotsOption').find('.overflowOptionCheckbox');
if ($(overflowMenuCheckbox).hasClass('selected')) {
if (!val) $('#showHotspotsOption').click();
} else {
if (val) $('#showHotspotsOption').click();
}
}
function showHotspots_click(event) {
var overflowMenuCheckbox = $('#showHotspotsOption').find('.overflowOptionCheckbox');
var projOptionsCheckbox = $('#projectOptionsHotspotsCheckbox');
if ($(overflowMenuCheckbox).hasClass('selected')) {
overflowMenuCheckbox.removeClass('selected');
if (projOptionsCheckbox.length > 0 ) projOptionsCheckbox.removeClass('selected');
$axure.messageCenter.postMessage('highlightInteractive', false);
//Delete 'hi' hash string var if it exists since default is unselected
$axure.player.deleteVarFromCurrentUrlHash(HIGHLIGHT_INTERACTIVE_VAR_NAME);
} else {
overflowMenuCheckbox.addClass('selected');
if (projOptionsCheckbox.length > 0) projOptionsCheckbox.addClass('selected');
$axure.messageCenter.postMessage('highlightInteractive', true);
//Add 'hi' hash string var so that stay highlighted across reloads
$axure.player.setVarInCurrentUrlHash(HIGHLIGHT_INTERACTIVE_VAR_NAME, 1);
}
}
function adaptiveViewOption_click(event) {
var currVal = $(this).attr('val');
$('.adaptiveViewOption').removeClass('currentAdaptiveView');
if(currVal) {$('.adaptiveViewOption[val="' + currVal + '"]').addClass('currentAdaptiveView');}
else $('.adaptiveViewOption[val="default"]').addClass('currentAdaptiveView');
$('.adapViewRadioButton').find('.selectedRadioButtonFill').hide();
$('.adapViewRadioButton').removeClass('selectedRadioButton');
$('div[val="' + currVal + '"]').find('.adapViewRadioButton').addClass('selectedRadioButton');
$('div[val="' + currVal + '"]').find('.selectedRadioButtonFill').show();
selectAdaptiveView(currVal);
$axure.player.closePopup();
updateAdaptiveViewHeader();
}
var selectAdaptiveView = $axure.player.selectAdaptiveView = function(currVal) {
if (currVal == 'auto') {
$axure.messageCenter.postMessage('setAdaptiveViewForSize', { 'width': $('#mainPanel').width(), 'height': $('#mainPanel').height() });
$axure.player.deleteVarFromCurrentUrlHash(ADAPTIVE_VIEW_VAR_NAME);
} else {
currentPageLoc = $axure.page.location.split("#")[0];
var decodedPageLoc = decodeURI(currentPageLoc);
var nodeUrl = decodedPageLoc.substr(decodedPageLoc.lastIndexOf('/')
? decodedPageLoc.lastIndexOf('/') + 1
: 0);
var adaptiveData = {
src: nodeUrl
};
adaptiveData.view = currVal;
$axure.messageCenter.postMessage('switchAdaptiveView', adaptiveData);
$axure.player.setVarInCurrentUrlHash(ADAPTIVE_VIEW_VAR_NAME, currVal);
}
}
$axure.player.updateAdaptiveViewHeader = updateAdaptiveViewHeader = function () {
var hasDefinedDim = true;
var dimensionlessViewStr = '(any x any)';
var viewString = $('.adaptiveViewOption.currentAdaptiveView').text();
if (viewString != null && viewString.indexOf(dimensionlessViewStr) >= 0) hasDefinedDim = false;
if (!hasDefinedDim) {
var viewName = viewString.substring(0, viewString.lastIndexOf(' ('));
var widthString = $('#mainPanelContainer').width();
viewString = viewName + ' (' + widthString + ' x any)';
}
$('.adaptiveViewHeader').html(viewString);
}
$axure.player.selectScaleOption = function (scaleVal) {
var $scale = $('.vpScaleOption[val="' + scaleVal + '"]');
if ($scale.length > 0) $scale.click();
}
function vpScaleOption_click(event) {
var scaleCheckDiv = $(this).find('.scaleRadioButton');
var scaleVal = $(this).attr('val');
if (scaleCheckDiv.hasClass('selectedRadioButton')) return false;
var $selectedScaleOption = $('.vpScaleOption[val="' + scaleVal + '"], .projectOptionsScaleRow[val="' + scaleVal + '"]');
var $allScaleOptions = $('.vpScaleOption, .projectOptionsScaleRow');
$allScaleOptions.find('.scaleRadioButton').removeClass('selectedRadioButton');
$allScaleOptions.find('.selectedRadioButtonFill').hide();
$selectedScaleOption.find('.scaleRadioButton').addClass('selectedRadioButton');
$selectedScaleOption.find('.selectedRadioButtonFill').show();
if (scaleVal == '0') {
$axure.player.deleteVarFromCurrentUrlHash(SCALE_VAR_NAME);
} else if (typeof scaleVal !== 'undefined') {
$axure.player.setVarInCurrentUrlHash(SCALE_VAR_NAME, scaleVal);
}
$axure.player.refreshViewPort();
}
function search_input_keyup(event) {
var searchVal = $(this).val().toLowerCase();
//If empty search field, show all nodes, else grey+hide all nodes and
//ungrey+unhide all matching nodes, as well as unhide their parent nodes
if(searchVal == '') {
$('.sitemapPageName').removeClass('sitemapGreyedName');
$('.sitemapNode').show();
} else {
$('.sitemapNode').hide();
$('.sitemapPageName').addClass('sitemapGreyedName').each(function() {
var nodeName = $(this).text().toLowerCase();
if(nodeName.indexOf(searchVal) != -1) {
$(this).removeClass('sitemapGreyedName').parents('.sitemapNode:first').show().parents('.sitemapExpandableNode').show();
}
});
}
}
function generateSitemap() {
var treeUl = "<div id='sitemapHeader'' class='sitemapHeader'>";
treeUl += "<div id='sitemapToolbar' class='sitemapToolbar'>";
treeUl += '<div id="searchDiv"><span id="searchIcon" class="sitemapToolbarButton"></span><input id="searchBox" type="text"/></div>';
treeUl += "<div class='leftArrow sitemapToolbarButton'></div>";
treeUl += "<div class='rightArrow sitemapToolbarButton'></div>";
treeUl += "</div>";
treeUl += "</div>";
///////////////////
var sitemapTitle = $axure.player.getProjectName();
if (!sitemapTitle) sitemapTitle = "Pages";
treeUl += "<div class='sitemapPluginNameHeader pluginNameHeader'>" + sitemapTitle + "</div>";
treeUl += "<div id='sitemapTreeContainer'>";
treeUl += "<ul class='sitemapTree' style='clear:both;'>";
var rootNodes = $axure.document.sitemap.rootNodes;
for(var i = 0; i < rootNodes.length; i++) {
treeUl += generateNode(rootNodes[i], 0);
}
treeUl += "</ul></div>";
if (!MOBILE_DEVICE) {
treeUl += "<div id='changePageInstructions' class='pageSwapInstructions'>Use ";
treeUl += '<span class="backKeys"></span>';
treeUl += " and ";
treeUl += '<span class="forwardKeys"></span>';
treeUl += " keys<br>to move between pages";
treeUl += "</div>";
}
$('#sitemapHost').html(treeUl);
}
function generateNode(node, level) {
var hasChildren = (node.children && node.children.length > 0);
var margin, returnVal;
if(hasChildren) {
margin = (9 + level * 17);
returnVal = "<li class='sitemapNode sitemapExpandableNode'><div><div class='sitemapPageLinkContainer' style='margin-left:" + margin + "px'><a class='sitemapPlusMinusLink'><span class='sitemapMinus'></span></a>";
} else {
margin = (19 + level * 17);
returnVal = "<li class='sitemapNode sitemapLeafNode'><div><div class='sitemapPageLinkContainer' style='margin-left:" + margin + "px'>";
}
var isFolder = node.type == "Folder";
if(!isFolder) {
returnVal += "<a class='sitemapPageLink' nodeUrl='" + node.url + "'>";
allNodeUrls.push(node.url);
}
returnVal += "<span class='sitemapPageIcon";
if(node.type == "Flow"){ returnVal += " sitemapFlowIcon";}
if(isFolder) { returnVal += " sitemapFolderIcon"; }
returnVal += "'></span><span class='sitemapPageName'>";
returnVal += $('<div/>').text(node.pageName).html();
returnVal += "</span>";
if(!isFolder) returnVal += "</a>";
returnVal += "</div></div>";
if(hasChildren) {
returnVal += "<ul>";
for(var i = 0; i < node.children.length; i++) {
var child = node.children[i];
returnVal += generateNode(child, level + 1);
}
returnVal += "</ul>";
}
returnVal += "</li>";
return returnVal;
}
})();

7
web/main/static/plugins/sitemap/styles/images/back_keys.svg

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<g fill="none" fill-rule="evenodd">
<rect width="18" height="18" x="1" y="1" stroke="#E1E0E0" stroke-width="2" rx="4"/>
<rect width="19" height="19" x=".5" y=".5" stroke="#979797" rx="4"/>
<path fill="#666" d="M9 5V4L4.448 6.5v1L9 9.5v-1C6.733 7.513 5.567 7.013 5.5 7c.069-.017 1.235-.683 3.5-2zM5.292 14.262a.675.675 0 0 1 .195-.477.676.676 0 0 1 .225-.147.753.753 0 0 1 .288-.054c.12 0 .227.022.321.066a.641.641 0 0 1 .234.183.827.827 0 0 1 .141.27c.032.102.048.213.048.333 0 .18-.026.367-.078.561a2.996 2.996 0 0 1-.222.576 3.439 3.439 0 0 1-.84 1.053l-.18-.174a.222.222 0 0 1-.078-.168c0-.052.028-.106.084-.162.04-.044.091-.103.153-.177s.125-.159.189-.255.123-.202.177-.318c.054-.116.093-.24.117-.372h-.078a.709.709 0 0 1-.282-.054.647.647 0 0 1-.219-.153.698.698 0 0 1-.144-.234.834.834 0 0 1-.051-.297z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 940 B

14
web/main/static/plugins/sitemap/styles/images/closed_item.svg

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="5px" height="8px" viewBox="0 0 5 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>open item copy</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Tree-item" transform="translate(-6.000000, -9.000000)" fill="#8C8C8C">
<g id="closed-item" transform="translate(5.062500, 9.000000)">
<polygon id="Rectangle-13" transform="translate(3.500000, 4.000000) rotate(-90.000000) translate(-3.500000, -4.000000) " points="0 1.6 7 1.6 3.5 6.4"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 820 B

15
web/main/static/plugins/sitemap/styles/images/flow.svg

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="13" height="13" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<g id="Page-1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="flow" sketch:type="MSArtboardGroup">
<rect id="Rectangle-38" fill="#62666b" sketch:type="MSShapeGroup" x="1" y="10" width="3" height="3"/>
<rect id="Rectangle-38-Copy" fill="#62666b" sketch:type="MSShapeGroup" x="9" y="10" width="3" height="3"/>
<path d="M6.90806226,0.277785818 C8.60537484,1.36088413 12,3.52708074 12,3.52708074 L6.47274687,7 L1,3.52708074 L6.47274687,0 C6.47274687,0 6.76295713,0.185190545 6.90806226,0.277785818 Z" id="Shape" fill="#62666b" sketch:type="MSShapeGroup"/>
<path d="M7.33917705,2.07093789 C8.05945137,2.55245016 9.5,3.51547471 9.5,3.51547471 L6.48513465,5.5 L3.5,3.51547471 L6.48513465,1.5 C6.48513465,1.5 7.05449625,1.88062526 7.33917705,2.07093789 Z" id="Shape-Copy-3" fill="#ffffff" sketch:type="MSShapeGroup"/>
<path d="M2.45,9 L10.55,9 L11,9 L11,8 L10.55,8 L2.45,8 L2,8 L2,9 L2.45,9 L2.45,9 Z" id="Shape" fill="#62666b" sketch:type="MSShapeGroup"/>
<path d="M7,7.66666667 L7,6.33333333 L7,6 L6,6 L6,6.33333333 L6,7.66666667 L6,8 L7,8 L7,7.66666667 L7,7.66666667 Z" id="Shape" fill="#62666b" sketch:type="MSShapeGroup"/>
<path d="M3,10.6666667 L3,9.33333333 L3,9 L2,9 L2,9.33333333 L2,10.6666667 L2,11 L3,11 L3,10.6666667 L3,10.6666667 Z" id="Shape-Copy" fill="#62666b" sketch:type="MSShapeGroup"/>
<path d="M11,10.6666667 L11,9.33333333 L11,9 L10,9 L10,9.33333333 L10,10.6666667 L10,11 L11,11 L11,10.6666667 L11,10.6666667 Z" id="Shape-Copy-2" fill="#62666b" sketch:type="MSShapeGroup"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

6
web/main/static/plugins/sitemap/styles/images/folder_closed_blue.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
<g fill="#138CDE" fill-rule="evenodd">
<path d="M2 4.061h11v8.485H2z"/>
<path d="M2 3h4.583v3.182H2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 224 B

10
web/main/static/plugins/sitemap/styles/images/forward_keys.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<g fill="none" fill-rule="evenodd">
<rect width="18" height="18" x="1" y="1" stroke="#E1E0E0" stroke-width="2" rx="4"/>
<rect width="19" height="19" x=".5" y=".5" stroke="#979797" rx="4"/>
<path fill="#666" d="M4.448 8.5v1L9 7V6L4.448 4v1c2.267.987 3.433 1.487 3.5 1.5-.069.017-1.235.683-3.5 2z"/>
<text fill="#666" font-family="Lato-Regular, Lato" font-size="12">
<tspan x="4.728" y="16">.</tspan>
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 563 B

3
web/main/static/plugins/sitemap/styles/images/left_arrow.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="11" viewBox="0 0 6 11">
<path fill="#6D6D6D" fill-rule="evenodd" d="M5.5 11L0 5.5 5.5 0v2L2 5.5 5.5 9z"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

12
web/main/static/plugins/sitemap/styles/images/open_item.svg

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="9px" height="10px" viewBox="0 0 9 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>open item</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="open-item" fill="#8C8C8C">
<polygon id="Rectangle-13" points="0 0 9 0 4.5 6"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 577 B

6
web/main/static/plugins/sitemap/styles/images/page_lt_grey.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="11" viewBox="0 0 9 11">
<g fill="none" fill-rule="evenodd" stroke="#979797">
<path d="M.5.5h8v10h-8z"/>
<path stroke-linecap="square" d="M2.5 7.5h4M2.5 3.5h4M2.5 5.5h4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 265 B

3
web/main/static/plugins/sitemap/styles/images/right_arrow.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="11" viewBox="0 0 6 11">
<path fill="#6D6D6D" fill-rule="evenodd" d="M.5 11L6 5.5.5 0v2L4 5.5.5 9z"/>
</svg>

After

Width:  |  Height:  |  Size: 170 B

6
web/main/static/plugins/sitemap/styles/images/search_off.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
<g fill="none" fill-rule="evenodd" stroke="#018DCC" transform="translate(1 1)">
<path stroke-linecap="square" d="M6.5 6.5l2.791 2.865"/>
<circle cx="3.5" cy="3.5" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 293 B

6
web/main/static/plugins/sitemap/styles/images/search_on.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
<g fill="none" fill-rule="evenodd" stroke="#535353" transform="translate(1 1)">
<path stroke-linecap="square" d="M6.5 6.5l2.791 2.865"/>
<circle cx="3.5" cy="3.5" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 293 B

3
web/main/static/plugins/sitemap/styles/images/sitemap_panel_off.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14" viewBox="0 0 16 14">
<path fill="#008DCB" fill-rule="nonzero" d="M14.965 6C15.532 6 16 6.433 16 7s-.434 1-1.002 1H1.002A.983.983 0 0 1 0 7c0-.567.434-1 1.002-1h13.963zm-4.001 6c.568 0 1.036.433 1.036 1s-.435 1-1.003 1H1.003A.984.984 0 0 1 0 13c0-.567.435-1 1.003-1h9.96zM1.003 2A.984.984 0 0 1 0 1c0-.567.435-1 1.003-1h9.994A.984.984 0 0 1 12 1c0 .567-.435 1-1.003 1H1.003z"/>
</svg>

After

Width:  |  Height:  |  Size: 451 B

3
web/main/static/plugins/sitemap/styles/images/sitemap_panel_on.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="14" viewBox="0 0 16 14">
<path fill="#6D6D6D" fill-rule="nonzero" d="M14.965 6C15.532 6 16 6.433 16 7s-.434 1-1.002 1H1.002A.983.983 0 0 1 0 7c0-.567.434-1 1.002-1h13.963zm-4.001 6c.568 0 1.036.433 1.036 1s-.435 1-1.003 1H1.003A.984.984 0 0 1 0 13c0-.567.435-1 1.003-1h9.96zM1.003 2A.984.984 0 0 1 0 1c0-.567.435-1 1.003-1h9.994A.984.984 0 0 1 12 1c0 .567-.435 1-1.003 1H1.003z"/>
</svg>

After

Width:  |  Height:  |  Size: 451 B

384
web/main/static/plugins/sitemap/styles/sitemap.css

@ -0,0 +1,384 @@

#sitemapHost {
display: flex;
flex-direction: column;
height: 100%;
}
#sitemapHostBtn a {
background: url('images/sitemap_panel_on.svg') no-repeat center center, linear-gradient(transparent, transparent);
}
#sitemapHostBtn a.selected, #sitemapHostBtn a.selected:hover {
background: url('images/sitemap_panel_off.svg') no-repeat center center, linear-gradient(transparent, transparent);
}
#sitemapHost .pageButtonHeader {
top: -27px;
}
#sitemapTreeContainer {
overflow: auto;
width: 100%;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.mobileMode #sitemapTreeContainer {
margin-left: 5px;
overflow-x: hidden;
}
.sitemapTree {
margin: 0px 0px 10px 0px;
overflow:visible;
}
.sitemapTree ul {
list-style-type: none;
margin: 0px 0px 0px 0px;
padding-left: 0px;
}
ul.sitemapTree {
display: inline-block;
min-width: 100%;
}
.pageSwapInstructions {
width: 129px;
font-size: 12px;
text-align: center;
color: #8c8c8c;
margin: 0 auto;
padding: 12px 0px;
line-height: 20px;
}
.sitemapMinus, .sitemapPlus {
vertical-align:middle;
background-repeat: no-repeat;
margin-right: 3px;
width: 7px;
height: 8px;
object-fit: contain;
display:inline-block;
}
.sitemapMinus {
margin-bottom: 0px;
background: url('images/open_item.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.sitemapPlus {
margin-bottom: 2px;
background: url('images/closed_item.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.mobileMode .sitemapMinus, .mobileMode .sitemapPlus {
width: 10.5px;
height: 12px;
margin-right: 5px;
background-size: contain;
}
.sitemapPageLink {
margin-left: 0px;
}
.sitemapPageIcon {
margin: 0px 6px -3px 3px;
width: 16px;
height: 16px;
display: inline-block;
background: url('images/page_lt_grey.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.mobileMode .sitemapPageIcon {
margin-right: 7px;
background-size: contain;
}
.sitemapFolderIcon {
background: url('images/folder_closed_blue.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.mobileMode .sitemapFolderIcon {
width: 18px;
height: 18px;
margin-left: 1px;
background-position-y: 1px;
background-size: contain;
}
.sitemapFlowIcon {
background: url('images/flow.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.sitemapFolderOpenIcon {
background: url('images/folder_open.png') no-repeat center center;
background: url('images/folder_open.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.sitemapPageName {
font-size: 14px;
line-height: 1.93;
color: #4a4a4a;
}
.sitemapPageName.mobileText {
line-height: 1.69;
}
.sitemapNode {
white-space:nowrap;
}
.sitemapPageLinkContainer {
cursor: pointer;
padding-right: 10px;
}
.mobileMode .sitemapPageLinkContainer {
margin-bottom: 13px;
}
.sitemapHighlight {
background-color: #e6e6e6;
}
.sitemapGreyedName
{
color: #AAA;
}
.sitemapPluginNameHeader {
margin: 13px 9px 5px 9px;
font-size: 14px;
color: #444444;
}
.sitemapHeader {
padding-top: 7px;
}
.mobileMode .sitemapHeader {
padding-top: 0px;
}
.sitemapToolbar {
margin: 0px 3px 0px 5px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.sitemapToolbarButton {
width: 19px;
height: 18px;
border: 1px solid transparent;
cursor: pointer;
flex: 0 0 auto;
}
.hashover .sitemapToolbarButton:hover {
border-radius: 3px;
background-color: #e6e6e6 !important;
}
.sitemapToolbarButton.sitemapToolbarButtonSelected, .sitemapToolbarButton.sitemapToolbarButtonSelected:hover{
background-color: inherit !important;
}
.leftArrow {
background: url('images/left_arrow.svg') no-repeat center center, linear-gradient(transparent,transparent);
margin-left: 11px;
}
.rightArrow {
background: url('images/right_arrow.svg') no-repeat center center, linear-gradient(transparent,transparent);
margin-left: 3px;
margin-right: 2px;
}
#searchIcon {
width: 10px;
height: 10px;
object-fit: contain;
background: url('images/search_on.svg') no-repeat center center, linear-gradient(transparent,transparent);
vertical-align: bottom;
padding: 5px 4px 5px 4px;
display: inline-block;
}
#searchIcon.sitemapToolbarButtonSelected {
padding: 5px 3px 5px 5px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-left: solid 1px #cccccc;
border-top: solid 1px #cccccc;
border-bottom: solid 1px #cccccc;
background: url('images/search_off.svg') no-repeat center center, linear-gradient(transparent,transparent);
background-color: #FFFFFF !important;
}
.backKeys {
width: 20px;
height: 21px;
object-fit: contain;
vertical-align: bottom;
margin: 2px;
display: inline-block;
background: url('images/back_keys.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
.forwardKeys {
width: 20px;
height: 21px;
object-fit: contain;
vertical-align: bottom;
margin: 2px;
display: inline-block;
background: url('images/forward_keys.svg') no-repeat center center, linear-gradient(transparent,transparent);
}
#interfaceAdaptiveViewsListContainer {
position: absolute;
display: none;
width: 220px;
left: 155px;
padding: 6px 9px;
top: 36px;
}
#interfaceScaleListContainer {
padding: 7.5px 9px 12px 16px;
margin-top: 9px;
border-top: solid 1px #bdbcbc;
order: 10;
}
.adaptiveViewOption, .vpPresetOption, .vpScaleOption {
padding: 3px 0px 3px 0px;
color: #3B3B3B;
display: flex;
}
.projectOptionsScaleRow, .projectOptionsAdaptiveViewRow, .projectOptionsHotspotsRow {
border-top: solid 1px #c7c7c7;
display: flex;
padding: 13px 7px 13px 0px;
}
.adaptiveViewOption:hover, .vpScaleOption:hover, .vpPresetOption:hover, .projectOptionsAdaptiveViewRow:hover, .projectOptionsScaleRow:hover
{
cursor: pointer;
}
.scaleRadioButton, .adapViewRadioButton {
border: solid 1px #8c8c8c;
display: inline-block;
position: relative;
width: 12px;
height: 12px;
border-radius: 48px;
margin-right: 12px;
top: 2px;
flex-shrink: 0;
}
.mobileMode .scaleRadioButton, .mobileMode .adapViewRadioButton {
width: 20px;
height: 20px;
border-radius: 60px;
margin-right: 22px;
margin-left: 22px;
top: 0px;
flex-shrink: 0;
}
.selectedRadioButton {
border: solid 1px #20aca9;
}
.selectedRadioButtonFill {
position: relative;
display: none;
background-color: #20aca9;
margin: auto;
width: 8px;
height: 8px;
border-radius: 30px;
top: 2px;
}
.mobileMode .selectedRadioButtonFill {
width: 12px;
height: 12px;
border-radius: 48px;
top: 4px;
}
#searchDiv {
display: flex;
margin-right: auto;
flex: 1;
}
#searchBox {
display: none;
width: 0%;
height: 22px;
padding-left: 5px;
border-radius: 0px 5px 5px 0px;
border-right: solid 1px #cccccc;
border-top: solid 1px #cccccc;
border-bottom: solid 1px #cccccc;
border-left: none;
-webkit-appearance: none;
}
#searchBox:focus {
outline-width: 0;
}
.searchBoxHint {
color: #8f949a;
}
#sitemapHost.popup #searchDiv{
display: none;
}
#sitemapHost.popup #sitemapHeader{
display: none;
}
#sitemapHost.popup #changePageInstructions{
display: none;
}
.mobileMode #sitemapHeader {
display: none;
}
/* Expo Sitemap
******************************************************************************/
.expoSitemapNode {
padding: 15px;
text-align: center;
}
.sitemapPageImg {
max-width: 90%;
max-height: 150px;
}
.popup .sitemapPageImg {
display: none;
}
.popup .expoSitemapNode {
padding: 0 0 0 10px;
text-align: left;
}

35
web/main/static/resources/Other.html

@ -0,0 +1,35 @@
<html>
<head>
<title></title>
</head>
<body>
<br />
<div style="width:100%; text-align:center; font-family:Arial; font-size:12px;" id=other></div>
<br />
<div style="width:100%; text-align:center; font-family:Arial; font-size:12px;">
<button onclick="parent.window.close();">
Close
</button>
</div>
<SCRIPT src="axurerp_pagescript.js"></SCRIPT>
<script language=javascript>
function getQueryVariable(variable) {
var query = window.location.hash.substring(1);
var vars = query.split("&&&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return decodeURI(pair[1]);
}
}
}
var other = document.getElementById('other');
other.innerHTML = getQueryVariable('other');
</script>
</body>
</html>

292
web/main/static/resources/css/axure_rp_page.css

@ -0,0 +1,292 @@
/* so the window resize fires within a frame in IE7 */
html, body {
height: 100%;
}
.mobileFrameCursor div * {
cursor: inherit !important;
}
a {
color: inherit;
}
p {
margin: 0px;
text-rendering: optimizeLegibility;
font-feature-settings: "kern" 1;
-webkit-font-feature-settings: "kern";
-moz-font-feature-settings: "kern";
-moz-font-feature-settings: "kern=1";
font-kerning: normal;
}
ul {
margin:0px;
}
iframe {
background: #FFFFFF;
}
/* to match IE with C, FF */
input {
padding: 1px 0px 1px 0px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
input[type=text]::-ms-clear {
width: 0;
height: 0;
display: none;
}
textarea {
margin: 0px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.focused:focus, .selectedFocused:focus {
outline: none;
}
div.intcases {
font-family: arial;
font-size: 12px;
text-align:left;
border:1px solid #AAA;
background:#FFF none repeat scroll 0% 0%;
z-index:9999;
visibility:hidden;
position:absolute;
padding: 0px;
border-radius: 3px;
white-space: nowrap;
}
div.intcaselink {
cursor: pointer;
padding: 3px 8px 3px 8px;
margin: 5px;
background:#EEE none repeat scroll 0% 0%;
border:1px solid #AAA;
border-radius: 3px;
}
div.refpageimage {
position: absolute;
left: 0px;
top: 0px;
font-size: 0px;
width: 16px;
height: 16px;
cursor: pointer;
background-image: url(images/newwindow.gif);
background-repeat: no-repeat;
}
div.annnoteimage {
position: absolute;
left: 0px;
top: 0px;
font-size: 0px;
/*width: 16px;
height: 12px;*/
cursor: help;
/*background-image: url(images/note.gif);*/
/*background-repeat: no-repeat;*/
width: 13px;
height: 12px;
padding-top: 1px;
text-align: center;
background-color: #138CDD;
-moz-box-shadow: 1px 1px 3px #aaa;
-webkit-box-shadow: 1px 1px 3px #aaa;
box-shadow: 1px 1px 3px #aaa;
}
div.annnoteline {
display: inline-block;
width: 9px;
height: 1px;
border-bottom: 1px solid white;
margin-top: 1px;
}
div.annnotelabel {
/*position: absolute;
left: 0px;
top: 0px;*/
font-family: Helvetica,Arial;
white-space: nowrap;
padding-top: 1px;
background-color: #fff849;
font-size: 10px;
font-weight: bold;
line-height: 14px;
margin-right: 3px;
padding: 0px 4px;
color: #000;
-moz-box-shadow: 1px 1px 3px #aaa;
-webkit-box-shadow: 1px 1px 3px #aaa;
box-shadow: 1px 1px 3px #aaa;
}
div.annnote {
display: flex;
position: absolute;
cursor: help;
line-height: 14px;
}
.annotation {
font-size: 12px;
padding-left: 2px;
margin-bottom: 5px;
}
.annotationName {
/*font-size: 13px;
font-weight: bold;
margin-bottom: 3px;
white-space: nowrap;*/
font-family: 'Trebuchet MS';
font-size: 14px;
font-weight: bold;
margin-bottom: 5px;
white-space: nowrap;
}
.annotationValue {
font-family: Arial, Helvetica, Sans-Serif;
font-size: 12px;
color: #4a4a4a;
line-height: 21px;
margin-bottom: 20px;
}
.noteLink {
text-decoration: inherit;
color: inherit;
}
.noteLink:hover {
background-color: white;
}
/* this is a fix for the issue where dialogs jump around and takes the text-align from the body */
.dialogFix {
position:absolute;
text-align:left;
border: 1px solid #8f949a;
}
@keyframes pulsate {
from {
box-shadow: 0 0 10px #15d6ba;
}
to {
box-shadow: 0 0 20px #15d6ba;
}
}
@-webkit-keyframes pulsate {
from {
-webkit-box-shadow: 0 0 10px #15d6ba;
box-shadow: 0 0 10px #15d6ba;
}
to {
-webkit-box-shadow: 0 0 20px #15d6ba;
box-shadow: 0 0 20px #15d6ba;
}
}
@-moz-keyframes pulsate {
from {
-moz-box-shadow: 0 0 10px #15d6ba;
box-shadow: 0 0 10px #15d6ba;
}
to {
-moz-box-shadow: 0 0 20px #15d6ba;
box-shadow: 0 0 20px #15d6ba;
}
}
.legacyPulsateBorder {
/*border: 5px solid #15d6ba;
margin: -5px;*/
-moz-box-shadow: 0 0 10px 3px #15d6ba;
box-shadow: 0 0 10px 3px #15d6ba;
}
.pulsateBorder {
animation-name: pulsate;
animation-timing-function: ease-in-out;
animation-duration: 0.9s;
animation-iteration-count: infinite;
animation-direction: alternate;
-webkit-animation-name: pulsate;
-webkit-animation-timing-function: ease-in-out;
-webkit-animation-duration: 0.9s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-direction: alternate;
-moz-animation-name: pulsate;
-moz-animation-timing-function: ease-in-out;
-moz-animation-duration: 0.9s;
-moz-animation-iteration-count: infinite;
-moz-animation-direction: alternate;
}
.ax_default_hidden, .ax_default_unplaced{
display: none;
visibility: hidden;
}
.widgetNoteSelected {
-moz-box-shadow: 0 0 10px 3px #138CDD;
box-shadow: 0 0 10px 3px #138CDD;
/*-moz-box-shadow: 0 0 20px #3915d6;
box-shadow: 0 0 20px #3915d6;*/
/*border: 3px solid #3915d6;*/
/*margin: -3px;*/
}
.singleImg {
display: none;
visibility: hidden;
}
#ios-safari {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
#ios-safari-html {
display: block;
overflow: auto;
-webkit-overflow-scrolling: touch;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#ios-safari-fixed {
position: absolute;
pointer-events: none;
width: initial;
}
#ios-safari-fixed div {
pointer-events: auto;
}

6
web/main/static/resources/css/bootstrap.min.css

File diff suppressed because one or more lines are too long

1052
web/main/static/resources/css/default.css

File diff suppressed because it is too large

15
web/main/static/resources/css/headers.css

@ -0,0 +1,15 @@
.form-control-dark {
border-color: var(--bs-gray);
}
.form-control-dark:focus {
border-color: #fff;
box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25);
}
.text-small {
font-size: 85%;
}
.dropdown-toggle {
outline: 0;
}

25
web/main/static/resources/css/images/images.html

@ -0,0 +1,25 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title></title>
</head>
<body>
<p>
<img border="0" src="note.gif" width="1" height="1">
<img border="0" src="newwindow.gif" width="1" height="1">
<img border="0" src="ui-bg_flat_0_aaaaaa_40x100.png" width="1" height="1">
<img border="0" src="ui-bg_glass_55_fbf9ee_1x400.png" width="1" height="1">
<img border="0" src="ui-bg_glass_65_ffffff_1x400.png" width="1" height="1">
<img border="0" src="ui-bg_glass_75_dadada_1x400.png" width="1" height="1">
<img border="0" src="ui-bg_glass_75_e6e6e6_1x400.png" width="1" height="1">
<img border="0" src="ui-bg_glass_75_ffffff_1x400.png" width="1" height="1">
<img border="0" src="ui-bg_highlight-soft_75_cccccc_1x100.png" width="1" height="1">
<img border="0" src="ui-bg_inset-soft_95_fef1ec_1x100.png" width="1" height="1">
<img border="0" src="ui-icons_222222_256x240.png" width="1" height="1">
<img border="0" src="ui-icons_2e83ff_256x240.png" width="1" height="1">
<img border="0" src="ui-icons_454545_256x240.png" width="1" height="1">
<img border="0" src="ui-icons_888888_256x240.png" width="1" height="1">
<img border="0" src="ui-icons_cd0a0a_256x240.png" width="1" height="1">
</p>
</body>
</html>

BIN
web/main/static/resources/css/images/newwindow.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

BIN
web/main/static/resources/css/images/note.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

BIN
web/main/static/resources/css/images/touch.cur

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

32
web/main/static/resources/css/images/touch.svg

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="64px" height="64px" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>touch_update</title>
<desc>Created with Sketch.</desc>
<defs>
<circle id="path-1" cx="859" cy="783" r="24"></circle>
<filter x="-26.0%" y="-26.0%" width="152.1%" height="152.1%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="0.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="4" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
<filter x="-41.7%" y="-41.7%" width="183.3%" height="183.3%" filterUnits="objectBoundingBox" id="filter-3">
<feGaussianBlur stdDeviation="7.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
<feOffset dx="5" dy="8" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.356034873 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Sitemap-expanded" transform="translate(-827.000000, -751.000000)" fill-rule="nonzero">
<g id="touch_update">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill-opacity="0.236894248" fill="#E4DEDE" fill-rule="evenodd" xlink:href="#path-1"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-1"></use>
<use stroke-opacity="0.225798234" stroke="#CACACA" stroke-width="1" xlink:href="#path-1"></use>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
web/main/static/resources/css/images/ui-bg_flat_0_aaaaaa_40x100.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

BIN
web/main/static/resources/css/images/ui-bg_glass_55_fbf9ee_1x400.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

BIN
web/main/static/resources/css/images/ui-bg_glass_65_ffffff_1x400.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

BIN
web/main/static/resources/css/images/ui-bg_glass_75_dadada_1x400.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

BIN
web/main/static/resources/css/images/ui-bg_glass_75_e6e6e6_1x400.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
web/main/static/resources/css/images/ui-bg_glass_75_ffffff_1x400.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

BIN
web/main/static/resources/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

BIN
web/main/static/resources/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

BIN
web/main/static/resources/css/images/ui-icons_222222_256x240.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
web/main/static/resources/css/images/ui-icons_2e83ff_256x240.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
web/main/static/resources/css/images/ui-icons_454545_256x240.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
web/main/static/resources/css/images/ui-icons_888888_256x240.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
web/main/static/resources/css/images/ui-icons_cd0a0a_256x240.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

412
web/main/static/resources/css/jquery-ui-themes.css

@ -0,0 +1,412 @@
/*
* jQuery UI CSS Framework
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
.ui-helper-clearfix { display: inline-block; }
/* required comment for clearfix to work in Opera \*/
* html .ui-helper-clearfix { height:1%; }
.ui-helper-clearfix { display:block; }
/* end clearfix */
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }/* Accordion
----------------------------------*/
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
.ui-accordion .ui-accordion-li-fix { display: inline; }
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
.ui-accordion .ui-accordion-content-active { display: block; }
/* Datepicker
----------------------------------*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
.ui-datepicker .ui-datepicker-prev { left:2px; }
.ui-datepicker .ui-datepicker-next { right:2px; }
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; }
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; }
/* RTL support */
.ui-datepicker-rtl { direction: rtl; }
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
.ui-datepicker-cover {
display: none; /*sorry for IE5*/
display/**/: block; /*sorry for IE5*/
position: absolute; /*must have*/
z-index: -1; /*must have*/
filter: mask(); /*must have*/
top: -4px; /*must have*/
left: -4px; /*must have*/
width: 200px; /*must have*/
height: 200px; /*must have*/
}
/* Dialog
----------------------------------*/
.ui-dialog { position: relative; padding: 0px; width: 300px;}
.ui-dialog .ui-dialog-titlebar { padding: .3em .3em .1em .8em; font-size:.7em; position: relative; background-image: none; }
.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em;
font-family: 'Trebuchet MS';
font-size: 15px;
font-weight: normal;
color: #ffffff;}
.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .1em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { /*padding: 0;*/ }
.ui-dialog .ui-dialog-content { border: 0; padding: .5em .2em; background: none; overflow: auto; zoom: 1; background-color: #ffffff;}
.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
.ui-draggable .ui-dialog-titlebar { cursor: move; background-color: #8f949a; border-bottom: 1px solid #d9d9d9;}
/* Progressbar
----------------------------------*/
.ui-progressbar { height:2em; text-align: left; }
.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
----------------------------------*/
.ui-resizable { position: relative;}
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
----------------------------------*/
.ui-slider { position: relative; text-align: left; }
.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
.ui-slider-horizontal { height: .8em; }
.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
.ui-slider-vertical { width: .8em; height: 100px; }
.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
----------------------------------*/
.ui-tabs { padding: .2em; zoom: 1; }
.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
.ui-tabs .ui-tabs-hide { display: none !important; }
/*
* jQuery UI CSS Framework
* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* To view and modify this theme, visit http://jqueryui.com/themeroller/
*/
/* Component containers
----------------------------------*/
.ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; }
.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_glass_75_ffffff_1x400.png)/*{bgImgUrlContent}*/ 0/*{bgContentXPos}*/ 0/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; }
.ui-widget-content a { /*color: #222222*//*{fcContent}*/; }
.ui-widget-header { border: none /*1px solid #aaaaaa*//*{borderColorHeader}*/; background: #D3D3D3/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 0/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #000000/*{fcHeader}*/; font-weight: bold; }
.ui-widget-header a { color: #222222/*{fcHeader}*/; }
/* Interaction states
----------------------------------*/
.ui-state-default, .ui-widget-content .ui-state-default { border: none /*1px solid #d3d3d3*//*{borderColorDefault}*/; /*background: #e6e6e6*//*{bgColorDefault}*/ /*url(images/ui-bg_glass_75_e6e6e6_1x400.png)*//*{bgImgUrlDefault}*/ /*0*//*{bgDefaultXPos}*/ /*50%*//*{bgDefaultYPos}*/ /*repeat-x*//*{bgDefaultRepeat}*/ font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; outline: none; }
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; outline: none; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: none /*1px solid #999999*//*{borderColorHover}*/; /*background: #dadada*//*{bgColorHover}*/ /*url(images/ui-bg_glass_75_dadada_1x400.png)*//*{bgImgUrlHover}*/ /*0*//*{bgHoverXPos}*/ /*50%*//*{bgHoverYPos}*/ /*repeat-x*//*{bgHoverRepeat}*/ font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; outline: none; }
.ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; outline: none; }
.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 0/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; outline: none; }
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; outline: none; text-decoration: none; }
/* Interaction Cues
----------------------------------*/
.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 0/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; }
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636/*{fcHighlight}*/; }
.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_inset-soft_95_fef1ec_1x100.png)/*{bgImgUrlError}*/ 0/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; }
.ui-state-error a, .ui-widget-content .ui-state-error a { color: #363636/*{fcError}*/; }
.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a/*{fcError}*/; }
.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHeader}*/; }
.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; }
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHover}*/; }
.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; }
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; }
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; }
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-off { background-position: -96px -144px; }
.ui-icon-radio-on { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; }
.ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; }
.ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; }
.ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; }
.ui-corner-top { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; }
.ui-corner-bottom { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; }
.ui-corner-right { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; }
.ui-corner-left { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; }
.ui-corner-all { -moz-border-radius: 0px/*{cornerRadius}*/; -webkit-border-radius: 0px/*{cornerRadius}*/; }
/* Overlays */
.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ none/*{bgImgUrlOverlay}*/ 0/*{bgOverlayXPos}*/ 0/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; }
.ui-widget-shadow { margin: -4px/*{offsetTopShadow}*/ 0 0 -4px/*{offsetLeftShadow}*/; padding: 4px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ none/*{bgImgUrlShadow}*/ 0/*{bgShadowXPos}*/ 0/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .35;filter:Alpha(Opacity=35)/*{opacityShadow}*/; -moz-border-radius: 4px/*{cornerRadiusShadow}*/; -webkit-border-radius: 4px/*{cornerRadiusShadow}*/; }

12
web/main/static/resources/css/previewfonts.css

@ -0,0 +1,12 @@
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('previewfonts/SourceSansPro-Regular.woff2') format('woff2'), url('previewfonts/SourceSansPro-Regular.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro Semibold';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), url('previewfonts/SourceSansPro-Semibold.woff2') format('woff2'), url('previewfonts/SourceSansPro-Semibold.woff') format('woff');
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save