Unverified Commit b05f7d8e authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #35 from jumpserver/dev

Dev
parents ebda194f f844a768
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
"styles": [ "styles": [
"../node_modules/animate.css/animate.min.css", "../node_modules/animate.css/animate.min.css",
"../node_modules/xterm/dist/xterm.css", "../node_modules/xterm/dist/xterm.css",
"../node_modules/elfinder/css/elfinder.min.css",
"sass/style.scss", "sass/style.scss",
"styles.css", "styles.css",
"assets/ztree/awesomeStyle/awesome.css" "assets/ztree/awesomeStyle/awesome.css"
...@@ -35,7 +34,6 @@ ...@@ -35,7 +34,6 @@
"../node_modules/bootstrap/dist/js/bootstrap.min.js", "../node_modules/bootstrap/dist/js/bootstrap.min.js",
"assets/inspinia/inspinia.js", "assets/inspinia/inspinia.js",
"assets/slimscroll/jquery.slimscroll.min.js", "assets/slimscroll/jquery.slimscroll.min.js",
"../node_modules/elfinder/js/elfinder.min.js",
"../node_modules/xterm/dist/xterm.js", "../node_modules/xterm/dist/xterm.js",
"assets/ztree/jquery.ztree.all.min.js", "assets/ztree/jquery.ztree.all.min.js",
"assets/ztree/jquery.ztree.exhide.min.js" "assets/ztree/jquery.ztree.exhide.min.js"
......
#!/usr/bin/env python3 #!/usr/bin/env python3
from flask import Flask, send_from_directory, render_template, request, jsonify, redirect, send_file import sys
import threading
sys.path.append('/Users/guang/projects/coco')
from flask import Flask, send_from_directory, render_template, request, jsonify, \
redirect, send_file, abort
from flask_socketio import SocketIO, Namespace, emit, join_room, leave_room from flask_socketio import SocketIO, Namespace, emit, join_room, leave_room
import paramiko import paramiko
import uuid import uuid
import eventlet
from threading import Lock from threading import Lock
from flask import Flask, request, current_app, redirect from flask import Flask, request, current_app, redirect
import eventlet
import time
import json
import socket
import logging
import select
from coco.models import WSProxy, Client, Request
from coco.httpd import ProxyNamespace
logger = logging.getLogger(__file__)
logger.setLevel(logging.DEBUG)
sh = logging.StreamHandler(stream=None)
logger.addHandler(sh)
logger2 = logging.getLogger('coco')
logger2.setLevel(logging.DEBUG)
logger2.addHandler(sh)
eventlet.monkey_patch()
# async_mode = 'threading'
async_mode = 'eventlet'
app = Flask(__name__, template_folder='dist') app = Flask(__name__, template_folder='dist')
socketio = None socketio = None
thread = None
thread_lock = Lock() nodes = '[{"id":"03059e2e-06b8-4ef1-b949-72e230b706fa","key":"0:9:5","name":"部门1","value":"部门1","parent":"ea4688ef-2b65-40cd-944d-5fca39e34f42","assets_granted":[{"id":"ad594b10-9f64-4913-b7b1-135fe63561d1","hostname":"ali-windows","ip":"47.104.243.139","port":3389,"system_users_granted":[{"id":"8763b81a-bb5e-484a-abca-10514c7bb185","name":"组织1-部门1-Administrator","username":"administrator","priority":10,"protocol":"rdp","comment":""}],"is_active":true,"system_users_join":"administrator","os":null,"domain":null,"platform":"Windows","comment":""},{"id":"d9020939-1dd7-4b18-9165-5124f20d1f77","hostname":"newwindows","ip":"10.1.10.114","port":3389,"system_users_granted":[{"id":"46b57293-c662-46f9-8bc4-dcf64f01bedc","name":"newwindows","username":"administrator","priority":10,"protocol":"rdp","comment":""},{"id":"8763b81a-bb5e-484a-abca-10514c7bb185","name":"组织1-部门1-Administrator","username":"administrator","priority":10,"protocol":"rdp","comment":""}],"is_active":true,"system_users_join":"administrator, administrator","os":null,"domain":null,"platform":"Windows","comment":""},{"id":"9ef36bb3-1bed-455f-be09-3770d3f4bf97","hostname":"test-vm1","ip":"172.19.185.6","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":null,"domain":null,"platform":"Linux","comment":""},{"id":"1600ed6d-e3b6-434c-a960-c5bb818806b6","hostname":"windows1","ip":"10.1.10.178","port":3389,"system_users_granted":[{"id":"413ea1d2-ef73-4a90-bae3-571ac1b39d93","name":"2012-test-no-passwd-rdp","username":"administrator","priority":10,"protocol":"rdp","comment":""},{"id":"46b57293-c662-46f9-8bc4-dcf64f01bedc","name":"newwindows","username":"administrator","priority":10,"protocol":"rdp","comment":""},{"id":"8763b81a-bb5e-484a-abca-10514c7bb185","name":"组织1-部门1-Administrator","username":"administrator","priority":10,"protocol":"rdp","comment":""}],"is_active":true,"system_users_join":"administrator, administrator, administrator","os":null,"domain":null,"platform":"Windows","comment":""},{"id":"27e50edc-52d9-41ef-8c9e-1bff9d1628b2","hostname":"test-vm2","ip":"172.19.185.7","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":null,"domain":null,"platform":"Linux","comment":""},{"id":"b6f16269-d02a-4055-9cd8-460fa10b1540","hostname":"test-vm3","ip":"172.19.185.8","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":null,"domain":null,"platform":"Linux","comment":""},{"id":"b952a481-a624-467e-b97f-9435155f0d53","hostname":"testserver","ip":"10.1.10.192","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":"CentOS","domain":"8789580f-b5ca-4478-b6d3-d0dafc4c48e8","platform":"Linux","comment":""},{"id":"969247e0-3796-4090-9aa6-3248560079e6","hostname":"test01","ip":"123.123.123.1","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":null,"domain":null,"platform":"Linux","comment":""},{"id":"7e8451cb-8eb7-4c9d-b652-961a6fdce3c4","hostname":"wz-test","ip":"54.222.180.235","port":22,"system_users_granted":[{"id":"7e326f71-aee5-4688-8cc1-717919470a09","name":"root","username":"root","priority":10,"protocol":"ssh","comment":""}],"is_active":true,"system_users_join":"root","os":"RedHat","domain":null,"platform":"Linux","comment":""}],"assets_amount":9}]'
class SSHws(Namespace): class Forwarder:
def __init__(self, *args, **kwargs): def __init__(self, client):
""" self.client = client
:param args: width = client.request.meta['width']
:param kwargs: height = client.request.meta['height']
self.server = self.ssh_with_password(width, height)
self.connections = { self.watch_win_size_change_async()
"request_sid": {
"room_id": { def proxy(self, asset, system_user):
"id": room_id, while True:
"proxy": None, r, w, x = select.select([self.server, self.client], [], [])
"client": None, if self.server in r:
"forwarder": None, data = self.server.recv(1024)
"request": None, if len(data) == 0:
"cols": 80, break
"rows": 24 self.client.send(data)
}, if self.client in r:
... data = self.client.recv(1024)
}, if len(data) == 0:
... break
} self.server.send(data)
"""
super().__init__(*args, **kwargs) def watch_win_size_change_async(self):
self.connections = dict() thread = threading.Thread(target=self.watch_win_size_change)
thread.daemon = True
def ssh_with_password(self, ws, room_id): thread.start()
def ssh_with_password(self, width=80, height=24):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("192.168.244.176", 22, "root", "redhat123") ssh.connect("192.168.244.176", 22, "root", "redhat123")
width, height = self.get_win_size()
chan = ssh.invoke_shell(term='xterm', width=width, height=height) chan = ssh.invoke_shell(term='xterm', width=width, height=height)
# self.socketio.start_background_task(self.send_data, self.clients[request.sid]["chan"])
# self.chan.settimeout(0.1)
return chan return chan
def proxy(self, ws, chan, room_id): def watch_win_size_change(self):
# eventlet.monkey_patch(thread=True, select=True, socket=True) while self.client.request.change_size_event.wait():
while True: self.client.request.change_size_event.clear()
data = chan.recv(1024) width = self.client.request.meta.get('width', 80)
socketio.sleep(1) height = self.client.request.meta.get('height', 24)
if len(data) == 0: logger.debug("Change win size: %s - %s" % (width, height))
try:
self.server.resize_pty(width=width, height=height)
# self.server.chan.resize_pty(width=width, height=height)
except Exception:
break break
ws.emit(event="data", data={"data": data.decode(), "room": room_id}, room=room_id, namespace='/ssh')
def new_connection(self): class SSHws(ProxyNamespace):
self.connections[request.sid] = dict()
def new_room(self):
room_id = str(uuid.uuid4())
room = {
"id": room_id,
"proxy": None,
"client": None,
"forwarder": None,
"request": self.ssh_with_password(self, room_id),
"cols": 80,
"rows": 24
}
self.connections[request.sid][room_id] = room
return room
@staticmethod
def get_win_size():
cols_request = request.cookies.get('cols')
rows_request = request.cookies.get('rows')
if cols_request and cols_request.isdigit():
cols = int(cols_request)
else:
cols = 80
if rows_request and rows_request.isdigit():
rows = int(rows_request)
else:
rows = 24
return cols, rows
def on_connect(self): def on_connect(self):
print("On connect event trigger") logger.debug("ON connect")
self.new_connection() self.new_connection()
# self.connections[request.sid] = connection
# self.rooms[connection['room']] = {
# "admin": request.sid,
# "member": [],
# "rw": []
# }
# join_room(connection['room'])
def on_host(self, message): def on_host(self, message):
# 此处获取主机的信息 # 此处获取主机的信息
print("On host event trigger") logger.debug("On host event trigger")
asset_id = message.get('uuid', None) asset_id = message.get('uuid', None)
user_id = message.get('userid', None) user_id = message.get('userid', None)
secret = message.get('secret', None) secret = message.get('secret', None)
room = self.new_room() win_size = message.get('size', (80, 24))
req = self.make_coco_request(win_size[0], win_size[1])
room = self.new_room(req)
self.emit('room', {'room': room["id"], 'secret': secret}) self.emit('room', {'room': room["id"], 'secret': secret})
print("Join room: {}".format(room["id"]))
join_room(room["id"]) join_room(room["id"])
if not asset_id or not user_id: if not asset_id or not user_id:
# self.on_connect()
return return
global thread
if thread is None:
thread = self.socketio.start_background_task(
self.proxy, self, room["request"], room["id"]
)
def on_data(self, message): # asset = app_service.get_asset(asset_id)
""" # system_user = app_service.get_system_user(user_id)
收到浏览器请求
:param message: {"data": "xxx", "room": "xxx"} # if not asset or not system_user:
:return: # self.on_connect()
""" # return
room_id = message.get('room') child, parent = socket.socketpair()
room = self.connections.get(request.sid, {}).get(room_id) client = Client(parent, room["request"])
if not room: forwarder = Forwarder(client)
return room["client"] = client
room["request"].send(message['data']) room["forwarder"] = forwarder
room["proxy"] = WSProxy(self, child, room["id"])
room["cols"], room["rows"] = win_size
self.socketio.start_background_task(
forwarder.proxy, None, None
)
def on_token(self, message): def on_token(self, message):
# 此处获取token含有的主机的信息 # 此处获取token含有的主机的信息
print("On token trigger") logger.debug("On token trigger")
token = message.get('token', None) token = message.get('token', None)
secret = message.get('secret', None) secret = message.get('secret', None)
size = message.get('size', (80, 24))
room = self.new_room() room = self.new_room()
self.emit('room', {'room': room["id"], 'secret': secret}) self.emit('room', {'room': room["id"], 'secret': secret})
if not token or not secret: if not token or not secret:
print("Token or secret is None") logger.debug("Token or secret is None: {}".format(token, secret))
self.emit('data', {'data': "\nOperation not permitted!", self.emit('data', {'data': "\nOperation not permitted!",
'room': room["id"]}) 'room': room["id"]})
self.emit('disconnect') self.emit('disconnect')
return None return None
info = self.app.service.get_token_asset(token) logger.debug(self.current_user)
print(info)
if not info:
print("Token info is None")
self.emit('data', {'data': "\nOperation not permitted!",
'room': room["id"]})
self.emit('disconnect')
return None
user_id = info.get('user', None)
self.current_user = self.app.service.get_user_profile(user_id)
room["request"].user = self.current_user
print(self.current_user)
self.on_host({ self.on_host({
'secret': secret, 'secret': secret,
'uuid': info['asset'], 'uuid': 'asset',
'userid': info['system_user'], 'userid': 'system_user',
'size': size
}) })
def on_resize(self, message):
cols, rows = message.get('cols', None), message.get('rows', None)
print("On resize event trigger: {}*{}".format(cols, rows))
rooms = self.connections.get(request.sid)
if not rooms:
return
room_tmp = list(rooms.values())[0]
if (room_tmp["cols"], room_tmp["rows"]) != (cols, rows):
for room in rooms.values():
room["request"].resize_pty(width=cols, height=rows)
# room["request"].change_size_event.set()
# room.update({"cols": cols, "rows": rows})
# def on_room(self, session_id):
# print("On room event trigger")
# if session_id not in self.connections.keys():
# self.emit(
# 'error', "no such session",
# room=self.connections[request.sid]["room"]
# )
# else:
# self.emit(
# 'room', self.connections[session_id]["room"],
# room=self.connections[request.sid]["room"]
# )
#
# def on_join(self, room):
# print("On join room event trigger")
# self.on_leave(self.connections[request.id]["room"])
# self.connections[request.sid]["room"] = room
# self.rooms[room]["member"].append(request.sid)
# join_room(room=room)
#
# def on_leave(self, room):
# print("On leave room event trigger")
# if self.rooms[room]["admin"] == request.sid:
# self.emit("data", "\nAdmin leave", room=room)
# del self.rooms[room]
# leave_room(room=room)
def on_disconnect(self):
print("On disconnect event trigger")
# self.on_leave(self.clients[request.sid]["room"])
for room in self.connections.get(request.sid, {}):
self.on_logout(room["id"])
del self.connections[request.sid]
def on_logout(self, room_id):
print("On logout event trigger")
room = self.connections.get(request.sid, {}).get(room_id)
if room:
room["proxy"].close()
del self.connections[request.sid][room_id]
del room
@app.route('/luna/<path:path>') @app.route('/luna/<path:path>')
def send_js(path): def send_js(path):
...@@ -234,649 +156,14 @@ def send_js(path): ...@@ -234,649 +156,14 @@ def send_js(path):
@app.route('/api/perms/v1/user/nodes-assets/') @app.route('/api/perms/v1/user/nodes-assets/')
def asset_groups_assets(): def asset_groups_assets():
assets = [ node = json.loads(nodes)
{ return jsonify(node)
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"name": "新节点 12",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_granted": [
{
"id": "1600ed6d-e3b6-434c-a960-c5bb818806b6",
"hostname": "windows1",
"ip": "10.1.10.178",
"port": 3389,
"system_users_granted": [
{
"id": "8763b81a-bb5e-484a-abca-10514c7bb185",
"name": "组织1-部门1-Administrator",
"username": "Administrator",
"priority": 10,
"protocol": "rdp",
"comment": ""
}
],
"is_active": True,
"system_users_join": "Administrator",
"os": "",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Windows",
"comment": ""
},
{
"id": "b952a481-a624-467e-b97f-9435155f0d53",
"hostname": "testserver",
"ip": "10.1.10.192",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "CentOS",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "9fcd7a09-a171-4cb7-b2f9-a025754f8635",
"hostname": "ali-windows",
"ip": "47.104.206.228",
"port": 3389,
"system_users_granted": [
{
"id": "8763b81a-bb5e-484a-abca-10514c7bb185",
"name": "组织1-部门1-Administrator",
"username": "Administrator",
"priority": 10,
"protocol": "rdp",
"comment": ""
}
],
"is_active": True,
"system_users_join": "Administrator",
"os": "",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Windows",
"comment": ""
},
{
"id": "b6f16269-d02a-4055-9cd8-460fa10b1540",
"hostname": "test-vm3",
"ip": "172.19.185.8",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "27e50edc-52d9-41ef-8c9e-1bff9d1628b2",
"hostname": "test-vm2",
"ip": "172.19.185.7",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "9ef36bb3-1bed-455f-be09-3770d3f4bf97",
"hostname": "test-vm1",
"ip": "172.19.185.6",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
{
"id": "17f384f4-683d-4944-a38d-db73608b92a9",
"name": "zbh-test",
"username": "zbh",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
}
],
"assets_amount": 6
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"name": "网域测试",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_granted": [
{
"id": "1600ed6d-e3b6-434c-a960-c5bb818806b6",
"hostname": "windows1",
"ip": "10.1.10.178",
"port": 3389,
"system_users_granted": [
{
"id": "8763b81a-bb5e-484a-abca-10514c7bb185",
"name": "组织1-部门1-Administrator",
"username": "Administrator",
"priority": 10,
"protocol": "rdp",
"comment": ""
}
],
"is_active": True,
"system_users_join": "Administrator",
"os": "",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Windows",
"comment": ""
},
{
"id": "b952a481-a624-467e-b97f-9435155f0d53",
"hostname": "testserver",
"ip": "10.1.10.192",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "CentOS",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "b6f16269-d02a-4055-9cd8-460fa10b1540",
"hostname": "test-vm3",
"ip": "172.19.185.8",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
{
"id": "17f384f4-683d-4944-a38d-db73608b92a9",
"name": "zbh-test",
"username": "zbh",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "27e50edc-52d9-41ef-8c9e-1bff9d1628b2",
"hostname": "test-vm2",
"ip": "172.19.185.7",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
{
"id": "17f384f4-683d-4944-a38d-db73608b92a9",
"name": "zbh-test",
"username": "zbh",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "9ef36bb3-1bed-455f-be09-3770d3f4bf97",
"hostname": "test-vm1",
"ip": "172.19.185.6",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
{
"id": "17f384f4-683d-4944-a38d-db73608b92a9",
"name": "zbh-test",
"username": "zbh",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "",
"domain": "8789580f-b5ca-4478-b6d3-d0dafc4c48e8",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
}
],
"assets_amount": 5
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"name": "Fit2cloud",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_granted": [
{
"id": "b952a481-a624-467e-b97f-9435155f0d53",
"hostname": "testserver",
"ip": "10.1.10.192",
"port": 22,
"system_users_granted": [
{
"id": "7e326f71-aee5-4688-8cc1-717919470a09",
"name": "root",
"username": "root",
"priority": 10,
"protocol": "ssh",
"comment": ""
},
{
"id": "17f384f4-683d-4944-a38d-db73608b92a9",
"name": "zbh-test",
"username": "zbh",
"priority": 10,
"protocol": "ssh",
"comment": ""
}
],
"is_active": True,
"system_users_join": "root, zbh",
"os": "CentOS",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"key": "0:11",
"value": "网域测试",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Linux",
"comment": ""
},
{
"id": "ad594b10-9f64-4913-b7b1-135fe63561d1",
"hostname": "ali-windows",
"ip": "47.104.206.228",
"port": 3389,
"system_users_granted": [
{
"id": "8763b81a-bb5e-484a-abca-10514c7bb185",
"name": "组织1-部门1-Administrator",
"username": "Administrator",
"priority": 10,
"protocol": "rdp",
"comment": ""
}
],
"is_active": True,
"system_users_join": "Administrator",
"os": "",
"domain": "",
"nodes": [
{
"id": "67f92d6c-0f91-4d20-a0e4-ac83b7dd02b6",
"key": "0:11:77",
"value": "新节点 12",
"parent": "9c83d432-a353-4a4e-9fd9-be27a5851c2d",
"assets_amount": 6,
"is_asset": False
},
{
"id": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"key": "0",
"value": "Fit2cloud",
"parent": "be9d9c3a-68d0-40ec-887c-5815d68e2f2c",
"assets_amount": 6,
"is_asset": False
}
],
"platform": "Windows",
"comment": ""
}
],
"assets_amount": 2
}
]
return jsonify(assets)
@app.route('/api/users/v1/profile/')
def user_profile():
assets = {
"id": "4fc67feb-9efa-4e7b-94b0-b73356a87b2e",
"username": "admin",
"name": "Administrator",
"email": "admin@mycomany.com",
"is_active": True,
"is_superuser": True,
"role": "Administrator",
"groups": [
"Default"
],
"wechat": "",
"phone": 13888888888,
"comment": "",
"date_expired": "2087-12-16 07:41:35"
}
return jsonify(assets)
@app.route('/api/terminal/v1/sessions/test/replay/') @app.route('/api/terminal/v1/sessions/test/replay/')
def replay(): def replay():
return redirect("http://jps.ilz.me/media/2017-12-24/ec87a486-0344-4f12-b27a-620321944f7f.gz") return redirect(
"http://jps.ilz.me/media/2017-12-24/ec87a486-0344-4f12-b27a-620321944f7f.gz")
@app.route('/api/terminal/v2/sessions/<pk>/replay/') @app.route('/api/terminal/v2/sessions/<pk>/replay/')
...@@ -904,7 +191,6 @@ def read_file(filename, charset='utf-8'): ...@@ -904,7 +191,6 @@ def read_file(filename, charset='utf-8'):
if __name__ == '__main__': if __name__ == '__main__':
async_mode = 'threading'
socketio = SocketIO(app, async_mode=async_mode) socketio = SocketIO(app, async_mode=async_mode)
socketio.on_namespace(SSHws('/ssh')) socketio.on_namespace(SSHws('/ssh'))
socketio.run(app, debug=True) socketio.run(app, port=5001)
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"license": "GPLv3", "license": "GPLv3",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json", "start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0",
"build": "ng build --environment prod --aot --prod --base-href=/luna/ --deploy '/luna/'", "build": "ng build --environment prod --aot --prod --base-href=/luna/ --deploy '/luna/'",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"@angular/router": "5.2.0", "@angular/router": "5.2.0",
"@swimlane/ngx-datatable": "^11.3.2", "@swimlane/ngx-datatable": "^11.3.2",
"@swimlane/ngx-ui": "^20.2.1", "@swimlane/ngx-ui": "^20.2.1",
"@types/socket.io-client": "^1.4.32",
"ajv": "^6.5.0", "ajv": "^6.5.0",
"animate.css": "^3.6.1", "animate.css": "^3.6.1",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
......
{ {
"/api": { "/api": {
"target": "http://127.0.0.1:5000", "target": "http://127.0.0.1:5001",
"secure": false "secure": false
}, },
"/luna/i18n": { "/luna/i18n": {
"target": "http://127.0.0.1:8088", "target": "http://127.0.0.1:5001",
"secure": false "secure": false
}, },
"/socket.io/": { "/socket.io/": {
"target": "http://127.0.0.1:5000", "target": "http://127.0.0.1:5001",
"secure": false "secure": false,
"ws": true
}, },
"/rdp/socket.io/": { "/rdp/socket.io/": {
"target": "http://localhost:9250", "target": "http://localhost:9250",
......
import {Component, Input, OnInit, Inject, SimpleChanges, OnChanges} from '@angular/core'; import {Component, Input, OnInit, Inject, SimpleChanges, OnChanges, EventEmitter} from '@angular/core';
import {NavList, View} from '../../pages/control/control/control.component'; import {NavList, View} from '../../pages/control/control/control.component';
import {AppService, LogService} from '../../app.service'; import {AppService, LogService} from '../../app.service';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {FormControl, Validators} from '@angular/forms'; import {FormControl, Validators} from '@angular/forms';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
declare var $: any; declare var $: any;
...@@ -14,6 +15,7 @@ declare var $: any; ...@@ -14,6 +15,7 @@ declare var $: any;
export class ElementAssetTreeComponent implements OnInit, OnChanges { export class ElementAssetTreeComponent implements OnInit, OnChanges {
@Input() Data: any; @Input() Data: any;
@Input() query: string; @Input() query: string;
@Input() searchEvt$: BehaviorSubject<string>;
nodes = []; nodes = [];
setting = { setting = {
view: { view: {
...@@ -30,6 +32,7 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -30,6 +32,7 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
}, },
}; };
hiddenNodes: any; hiddenNodes: any;
expandNodes: any;
onCzTreeOnClick(event, treeId, treeNode, clickFlag) { onCzTreeOnClick(event, treeId, treeNode, clickFlag) {
if (treeNode.isParent) { if (treeNode.isParent) {
...@@ -43,12 +46,19 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -43,12 +46,19 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
constructor(private _appService: AppService, constructor(private _appService: AppService,
public _dialog: MatDialog, public _dialog: MatDialog,
public _logger: LogService) { public _logger: LogService) {
this.searchEvt$ = new BehaviorSubject<string>(this.query);
} }
ngOnInit() { ngOnInit() {
if (this.Data) { if (this.Data) {
this.draw(); this.draw();
} }
this.searchEvt$.asObservable()
.debounceTime(300)
.distinctUntilChanged()
.subscribe((n) => {
this.filter();
});
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
...@@ -56,7 +66,8 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -56,7 +66,8 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.draw(); this.draw();
} }
if (changes['query'] && !changes['query'].firstChange) { if (changes['query'] && !changes['query'].firstChange) {
this.filter(); this.searchEvt$.next(this.query);
// this.filter();
} }
} }
...@@ -98,8 +109,11 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -98,8 +109,11 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
this.nodes.sort(function(node1, node2) { this.nodes.sort(function(node1, node2) {
if (node1.isParent && !node2.isParent) { if (node1.isParent && !node2.isParent) {
return -1; return -1;
} } else if (!node1.isParent && node2.isParent) {
return 1;
} else {
return node1.name < node2.name ? -1 : 1; return node1.name < node2.name ? -1 : 1;
}
}); });
$.fn.zTree.init($('#ztree'), this.setting, this.nodes); $.fn.zTree.init($('#ztree'), this.setting, this.nodes);
} }
...@@ -174,25 +188,72 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges { ...@@ -174,25 +188,72 @@ export class ElementAssetTreeComponent implements OnInit, OnChanges {
return user; return user;
} }
recurseParent(node) {
const parentNode = node.getParentNode();
if (parentNode && parentNode.pId) {
return [parentNode, ...this.recurseParent(parentNode)];
} else if (parentNode) {
return [parentNode];
} else {
return [];
}
}
recurseChildren(node) {
if (!node.isParent) {
return [];
}
const children = node.children;
if (!children) {
return [];
}
let all_children = [];
children.forEach((n) => {
all_children = [...children, ...this.recurseChildren(n)];
});
return all_children;
}
filter() { filter() {
const zTreeObj = $.fn.zTree.getZTreeObj('ztree'); const zTreeObj = $.fn.zTree.getZTreeObj('ztree');
const _keywords = $('#keyword').val(); if (!zTreeObj) {
return null;
}
const _keywords = this.query;
const nodes = zTreeObj.transformToArray(zTreeObj.getNodes());
if (!_keywords) {
if (this.hiddenNodes) {
zTreeObj.showNodes(this.hiddenNodes); zTreeObj.showNodes(this.hiddenNodes);
this.hiddenNodes = null;
function filterFunc(node) {
if (node.isParent || node.name.indexOf(_keywords) !== -1) {
return false;
} }
return true; if (this.expandNodes) {
this.expandNodes.forEach((node) => {
if (node.id !== nodes[0].id) {
zTreeObj.expandNode(node, false);
} }
});
this.hiddenNodes = zTreeObj.getNodesByFilter(filterFunc); this.expandNodes = null;
zTreeObj.hideNodes(this.hiddenNodes);
if (_keywords) {
zTreeObj.expandAll(true);
} else {
zTreeObj.expandAll(false);
} }
return null;
}
let shouldShow = [];
nodes.forEach((node) => {
if (shouldShow.indexOf(node) === -1 && node.name.indexOf(_keywords) !== -1) {
const parents = this.recurseParent(node);
const children = this.recurseChildren(node);
shouldShow = [...shouldShow, ...parents, ...children, node];
}
});
this.hiddenNodes = nodes;
this.expandNodes = shouldShow;
zTreeObj.hideNodes(nodes);
zTreeObj.showNodes(shouldShow);
shouldShow.forEach((node) => {
if (node.isParent) {
zTreeObj.expandNode(node, true);
}
});
// zTreeObj.expandAll(true);
} }
} }
......
...@@ -34,9 +34,6 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit { ...@@ -34,9 +34,6 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
background: '#1f1b1b' background: '#1f1b1b'
} }
}); });
const rowInit = parseInt(this._cookie.get('rows') || '24', 10);
const colsInit = parseInt(this._cookie.get('cols') || '80', 10);
this.term.resize(colsInit, rowInit);
} }
ngAfterViewInit() { ngAfterViewInit() {
...@@ -49,11 +46,21 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit { ...@@ -49,11 +46,21 @@ export class ElementSshTermComponent implements OnInit, AfterViewInit {
joinRoom() { joinRoom() {
NavList.List[this.index].Term = this.term; NavList.List[this.index].Term = this.term;
console.log(this.term);
console.log('Col: ', this.term.cols, 'rows', this.term.rows);
if (this.host) { if (this.host) {
ws.emit('host', {'uuid': this.host.id, 'userid': this.userid, 'secret': this.secret}); ws.emit('host', {
'uuid': this.host.id,
'userid': this.userid,
'secret': this.secret,
'size': [this.term.cols, this.term.rows]
});
} }
if (this.token) { if (this.token) {
ws.emit('token', {'token': this.token, 'secret': this.secret}); ws.emit('token', {
'token': this.token, 'secret': this.secret,
'size': [this.term.cols, this.term.rows]
});
} }
const that = this; const that = this;
......
...@@ -20,6 +20,7 @@ import {NavList} from '../../pages/control/control/control.component'; ...@@ -20,6 +20,7 @@ import {NavList} from '../../pages/control/control/control.component';
export class ElementTermComponent implements OnInit, AfterViewInit { export class ElementTermComponent implements OnInit, AfterViewInit {
@ViewChild('term') el: ElementRef; @ViewChild('term') el: ElementRef;
@Input() term: Terminal; @Input() term: Terminal;
@Input() offset: Array<number>;
@Output() winSizeChangeTrigger = new EventEmitter<Array<number>>(); @Output() winSizeChangeTrigger = new EventEmitter<Array<number>>();
winSizeChange$: Observable<any>; winSizeChange$: Observable<any>;
...@@ -33,7 +34,7 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -33,7 +34,7 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
this.winSizeChange$ this.winSizeChange$
.subscribe(() => { .subscribe(() => {
if (NavList.List[NavList.Active].type === 'ssh') { if (NavList.List[NavList.Active].type !== 'rdp') {
this.resizeTerm(); this.resizeTerm();
} }
}); });
...@@ -62,15 +63,10 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -62,15 +63,10 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
Math.floor(availableHeight / (<any>this.term).renderer.dimensions.actualCellHeight) - 1 Math.floor(availableHeight / (<any>this.term).renderer.dimensions.actualCellHeight) - 1
]; ];
return geometry; return geometry;
// const cols = Math.floor((activeEle.width() - 15) / markerEle.width() * 6) - 1;
// const rows = Math.floor(activeEle.height() / markerEle.height()) - 1;
// return [cols, rows];
} }
resizeTerm() { resizeTerm() {
const size = this.getWinSize(); const size = this.getWinSize();
console.log('get SIze', size);
if (isNaN(size[0]) || isNaN(size[1])) { if (isNaN(size[0]) || isNaN(size[1])) {
fit(this.term); fit(this.term);
} else { } else {
...@@ -78,8 +74,6 @@ export class ElementTermComponent implements OnInit, AfterViewInit { ...@@ -78,8 +74,6 @@ export class ElementTermComponent implements OnInit, AfterViewInit {
this.term.resize(size[0], size[1]); this.term.resize(size[0], size[1]);
} }
this.winSizeChangeTrigger.emit([this.term.cols, this.term.rows]); this.winSizeChangeTrigger.emit([this.term.cols, this.term.rows]);
this._cookie.set('cols', this.term.cols.toString(), 0, '/', document.domain);
this._cookie.set('rows', this.term.rows.toString(), 0, '/', document.domain);
} }
active() { active() {
......
<div class="sidebar" fxLayout="column" ngxSplit="column"> <div class="sidebar" fxLayout="column" ngxSplit="column">
<div fxflex="0 0 30px" class="search"> <div fxflex="0 0 30px" class="search">
<input id="keyword" class="left-search" placeholder=" {{'Search'| trans }} ..." maxlength="2048" name="q" <input #keyword id="keyword" class="left-search" placeholder=" {{'Search'| trans }} ..." maxlength="2048" name="q"
autocomplete="off" autocomplete="off"
title="Search" title="Search"
type="text" tabindex="1" spellcheck="false" autofocus [(ngModel)]="q" (keyup.enter)="Search(q)"> type="text" tabindex="1" spellcheck="false" [(ngModel)]="q">
</div> </div>
<div class="overflow ngx-scroll-overlay" fxflex="1 1 90%"> <div class="overflow ngx-scroll-overlay" fxflex="1 1 90%">
<elements-asset-tree [Data]="zNodes" [query]="q"></elements-asset-tree> <elements-asset-tree [Data]="zNodes" [query]="q"></elements-asset-tree>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* @author liuzheng <liuzheng712@gmail.com> * @author liuzheng <liuzheng712@gmail.com>
*/ */
import {Component, Inject, OnInit} from '@angular/core'; import {Component, Inject, OnInit, ViewChild, ElementRef} from '@angular/core';
import {AppService, HttpService, LogService} from '../../../app.service'; import {AppService, HttpService, LogService} from '../../../app.service';
import {SearchComponent} from '../search/search.component'; import {SearchComponent} from '../search/search.component';
import {DataStore} from '../../../globals'; import {DataStore} from '../../../globals';
...@@ -17,6 +17,7 @@ import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material'; ...@@ -17,6 +17,7 @@ import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material';
import {FormControl, Validators} from '@angular/forms'; import {FormControl, Validators} from '@angular/forms';
import {ElementServerMenuComponent} from '../../../elements/server-menu/server-menu.component'; import {ElementServerMenuComponent} from '../../../elements/server-menu/server-menu.component';
import {DialogService} from '../../../elements/dialog/dialog.service'; import {DialogService} from '../../../elements/dialog/dialog.service';
import {Observable} from '../../../../../node_modules/rxjs';
export interface HostGroup { export interface HostGroup {
name: string; name: string;
...@@ -102,7 +103,6 @@ export class CleftbarComponent implements OnInit { ...@@ -102,7 +103,6 @@ export class CleftbarComponent implements OnInit {
}); });
} }
Search(q) { Search(q) {
this._search.Search(q); this._search.Search(q);
} }
......
...@@ -44,7 +44,8 @@ ...@@ -44,7 +44,8 @@
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-size: 13px; font-size: 13px;
text-decoration: none; text-decoration: none;
padding-left: 24px; padding-left: 12px;
padding-right: 14px;
line-height: 26px; line-height: 26px;
cursor: default; cursor: default;
width: 115px; width: 115px;
...@@ -68,7 +69,7 @@ ...@@ -68,7 +69,7 @@
} }
.tabs ul li.active span { .tabs ul li.active span {
padding-left: 24px; padding-left: 12px;
line-height: 26px; line-height: 26px;
color: white; color: white;
height: 18px; height: 18px;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<li *ngFor="let m of NavList.List;let i = index" <li *ngFor="let m of NavList.List;let i = index"
[ngClass]="{'active':i==NavList.Active,'disconnected':!m.connected, 'hidden': m.closed != false}" [ngClass]="{'active':i==NavList.Active,'disconnected':!m.connected, 'hidden': m.closed != false}"
id="termnav-{{i}}" (click)="setActive(i)" (dblclick)="m.edit=true;setActive(i)"> id="termnav-{{i}}" (click)="setActive(i)" (dblclick)="m.edit=true;setActive(i)">
<span *ngIf="!m.edit">{{m.nick}}</span> <span *ngIf="!m.edit">{{m.nick | truncatechars:25 }}</span>
<input *ngIf="m.edit" [(ngModel)]="m.nick" (blur)="m.edit=false" (keyup.enter)="m.edit=false" autofocus="true"/> <input *ngIf="m.edit" [(ngModel)]="m.nick" (blur)="m.edit=false" (keyup.enter)="m.edit=false" autofocus="true"/>
<a class="close" (click)="close(m,i)">&times;</a> <a class="close" (click)="close(m,i)">&times;</a>
</li> </li>
......
...@@ -11,13 +11,40 @@ function zeroPad(num, minLength) { ...@@ -11,13 +11,40 @@ function zeroPad(num, minLength) {
return str; return str;
} }
function formatTimeWithSeconds(seconds) {
let hour = 0, minute = 0, second = 0;
const ref = [3600, 60, 1];
for (let i = 0; i < ref.length; i++) {
const val = ref[i];
while (val <= seconds) {
seconds -= val;
switch (i) {
case 0:
hour++;
break;
case 1:
minute++;
break;
case 2:
second++;
break;
}
}
}
return [hour, minute, second];
}
function formatTime(millis: number) { function formatTime(millis: number) {
const totalSeconds = Math.floor(millis / 1000); const totalSeconds = millis / 1000;
const seconds = totalSeconds % 60; const [hour, minute, second] = formatTimeWithSeconds(totalSeconds);
const minutes = Math.floor(totalSeconds / 60); let time = zeroPad(minute, 2) + ':' + zeroPad(second, 2);
return zeroPad(minutes, 2) + ':' + zeroPad(seconds, 2); if (hour > 0) {
time = zeroPad(hour, 2) + ':' + time;
}
return time;
} }
@Component({ @Component({
selector: 'app-replay-guacamole', selector: 'app-replay-guacamole',
templateUrl: './guacamole.component.html', templateUrl: './guacamole.component.html',
...@@ -115,6 +142,4 @@ export class ReplayGuacamoleComponent implements OnInit { ...@@ -115,6 +142,4 @@ export class ReplayGuacamoleComponent implements OnInit {
this.isPlaying = false; this.isPlaying = false;
} }
} }
} }
<div> <div>
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-stop" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-step-backward" aria-hidden="true"></i>-->
<!--</button>-->
<button type="button" class="btn"> <button type="button" class="btn">
<i class="fa fa-backward" aria-hidden="true" (click)="speedDown()"></i> <i class="fa fa-backward" aria-hidden="true" (click)="speedDown()"></i>
</button> </button>
<button type="button" class="btn" (click)="toggle()"> <button type="button" class="btn" (click)="toggle()">
<i class="fa" aria-hidden="true" [ngClass]="{'fa-play':!play,'fa-pause': play}"></i> <i class="fa" aria-hidden="true" [ngClass]="{'fa-play':!isPlaying, 'fa-pause': isPlaying}"></i>
</button> </button>
<button type="button" class="btn" (click)="speedUp()"> <button type="button" class="btn" (click)="speedUp()">
<i class="fa fa-forward" aria-hidden="true"></i> <i class="fa fa-forward" aria-hidden="true"></i>
</button> </button>
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-step-forward" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-expand" aria-hidden="true"></i>-->
<!--</button>-->
<!--<button type="button" class="btn">-->
<!--<i class="fa fa-compress" aria-hidden="true"></i>-->
<!--</button>-->
<button type="button" class="btn" (click)="restart()"> <button type="button" class="btn" (click)="restart()">
<i class="fa fa-repeat" aria-hidden="true"></i> <i class="fa fa-repeat" aria-hidden="true"></i>
</button> </button>
<input id="scrubber" type="range" [(ngModel)]="percent" min=0 max=100 (mousedown)="stop()" (mouseup)="runFrom()"/> <input id="scrubber" type="range" [(ngModel)]="time" min=0 [attr.max]="max" (mouseup)="runFrom()"/>
{{time | utcDate | date:"HH:mm:ss"}} <span id="position">{{ position }}</span>
<span>/</span>
<span id="duration">{{ duration }}</span>
{{"Speed"|trans}}: {{speed}} {{"Speed"|trans}}: {{speed}}
</div> </div>
<elements-term [term]="term"></elements-term> <div id="winContainer" style="height: 90%;width: 100%">
<elements-term [term]="term"></elements-term>
</div>
<!--<asciinema-player></asciinema-player>--> <!--<asciinema-player></asciinema-player>-->
...@@ -3,23 +3,75 @@ import {Terminal} from 'xterm'; ...@@ -3,23 +3,75 @@ import {Terminal} from 'xterm';
import {HttpService, LogService} from '../../../app.service'; import {HttpService, LogService} from '../../../app.service';
import {Replay} from '../replay.model'; import {Replay} from '../replay.model';
function zeroPad(num, minLength) {
let str = num.toString();
// Add leading zeroes until string is long enough
while (str.length < minLength) {
str = '0' + str;
}
return str;
}
function formatTimeWithSeconds(seconds) {
let hour = 0, minute = 0, second = 0;
const ref = [3600, 60, 1];
for (let i = 0; i < ref.length; i++) {
const val = ref[i];
while (val <= seconds) {
seconds -= val;
switch (i) {
case 0:
hour++;
break;
case 1:
minute++;
break;
case 2:
second++;
break;
}
}
}
return [hour, minute, second];
}
function formatTime(millis: number) {
const totalSeconds = millis / 1000;
const [hour, minute, second] = formatTimeWithSeconds(totalSeconds);
let time = zeroPad(minute, 2) + ':' + zeroPad(second, 2);
if (hour > 0) {
time = zeroPad(hour, 2) + ':' + time;
}
return time;
}
@Component({ @Component({
selector: 'app-replay-json', selector: 'app-replay-json',
templateUrl: './json.component.html', templateUrl: './json.component.html',
styleUrls: ['./json.component.css'] styleUrls: ['./json.component.css']
}) })
export class JsonComponent implements OnInit { export class JsonComponent implements OnInit {
isPlaying = false;
recording: any;
playerRef: any;
displayRef: any;
max = 0;
time = 0;
duration = '00:00';
timeList = [];
replayData = {};
speed = 2; speed = 2;
percent = 0; tick = 33; // 每33s滴答一次
play = false; timeStep = 33; // 步长
tick = 33;
timeStep = 33;
time = 1;
timer: any; // 多长时间播放下一个 timer: any; // 多长时间播放下一个
pos = 0; // 播放点 pos = 0; // 播放点
scrubber: number;
term: Terminal; term: Terminal;
get position() {
return formatTime(this.time);
}
set position(data) {
}
@Input() replay: Replay; @Input() replay: Replay;
constructor(private _http: HttpService) {} constructor(private _http: HttpService) {}
...@@ -37,12 +89,13 @@ export class JsonComponent implements OnInit { ...@@ -37,12 +89,13 @@ export class JsonComponent implements OnInit {
this._http.get_replay_data(this.replay.src) this._http.get_replay_data(this.replay.src)
.subscribe( .subscribe(
data => { data => {
this.replay.json = data; this.replayData = data;
this.replay.timelist = Object.keys(this.replay.json).map(Number); this.timeList = Object.keys(this.replayData).map(Number);
this.replay.timelist = this.replay.timelist.sort((a, b) => { this.timeList = this.timeList.sort((a, b) => {
return a - b; return a - b;
}); });
this.replay.totalTime = this.replay.timelist[this.replay.timelist.length - 1] * 1000; this.max = this.timeList[this.timeList.length - 1] * 1000;
this.duration = formatTime(this.max);
this.toggle(); this.toggle();
}, },
err => { err => {
...@@ -56,55 +109,48 @@ export class JsonComponent implements OnInit { ...@@ -56,55 +109,48 @@ export class JsonComponent implements OnInit {
restart() { restart() {
clearInterval(this.timer); clearInterval(this.timer);
this.term.reset(); this.term.reset();
this.time = 1;
this.pos = 0; this.pos = 0;
this.play = true; this.isPlaying = true;
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.advance(); this.advance();
}, this.tick); }, this.tick);
} }
toggle() { toggle() {
if (this.play) { if (this.isPlaying) {
clearInterval(this.timer); clearInterval(this.timer);
this.play = !this.play; this.isPlaying = !this.isPlaying;
} else { } else {
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.advance(); this.advance();
}, this.tick); }, this.tick);
this.play = !this.play; this.isPlaying = !this.isPlaying;
} }
} }
advance() { advance() {
// 每个time间隔执行一次 // 每个time间隔执行一次
// this.scrubber = Math.ceil((this.time / this.replay.totalTime) * 100); // for (let i in this.timeList) {
for (; this.pos < this.replay.timelist.length; this.pos++) { // }
if (this.replay.timelist[this.pos] * 1000 <= this.time) { for (; this.pos < this.timeList.length; this.pos++) {
this.term.write(this.replay.json[this.replay.timelist[this.pos].toString()]); if (this.timeList[this.pos] * 1000 <= this.time) {
this.term.write(this.replayData[this.timeList[this.pos].toString()]);
} else { } else {
break; break;
} }
} }
// 超过了总的时间点, 停止播放 // 超过了总的时间点, 停止播放
if (this.pos >= this.replay.timelist.length) { if (this.pos >= this.timeList.length) {
this.play = !this.play; this.isPlaying = !this.isPlaying;
clearInterval(this.timer); clearInterval(this.timer);
} }
// 如果两次时间间隔超过了5s
if (this.replay.timelist[this.pos] - this.replay.timelist[this.pos - 1] > 5) {
this.time += 5000;
}
this.time += this.timeStep * this.speed; this.time += this.timeStep * this.speed;
this.percent = this.time / this.replay.totalTime * 100;
} }
stop() { stop() {
clearInterval(this.timer); clearInterval(this.timer);
this.play = false; this.isPlaying = false;
} }
speedUp() { speedUp() {
...@@ -116,31 +162,15 @@ export class JsonComponent implements OnInit { ...@@ -116,31 +162,15 @@ export class JsonComponent implements OnInit {
} }
runFrom() { runFrom() {
clearInterval(this.timer); for (let i = 0; i < this.timeList.length; i++) {
const time = this.replay.totalTime * this.percent / 100; const v = this.timeList[i];
this.replay.timelist.forEach((v, i) => { const preTime = this.timeList[i - 1];
const preTime = this.replay.timelist[i - 1]; if (this.time <= v * 1000 && this.time >= preTime * 1000) {
if (time <= v * 1000 && time >= preTime * 1000) {
this.time = v * 1000; this.time = v * 1000;
this.pos = i; this.pos = i;
return; break;
}
} }
});
this.timer = setInterval(() => {
this.advance(); this.advance();
}, this.tick); }
this.play = !this.play;
}
// this.pos = 0;
// this.term.reset();
// this.play = false;
// for (; this.pos < this.replay.timelist.length; this.pos++) {
// if (this.replay.timelist[this.pos] * 1000 <= this.percent / 100 * this.replay.totalTime) {
// this.term.term.write(this.replay.json[this.replay.timelist[this.pos].toString()]);
// } else {
// break;
// }
// }
// this.time = this.replay.totalTime * this.percent / 100;
// }
} }
import {TransPipe} from './trans.pipe'; import {TransPipe} from './trans.pipe';
import {UtcDatePipe} from './date.pipe'; import {UtcDatePipe} from './date.pipe';
import {TruncatecharsPipe} from './truncatechars.pipe';
export const Pipes = [ export const Pipes = [
UtcDatePipe, UtcDatePipe,
TransPipe TransPipe,
TruncatecharsPipe
]; ];
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'truncatechars'})
export class TruncatecharsPipe implements PipeTransform {
transform(value: string, length: number): string {
if (!value) {
return value;
}
if (value.length < length) {
return value;
}
return value.slice(0, length) + '..';
}
}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "es5", "target": "es5",
"importHelpers": true,
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment