ssh-hull 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +44 -0
  3. data/.gitignore +27 -0
  4. data/.rubocop.yml +62 -0
  5. data/.tool-versions +1 -0
  6. data/Gemfile +5 -0
  7. data/Gemfile.lock +142 -0
  8. data/LICENSE +19 -0
  9. data/README.md +12 -0
  10. data/Rakefile +7 -0
  11. data/bin/bundle +114 -0
  12. data/bin/rake +29 -0
  13. data/bin/rspec +29 -0
  14. data/bin/rubocop +29 -0
  15. data/bomb +12 -0
  16. data/config/locales/en.yml +71 -0
  17. data/config/locales/fr.yml +71 -0
  18. data/exe/ssh-hull +12 -0
  19. data/exe/ssh-tunnel +1 -0
  20. data/lib/ssh-hull/cli.rb +113 -0
  21. data/lib/ssh-hull/logger.rb +6 -0
  22. data/lib/ssh-hull/ui/application.rb +47 -0
  23. data/lib/ssh-hull/ui/forms/application_form.rb +69 -0
  24. data/lib/ssh-hull/ui/forms/host_form.rb +27 -0
  25. data/lib/ssh-hull/ui/forms/tunnel_form.rb +31 -0
  26. data/lib/ssh-hull/ui/helpers/application_window_helper.rb +233 -0
  27. data/lib/ssh-hull/ui/helpers/common/form_helper.rb +169 -0
  28. data/lib/ssh-hull/ui/helpers/common/minimize_helper.rb +46 -0
  29. data/lib/ssh-hull/ui/helpers/common/modal_helper.rb +43 -0
  30. data/lib/ssh-hull/ui/helpers/common/toolbar_helper.rb +106 -0
  31. data/lib/ssh-hull/ui/helpers/common/translation_helper.rb +18 -0
  32. data/lib/ssh-hull/ui/helpers/common/tree_view_helper.rb +40 -0
  33. data/lib/ssh-hull/ui/helpers/host_window_helper.rb +230 -0
  34. data/lib/ssh-hull/ui/helpers/tunnel_window_helper.rb +96 -0
  35. data/lib/ssh-hull/ui/models/config.rb +82 -0
  36. data/lib/ssh-hull/ui/models/host.rb +90 -0
  37. data/lib/ssh-hull/ui/models/tunnel.rb +118 -0
  38. data/lib/ssh-hull/ui/status_icon.rb +45 -0
  39. data/lib/ssh-hull/ui/windows/about_window.rb +32 -0
  40. data/lib/ssh-hull/ui/windows/application_window.rb +42 -0
  41. data/lib/ssh-hull/ui/windows/hosts/delete_window.rb +56 -0
  42. data/lib/ssh-hull/ui/windows/hosts/edit_window.rb +39 -0
  43. data/lib/ssh-hull/ui/windows/hosts/new_window.rb +45 -0
  44. data/lib/ssh-hull/ui/windows/tunnels/delete_window.rb +57 -0
  45. data/lib/ssh-hull/ui/windows/tunnels/edit_window.rb +39 -0
  46. data/lib/ssh-hull/ui/windows/tunnels/new_window.rb +45 -0
  47. data/lib/ssh-hull/version.rb +17 -0
  48. data/lib/ssh-tunnel.rb +94 -0
  49. data/resources/gresources.xml +13 -0
  50. data/resources/ui/about_window.glade +48 -0
  51. data/resources/ui/application_window.glade +196 -0
  52. data/resources/ui/hosts/delete_window.glade +74 -0
  53. data/resources/ui/hosts/edit_window.glade +331 -0
  54. data/resources/ui/hosts/new_window.glade +328 -0
  55. data/resources/ui/tunnels/delete_window.glade +73 -0
  56. data/resources/ui/tunnels/edit_window.glade +305 -0
  57. data/resources/ui/tunnels/new_window.glade +305 -0
  58. data/snap/snapcraft.yaml +48 -0
  59. data/spec/factories/host.rb +22 -0
  60. data/spec/factories/tunnel.rb +16 -0
  61. data/spec/spec_helper.rb +31 -0
  62. data/spec/ssh_tunnel/ui/forms/host_form_spec.rb +103 -0
  63. data/spec/ssh_tunnel/ui/forms/tunnel_form_spec.rb +132 -0
  64. data/spec/ssh_tunnel/ui/models/host_spec.rb +116 -0
  65. data/spec/ssh_tunnel/ui/models/tunnel_spec.rb +43 -0
  66. data/spec/ssh_tunnel_spec.rb +45 -0
  67. data/ssh-hull.gemspec +38 -0
  68. metadata +320 -0
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Helpers
6
+ module TunnelWindowHelper
7
+
8
+ include SSHTunnel::UI::Helpers::Common::ModalHelper
9
+ include SSHTunnel::UI::Helpers::Common::TranslationHelper
10
+ include SSHTunnel::UI::Helpers::Common::FormHelper::InstanceMethods
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ base.extend(SSHTunnel::UI::Helpers::Common::FormHelper::ClassMethods)
15
+ end
16
+
17
+ FORM_BUTTONS = %i[submit cancel].freeze
18
+ FORM_FIELDS = {
19
+ name: {
20
+ type: :text,
21
+ },
22
+ type: {
23
+ type: :select,
24
+ },
25
+ local_host: {
26
+ type: :text,
27
+ },
28
+ local_port: {
29
+ type: :text,
30
+ },
31
+ remote_host: {
32
+ type: :text,
33
+ },
34
+ remote_port: {
35
+ type: :text,
36
+ },
37
+ auto_start: {
38
+ type: :checkbox,
39
+ },
40
+ }.freeze
41
+
42
+
43
+ module ClassMethods
44
+
45
+ def init
46
+ bind_buttons(FORM_BUTTONS)
47
+ bind_form_fields(FORM_FIELDS.keys)
48
+ end
49
+
50
+ end
51
+
52
+
53
+ def initialize(application, window, tunnel)
54
+ super
55
+
56
+ # Set instance variables
57
+ @tunnel = tunnel
58
+ @host = tunnel.parent
59
+
60
+ # Bind listeners
61
+ set_input_labels(scope: :tunnel)
62
+
63
+ # Load tunnels combobox
64
+ load_tunnels_combobox
65
+ end
66
+
67
+
68
+ private
69
+
70
+
71
+ def load_tunnels_combobox
72
+ input_type.append('local', t('form.tunnel.local'))
73
+ input_type.append('remote', t('form.tunnel.remote'))
74
+ end
75
+
76
+
77
+ def form_fields
78
+ FORM_FIELDS
79
+ end
80
+
81
+
82
+ def form_object
83
+ SSHTunnel::UI::Forms::TunnelForm.new(@tunnel)
84
+ end
85
+
86
+
87
+ def save_and_reload_view
88
+ @application.config.save!
89
+ close
90
+ @window.reload_tunnels_treeview(@host)
91
+ end
92
+
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Models
6
+ class Config
7
+
8
+ attr_reader :file, :data, :hosts
9
+
10
+
11
+ def initialize(file)
12
+ @file = file
13
+ load!
14
+ end
15
+
16
+
17
+ def load!
18
+ @data = load_json_file(file)
19
+ @hosts = load_hosts
20
+ end
21
+
22
+
23
+ def add_host(host)
24
+ @hosts << host
25
+ end
26
+
27
+
28
+ def remove_host(host)
29
+ @hosts.delete(host)
30
+ end
31
+
32
+
33
+ def save!
34
+ write_yaml_file(file, to_hash)
35
+ end
36
+
37
+
38
+ def to_hash
39
+ marshal_dump(hosts: hosts.sort_by(&:name).map(&:to_hash))
40
+ end
41
+
42
+
43
+ private
44
+
45
+
46
+ def load_json_file(file)
47
+ return hash_with_indifferent_access unless File.exist?(file)
48
+
49
+ content = File.read(file)
50
+ return hash_with_indifferent_access if content.empty?
51
+
52
+ hash_with_indifferent_access JSON.parse(content)
53
+ end
54
+
55
+
56
+ def load_hosts
57
+ (data[:hosts] || []).map do |host_attr|
58
+ SSHTunnel::UI::Models::Host.new(host_attr)
59
+ end.sort_by(&:name)
60
+ end
61
+
62
+
63
+ def write_yaml_file(file, data)
64
+ File.open(file, 'w+') do |f|
65
+ f.write JSON.pretty_generate(data)
66
+ end
67
+ end
68
+
69
+
70
+ def hash_with_indifferent_access(hash = {})
71
+ hash.with_indifferent_access
72
+ end
73
+
74
+
75
+ def marshal_dump(data = {})
76
+ JSON.parse(data.to_json)
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Models
6
+ class Host
7
+
8
+ attr_accessor :uuid, :name, :user, :host, :port, :identity_file, :tunnels
9
+
10
+
11
+ def initialize(opts = {})
12
+ @uuid = opts.fetch(:uuid) { SecureRandom.uuid }
13
+ @name = opts.fetch(:name, '')
14
+ @user = opts[:user]
15
+ @host = opts[:host]
16
+ @port = opts[:port]
17
+ @identity_file = opts[:identity_file]
18
+ @tunnels = opts.fetch(:tunnels, []).map { |t_attr| Tunnel.new(t_attr.merge(parent: self)) }
19
+ @started = false
20
+ end
21
+
22
+
23
+ def to_s
24
+ name
25
+ end
26
+
27
+
28
+ def port
29
+ @port.to_s
30
+ end
31
+
32
+
33
+ def to_hash
34
+ {
35
+ uuid: uuid,
36
+ name: name,
37
+ user: user,
38
+ host: host,
39
+ port: @port,
40
+ identity_file: identity_file,
41
+ tunnels: tunnels.sort_by(&:name).map(&:to_hash),
42
+ }
43
+ end
44
+
45
+
46
+ def add_tunnel(tunnel)
47
+ @tunnels << tunnel
48
+ end
49
+
50
+
51
+ def remove_tunnel(tunnel)
52
+ @tunnels.delete(tunnel)
53
+ end
54
+
55
+
56
+ def auto_start!
57
+ started = tunnels.map(&:auto_start!)
58
+ @started = started.any?
59
+ end
60
+
61
+
62
+ def started?
63
+ @started
64
+ end
65
+
66
+
67
+ def start_tunnels!
68
+ tunnels.each(&:start!)
69
+ @started = true
70
+ end
71
+
72
+
73
+ def stop_tunnels!
74
+ tunnels.each(&:stop!)
75
+ @started = false
76
+ end
77
+
78
+
79
+ def toggle_tunnels!
80
+ if started?
81
+ stop_tunnels!
82
+ else
83
+ start_tunnels!
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Models
6
+ class Tunnel
7
+
8
+ attr_accessor :uuid, :name, :parent, :type, :local_host, :local_port, :remote_host, :remote_port, :auto_start
9
+
10
+
11
+ def initialize(opts = {})
12
+ @uuid = opts.fetch(:uuid) { SecureRandom.uuid }
13
+ @name = opts.fetch(:name, '')
14
+ @parent = opts[:parent]
15
+ @type = opts[:type]
16
+ @local_host = opts[:local_host]
17
+ @local_port = opts[:local_port]
18
+ @remote_host = opts[:remote_host]
19
+ @remote_port = opts[:remote_port]
20
+ @auto_start = opts[:auto_start]
21
+ @process = nil
22
+ @started = false
23
+ end
24
+
25
+
26
+ def to_s
27
+ "#{parent} - #{name}"
28
+ end
29
+
30
+
31
+ def local_port
32
+ @local_port.to_s
33
+ end
34
+
35
+
36
+ def remote_port
37
+ @remote_port.to_s
38
+ end
39
+
40
+
41
+ def to_hash
42
+ {
43
+ uuid: uuid,
44
+ name: name,
45
+ type: type,
46
+ local_host: local_host,
47
+ local_port: @local_port,
48
+ remote_host: remote_host,
49
+ remote_port: @remote_port,
50
+ auto_start: auto_start,
51
+ }
52
+ end
53
+
54
+
55
+ def auto_start?
56
+ @auto_start == true
57
+ end
58
+
59
+
60
+ def auto_start!
61
+ return false unless auto_start?
62
+
63
+ start!
64
+ true
65
+ end
66
+
67
+
68
+ def started?
69
+ @started
70
+ end
71
+
72
+
73
+ def start!
74
+ return if started?
75
+
76
+ SSHTunnel.logger.info "Starting tunnel : #{self}"
77
+
78
+ @process = Subprocess.popen(command, stdout: '/dev/null', stderr: '/dev/null', stdin: Subprocess::PIPE)
79
+ @started = true
80
+ end
81
+
82
+
83
+ # See: https://www.redhat.com/archives/libvir-list/2007-September/msg00106.html
84
+ # to avoid zombie when terminating process
85
+ def stop!
86
+ return unless started?
87
+ return if @process.nil?
88
+
89
+ SSHTunnel.logger.info "Stopping tunnel : #{self}"
90
+
91
+ @process.terminate
92
+ @process.wait
93
+ @started = false
94
+ @process = nil
95
+ end
96
+
97
+
98
+ def command
99
+ cmd = [
100
+ '/usr/bin/ssh',
101
+ '-N',
102
+ '-t',
103
+ '-x',
104
+ '-o', 'ExitOnForwardFailure=yes',
105
+ "-l#{parent.user}",
106
+ "-L#{local_host}:#{local_port}:#{remote_host}:#{remote_port}",
107
+ "-p#{parent.port}"
108
+ ]
109
+
110
+ cmd << "-i#{parent.identity_file}" if parent.identity_file.present?
111
+ cmd << parent.host
112
+ cmd
113
+ end
114
+
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ class StatusIcon
6
+
7
+ def initialize(application, window)
8
+ @application = application
9
+ @window = window
10
+ @status_icon = Gtk::StatusIcon.new
11
+ @menu = Gtk::Menu.new
12
+
13
+ configure_status_icon
14
+ create_popup_menu
15
+ end
16
+
17
+
18
+ private
19
+
20
+
21
+ def configure_status_icon
22
+ @status_icon.icon_name = 'network-workgroup-symbolic'
23
+ @status_icon.signal_connect('activate') { |_icon| @window.toggle! }
24
+ @status_icon.signal_connect('popup-menu') { |_tray, button, time| @menu.popup(nil, nil, button, time) }
25
+ end
26
+
27
+
28
+ def create_popup_menu
29
+ # Build menu items
30
+ info = Gtk::ImageMenuItem.new(label: 'Modifier')
31
+ info.signal_connect('activate') { @window.show }
32
+
33
+ quit = Gtk::ImageMenuItem.new(label: 'Quitter')
34
+ quit.signal_connect('activate') { @application.quit }
35
+
36
+ # Create menu
37
+ @menu.append(info)
38
+ @menu.append(Gtk::SeparatorMenuItem.new)
39
+ @menu.append(quit)
40
+ @menu.show_all
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ class AboutWindow < Gtk::AboutDialog
7
+
8
+ # Register the class in the GLib world
9
+ type_register
10
+
11
+ class << self
12
+
13
+ def init
14
+ # Set the template from the resources binary
15
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/about_window.glade'
16
+ end
17
+
18
+ end
19
+
20
+ include SSHTunnel::UI::Helpers::Common::TranslationHelper
21
+
22
+
23
+ def initialize(application)
24
+ super application: application
25
+
26
+ set_title t('window.about')
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ class ApplicationWindow < Gtk::ApplicationWindow
7
+
8
+ # Register the class in the GLib world
9
+ type_register
10
+
11
+ class << self
12
+
13
+ def init
14
+ # Set the template from the resources binary
15
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/application_window.glade'
16
+ super
17
+
18
+ bind_template_child 'hosts_scrolled_window'
19
+ end
20
+
21
+ end
22
+
23
+ include SSHTunnel::UI::Helpers::ApplicationWindowHelper
24
+
25
+
26
+ def initialize(application)
27
+ super application: application
28
+
29
+ # Set instance variables
30
+ @application = application
31
+
32
+ # Set window title
33
+ set_title 'SSH Tunnel Manager'
34
+
35
+ # Load hosts treeview
36
+ load_hosts_treeview
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ module Hosts
7
+ class DeleteWindow < Gtk::Window
8
+
9
+ # Register the class in the GLib world
10
+ type_register
11
+
12
+ class << self
13
+
14
+ def init
15
+ # Set the template from the resources binary
16
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/hosts/delete_window.glade'
17
+
18
+ bind_template_child 'button_submit'
19
+ bind_template_child 'button_cancel'
20
+ end
21
+
22
+ end
23
+
24
+ include SSHTunnel::UI::Helpers::Common::ModalHelper
25
+ include SSHTunnel::UI::Helpers::Common::TranslationHelper
26
+
27
+
28
+ def initialize(application, window, host)
29
+ super
30
+
31
+ # Set window title
32
+ set_title t('window.host.remove', host: host)
33
+
34
+ # Set instance variables
35
+ @host = host
36
+ end
37
+
38
+
39
+ private
40
+
41
+
42
+ def bind_submit_button
43
+ button_submit.label = t('button.submit')
44
+ button_submit.signal_connect :clicked do
45
+ @application.config.remove_host(@host)
46
+ @application.config.save!
47
+ @window.reload_hosts_treeview
48
+ close
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ module Hosts
7
+ class EditWindow < Gtk::Window
8
+
9
+ # Register the class in the GLib world
10
+ type_register
11
+
12
+ class << self
13
+
14
+ def init
15
+ # Set the template from the resources binary
16
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/hosts/edit_window.glade'
17
+ super
18
+ end
19
+
20
+ end
21
+
22
+ include SSHTunnel::UI::Helpers::HostWindowHelper
23
+
24
+
25
+ def initialize(application, window, host)
26
+ super
27
+
28
+ # Set window title
29
+ set_title t('window.host.edit', host: @host)
30
+
31
+ # Fills input fields
32
+ restore_form_values(@host)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ module Hosts
7
+ class NewWindow < Gtk::Window
8
+
9
+ # Register the class in the GLib world
10
+ type_register
11
+
12
+ class << self
13
+
14
+ def init
15
+ # Set the template from the resources binary
16
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/hosts/new_window.glade'
17
+ super
18
+ end
19
+
20
+ end
21
+
22
+ include SSHTunnel::UI::Helpers::HostWindowHelper
23
+
24
+
25
+ def initialize(application, window, host)
26
+ super
27
+
28
+ # Set window title
29
+ set_title t('window.host.new')
30
+ end
31
+
32
+
33
+ private
34
+
35
+
36
+ def save_and_reload_view
37
+ @application.config.add_host(@host)
38
+ super
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHTunnel
4
+ module UI
5
+ module Windows
6
+ module Tunnels
7
+ class DeleteWindow < Gtk::Window
8
+
9
+ # Register the class in the GLib world
10
+ type_register
11
+
12
+ class << self
13
+
14
+ def init
15
+ # Set the template from the resources binary
16
+ set_template resource: '/com/ungtb10d/ssh-hull/ui/tunnels/delete_window.glade'
17
+
18
+ bind_template_child 'button_submit'
19
+ bind_template_child 'button_cancel'
20
+ end
21
+
22
+ end
23
+
24
+ include SSHTunnel::UI::Helpers::Common::ModalHelper
25
+ include SSHTunnel::UI::Helpers::Common::TranslationHelper
26
+
27
+
28
+ def initialize(application, window, tunnel)
29
+ super
30
+
31
+ # Set window title
32
+ set_title t('window.tunnel.remove', tunnel: tunnel.name)
33
+
34
+ # Set instance variables
35
+ @tunnel = tunnel
36
+ @host = tunnel.parent
37
+ end
38
+
39
+
40
+ private
41
+
42
+
43
+ def bind_submit_button
44
+ button_submit.label = t('button.submit')
45
+ button_submit.signal_connect :clicked do
46
+ @host.remove_tunnel(@tunnel)
47
+ @application.config.save!
48
+ @window.reload_tunnels_treeview(@host)
49
+ close
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end