ntermqt 0.1.0__tar.gz → 0.1.1__tar.gz
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.
- {ntermqt-0.1.0/ntermqt.egg-info → ntermqt-0.1.1}/PKG-INFO +2 -2
- {ntermqt-0.1.0 → ntermqt-0.1.1}/README.md +1 -1
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/__main__.py +213 -10
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/engine.py +238 -0
- ntermqt-0.1.1/nterm/theme/themes/enterprise_dark.yaml +42 -0
- ntermqt-0.1.1/nterm/theme/themes/enterprise_hybrid.yaml +44 -0
- ntermqt-0.1.1/nterm/theme/themes/enterprise_light.yaml +42 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1/ntermqt.egg-info}/PKG-INFO +2 -2
- {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/SOURCES.txt +3 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/pyproject.toml +1 -1
- {ntermqt-0.1.0 → ntermqt-0.1.1}/MANIFEST.in +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/askpass/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/askpass/server.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/config.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/connection/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/connection/profile.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/connect_dialog.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/editor.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/io.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/models.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/settings.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/manager/tree.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/resources.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/askpass_ssh.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/base.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/interactive_ssh.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/pty_transport.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/session/ssh.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/bridge.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/terminal.html +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/terminal.js +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-fit.min.js +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-unicode11.min.js +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm-addon-web-links.min.js +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm.css +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/resources/xterm.min.js +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/terminal/widget.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/stylesheet.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/clean.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/default.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/dracula.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_dark.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_hybrid.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/theme/themes/gruvbox_light.yaml +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/__init__.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/credential_manager.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/keychain.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/manager_ui.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/profile.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/resolver.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/nterm/vault/store.py +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/dependency_links.txt +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/entry_points.txt +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/requires.txt +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/ntermqt.egg-info/top_level.txt +0 -0
- {ntermqt-0.1.0 → ntermqt-0.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ntermqt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
|
|
5
5
|
Author: Scott Peterman
|
|
6
6
|
License: GPL-3.0
|
|
@@ -42,7 +42,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
|
|
|
42
42
|
|
|
43
43
|
Built for managing hundreds of devices through bastion hosts with hardware security keys.
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -6,7 +6,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
|
|
|
6
6
|
|
|
7
7
|
Built for managing hundreds of devices through bastion hosts with hardware security keys.
|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -11,7 +11,7 @@ from PyQt6.QtWidgets import (
|
|
|
11
11
|
QApplication, QMainWindow, QSplitter, QTabWidget,
|
|
12
12
|
QWidget, QVBoxLayout, QHBoxLayout, QMessageBox,
|
|
13
13
|
QDialog, QLabel, QLineEdit, QPushButton, QCheckBox,
|
|
14
|
-
QMenuBar, QMenu
|
|
14
|
+
QMenuBar, QMenu, QTabBar
|
|
15
15
|
)
|
|
16
16
|
from PyQt6.QtCore import Qt
|
|
17
17
|
from PyQt6.QtGui import QAction, QKeySequence
|
|
@@ -207,6 +207,23 @@ class TerminalTab(QWidget):
|
|
|
207
207
|
"""Disconnect the session."""
|
|
208
208
|
self.ssh_session.disconnect()
|
|
209
209
|
|
|
210
|
+
def is_connected(self) -> bool:
|
|
211
|
+
"""Check if the session is currently connected."""
|
|
212
|
+
# Try multiple ways to detect connection state
|
|
213
|
+
if hasattr(self.ssh_session, 'is_connected'):
|
|
214
|
+
return self.ssh_session.is_connected
|
|
215
|
+
if hasattr(self.ssh_session, 'connected'):
|
|
216
|
+
return self.ssh_session.connected
|
|
217
|
+
if hasattr(self.ssh_session, '_connected'):
|
|
218
|
+
return self.ssh_session._connected
|
|
219
|
+
if hasattr(self.ssh_session, '_channel'):
|
|
220
|
+
return self.ssh_session._channel is not None
|
|
221
|
+
if hasattr(self.ssh_session, '_transport'):
|
|
222
|
+
transport = self.ssh_session._transport
|
|
223
|
+
return transport is not None and transport.is_active()
|
|
224
|
+
# Default to True if we can't determine - safer to warn
|
|
225
|
+
return True
|
|
226
|
+
|
|
210
227
|
|
|
211
228
|
class MainWindow(QMainWindow):
|
|
212
229
|
"""
|
|
@@ -249,12 +266,6 @@ class MainWindow(QMainWindow):
|
|
|
249
266
|
|
|
250
267
|
# Apply initial stylesheet
|
|
251
268
|
self._apply_qt_theme(self.current_theme)
|
|
252
|
-
self._setup_ui()
|
|
253
|
-
self._connect_signals()
|
|
254
|
-
self._refresh_credentials()
|
|
255
|
-
|
|
256
|
-
# Apply initial stylesheet
|
|
257
|
-
self._apply_qt_theme(self.current_theme)
|
|
258
269
|
|
|
259
270
|
def _on_settings_changed(self, settings):
|
|
260
271
|
"""Handle settings changes from dialog."""
|
|
@@ -298,6 +309,10 @@ class MainWindow(QMainWindow):
|
|
|
298
309
|
self.tab_widget.setDocumentMode(True)
|
|
299
310
|
splitter.addWidget(self.tab_widget)
|
|
300
311
|
|
|
312
|
+
# Enable tab context menu
|
|
313
|
+
self.tab_widget.tabBar().setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
314
|
+
self.tab_widget.tabBar().customContextMenuRequested.connect(self._show_tab_context_menu)
|
|
315
|
+
|
|
301
316
|
# Set initial sizes (tree: 250px, tabs: rest)
|
|
302
317
|
splitter.setSizes([250, 950])
|
|
303
318
|
|
|
@@ -323,6 +338,20 @@ class MainWindow(QMainWindow):
|
|
|
323
338
|
|
|
324
339
|
file_menu.addSeparator()
|
|
325
340
|
|
|
341
|
+
# --- NEW: Tab management actions ---
|
|
342
|
+
close_tab_action = QAction("Close Tab", self)
|
|
343
|
+
close_tab_action.setShortcut(QKeySequence("Ctrl+W"))
|
|
344
|
+
close_tab_action.triggered.connect(self._close_current_tab)
|
|
345
|
+
file_menu.addAction(close_tab_action)
|
|
346
|
+
|
|
347
|
+
close_all_action = QAction("Close All Tabs", self)
|
|
348
|
+
close_all_action.setShortcut(QKeySequence("Ctrl+Shift+W"))
|
|
349
|
+
close_all_action.triggered.connect(self._close_all_tabs)
|
|
350
|
+
file_menu.addAction(close_all_action)
|
|
351
|
+
|
|
352
|
+
file_menu.addSeparator()
|
|
353
|
+
# --- END NEW ---
|
|
354
|
+
|
|
326
355
|
import_action = QAction("&Import Sessions...", self)
|
|
327
356
|
import_action.setShortcut(QKeySequence("Ctrl+I"))
|
|
328
357
|
import_action.triggered.connect(self._on_import_sessions)
|
|
@@ -531,15 +560,159 @@ class MainWindow(QMainWindow):
|
|
|
531
560
|
self._child_windows.append(window)
|
|
532
561
|
window.destroyed.connect(lambda: self._child_windows.remove(window))
|
|
533
562
|
|
|
563
|
+
# -------------------------------------------------------------------------
|
|
564
|
+
# Tab Management (NEW)
|
|
565
|
+
# -------------------------------------------------------------------------
|
|
566
|
+
|
|
567
|
+
def _get_active_session_count(self) -> int:
|
|
568
|
+
"""Count tabs with active connections."""
|
|
569
|
+
count = 0
|
|
570
|
+
for i in range(self.tab_widget.count()):
|
|
571
|
+
tab = self.tab_widget.widget(i)
|
|
572
|
+
if isinstance(tab, TerminalTab) and tab.is_connected():
|
|
573
|
+
count += 1
|
|
574
|
+
return count
|
|
575
|
+
|
|
576
|
+
def _show_tab_context_menu(self, pos):
|
|
577
|
+
"""Show context menu for tab bar."""
|
|
578
|
+
tab_bar = self.tab_widget.tabBar()
|
|
579
|
+
index = tab_bar.tabAt(pos)
|
|
580
|
+
|
|
581
|
+
menu = QMenu(self)
|
|
582
|
+
|
|
583
|
+
if index >= 0:
|
|
584
|
+
# Clicked on a tab
|
|
585
|
+
menu.addAction("Close", lambda: self._close_tab(index))
|
|
586
|
+
menu.addAction("Close Others", lambda: self._close_other_tabs(index))
|
|
587
|
+
menu.addAction("Close Tabs to the Right", lambda: self._close_tabs_to_right(index))
|
|
588
|
+
menu.addSeparator()
|
|
589
|
+
|
|
590
|
+
if self.tab_widget.count() > 0:
|
|
591
|
+
menu.addAction("Close All Tabs", self._close_all_tabs)
|
|
592
|
+
|
|
593
|
+
if menu.actions():
|
|
594
|
+
menu.exec(tab_bar.mapToGlobal(pos))
|
|
595
|
+
|
|
596
|
+
def _close_current_tab(self):
|
|
597
|
+
"""Close the currently active tab."""
|
|
598
|
+
index = self.tab_widget.currentIndex()
|
|
599
|
+
if index >= 0:
|
|
600
|
+
self._close_tab(index)
|
|
601
|
+
|
|
534
602
|
def _close_tab(self, index: int):
|
|
535
|
-
"""Close a terminal tab."""
|
|
603
|
+
"""Close a terminal tab with confirmation if connected."""
|
|
536
604
|
tab = self.tab_widget.widget(index)
|
|
605
|
+
|
|
537
606
|
if isinstance(tab, TerminalTab):
|
|
607
|
+
# Check if session is active
|
|
608
|
+
if tab.is_connected():
|
|
609
|
+
reply = QMessageBox.question(
|
|
610
|
+
self,
|
|
611
|
+
"Close Tab",
|
|
612
|
+
f"'{tab.session.name}' has an active connection.\n\n"
|
|
613
|
+
"Disconnect and close this tab?",
|
|
614
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
615
|
+
QMessageBox.StandardButton.No
|
|
616
|
+
)
|
|
617
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
618
|
+
return
|
|
619
|
+
|
|
538
620
|
tab.disconnect()
|
|
621
|
+
|
|
539
622
|
self.tab_widget.removeTab(index)
|
|
540
623
|
|
|
624
|
+
def _close_other_tabs(self, keep_index: int):
|
|
625
|
+
"""Close all tabs except the specified one."""
|
|
626
|
+
active_count = self._get_active_session_count()
|
|
627
|
+
tab_to_keep = self.tab_widget.widget(keep_index)
|
|
628
|
+
keep_is_active = isinstance(tab_to_keep, TerminalTab) and tab_to_keep.is_connected()
|
|
629
|
+
other_active = active_count - (1 if keep_is_active else 0)
|
|
630
|
+
|
|
631
|
+
if other_active > 0:
|
|
632
|
+
reply = QMessageBox.question(
|
|
633
|
+
self,
|
|
634
|
+
"Close Other Tabs",
|
|
635
|
+
f"{other_active} other tab(s) have active connections.\n\n"
|
|
636
|
+
"Disconnect and close them?",
|
|
637
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
638
|
+
QMessageBox.StandardButton.No
|
|
639
|
+
)
|
|
640
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
641
|
+
return
|
|
642
|
+
|
|
643
|
+
# Close tabs from end to avoid index shifting issues
|
|
644
|
+
for i in range(self.tab_widget.count() - 1, -1, -1):
|
|
645
|
+
if i != keep_index:
|
|
646
|
+
tab = self.tab_widget.widget(i)
|
|
647
|
+
if isinstance(tab, TerminalTab):
|
|
648
|
+
tab.disconnect()
|
|
649
|
+
self.tab_widget.removeTab(i)
|
|
650
|
+
|
|
651
|
+
def _close_tabs_to_right(self, index: int):
|
|
652
|
+
"""Close all tabs to the right of the specified index."""
|
|
653
|
+
tabs_to_close = self.tab_widget.count() - index - 1
|
|
654
|
+
if tabs_to_close <= 0:
|
|
655
|
+
return
|
|
656
|
+
|
|
657
|
+
# Count active sessions to the right
|
|
658
|
+
active_count = 0
|
|
659
|
+
for i in range(index + 1, self.tab_widget.count()):
|
|
660
|
+
tab = self.tab_widget.widget(i)
|
|
661
|
+
if isinstance(tab, TerminalTab) and tab.is_connected():
|
|
662
|
+
active_count += 1
|
|
663
|
+
|
|
664
|
+
if active_count > 0:
|
|
665
|
+
reply = QMessageBox.question(
|
|
666
|
+
self,
|
|
667
|
+
"Close Tabs",
|
|
668
|
+
f"{active_count} tab(s) to the right have active connections.\n\n"
|
|
669
|
+
"Disconnect and close them?",
|
|
670
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
671
|
+
QMessageBox.StandardButton.No
|
|
672
|
+
)
|
|
673
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
674
|
+
return
|
|
675
|
+
|
|
676
|
+
# Close from end to avoid index shifting
|
|
677
|
+
for i in range(self.tab_widget.count() - 1, index, -1):
|
|
678
|
+
tab = self.tab_widget.widget(i)
|
|
679
|
+
if isinstance(tab, TerminalTab):
|
|
680
|
+
tab.disconnect()
|
|
681
|
+
self.tab_widget.removeTab(i)
|
|
682
|
+
|
|
683
|
+
def _close_all_tabs(self):
|
|
684
|
+
"""Close all tabs with confirmation."""
|
|
685
|
+
if self.tab_widget.count() == 0:
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
active_count = self._get_active_session_count()
|
|
689
|
+
|
|
690
|
+
if active_count > 0:
|
|
691
|
+
reply = QMessageBox.question(
|
|
692
|
+
self,
|
|
693
|
+
"Close All Tabs",
|
|
694
|
+
f"{active_count} tab(s) have active connections.\n\n"
|
|
695
|
+
"Disconnect and close all tabs?",
|
|
696
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
697
|
+
QMessageBox.StandardButton.No
|
|
698
|
+
)
|
|
699
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
700
|
+
return
|
|
701
|
+
|
|
702
|
+
# Close all tabs
|
|
703
|
+
while self.tab_widget.count() > 0:
|
|
704
|
+
tab = self.tab_widget.widget(0)
|
|
705
|
+
if isinstance(tab, TerminalTab):
|
|
706
|
+
tab.disconnect()
|
|
707
|
+
self.tab_widget.removeTab(0)
|
|
708
|
+
|
|
709
|
+
# -------------------------------------------------------------------------
|
|
710
|
+
# End Tab Management
|
|
711
|
+
# -------------------------------------------------------------------------
|
|
712
|
+
|
|
541
713
|
def closeEvent(self, event):
|
|
542
|
-
"""Clean up on close."""
|
|
714
|
+
"""Clean up on close with confirmation for active sessions."""
|
|
715
|
+
# Save window geometry first
|
|
543
716
|
if not self.isMaximized():
|
|
544
717
|
self.app_settings.window_width = self.width()
|
|
545
718
|
self.app_settings.window_height = self.height()
|
|
@@ -547,7 +720,24 @@ class MainWindow(QMainWindow):
|
|
|
547
720
|
self.app_settings.window_y = self.y()
|
|
548
721
|
self.app_settings.window_maximized = self.isMaximized()
|
|
549
722
|
|
|
723
|
+
# Check for active connections
|
|
724
|
+
active_count = self._get_active_session_count()
|
|
725
|
+
|
|
726
|
+
if active_count > 0:
|
|
727
|
+
reply = QMessageBox.question(
|
|
728
|
+
self,
|
|
729
|
+
"Quit nterm",
|
|
730
|
+
f"You have {active_count} active session(s).\n\n"
|
|
731
|
+
"Disconnect all and quit?",
|
|
732
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
733
|
+
QMessageBox.StandardButton.No
|
|
734
|
+
)
|
|
735
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
736
|
+
event.ignore()
|
|
737
|
+
return
|
|
738
|
+
|
|
550
739
|
save_settings()
|
|
740
|
+
|
|
551
741
|
# Disconnect all tabs
|
|
552
742
|
for i in range(self.tab_widget.count()):
|
|
553
743
|
tab = self.tab_widget.widget(i)
|
|
@@ -582,7 +772,20 @@ class TerminalWindow(QMainWindow):
|
|
|
582
772
|
self.tab.connect()
|
|
583
773
|
|
|
584
774
|
def closeEvent(self, event):
|
|
585
|
-
"""Disconnect on close."""
|
|
775
|
+
"""Disconnect on close with confirmation."""
|
|
776
|
+
if self.tab.is_connected():
|
|
777
|
+
reply = QMessageBox.question(
|
|
778
|
+
self,
|
|
779
|
+
"Close Window",
|
|
780
|
+
f"'{self.tab.session.name}' has an active connection.\n\n"
|
|
781
|
+
"Disconnect and close?",
|
|
782
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
783
|
+
QMessageBox.StandardButton.No
|
|
784
|
+
)
|
|
785
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
786
|
+
event.ignore()
|
|
787
|
+
return
|
|
788
|
+
|
|
586
789
|
self.tab.disconnect()
|
|
587
790
|
event.accept()
|
|
588
791
|
|
|
@@ -285,6 +285,241 @@ class Theme:
|
|
|
285
285
|
overlay_text_color="#3c3836",
|
|
286
286
|
)
|
|
287
287
|
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def enterprise_dark(cls) -> Theme:
|
|
291
|
+
"""
|
|
292
|
+
Enterprise Dark theme.
|
|
293
|
+
|
|
294
|
+
Classic corporate dark theme with pure black terminal.
|
|
295
|
+
Microsoft-inspired blue accent palette.
|
|
296
|
+
"""
|
|
297
|
+
return cls(
|
|
298
|
+
name="enterprise_dark",
|
|
299
|
+
terminal_colors={
|
|
300
|
+
"background": "#0c0c0c",
|
|
301
|
+
"foreground": "#cccccc",
|
|
302
|
+
"cursor": "#ffffff",
|
|
303
|
+
"cursorAccent": "#0c0c0c",
|
|
304
|
+
"selectionBackground": "#264f78",
|
|
305
|
+
"selectionForeground": "#ffffff",
|
|
306
|
+
# Windows Terminal-inspired palette
|
|
307
|
+
"black": "#0c0c0c",
|
|
308
|
+
"red": "#c50f1f",
|
|
309
|
+
"green": "#13a10e",
|
|
310
|
+
"yellow": "#c19c00",
|
|
311
|
+
"blue": "#0037da",
|
|
312
|
+
"magenta": "#881798",
|
|
313
|
+
"cyan": "#3a96dd",
|
|
314
|
+
"white": "#cccccc",
|
|
315
|
+
"brightBlack": "#767676",
|
|
316
|
+
"brightRed": "#e74856",
|
|
317
|
+
"brightGreen": "#16c60c",
|
|
318
|
+
"brightYellow": "#f9f1a5",
|
|
319
|
+
"brightBlue": "#3b78ff",
|
|
320
|
+
"brightMagenta": "#b4009e",
|
|
321
|
+
"brightCyan": "#61d6d6",
|
|
322
|
+
"brightWhite": "#f2f2f2",
|
|
323
|
+
},
|
|
324
|
+
font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
|
|
325
|
+
font_size=14,
|
|
326
|
+
background_color="#1e1e1e",
|
|
327
|
+
foreground_color="#d4d4d4",
|
|
328
|
+
border_color="#3c3c3c",
|
|
329
|
+
accent_color="#0078d4",
|
|
330
|
+
overlay_background="rgba(30, 30, 30, 0.95)",
|
|
331
|
+
overlay_text_color="#d4d4d4",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Add these class methods to Theme class in engine.py
|
|
335
|
+
# Add to _themes dict in ThemeEngine.__init__():
|
|
336
|
+
# self._themes["enterprise_dark"] = Theme.enterprise_dark()
|
|
337
|
+
# self._themes["enterprise_light"] = Theme.enterprise_light()
|
|
338
|
+
# self._themes["enterprise_hybrid"] = Theme.enterprise_hybrid()
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def enterprise_dark(cls) -> Theme:
|
|
342
|
+
"""
|
|
343
|
+
Enterprise Dark theme.
|
|
344
|
+
|
|
345
|
+
Classic corporate dark theme with pure black terminal.
|
|
346
|
+
Microsoft-inspired blue accent palette.
|
|
347
|
+
"""
|
|
348
|
+
return cls(
|
|
349
|
+
name="enterprise_dark",
|
|
350
|
+
terminal_colors={
|
|
351
|
+
"background": "#0c0c0c",
|
|
352
|
+
"foreground": "#cccccc",
|
|
353
|
+
"cursor": "#ffffff",
|
|
354
|
+
"cursorAccent": "#0c0c0c",
|
|
355
|
+
"selectionBackground": "#264f78",
|
|
356
|
+
"selectionForeground": "#ffffff",
|
|
357
|
+
# Windows Terminal-inspired palette
|
|
358
|
+
"black": "#0c0c0c",
|
|
359
|
+
"red": "#c50f1f",
|
|
360
|
+
"green": "#13a10e",
|
|
361
|
+
"yellow": "#c19c00",
|
|
362
|
+
"blue": "#0037da",
|
|
363
|
+
"magenta": "#881798",
|
|
364
|
+
"cyan": "#3a96dd",
|
|
365
|
+
"white": "#cccccc",
|
|
366
|
+
"brightBlack": "#767676",
|
|
367
|
+
"brightRed": "#e74856",
|
|
368
|
+
"brightGreen": "#16c60c",
|
|
369
|
+
"brightYellow": "#f9f1a5",
|
|
370
|
+
"brightBlue": "#3b78ff",
|
|
371
|
+
"brightMagenta": "#b4009e",
|
|
372
|
+
"brightCyan": "#61d6d6",
|
|
373
|
+
"brightWhite": "#f2f2f2",
|
|
374
|
+
},
|
|
375
|
+
font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
|
|
376
|
+
font_size=14,
|
|
377
|
+
background_color="#1e1e1e",
|
|
378
|
+
foreground_color="#d4d4d4",
|
|
379
|
+
border_color="#3c3c3c",
|
|
380
|
+
accent_color="#0078d4",
|
|
381
|
+
overlay_background="rgba(30, 30, 30, 0.95)",
|
|
382
|
+
overlay_text_color="#d4d4d4",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
@classmethod
|
|
386
|
+
def enterprise_light(cls) -> Theme:
|
|
387
|
+
"""
|
|
388
|
+
Enterprise Light theme.
|
|
389
|
+
|
|
390
|
+
Classic corporate light theme with white terminal.
|
|
391
|
+
Microsoft-inspired blue accent palette.
|
|
392
|
+
"""
|
|
393
|
+
return cls(
|
|
394
|
+
name="enterprise_light",
|
|
395
|
+
terminal_colors={
|
|
396
|
+
"background": "#ffffff",
|
|
397
|
+
"foreground": "#1e1e1e",
|
|
398
|
+
"cursor": "#1e1e1e",
|
|
399
|
+
"cursorAccent": "#ffffff",
|
|
400
|
+
"selectionBackground": "#add6ff",
|
|
401
|
+
"selectionForeground": "#1e1e1e",
|
|
402
|
+
# Light terminal palette
|
|
403
|
+
"black": "#1e1e1e",
|
|
404
|
+
"red": "#c72e2e",
|
|
405
|
+
"green": "#098658",
|
|
406
|
+
"yellow": "#795e26",
|
|
407
|
+
"blue": "#0451a5",
|
|
408
|
+
"magenta": "#a626a4",
|
|
409
|
+
"cyan": "#0598bc",
|
|
410
|
+
"white": "#d4d4d4",
|
|
411
|
+
"brightBlack": "#5a5a5a",
|
|
412
|
+
"brightRed": "#e51400",
|
|
413
|
+
"brightGreen": "#14a114",
|
|
414
|
+
"brightYellow": "#b5a000",
|
|
415
|
+
"brightBlue": "#0078d4",
|
|
416
|
+
"brightMagenta": "#bc05bc",
|
|
417
|
+
"brightCyan": "#00b7c3",
|
|
418
|
+
"brightWhite": "#f8f8f8",
|
|
419
|
+
},
|
|
420
|
+
font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
|
|
421
|
+
font_size=14,
|
|
422
|
+
background_color="#f3f3f3",
|
|
423
|
+
foreground_color="#1e1e1e",
|
|
424
|
+
border_color="#d1d1d1",
|
|
425
|
+
accent_color="#0078d4",
|
|
426
|
+
overlay_background="rgba(243, 243, 243, 0.95)",
|
|
427
|
+
overlay_text_color="#1e1e1e",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
@classmethod
|
|
431
|
+
def enterprise_hybrid(cls) -> Theme:
|
|
432
|
+
"""
|
|
433
|
+
Enterprise Hybrid theme.
|
|
434
|
+
|
|
435
|
+
Dark UI chrome with white terminal.
|
|
436
|
+
Best of both: comfortable dark UI, high-contrast readable terminal.
|
|
437
|
+
Microsoft-inspired blue accent palette.
|
|
438
|
+
"""
|
|
439
|
+
return cls(
|
|
440
|
+
name="enterprise_hybrid",
|
|
441
|
+
# Light terminal for readability
|
|
442
|
+
terminal_colors={
|
|
443
|
+
"background": "#ffffff",
|
|
444
|
+
"foreground": "#1e1e1e",
|
|
445
|
+
"cursor": "#1e1e1e",
|
|
446
|
+
"cursorAccent": "#ffffff",
|
|
447
|
+
"selectionBackground": "#add6ff",
|
|
448
|
+
"selectionForeground": "#1e1e1e",
|
|
449
|
+
"black": "#1e1e1e",
|
|
450
|
+
"red": "#c72e2e",
|
|
451
|
+
"green": "#098658",
|
|
452
|
+
"yellow": "#795e26",
|
|
453
|
+
"blue": "#0451a5",
|
|
454
|
+
"magenta": "#a626a4",
|
|
455
|
+
"cyan": "#0598bc",
|
|
456
|
+
"white": "#d4d4d4",
|
|
457
|
+
"brightBlack": "#5a5a5a",
|
|
458
|
+
"brightRed": "#e51400",
|
|
459
|
+
"brightGreen": "#14a114",
|
|
460
|
+
"brightYellow": "#b5a000",
|
|
461
|
+
"brightBlue": "#0078d4",
|
|
462
|
+
"brightMagenta": "#bc05bc",
|
|
463
|
+
"brightCyan": "#00b7c3",
|
|
464
|
+
"brightWhite": "#f8f8f8",
|
|
465
|
+
},
|
|
466
|
+
font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
|
|
467
|
+
font_size=14,
|
|
468
|
+
# Dark UI chrome
|
|
469
|
+
background_color="#1e1e1e",
|
|
470
|
+
foreground_color="#d4d4d4",
|
|
471
|
+
border_color="#3c3c3c",
|
|
472
|
+
accent_color="#0078d4",
|
|
473
|
+
overlay_background="rgba(30, 30, 30, 0.95)",
|
|
474
|
+
overlay_text_color="#d4d4d4",
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@classmethod
|
|
479
|
+
def enterprise_light(cls) -> Theme:
|
|
480
|
+
"""
|
|
481
|
+
Enterprise Light theme.
|
|
482
|
+
|
|
483
|
+
Classic corporate light theme with white terminal.
|
|
484
|
+
Microsoft-inspired blue accent palette.
|
|
485
|
+
"""
|
|
486
|
+
return cls(
|
|
487
|
+
name="enterprise_light",
|
|
488
|
+
terminal_colors={
|
|
489
|
+
"background": "#ffffff",
|
|
490
|
+
"foreground": "#1e1e1e",
|
|
491
|
+
"cursor": "#1e1e1e",
|
|
492
|
+
"cursorAccent": "#ffffff",
|
|
493
|
+
"selectionBackground": "#add6ff",
|
|
494
|
+
"selectionForeground": "#1e1e1e",
|
|
495
|
+
# Light terminal palette
|
|
496
|
+
"black": "#1e1e1e",
|
|
497
|
+
"red": "#c72e2e",
|
|
498
|
+
"green": "#098658",
|
|
499
|
+
"yellow": "#795e26",
|
|
500
|
+
"blue": "#0451a5",
|
|
501
|
+
"magenta": "#a626a4",
|
|
502
|
+
"cyan": "#0598bc",
|
|
503
|
+
"white": "#d4d4d4",
|
|
504
|
+
"brightBlack": "#5a5a5a",
|
|
505
|
+
"brightRed": "#e51400",
|
|
506
|
+
"brightGreen": "#14a114",
|
|
507
|
+
"brightYellow": "#b5a000",
|
|
508
|
+
"brightBlue": "#0078d4",
|
|
509
|
+
"brightMagenta": "#bc05bc",
|
|
510
|
+
"brightCyan": "#00b7c3",
|
|
511
|
+
"brightWhite": "#f8f8f8",
|
|
512
|
+
},
|
|
513
|
+
font_family="Cascadia Code, Consolas, JetBrains Mono, monospace",
|
|
514
|
+
font_size=14,
|
|
515
|
+
background_color="#f3f3f3",
|
|
516
|
+
foreground_color="#1e1e1e",
|
|
517
|
+
border_color="#d1d1d1",
|
|
518
|
+
accent_color="#0078d4",
|
|
519
|
+
overlay_background="rgba(243, 243, 243, 0.95)",
|
|
520
|
+
overlay_text_color="#1e1e1e",
|
|
521
|
+
)
|
|
522
|
+
|
|
288
523
|
@classmethod
|
|
289
524
|
def clean(cls) -> Theme:
|
|
290
525
|
"""
|
|
@@ -400,6 +635,9 @@ class ThemeEngine:
|
|
|
400
635
|
self._themes["gruvbox_light"] = Theme.gruvbox_light()
|
|
401
636
|
self._themes["gruvbox_hybrid"] = Theme.gruvbox_hybrid()
|
|
402
637
|
self._themes["clean"] = Theme.clean()
|
|
638
|
+
self._themes["enterprise_dark"] = Theme.enterprise_dark()
|
|
639
|
+
self._themes["enterprise_light"] = Theme.enterprise_light()
|
|
640
|
+
self._themes["enterprise_hybrid"] = Theme.enterprise_hybrid()
|
|
403
641
|
|
|
404
642
|
def load_themes(self) -> None:
|
|
405
643
|
"""Load all themes from theme directory."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: enterprise_dark
|
|
2
|
+
|
|
3
|
+
# Classic enterprise dark theme
|
|
4
|
+
# Dark UI chrome with pure black terminal
|
|
5
|
+
# Microsoft-inspired blue accent palette
|
|
6
|
+
|
|
7
|
+
terminal_colors:
|
|
8
|
+
background: "#0c0c0c"
|
|
9
|
+
foreground: "#cccccc"
|
|
10
|
+
cursor: "#ffffff"
|
|
11
|
+
cursorAccent: "#0c0c0c"
|
|
12
|
+
selectionBackground: "#264f78"
|
|
13
|
+
selectionForeground: "#ffffff"
|
|
14
|
+
# Windows Terminal-inspired palette
|
|
15
|
+
black: "#0c0c0c"
|
|
16
|
+
red: "#c50f1f"
|
|
17
|
+
green: "#13a10e"
|
|
18
|
+
yellow: "#c19c00"
|
|
19
|
+
blue: "#0037da"
|
|
20
|
+
magenta: "#881798"
|
|
21
|
+
cyan: "#3a96dd"
|
|
22
|
+
white: "#cccccc"
|
|
23
|
+
brightBlack: "#767676"
|
|
24
|
+
brightRed: "#e74856"
|
|
25
|
+
brightGreen: "#16c60c"
|
|
26
|
+
brightYellow: "#f9f1a5"
|
|
27
|
+
brightBlue: "#3b78ff"
|
|
28
|
+
brightMagenta: "#b4009e"
|
|
29
|
+
brightCyan: "#61d6d6"
|
|
30
|
+
brightWhite: "#f2f2f2"
|
|
31
|
+
|
|
32
|
+
font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
|
|
33
|
+
font_size: 14
|
|
34
|
+
|
|
35
|
+
# UI chrome colors
|
|
36
|
+
background_color: "#1e1e1e"
|
|
37
|
+
foreground_color: "#d4d4d4"
|
|
38
|
+
border_color: "#3c3c3c"
|
|
39
|
+
accent_color: "#0078d4"
|
|
40
|
+
|
|
41
|
+
overlay_background: "rgba(30, 30, 30, 0.95)"
|
|
42
|
+
overlay_text_color: "#d4d4d4"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: enterprise_hybrid
|
|
2
|
+
|
|
3
|
+
# Enterprise Hybrid theme
|
|
4
|
+
# Dark UI chrome with white terminal
|
|
5
|
+
# Best of both: comfortable dark UI, high-contrast readable terminal
|
|
6
|
+
# Microsoft-inspired blue accent palette
|
|
7
|
+
|
|
8
|
+
terminal_colors:
|
|
9
|
+
# Light terminal for readability
|
|
10
|
+
background: "#ffffff"
|
|
11
|
+
foreground: "#1e1e1e"
|
|
12
|
+
cursor: "#1e1e1e"
|
|
13
|
+
cursorAccent: "#ffffff"
|
|
14
|
+
selectionBackground: "#add6ff"
|
|
15
|
+
selectionForeground: "#1e1e1e"
|
|
16
|
+
# Light terminal palette
|
|
17
|
+
black: "#1e1e1e"
|
|
18
|
+
red: "#c72e2e"
|
|
19
|
+
green: "#098658"
|
|
20
|
+
yellow: "#795e26"
|
|
21
|
+
blue: "#0451a5"
|
|
22
|
+
magenta: "#a626a4"
|
|
23
|
+
cyan: "#0598bc"
|
|
24
|
+
white: "#d4d4d4"
|
|
25
|
+
brightBlack: "#5a5a5a"
|
|
26
|
+
brightRed: "#e51400"
|
|
27
|
+
brightGreen: "#14a114"
|
|
28
|
+
brightYellow: "#b5a000"
|
|
29
|
+
brightBlue: "#0078d4"
|
|
30
|
+
brightMagenta: "#bc05bc"
|
|
31
|
+
brightCyan: "#00b7c3"
|
|
32
|
+
brightWhite: "#f8f8f8"
|
|
33
|
+
|
|
34
|
+
font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
|
|
35
|
+
font_size: 14
|
|
36
|
+
|
|
37
|
+
# Dark UI chrome
|
|
38
|
+
background_color: "#1e1e1e"
|
|
39
|
+
foreground_color: "#d4d4d4"
|
|
40
|
+
border_color: "#3c3c3c"
|
|
41
|
+
accent_color: "#0078d4"
|
|
42
|
+
|
|
43
|
+
overlay_background: "rgba(30, 30, 30, 0.95)"
|
|
44
|
+
overlay_text_color: "#d4d4d4"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: enterprise_light
|
|
2
|
+
|
|
3
|
+
# Classic enterprise light theme
|
|
4
|
+
# Light UI chrome with white terminal
|
|
5
|
+
# Microsoft-inspired blue accent palette
|
|
6
|
+
|
|
7
|
+
terminal_colors:
|
|
8
|
+
background: "#ffffff"
|
|
9
|
+
foreground: "#1e1e1e"
|
|
10
|
+
cursor: "#1e1e1e"
|
|
11
|
+
cursorAccent: "#ffffff"
|
|
12
|
+
selectionBackground: "#add6ff"
|
|
13
|
+
selectionForeground: "#1e1e1e"
|
|
14
|
+
# Light terminal palette (VS Code light-inspired)
|
|
15
|
+
black: "#1e1e1e"
|
|
16
|
+
red: "#c72e2e"
|
|
17
|
+
green: "#098658"
|
|
18
|
+
yellow: "#795e26"
|
|
19
|
+
blue: "#0451a5"
|
|
20
|
+
magenta: "#a626a4"
|
|
21
|
+
cyan: "#0598bc"
|
|
22
|
+
white: "#d4d4d4"
|
|
23
|
+
brightBlack: "#5a5a5a"
|
|
24
|
+
brightRed: "#e51400"
|
|
25
|
+
brightGreen: "#14a114"
|
|
26
|
+
brightYellow: "#b5a000"
|
|
27
|
+
brightBlue: "#0078d4"
|
|
28
|
+
brightMagenta: "#bc05bc"
|
|
29
|
+
brightCyan: "#00b7c3"
|
|
30
|
+
brightWhite: "#f8f8f8"
|
|
31
|
+
|
|
32
|
+
font_family: "Cascadia Code, Consolas, JetBrains Mono, monospace"
|
|
33
|
+
font_size: 14
|
|
34
|
+
|
|
35
|
+
# UI chrome colors - classic light gray
|
|
36
|
+
background_color: "#f3f3f3"
|
|
37
|
+
foreground_color: "#1e1e1e"
|
|
38
|
+
border_color: "#d1d1d1"
|
|
39
|
+
accent_color: "#0078d4"
|
|
40
|
+
|
|
41
|
+
overlay_background: "rgba(243, 243, 243, 0.95)"
|
|
42
|
+
overlay_text_color: "#1e1e1e"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ntermqt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
|
|
5
5
|
Author: Scott Peterman
|
|
6
6
|
License: GPL-3.0
|
|
@@ -42,7 +42,7 @@ PyQt6 terminal widget with encrypted credential vault, jump host chaining, YubiK
|
|
|
42
42
|
|
|
43
43
|
Built for managing hundreds of devices through bastion hosts with hardware security keys.
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -38,6 +38,9 @@ nterm/theme/stylesheet.py
|
|
|
38
38
|
nterm/theme/themes/clean.yaml
|
|
39
39
|
nterm/theme/themes/default.yaml
|
|
40
40
|
nterm/theme/themes/dracula.yaml
|
|
41
|
+
nterm/theme/themes/enterprise_dark.yaml
|
|
42
|
+
nterm/theme/themes/enterprise_hybrid.yaml
|
|
43
|
+
nterm/theme/themes/enterprise_light.yaml
|
|
41
44
|
nterm/theme/themes/gruvbox_dark.yaml
|
|
42
45
|
nterm/theme/themes/gruvbox_hybrid.yaml
|
|
43
46
|
nterm/theme/themes/gruvbox_light.yaml
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|