跳板机管理平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
8.8 KiB

4 years ago
  1. #!/usr/bin/env python3
  2. # -*- coding: UTF-8 -*-
  3. import paramiko
  4. import os
  5. import sys
  6. import curses
  7. import termios
  8. import tty
  9. import socket
  10. import signal
  11. import getpass
  12. import platform
  13. import sqlite3
  14. KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r'))
  15. KEYS_UP = (curses.KEY_UP, ord('k'))
  16. KEYS_DOWN = (curses.KEY_DOWN, ord('j'))
  17. KEYS_SELECT = (curses.KEY_RIGHT, ord(' '))
  18. # 数据库文件
  19. gSqlite3File = "/usr/local/jumpserver/jumpserver.db"
  20. # ssh_private_path ssh私钥路径
  21. system_type = platform.system()
  22. if system_type == "Darwin":
  23. ssh_private_path = "/Users/%s/.ssh/id_rsa"
  24. elif system_type == "Linux":
  25. ssh_private_path = "/home/%s/.ssh/id_rsa"
  26. else:
  27. exit(1)
  28. class Picker(object):
  29. def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False,
  30. min_selection_count=0, options_map_func=None):
  31. if len(options) == 0:
  32. raise ValueError('options should not be an empty list')
  33. self.options = options
  34. self.title = title
  35. self.indicator = indicator
  36. self.multiselect = multiselect or multi_select
  37. self.min_selection_count = min_selection_count
  38. self.options_map_func = options_map_func
  39. self.all_selected = []
  40. if default_index >= len(options):
  41. raise ValueError('default_index should be less than the length of options')
  42. if multiselect and min_selection_count > len(options):
  43. raise ValueError(
  44. 'min_selection_count is bigger than the available options, you will not be able to make any selection')
  45. if options_map_func is not None and not callable(options_map_func):
  46. raise ValueError('options_map_func must be a callable function')
  47. self.index = default_index
  48. self.custom_handlers = {}
  49. def register_custom_handler(self, key, func):
  50. self.custom_handlers[key] = func
  51. def move_up(self):
  52. self.index -= 1
  53. if self.index < 0:
  54. self.index = len(self.options) - 1
  55. def move_down(self):
  56. self.index += 1
  57. if self.index >= len(self.options):
  58. self.index = 0
  59. def mark_index(self):
  60. if self.multiselect:
  61. if self.index in self.all_selected:
  62. self.all_selected.remove(self.index)
  63. else:
  64. self.all_selected.append(self.index)
  65. def get_selected(self):
  66. """return the current selected option as a tuple: (option, index)
  67. or as a list of tuples (in case multiselect==True)
  68. """
  69. if self.multiselect:
  70. return_tuples = []
  71. for selected in self.all_selected:
  72. return_tuples.append((self.options[selected], selected))
  73. return return_tuples
  74. else:
  75. return self.options[self.index], self.index
  76. def get_title_lines(self):
  77. if self.title:
  78. return self.title.split('\n') + ['']
  79. return []
  80. def get_option_lines(self):
  81. lines = []
  82. for index, option in enumerate(self.options):
  83. # pass the option through the options map of one was passed in
  84. if self.options_map_func:
  85. option = self.options_map_func(option)
  86. if index == self.index:
  87. prefix = self.indicator
  88. else:
  89. prefix = len(self.indicator) * ' '
  90. if self.multiselect and index in self.all_selected:
  91. format = curses.color_pair(1)
  92. line = ('{0} {1}'.format(prefix, option), format)
  93. else:
  94. line = '{0} {1}'.format(prefix, option)
  95. lines.append(line)
  96. return lines
  97. def get_lines(self):
  98. title_lines = self.get_title_lines()
  99. option_lines = self.get_option_lines()
  100. lines = title_lines + option_lines
  101. current_line = self.index + len(title_lines) + 1
  102. return lines, current_line
  103. def draw(self):
  104. """draw the curses ui on the screen, handle scroll if needed"""
  105. self.screen.clear()
  106. x, y = 1, 1 # start point
  107. max_y, max_x = self.screen.getmaxyx()
  108. max_rows = max_y - y # the max rows we can draw
  109. lines, current_line = self.get_lines()
  110. # calculate how many lines we should scroll, relative to the top
  111. scroll_top = getattr(self, 'scroll_top', 0)
  112. if current_line <= scroll_top:
  113. scroll_top = 0
  114. elif current_line - scroll_top > max_rows:
  115. scroll_top = current_line - max_rows
  116. self.scroll_top = scroll_top
  117. lines_to_draw = lines[scroll_top:scroll_top + max_rows]
  118. for line in lines_to_draw:
  119. if type(line) is tuple:
  120. self.screen.addnstr(y, x, line[0], max_x - 2, line[1])
  121. else:
  122. self.screen.addnstr(y, x, line, max_x - 2)
  123. y += 1
  124. self.screen.refresh()
  125. def run_loop(self):
  126. while True:
  127. self.draw()
  128. c = self.screen.getch()
  129. if c in KEYS_UP:
  130. self.move_up()
  131. elif c in KEYS_DOWN:
  132. self.move_down()
  133. elif c in KEYS_ENTER:
  134. if self.multiselect and len(self.all_selected) < self.min_selection_count:
  135. continue
  136. return self.get_selected()
  137. elif c in KEYS_SELECT and self.multiselect:
  138. self.mark_index()
  139. elif c in self.custom_handlers:
  140. ret = self.custom_handlers[c](self)
  141. if ret:
  142. return ret
  143. elif c == ord('q'):
  144. exit(0)
  145. def config_curses(self):
  146. try:
  147. # use the default colors of the terminal
  148. curses.use_default_colors()
  149. # hide the cursor
  150. curses.curs_set(0)
  151. # add some color for multi_select
  152. # @todo make colors configurable
  153. curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE)
  154. except:
  155. # Curses failed to initialize color support, eg. when TERM=vt100
  156. curses.initscr()
  157. def _start(self, screen):
  158. self.screen = screen
  159. self.config_curses()
  160. return self.run_loop()
  161. def start(self):
  162. return curses.wrapper(self._start)
  163. def updateWindowHandler(signum):
  164. if signum == signal.SIGWINCH:
  165. width, height = os.get_terminal_size()
  166. os.terminal_size((width, height))
  167. def posix_shell(chan):
  168. import select
  169. oldtty = termios.tcgetattr(sys.stdin)
  170. signal.signal(signal.SIGWINCH, updateWindowHandler)
  171. try:
  172. tty.setraw(sys.stdin.fileno(), termios.TCIOFLUSH)
  173. # tty.setcbreak(sys.stdin.fileno())
  174. chan.settimeout(0.0)
  175. while True:
  176. r, w, e = select.select([chan, sys.stdin], [], [])
  177. if chan in r:
  178. try:
  179. data = str(chan.recv(1024), encoding='utf-8')
  180. if len(data) == 0:
  181. break
  182. sys.stdout.write(data)
  183. sys.stdout.flush()
  184. except socket.timeout:
  185. pass
  186. if sys.stdin in r:
  187. ch = sys.stdin.read(1)
  188. if len(ch) == 0:
  189. break
  190. chan.send(ch)
  191. finally:
  192. termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
  193. def NewTerminal(host, port, user):
  194. # 建立ssh连接
  195. ssh = paramiko.SSHClient()
  196. ssh.load_system_host_keys()
  197. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  198. ssh.connect(
  199. host,
  200. port=port,
  201. username=user,
  202. key_filename=ssh_private_path % user,
  203. compress=False)
  204. # 建立交互式shell连接
  205. width, height = os.get_terminal_size()
  206. channel = ssh.invoke_shell("xterm-256color", width, height)
  207. # 建立交互式管道
  208. posix_shell(channel)
  209. # 关闭连接
  210. channel.close()
  211. ssh.close()
  212. # 初始化表
  213. def connect_db():
  214. # 连接数据库
  215. conn = sqlite3.connect(gSqlite3File)
  216. if conn == None:
  217. print("sqlite3.connect " + gSqlite3File + "failed!")
  218. exit(1)
  219. return conn
  220. def get_hosts(user):
  221. db = connect_db()
  222. hosts = db.execute("select name,ip,port from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s') order by id" % user).fetchall()
  223. resp = []
  224. for host in hosts:
  225. resp.append("%s:%s:%s" % (host[0], host[1], host[2]))
  226. if len(resp) == 0:
  227. print(user + " no valid hosts")
  228. exit(1)
  229. return resp
  230. def main():
  231. user = getpass.getuser()
  232. print("current user: " + user)
  233. title = "ssh hosts select:"
  234. menu = get_hosts(user)
  235. while True:
  236. option, index = Picker(menu, title).start()
  237. arr = option.split(":")
  238. if len(arr) == 3:
  239. host = arr[1]
  240. port = int(arr[2])
  241. NewTerminal(host, port, user)
  242. if __name__ == '__main__':
  243. main()