Add Claude Code integration: shared config + Setup tab
- Shared servers.json at ~/.server-connections/ (GUI + Claude Code) - Setup tab: one-click install of ssh.py, /ssh skill, SSH key - Duplicate checks — safe to run multiple times - tools/ssh.py + tools/skill-ssh.md bundled - Updated README with integration docs (EN/RU/ZH) - Deploy guide for new machines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
126
gui/tabs/setup_tab.py
Normal file
126
gui/tabs/setup_tab.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
Setup tab — one-click installation for Claude Code integration.
|
||||
"""
|
||||
|
||||
import threading
|
||||
import customtkinter as ctk
|
||||
from core.claude_setup import check_status, install_all, install_ssh_script, install_skill, generate_ssh_key
|
||||
|
||||
|
||||
class SetupTab(ctk.CTkFrame):
|
||||
def __init__(self, master, store):
|
||||
super().__init__(master, fg_color="transparent")
|
||||
self.store = store
|
||||
|
||||
# Header
|
||||
ctk.CTkLabel(
|
||||
self, text="Claude Code Integration",
|
||||
font=ctk.CTkFont(size=20, weight="bold")
|
||||
).pack(padx=20, pady=(20, 5))
|
||||
|
||||
ctk.CTkLabel(
|
||||
self,
|
||||
text="Setup everything so Claude Code can manage your servers via /ssh skill.\n"
|
||||
"Both GUI and Claude Code share the same servers.json — add a server here,\n"
|
||||
"Claude sees it immediately.",
|
||||
text_color="#9ca3af", justify="center"
|
||||
).pack(padx=20, pady=(0, 15))
|
||||
|
||||
# Status card
|
||||
self.status_frame = ctk.CTkFrame(self)
|
||||
self.status_frame.pack(fill="x", padx=20, pady=10)
|
||||
|
||||
ctk.CTkLabel(
|
||||
self.status_frame, text="Status",
|
||||
font=ctk.CTkFont(size=14, weight="bold"), anchor="w"
|
||||
).pack(fill="x", padx=15, pady=(10, 5))
|
||||
|
||||
self._status_labels: dict[str, ctk.CTkLabel] = {}
|
||||
status_items = [
|
||||
("shared_dir", "Shared config dir (~/.server-connections)"),
|
||||
("servers_json", "servers.json"),
|
||||
("ssh_script", "ssh.py (CLI tool)"),
|
||||
("skill_installed", "/ssh skill for Claude Code"),
|
||||
("ssh_key_exists", "SSH key (ed25519)"),
|
||||
]
|
||||
for key, label in status_items:
|
||||
row = ctk.CTkFrame(self.status_frame, fg_color="transparent")
|
||||
row.pack(fill="x", padx=15, pady=2)
|
||||
indicator = ctk.CTkLabel(row, text="\u25cf", width=20, text_color="#6b7280")
|
||||
indicator.pack(side="left")
|
||||
ctk.CTkLabel(row, text=label, anchor="w").pack(side="left", fill="x", expand=True)
|
||||
self._status_labels[key] = indicator
|
||||
|
||||
# Buttons
|
||||
btn_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
btn_frame.pack(fill="x", padx=20, pady=15)
|
||||
|
||||
self.install_all_btn = ctk.CTkButton(
|
||||
btn_frame, text="Install Everything",
|
||||
font=ctk.CTkFont(size=14, weight="bold"),
|
||||
height=40, fg_color="#22c55e", hover_color="#16a34a",
|
||||
command=self._install_all
|
||||
)
|
||||
self.install_all_btn.pack(fill="x", pady=(0, 10))
|
||||
|
||||
# Individual buttons row
|
||||
ind_frame = ctk.CTkFrame(btn_frame, fg_color="transparent")
|
||||
ind_frame.pack(fill="x")
|
||||
|
||||
ctk.CTkButton(ind_frame, text="ssh.py", width=100, fg_color="#6b7280",
|
||||
command=self._install_script).pack(side="left", padx=(0, 5))
|
||||
ctk.CTkButton(ind_frame, text="/ssh skill", width=100, fg_color="#6b7280",
|
||||
command=self._install_skill).pack(side="left", padx=5)
|
||||
ctk.CTkButton(ind_frame, text="SSH key", width=100, fg_color="#6b7280",
|
||||
command=self._gen_key).pack(side="left", padx=5)
|
||||
ctk.CTkButton(ind_frame, text="Refresh", width=80, fg_color="#3b82f6",
|
||||
command=self._refresh_status).pack(side="right")
|
||||
|
||||
# Log
|
||||
self.log = ctk.CTkTextbox(self, height=150, font=ctk.CTkFont(family="Consolas", size=11), state="disabled")
|
||||
self.log.pack(fill="both", expand=True, padx=20, pady=(5, 20))
|
||||
|
||||
# Initial status check
|
||||
self._refresh_status()
|
||||
|
||||
def _log(self, text: str):
|
||||
self.log.configure(state="normal")
|
||||
self.log.insert("end", text + "\n")
|
||||
self.log.configure(state="disabled")
|
||||
self.log.see("end")
|
||||
|
||||
def _refresh_status(self):
|
||||
status = check_status()
|
||||
for key, label in self._status_labels.items():
|
||||
if status.get(key, False):
|
||||
label.configure(text="\u25cf", text_color="#22c55e") # green
|
||||
else:
|
||||
label.configure(text="\u25cf", text_color="#ef4444") # red
|
||||
|
||||
def _install_all(self):
|
||||
self.install_all_btn.configure(state="disabled", text="Installing...")
|
||||
|
||||
def _do():
|
||||
results = install_all()
|
||||
for msg in results:
|
||||
self.after(0, lambda m=msg: self._log(m))
|
||||
self.after(0, self._refresh_status)
|
||||
self.after(0, lambda: self._log("\nDone! Claude Code can now use /ssh to manage your servers."))
|
||||
self.after(0, lambda: self.install_all_btn.configure(state="normal", text="Install Everything"))
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
|
||||
def _install_script(self):
|
||||
msg = install_ssh_script()
|
||||
self._log(msg)
|
||||
self._refresh_status()
|
||||
|
||||
def _install_skill(self):
|
||||
msg = install_skill()
|
||||
self._log(msg)
|
||||
self._refresh_status()
|
||||
|
||||
def _gen_key(self):
|
||||
msg = generate_ssh_key()
|
||||
self._log(msg)
|
||||
self._refresh_status()
|
||||
Reference in New Issue
Block a user