ntermqt 0.1.0__py3-none-any.whl
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.
- nterm/__init__.py +54 -0
- nterm/__main__.py +619 -0
- nterm/askpass/__init__.py +22 -0
- nterm/askpass/server.py +393 -0
- nterm/config.py +158 -0
- nterm/connection/__init__.py +17 -0
- nterm/connection/profile.py +296 -0
- nterm/manager/__init__.py +29 -0
- nterm/manager/connect_dialog.py +322 -0
- nterm/manager/editor.py +262 -0
- nterm/manager/io.py +678 -0
- nterm/manager/models.py +346 -0
- nterm/manager/settings.py +264 -0
- nterm/manager/tree.py +493 -0
- nterm/resources.py +48 -0
- nterm/session/__init__.py +60 -0
- nterm/session/askpass_ssh.py +399 -0
- nterm/session/base.py +110 -0
- nterm/session/interactive_ssh.py +522 -0
- nterm/session/pty_transport.py +571 -0
- nterm/session/ssh.py +610 -0
- nterm/terminal/__init__.py +11 -0
- nterm/terminal/bridge.py +83 -0
- nterm/terminal/resources/terminal.html +253 -0
- nterm/terminal/resources/terminal.js +414 -0
- nterm/terminal/resources/xterm-addon-fit.min.js +8 -0
- nterm/terminal/resources/xterm-addon-unicode11.min.js +8 -0
- nterm/terminal/resources/xterm-addon-web-links.min.js +8 -0
- nterm/terminal/resources/xterm.css +209 -0
- nterm/terminal/resources/xterm.min.js +8 -0
- nterm/terminal/widget.py +380 -0
- nterm/theme/__init__.py +10 -0
- nterm/theme/engine.py +456 -0
- nterm/theme/stylesheet.py +377 -0
- nterm/theme/themes/clean.yaml +0 -0
- nterm/theme/themes/default.yaml +36 -0
- nterm/theme/themes/dracula.yaml +36 -0
- nterm/theme/themes/gruvbox_dark.yaml +36 -0
- nterm/theme/themes/gruvbox_hybrid.yaml +38 -0
- nterm/theme/themes/gruvbox_light.yaml +36 -0
- nterm/vault/__init__.py +32 -0
- nterm/vault/credential_manager.py +163 -0
- nterm/vault/keychain.py +135 -0
- nterm/vault/manager_ui.py +962 -0
- nterm/vault/profile.py +219 -0
- nterm/vault/resolver.py +250 -0
- nterm/vault/store.py +642 -0
- ntermqt-0.1.0.dist-info/METADATA +327 -0
- ntermqt-0.1.0.dist-info/RECORD +52 -0
- ntermqt-0.1.0.dist-info/WHEEL +5 -0
- ntermqt-0.1.0.dist-info/entry_points.txt +5 -0
- ntermqt-0.1.0.dist-info/top_level.txt +1 -0
nterm/manager/editor.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session editor dialog.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtWidgets import (
|
|
9
|
+
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
|
|
10
|
+
QLineEdit, QSpinBox, QComboBox, QTextEdit,
|
|
11
|
+
QPushButton, QDialogButtonBox, QGroupBox, QLabel,
|
|
12
|
+
QWidget
|
|
13
|
+
)
|
|
14
|
+
from PyQt6.QtCore import Qt
|
|
15
|
+
|
|
16
|
+
from .models import SavedSession
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SessionEditorDialog(QDialog):
|
|
20
|
+
"""
|
|
21
|
+
Dialog for creating or editing a saved session.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
session: SavedSession = None,
|
|
27
|
+
credential_names: List[str] = None,
|
|
28
|
+
parent: QWidget = None
|
|
29
|
+
):
|
|
30
|
+
super().__init__(parent)
|
|
31
|
+
self._session = session or SavedSession()
|
|
32
|
+
self._credential_names = credential_names or []
|
|
33
|
+
|
|
34
|
+
self._setup_ui()
|
|
35
|
+
self._load_session()
|
|
36
|
+
|
|
37
|
+
def _setup_ui(self) -> None:
|
|
38
|
+
"""Build the dialog UI."""
|
|
39
|
+
self.setWindowTitle("Session" if self._session.id else "New Session")
|
|
40
|
+
self.setMinimumWidth(400)
|
|
41
|
+
|
|
42
|
+
layout = QVBoxLayout(self)
|
|
43
|
+
|
|
44
|
+
# Basic info group
|
|
45
|
+
basic_group = QGroupBox("Connection")
|
|
46
|
+
basic_layout = QFormLayout(basic_group)
|
|
47
|
+
|
|
48
|
+
self._name_input = QLineEdit()
|
|
49
|
+
self._name_input.setPlaceholderText("My Server")
|
|
50
|
+
basic_layout.addRow("Name:", self._name_input)
|
|
51
|
+
|
|
52
|
+
self._desc_input = QLineEdit()
|
|
53
|
+
self._desc_input.setPlaceholderText("Optional description")
|
|
54
|
+
basic_layout.addRow("Description:", self._desc_input)
|
|
55
|
+
|
|
56
|
+
# Host row
|
|
57
|
+
host_row = QHBoxLayout()
|
|
58
|
+
self._host_input = QLineEdit()
|
|
59
|
+
self._host_input.setPlaceholderText("hostname or IP")
|
|
60
|
+
host_row.addWidget(self._host_input, 1)
|
|
61
|
+
|
|
62
|
+
host_row.addWidget(QLabel(":"))
|
|
63
|
+
|
|
64
|
+
self._port_input = QSpinBox()
|
|
65
|
+
self._port_input.setRange(1, 65535)
|
|
66
|
+
self._port_input.setValue(22)
|
|
67
|
+
self._port_input.setFixedWidth(80)
|
|
68
|
+
host_row.addWidget(self._port_input)
|
|
69
|
+
|
|
70
|
+
basic_layout.addRow("Host:", host_row)
|
|
71
|
+
|
|
72
|
+
layout.addWidget(basic_group)
|
|
73
|
+
|
|
74
|
+
# Auth group
|
|
75
|
+
auth_group = QGroupBox("Authentication")
|
|
76
|
+
auth_layout = QFormLayout(auth_group)
|
|
77
|
+
|
|
78
|
+
self._cred_combo = QComboBox()
|
|
79
|
+
self._cred_combo.addItem("(SSH Agent)", None)
|
|
80
|
+
self._cred_combo.addItem("(Ask on connect)", "__ask__")
|
|
81
|
+
for name in self._credential_names:
|
|
82
|
+
self._cred_combo.addItem(name, name)
|
|
83
|
+
auth_layout.addRow("Credential:", self._cred_combo)
|
|
84
|
+
|
|
85
|
+
layout.addWidget(auth_group)
|
|
86
|
+
|
|
87
|
+
# Button box
|
|
88
|
+
buttons = QDialogButtonBox(
|
|
89
|
+
QDialogButtonBox.StandardButton.Ok |
|
|
90
|
+
QDialogButtonBox.StandardButton.Cancel
|
|
91
|
+
)
|
|
92
|
+
buttons.accepted.connect(self._on_accept)
|
|
93
|
+
buttons.rejected.connect(self.reject)
|
|
94
|
+
layout.addWidget(buttons)
|
|
95
|
+
|
|
96
|
+
# Focus
|
|
97
|
+
self._name_input.setFocus()
|
|
98
|
+
|
|
99
|
+
def _load_session(self) -> None:
|
|
100
|
+
"""Load session data into form."""
|
|
101
|
+
self._name_input.setText(self._session.name)
|
|
102
|
+
self._desc_input.setText(self._session.description)
|
|
103
|
+
self._host_input.setText(self._session.hostname)
|
|
104
|
+
self._port_input.setValue(self._session.port)
|
|
105
|
+
|
|
106
|
+
# Select credential
|
|
107
|
+
if self._session.credential_name:
|
|
108
|
+
idx = self._cred_combo.findData(self._session.credential_name)
|
|
109
|
+
if idx >= 0:
|
|
110
|
+
self._cred_combo.setCurrentIndex(idx)
|
|
111
|
+
|
|
112
|
+
def _on_accept(self) -> None:
|
|
113
|
+
"""Validate and accept."""
|
|
114
|
+
name = self._name_input.text().strip()
|
|
115
|
+
hostname = self._host_input.text().strip()
|
|
116
|
+
|
|
117
|
+
if not name:
|
|
118
|
+
self._name_input.setFocus()
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
if not hostname:
|
|
122
|
+
self._host_input.setFocus()
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
self.accept()
|
|
126
|
+
|
|
127
|
+
def get_session(self) -> SavedSession:
|
|
128
|
+
"""Get the edited session data."""
|
|
129
|
+
cred_data = self._cred_combo.currentData()
|
|
130
|
+
cred_name = cred_data if cred_data and cred_data != "__ask__" else None
|
|
131
|
+
|
|
132
|
+
return SavedSession(
|
|
133
|
+
id=self._session.id,
|
|
134
|
+
name=self._name_input.text().strip(),
|
|
135
|
+
description=self._desc_input.text().strip(),
|
|
136
|
+
hostname=self._host_input.text().strip(),
|
|
137
|
+
port=self._port_input.value(),
|
|
138
|
+
credential_name=cred_name,
|
|
139
|
+
folder_id=self._session.folder_id,
|
|
140
|
+
position=self._session.position,
|
|
141
|
+
extras=self._session.extras,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def set_credential_names(self, names: List[str]) -> None:
|
|
145
|
+
"""Update available credential names."""
|
|
146
|
+
current = self._cred_combo.currentData()
|
|
147
|
+
|
|
148
|
+
self._cred_combo.clear()
|
|
149
|
+
self._cred_combo.addItem("(SSH Agent)", None)
|
|
150
|
+
self._cred_combo.addItem("(Ask on connect)", "__ask__")
|
|
151
|
+
|
|
152
|
+
for name in names:
|
|
153
|
+
self._cred_combo.addItem(name, name)
|
|
154
|
+
|
|
155
|
+
# Restore selection
|
|
156
|
+
if current:
|
|
157
|
+
idx = self._cred_combo.findData(current)
|
|
158
|
+
if idx >= 0:
|
|
159
|
+
self._cred_combo.setCurrentIndex(idx)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class QuickConnectDialog(QDialog):
|
|
163
|
+
"""
|
|
164
|
+
Quick connect dialog - doesn't save the session.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
credential_names: List[str] = None,
|
|
170
|
+
parent: QWidget = None
|
|
171
|
+
):
|
|
172
|
+
super().__init__(parent)
|
|
173
|
+
self._credential_names = credential_names or []
|
|
174
|
+
self._setup_ui()
|
|
175
|
+
|
|
176
|
+
def _setup_ui(self) -> None:
|
|
177
|
+
"""Build the dialog UI."""
|
|
178
|
+
self.setWindowTitle("Quick Connect")
|
|
179
|
+
self.setMinimumWidth(350)
|
|
180
|
+
|
|
181
|
+
layout = QVBoxLayout(self)
|
|
182
|
+
|
|
183
|
+
form = QFormLayout()
|
|
184
|
+
|
|
185
|
+
# Host row
|
|
186
|
+
host_row = QHBoxLayout()
|
|
187
|
+
self._host_input = QLineEdit()
|
|
188
|
+
self._host_input.setPlaceholderText("hostname or IP")
|
|
189
|
+
host_row.addWidget(self._host_input, 1)
|
|
190
|
+
|
|
191
|
+
host_row.addWidget(QLabel(":"))
|
|
192
|
+
|
|
193
|
+
self._port_input = QSpinBox()
|
|
194
|
+
self._port_input.setRange(1, 65535)
|
|
195
|
+
self._port_input.setValue(22)
|
|
196
|
+
self._port_input.setFixedWidth(80)
|
|
197
|
+
host_row.addWidget(self._port_input)
|
|
198
|
+
|
|
199
|
+
form.addRow("Host:", host_row)
|
|
200
|
+
|
|
201
|
+
# Credential
|
|
202
|
+
self._cred_combo = QComboBox()
|
|
203
|
+
self._cred_combo.addItem("(SSH Agent)", None)
|
|
204
|
+
self._cred_combo.addItem("(Ask on connect)", "__ask__")
|
|
205
|
+
for name in self._credential_names:
|
|
206
|
+
self._cred_combo.addItem(name, name)
|
|
207
|
+
form.addRow("Credential:", self._cred_combo)
|
|
208
|
+
|
|
209
|
+
layout.addLayout(form)
|
|
210
|
+
|
|
211
|
+
# Buttons
|
|
212
|
+
btn_layout = QHBoxLayout()
|
|
213
|
+
btn_layout.addStretch()
|
|
214
|
+
|
|
215
|
+
self._connect_tab_btn = QPushButton("Connect")
|
|
216
|
+
self._connect_tab_btn.setDefault(True)
|
|
217
|
+
self._connect_tab_btn.clicked.connect(lambda: self._accept_with_mode("tab"))
|
|
218
|
+
btn_layout.addWidget(self._connect_tab_btn)
|
|
219
|
+
|
|
220
|
+
self._connect_win_btn = QPushButton("New Window")
|
|
221
|
+
self._connect_win_btn.clicked.connect(lambda: self._accept_with_mode("window"))
|
|
222
|
+
btn_layout.addWidget(self._connect_win_btn)
|
|
223
|
+
|
|
224
|
+
cancel_btn = QPushButton("Cancel")
|
|
225
|
+
cancel_btn.clicked.connect(self.reject)
|
|
226
|
+
btn_layout.addWidget(cancel_btn)
|
|
227
|
+
|
|
228
|
+
layout.addLayout(btn_layout)
|
|
229
|
+
|
|
230
|
+
# Focus
|
|
231
|
+
self._host_input.setFocus()
|
|
232
|
+
|
|
233
|
+
# Connect mode result
|
|
234
|
+
self._connect_mode = "tab"
|
|
235
|
+
|
|
236
|
+
def _accept_with_mode(self, mode: str) -> None:
|
|
237
|
+
"""Accept with specified connect mode."""
|
|
238
|
+
hostname = self._host_input.text().strip()
|
|
239
|
+
if not hostname:
|
|
240
|
+
self._host_input.setFocus()
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
self._connect_mode = mode
|
|
244
|
+
self.accept()
|
|
245
|
+
|
|
246
|
+
def get_session(self) -> SavedSession:
|
|
247
|
+
"""Get session data (not saved, just for connecting)."""
|
|
248
|
+
cred_data = self._cred_combo.currentData()
|
|
249
|
+
cred_name = cred_data if cred_data and cred_data != "__ask__" else None
|
|
250
|
+
|
|
251
|
+
hostname = self._host_input.text().strip()
|
|
252
|
+
|
|
253
|
+
return SavedSession(
|
|
254
|
+
name=hostname, # Use hostname as name
|
|
255
|
+
hostname=hostname,
|
|
256
|
+
port=self._port_input.value(),
|
|
257
|
+
credential_name=cred_name,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def get_connect_mode(self) -> str:
|
|
261
|
+
"""Get selected connect mode ('tab' or 'window')."""
|
|
262
|
+
return self._connect_mode
|