diff --git a/AGENTS.md b/AGENTS.md index c7a4160..38ca7d8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ > **Purpose**: Memungkinkan AI agent berikutnya melanjutkan development tanpa mengulang atau merusak. > **Last Updated**: 2026-07-07 -> **Current Phase**: Pre-development (setup completed, scaffolding not yet started) +> **Current Phase**: Pre-development (documentation updated, scaffolding not yet started) --- @@ -20,12 +20,24 @@ hostkeeper/ (root — bersih untuk V2) │ ├── go.mod module git.tukangketik.id/swanadiva/hostkeeper │ ├── Makefile │ └── PROJECT_STATE.md V1 full project state -├── app/ ← (BELUM DIBUAT) V2 backend + frontend + electron +├── app/ ← (BELUM DIBUAT) V2 backend GoFiber + views + static ├── mobile/ ← (BELUM DIBUAT) V2 mobile wrapper -├── template/ ← Referensi UI React (Lumina System) -│ └── src/components/ 7 React screens (Dashboard, Terminal, SFTP, Snippets, Keychain, Settings, Brief) -├── docs/ ← Dokumentasi perencanaan V2 (10 file, 6,310 baris) -└── AGENTS.md ← file ini (checkpoint AI) +├── template/ ← Referensi UI React (Lumina System) — hanya untuk desain +│ └── src/components/ 7 React screens +├── docs/ ← Dokumentasi perencanaan V2 (HTMX) +│ ├── ARCHITECTURE_HTMX.md Arsitektur HTMX (dokumen utama) +│ ├── ARCHITECTURE.md Ringkasan + pointer ke HTMX +│ ├── API.md REST + WebSocket spec +│ ├── DATA_MODELS.md Data models (Host, Snippet, Key, dll) +│ ├── UI_COMPONENTS.md HTMX component tree (bukan React) +│ ├── SPRINT_PLAN.md Sprint 0-6 untuk HTMX +│ ├── BUILD_SYSTEM.md Build pipeline (Go + CSS, tanpa Vite) +│ ├── TIMELINE.md Timeline 3.5 minggu +│ ├── PERFROMANCE.md Performance & stability +│ ├── UI_TERMIUS_REFERENCE.md Termius visual reference +│ └── PROGRESS.md Status tracker +├── AGENTS.md ← file ini (checkpoint AI) +└── README.md Git branches: - main → V2 development (current) @@ -46,115 +58,148 @@ Git branches: --- -## 3. V2 Status — Pre-Development +## 3. V2 Architecture — HTMX (Keputusan Penting) -### ✅ Sudah Selesai -- 10 dokumen perencanaan di `docs/`: - - `ARCHITECTURE.md` — GoFiber v2 + React + Electron + mobile WebView - - `API.md` — REST + WebSocket spec (1,303 baris) - - `DATA_MODELS.md` — Host, Group, Snippet, Key, PortForward, Workspace, Config - - `UI_COMPONENTS.md` — React component tree + Zustand stores - - `SPRINT_PLAN.md` — Sprint 0-6 breakdown - - `BUILD_SYSTEM.md` — Cross-platform pipeline - - `PERFORMANCE.md` — Performance & stability - - `TIMELINE.md` — 6-week timeline - - `TEMPLATE_ANALYSIS.md` — Template feature map + 5 bugs found - - `PROGRESS.md` — Status tracker -- Template UI dianalisis (Lumina System — bright-tech glassmorphic, TailwindCSS v4) -- Root dibersihkan — V1 di `v1/`, V2 siap dimulai di `app/` + `mobile/` +**Hostkeeper V2 menggunakan GoFiber + HTMX, BUKAN React.** -### ❌ Belum Dimulai (Scaffolding) -- `app/backend/` — GoFiber v2 HTTP server -- `app/frontend/` — React + TailwindCSS v4 + xterm.js -- `app/electron/` — Electron wrapper -- `mobile/` — Android + iOS WebView shells - -### 🐛 Bug Template yang Perlu Diperbaiki (saat copy) -1. **P0**: Missing CSS keyframes (fade-in, scale-up, slide-in) di `index.css` -2. **P1**: `w-4.5 h-4.5` invalid di `SettingsView.tsx:200` → ganti `w-5 h-5` -3. **P1**: Tidak ada Error Boundary di `App.tsx` -4. **P2**: `as any` casts di `DashboardView.tsx:402`, `KeychainView.tsx:302` -5. **P3**: Auto-focus loss di shortcut terminal `TerminalView.tsx` - ---- - -## 4. Arsitektur V2 (Ringkasan) +Alasan: +- Developer lebih nyaman dengan Go daripada JavaScript +- ~90% kode adalah Go + HTML template +- Hanya ~410 baris JS yang diperlukan (xterm.js, clipboard, toggle) +- Build lebih sederhana — tanpa Vite, npm, TypeScript, Webpack +- Bisa berjalan di browser biasa tanpa Electron ``` -┌─────────────────────────────────────────────────────┐ -│ Electron / WebView Shell │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ React SPA (Vite + TailwindCSS v4 + xterm.js)│ │ -│ │ ┌─────────────┐ ┌──────────────────────────┐│ │ -│ │ │ Zustand │ │ Components (template) ││ │ -│ │ │ Stores │ │ + 3 new screens ││ │ -│ │ └─────────────┘ └──────────────────────────┘│ │ -│ │ │ fetch/WS │ │ -│ └───────────┼──────────────────────────────────┘ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ GoFiber v2 (fasthttp) — localhost:1947 │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ -│ │ │ REST API │ │ SFTP RPC │ │ WebSocket/SSH │ │ │ -│ │ └──────────┘ └──────────┘ └──────────────┘ │ │ -│ │ │ │ │ -│ │ ┌────┴────┐ │ │ -│ │ │ pkg/ssh │ ← dari v1/pkg/ssh │ │ -│ │ │ pkg/... │ (via replace directive)│ │ -│ │ └─────────┘ │ │ -│ └──────────────────────────────────────────────┘ │ +┌───────────────────────────────────────────────────┐ +│ Browser / Electron WebView │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ HTMX (14kb) + Alpine.js (15kb) + xterm.js │ │ +│ │ ● HTMX: search, filter, modal, form, nav │ │ +│ │ ● Alpine: client state (toast, toggle) │ │ +│ │ ● xterm.js: terminal (halaman terminal) │ │ +│ │ ● Vanilla JS: clipboard, toggle, utils │ │ +│ └────────────────┬────────────────────────────┘ │ +│ │ hx-get/hx-post/hx-ws │ +│ ┌────────────────▼────────────────────────────┐ │ +│ │ GoFiber v2 (localhost:1947) │ │ +│ │ ┌──────────────┐ ┌────────────────────┐ │ │ +│ │ │ HTML handler │ │ WebSocket/SSH │ │ │ +│ │ │ (html/templ.) │ │ (xterm.js I/O) │ │ │ +│ │ └──────┬───────┘ └────────────────────┘ │ │ +│ │ ┌────▼────┐ │ │ +│ │ │ v1/pkg/ │ (via replace directive) │ │ +│ │ └─────────┘ │ │ +│ └──────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` -**Key decisions**: -- GoFiber v2 (fasthttp) — replaces chi/net/http -- gofiber/contrib/websocket — replaces gorilla/websocket -- React 19 + Zustand — state management -- v1/pkg/ssh, crypto, storage, config, knownhosts — di-reuse via `go.mod replace` -- V2 module name suggestion: `git.tukangketik.id/swanadiva/hostkeeper/v2` +| Library | Ukuran | Fungsi | +|---------|--------|--------| +| HTMX | 14kb | HTML-over-HTTP interaktivitas | +| Alpine.js | 15kb | Client state (modal, toast, tab) | +| xterm.js | ~100kb | Terminal emulator (WAJIB) | +| TailwindCSS v4 | ~27kb | Utility-first CSS | --- -## 5. Yang Harus Dilakukan Selanjutnya (First Actions) +## 4. V2 Status — Pre-Development -Urutan eksekusi pertama saat development dimulai: +### ✅ Sudah Selesai +- Semua dokumentasi diupdate ke HTMX (10 file di `docs/`) +- Template UI dianalisis (Lumina System — bright-tech glassmorphic, TailwindCSS v4) +- Root dibersihkan — V1 di `v1/`, V2 siap dimulai di `app/` +- `template/` tetap sebagai referensi desain (warna, layout, spacing) +- Bug template sudah diidentifikasi (animasi CSS, w-4.5, error boundary, dll) + +### ❌ Belum Dimulai +- `app/backend/` — GoFiber v2 server + handler + views + +### 🐛 Bug Template (dari template/src/ — catatan saat implementasi) +1. **P0**: Missing CSS keyframes (fade-in, scale-up, slide-in) → tambah di `custom.css` +2. **P1**: `w-4.5 h-4.5` → ganti `w-5 h-5` +3. **P2**: Tidak ada Error Boundary → not applicable (HTML template) +4. **P2**: `as any` casts → not applicable (Go typed) +5. **P3**: Auto-focus loss → handle via JS `focus()` di Alpine + +--- + +## 5. Struktur Folder V2 (Target) ``` -Step 1: mkdir -p app/backend app/frontend app/electron mobile +app/backend/ +├── main.go ← Fiber app entry +├── go.mod ← module git.tukangketik.id/swanadiva/hostkeeper/v2 +├── internal/ +│ ├── handler/ ← HTTP handlers (render HTML) +│ │ ├── dashboard.go +│ │ ├── terminal.go +│ │ ├── sftp.go +│ │ ├── snippets.go +│ │ ├── keychain.go +│ │ ├── settings.go +│ │ ├── brief.go +│ │ └── health.go +│ ├── model/ ← Go structs (Host, Snippet, Key, dll) +│ ├── template/ ← Template helper funcs +│ └── middleware/ ← View data injection +├── static/ +│ ├── css/ ← output.css + custom.css +│ ├── js/ ← htmx.min.js, alpine.min.js, utils.js, terminal.js +│ └── xterm/ ← xterm.js lib +└── views/ + ├── layout.html + ├── index.html + └── partials/ ← ~17 partial HTML files +``` + +--- + +## 6. Yang Harus Dilakukan Selanjutnya (First Actions) + +Urutan eksekusi saat development dimulai: + +``` +Sprint 0 — Setup: +Step 1: mkdir -p app/backend Step 2: cd app/backend && go mod init git.tukangketik.id/swanadiva/hostkeeper/v2 Step 3: go get github.com/gofiber/fiber/v2 -Step 4: Buat app/backend/main.go (Fiber app skeleton) -Step 5: cd ../frontend && npm create vite@latest . -- --template react-ts -Step 6: Install deps (tailwindcss v4, lucide-react, xterm, @xterm/xterm, zustand) -Step 7: Copy template/src/* ke app/frontend/src/ -Step 8: Fix 5 bugs template (lihat section 3 di atas) -Step 9: Setup Electron: npm install electron electron-builder --save-dev +Step 4: go get github.com/gofiber/template/html/v2 +Step 5: Buat main.go (Fiber app skeleton + static + template engine) +Step 6: Download htmx.min.js, alpine.min.js ke static/js/ +Step 7: Setup TailwindCSS v4: input.css → npx @tailwindcss/cli +Step 8: Buat custom.css (dot-grid, glassmorphism, keyframes) +Step 9: Buat views/layout.html + views/index.html + nav.html +Step 10: Buat handler/health.go + handler/dashboard.go +Step 11: Verify: buka http://localhost:1947 — layout terlihat ``` --- -## 6. Path Penting & Referensi +## 7. Path Penting & Referensi | Untuk | Lihat di | |-------|----------| -| Arsitektur lengkap | `docs/ARCHITECTURE.md` | +| Arsitektur HTMX lengkap | `docs/ARCHITECTURE_HTMX.md` | | API spec | `docs/API.md` | -| Data models | `docs/DATA_MODELS.md` | -| UI component tree | `docs/UI_COMPONENTS.md` | +| Data models (Go struct) | `docs/DATA_MODELS.md` | +| HTMX component tree | `docs/UI_COMPONENTS.md` | | Sprint plan detail | `docs/SPRINT_PLAN.md` | -| Template analysis + bugs | `docs/TEMPLATE_ANALYSIS.md` | -| Template source code | `template/src/components/` | -| Template AGENTS.md | `template/AGENTS.md` (desain system) | +| Timeline 3.5 minggu | `docs/TIMELINE.md` | +| Build system | `docs/BUILD_SYSTEM.md` | +| Status tracker | `docs/PROGRESS.md` | +| Template desain (React) | `template/AGENTS.md` (Lumina System) | +| Template komponen (referensi) | `template/src/components/` | | V1 shared packages | `v1/pkg/ssh/`, `v1/pkg/crypto/`, `v1/pkg/storage/`, `v1/pkg/config/`, `v1/pkg/knownhosts/` | | V1 build & test | `cd v1 && go build ./cmd/hostkeeper && go test ./...` | -| V1 project state | `v1/PROJECT_STATE.md` | --- -## 7. Aturan Penting +## 8. Aturan Penting - **JANGAN** mengubah file di `v1/` — V1 FROZEN -- **JANGAN** mengubah `template/` — itu referensi, bukan source aktif -- Semua kode V2 baru di `app/` dan `mobile/` +- **JANGAN** mengubah `template/` — itu referensi desain, bukan source aktif +- Semua kode V2 baru di `app/` - Saat reuse `v1/pkg/*`, pakai `go.mod replace` — jangan copy +- Template React hanya untuk referensi visual (warna, layout, spacing) +- Gunakan TailwindCSS v4 + custom CSS (Lumina System) - Pakai bahasa Indonesia untuk komunikasi dengan user (swanadiva) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6d2c3d5..38a6607 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,42 +1,26 @@ # Hostkeeper V2 — Architecture -> **Status**: V2 Planning Complete -> **Last Updated**: 2026-06-29 +> **Status**: Migrasi React → HTMX +> **Last Updated**: 2026-07-07 +> **Dokumen arsitektur baru**: [ARCHITECTURE_HTMX.md](ARCHITECTURE_HTMX.md) > **V1 (existing)**: CLI/TUI SSH/SFTP manager — frozen, no changes. --- -## 1. System Overview +## ⚠️ Keputusan Arsitektur -Hostkeeper V2 is a Termius-like cross-platform SSH/SFTP management GUI. The architecture follows a **Go backend + Web UI + Electron/WebView** pattern — the same approach used by Termius, VS Code, Discord, Slack, and Figma. +Hostkeeper V2 menggunakan **GoFiber + HTMX**, **bukan React/SPA**. -``` -┌──────────────────────────────────────────────────────┐ -│ ELECTRON / WEBVIEW │ -│ Native window, menu bar, tray icon, auto-updater │ -│ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ WEB UI (React + TypeScript) │ │ -│ │ Host List │ Terminal │ SFTP │ Keys │ Settings │ │ -│ │ ┌───────────────────────────────────────────┐ │ │ -│ │ │ xterm.js (terminal) │ │ │ -│ │ └───────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────┘ │ -│ │ HTTP + WebSocket │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ GO BACKEND (GoFiber v2) │ │ -│ │ REST API │ WebSocket │ SSH │ SFTP │ │ -│ │ ┌───────────────────────────────────────────┐ │ │ -│ │ │ pkg/ssh │ pkg/sftp │ pkg/crypto │ ... │ │ │ -│ │ └───────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────────┘ - │ Distributed as: - ├── .dmg (macOS) - ├── .exe (Windows) - ├── .AppImage (Linux) - └── .aab / .ipa (Android / iOS) -``` +Keputusan ini diambil karena: +1. Developer lebih nyaman dengan Go daripada JavaScript +2. ~90% kode adalah Go + HTML template +3. Hanya ~410 baris JS yang diperlukan (xterm.js, clipboard, toggle) +4. Build lebih sederhana — tanpa Vite, npm, TypeScript +5. Bisa berjalan di browser biasa tanpa Electron + +**Dokumen arsitektur lengkap ada di** [`ARCHITECTURE_HTMX.md`](ARCHITECTURE_HTMX.md). + +--- ### Communication Flow diff --git a/docs/ARCHITECTURE_HTMX.md b/docs/ARCHITECTURE_HTMX.md new file mode 100644 index 0000000..ed8c721 --- /dev/null +++ b/docs/ARCHITECTURE_HTMX.md @@ -0,0 +1,385 @@ +# Hostkeeper V2 — Architecture (HTMX) + +> **Status**: Approved — Menggantikan arsitektur React sebelumnya +> **Last Updated**: 2026-07-07 +> **Keputusan**: React → GoFiber + HTMX + Alpine.js + minimal JS + +--- + +## 1. Arsitektur Baru + +Hostkeeper V2 menggunakan arsitektur **server-rendered HTML** dengan interaktivitas via HTMX, bukan SPA React. + +``` +┌────────────────────────────────────────────────────┐ +│ Browser / Electron WebView │ +│ ┌──────────────────────────────────────────────┐ │ +│ │ HTMX (14kb) + Alpine.js (15kb) + xterm.js │ │ +│ │ │ │ +│ │ ● HTMX: semua interaksi (search, filter, │ │ +│ │ modal, form, navigasi) │ │ +│ │ ● Alpine.js: client state (toast, toggle, │ │ +│ │ passphrase reveal, tab) │ │ +│ │ ● xterm.js: terminal emulator (hanya │ │ +│ │ halaman terminal) │ │ +│ │ ● Vanilla JS: clipboard, toggle switch, │ │ +│ │ strength calc │ │ +│ └────────────────┬─────────────────────────────┘ │ +│ │ hx-get/hx-post/hx-ws │ +│ ┌────────────────▼─────────────────────────────┐ │ +│ │ GoFiber v2 Backend (localhost:1947) │ │ +│ │ │ │ +│ │ ┌──────────────┐ ┌──────────────────────┐ │ │ +│ │ │ HTML handlers │ │ WebSocket /xterm.js │ │ │ +│ │ │ (Fiber + html│ │ (SSH I/O via WS) │ │ │ +│ │ │ template) │ └──────────────────────┘ │ │ +│ │ └──────┬───────┘ │ │ +│ │ │ go.mod replace │ │ +│ │ ┌────▼────┐ │ │ +│ │ │ v1/pkg/ │ ssh, crypto, storage, config │ │ +│ │ └─────────┘ │ │ +│ └───────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ +``` + +### Alur Request + +``` +1. User klik tombol "Filter Active" + │ hx-get="/hosts?filter=active" + │ hx-target="#host-list" + ▼ +2. GoFiber handler: + func (h *Handler) ListHosts(c fiber.Ctx) error { + hosts := h.store.Filter("active") + return c.Render("partials/host_list", fiber.Map{ + "Hosts": hosts, + }) + } + ▼ +3. Server return HTML partial +
+ {{range .Hosts}} +
...
+ {{end}} +
+ ▼ +4. HTMX swap HTML ke DOM (tanpa reload) +``` + +--- + +## 2. Tech Stack + +| Layer | Teknologi | Versi | Fungsi | +|-------|-----------|-------|--------| +| Backend | GoFiber v2 | v2.52+ | HTTP server, routing, middleware | +| Template | html/template | stdlib | Server-side HTML rendering | +| Frontend | HTMX | v2.0+ | HTML-over-HTTP interaktivitas | +| Client state | Alpine.js | v3.14+ | Modal, toast, toggle state | +| Terminal | xterm.js | v5.5+ | SSH terminal emulator | +| Styling | TailwindCSS v4 | v4.0+ | Utility-first CSS | +| CSS custom | Custom CSS | — | dot-grid, glassmorphism, keyframes | +| Icons | Lucide (inline SVG) | — | SVG icon system | +| Desktop | Electron (opsional) | v35+ | Browser wrapper | +| Mobile | WebView | — | Android/iOS shell | + +--- + +## 3. Struktur Folder + +``` +app/backend/ +├── main.go ← Entry point Fiber app +├── go.mod ← module git.tukangketik.id/swanadiva/hostkeeper/v2 +├── go.sum +│ +├── internal/ +│ ├── handler/ ← HTTP handlers (render HTML + HTMX partials) +│ │ ├── dashboard.go GET/POST /hosts, /hosts/{id} +│ │ ├── terminal.go GET /terminal, WS /terminal/ws +│ │ ├── sftp.go GET /sftp/ls, POST /sftp/{action} +│ │ ├── snippets.go GET/POST /snippets +│ │ ├── keychain.go GET/POST /keys +│ │ ├── settings.go GET/POST /settings +│ │ ├── brief.go GET /brief (overlay) +│ │ └── health.go GET /api/health +│ │ +│ ├── model/ ← Go structs (data models) +│ │ ├── host.go Host, HostFilter +│ │ ├── file.go FileItem +│ │ ├── snippet.go Snippet +│ │ ├── keychain.go KeychainItem +│ │ ├── device.go Device +│ │ ├── transfer.go BackgroundTransfer +│ │ └── config.go Config, Toggle +│ │ +│ ├── template/ ← Template helpers/funcs +│ │ └── funcs.go activeClass, formatBytes, timeAgo, strengthBadge +│ │ +│ └── middleware/ +│ └── viewdata.go Inject .View, .ActiveNav, .User ke semua template +│ +├── static/ ← Static assets (served via Fiber) +│ ├── css/ +│ │ ├── output.css ← TailwindCSS v4 build output +│ │ └── custom.css ← dot-grid, glassmorphism, keyframes, scrollbar +│ ├── js/ +│ │ ├── htmx.min.js ← HTMX (14kb) +│ │ ├── alpine.min.js ← Alpine.js (15kb) +│ │ ├── terminal.js ← xterm.js + WebSocket init +│ │ ├── clipboard.js ← navigator.clipboard.writeText() +│ │ ├── toggles.js ← Custom toggle switch click handler +│ │ └── utils.js ← passphrase reveal, strength calc, toast +│ ├── xterm/ ← xterm.js build files +│ └── img/ ← Images/icons +│ +└── views/ ← Go HTML templates + ├── layout.html ← Base layout (sidebar, header, scripts) + ├── index.html ← Entry page (extends layout + content block) + │ + └── partials/ ← HTMX-swappable partials + ├── nav.html ← Sidebar navigation + ├── dashboard.html ← Host page container + ├── host_list.html ← Host card grid/list (HTMX swap target) + ├── host_card.html ← Single host card + ├── host_modal.html ← Add host modal form + ├── terminal.html ← Terminal page (xterm.js container) + ├── sftp.html ← Dual-pane file browser + ├── sftp_pane.html ← Single file pane (for HTMX swap) + ├── snippets.html ← Snippet page + ├── snippet_grid.html ← Snippet cards (HTMX swap target) + ├── snippet_modal.html ← Add snippet form + ├── keychain.html ← Key page + ├── key_grid.html ← Key cards (HTMX swap target) + ├── key_modal.html ← Add key form + ├── settings.html ← Settings form + toggles + ├── brief.html ← Brief overlay panel + └── toast.html ← Toast notification +``` + +--- + +## 4. Komponen UI & State Management + +### 4.1 Navigasi (HTMX) + +Sidebar navigation menggunakan HTMX untuk swap halaman: + +```html + + {{svg "server"}} + Hosts + +``` + +### 4.2 State Client (Alpine.js) + +State yang butuh client-side reactivity: + +```html + +
+ +
+ {{template "host_list" .}} +
+
+ + +
+ + +
+ + +
+ +
+``` + +### 4.3 JS Murni (Vanilla) + +```javascript +// clipboard.js — copy to clipboard +function copyToClipboard(text) { + navigator.clipboard.writeText(text); + // trigger toast via Alpine + document.querySelector('[x-data]').__x.$data.toast = 'Copied!'; + document.querySelector('[x-data]').__x.$data.show = true; +} + +// toggles.js — custom toggle switch +function toggleSwitch(el) { + const checked = el.getAttribute('aria-checked') === 'true'; + el.setAttribute('aria-checked', !checked); + fetch(`/settings/toggle?key=${el.dataset.key}&val=${!checked}`, { method: 'POST' }); +} +``` + +--- + +## 5. Data Models (Go Structs) + +Semua data model di `internal/model/`, mapping dari template React `types.ts`: + +```go +// model/host.go +type Host struct { + ID string `json:"id"` + Name string `json:"name"` + IP string `json:"ip"` + OS string `json:"os"` + Provider string `json:"provider"` + Status string `json:"status"` // "active" | "offline" + LastSeen string `json:"lastSeen"` + Type string `json:"type"` // "api" | "db" | "edge" | "web" | "desktop" | "server" +} + +// model/snippet.go +type Snippet struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Code string `json:"code"` + Tags []string `json:"tags"` + Collection string `json:"collection"` // "favorites" | "recent" | "all" + UpdatedAt string `json:"updatedAt"` + Icon string `json:"icon"` +} +``` + +Lihat `docs/DATA_MODELS.md` untuk definisi lengkap. + +--- + +## 6. HTMX Patterns yang Digunakan + +| Pattern | Contoh | Keterangan | +|---------|--------|------------| +| Search | `hx-get="/hosts?q=..." hx-trigger="keyup delay:200ms" hx-target="#host-list"` | Debounced search | +| Filter | `hx-get="/hosts?filter=active" hx-target="#host-list"` | Tab filter | +| Modal | Alpine `x-show` + `hx-post` | Form dalam modal | +| Infinite scroll | `hx-get="/hosts?page=2" hx-trigger="revealed" hx-target="#host-list" hx-swap="beforeend"` | Load more | +| Delete | `hx-delete="/hosts/123" hx-target="#host-card-123" hx-swap="delete" hx-confirm="Yakin?"` | Confirm + delete | +| Toast | Alpine state + `hx-trigger="htmx:afterRequest"` dari response header `HX-Trigger: showToast` | Notifikasi | +| Tab | Alpine `x-data` + `x-show` | Panel tab | + +--- + +## 7. Keuntungan HTMX Dibanding React + +| Aspek | React (sebelumnya) | HTMX (sekarang) | +|-------|-------------------|-----------------| +| Dependency JS | 10+ library (React, Zustand, dll) | 3 library (HTMX, Alpine, xterm) | +| Baris JS | ~2.878 baris TS/JSX | ~410 baris vanilla JS | +| Build step | Vite + TypeScript + npm | CSS build + copy static | +| Bahasa dominan | 60% TS + 40% Go | 90% Go + 10% HTML/JS | +| State management | Zustand store | Server-side (HTMX) + Alpine | +| API layer | REST JSON + manual fetch | HTML partials langsung | +| Electron | Wajib (SPA) | Opsional (bisa browser) | +| Debugging | React DevTools | Browser DevTools (HTML langsung) | +| Time to implement | ~6 minggu | ~4 minggu (estimasi) | + +--- + +## 8. Cara Kerja: React → HTMX Mapping per Halaman + +### Dashboard (Host List) + +| React | HTMX | +|-------|------| +| `useState` searchQuery | `hx-get="/hosts?q={search}"` | +| `useState` statusFilter | `hx-get="/hosts?filter=active"` | +| `useState` viewMode | Alpine `x-data="{ view: 'grid' }"` | +| `useState` showAddModal | Alpine `x-show="open"` | +| `onChange` filter | HTML `` | +| 1.8 | View toggle (grid/list) | Alpine `x-data` | +| 1.9 | Add host modal | `views/partials/host_modal.html` + Alpine | +| 1.10 | POST /hosts | Handler create host | +| 1.11 | Background transfers | `views/partials/dashboard.html` | + +### Files to Create (Sprint 1) -**Example handler** (`bridge/hosts.go`): -```go -func ListHosts(c *fiber.Ctx) error { - hosts, err := storage.ListHosts(c.Context()) - if err != nil { - return c.Status(500).JSON(fiber.Map{"error": err.Error()}) - } - return c.JSON(fiber.Map{"items": hosts, "total": len(hosts)}) -} ``` - -### Day 3: Groups + Config API - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 1.7 | Group CRUD handlers | `app/backend/bridge/groups.go` | Tree structure returned | -| 1.8 | Config handlers | `app/backend/bridge/config.go` | Get/set config works | -| 1.9 | Vault handlers | `app/backend/bridge/vault.go` | Lock/unlock/unlock status | -| 1.10 | Group tree builder | `app/backend/bridge/groups.go` | Recursive tree populated | - -### Day 4: Web UI Scaffold - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 1.11 | React Router | `app/frontend/src/router.tsx` | Navigate between screens | -| 1.12 | Layout component | `app/frontend/src/components/Layout.tsx` | Sidebar + tab bar + content | -| 1.13 | Sidebar component | `app/frontend/src/components/Sidebar.tsx` | Group tree + search | -| 1.14 | Theme system | `app/frontend/src/styles/themes.css` | CSS variables for dark/light | -| 1.15 | API client | `app/frontend/src/api/client.ts` | Fetch wrapper with errors | -| 1.16 | Zustand stores | `app/frontend/src/stores/*.ts` | Host, tab, vault stores | -| 1.17 | Keyboard shortcuts | `app/frontend/src/hooks/useKeyboard.ts` | Cmd+N, Cmd+K work | - -### Day 5: Host List Screen - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 1.18 | HostList screen | `app/frontend/src/screens/HostListScreen.tsx` | Hosts displayed | -| 1.19 | HostCard component | `app/frontend/src/components/HostCard.tsx` | Host info shown | -| 1.20 | GroupTree component | `app/frontend/src/components/GroupTree.tsx` | Collapsible tree | -| 1.21 | SearchBar component | `app/frontend/src/components/SearchBar.tsx` | Filter works | -| 1.22 | EmptyState component | `app/frontend/src/components/EmptyState.tsx` | Shown when no hosts | -| 1.23 | Context menu | `app/frontend/src/components/ContextMenu.tsx` | Right-click menu | - ---- - -## Sprint 2 — Core Features (Day 6-10) - -**Goal**: Host form + SSH terminal (the critical feature). - -### Day 6-7: Host Form + CRUD UI - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 2.1 | HostForm screen | `app/frontend/src/screens/HostFormScreen.tsx` | Add/edit host works | -| 2.2 | AuthSection component | `app/frontend/src/components/AuthSection.tsx` | Password/key selector | -| 2.3 | TagsInput component | `app/frontend/src/components/TagsInput.tsx` | Add/remove tags | -| 2.4 | GroupPicker component | `app/frontend/src/components/GroupPicker.tsx` | Tree picker | -| 2.5 | Form validation | `app/frontend/src/utils/validation.ts` | All field validation | -| 2.6 | Toast notifications | `app/frontend/src/components/Toast.tsx` | Success/error toasts | - -### Day 8-10: SSH Terminal (MOST CRITICAL) - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 2.7 | WebSocket handler | `app/backend/ws/terminal.go` | WS connection accepted | -| 2.8 | SSH session management | `app/backend/ws/session.go` | Connect, stream, disconnect | -| 2.9 | Terminal resize | `app/backend/ws/terminal.go` | Resize messages handled | -| 2.10 | XTermWrapper component | `app/frontend/src/components/XTermWrapper.tsx` | Terminal renders | -| 2.11 | TerminalScreen | `app/frontend/src/screens/TerminalScreen.tsx` | Full terminal view | -| 2.12 | TabBar component | `app/frontend/src/components/TabBar.tsx` | Multi-tab support | -| 2.13 | Tab management | `app/frontend/src/stores/tabStore.ts` | Add/close/switch tabs | -| 2.14 | SnippetPanel | `app/frontend/src/components/SnippetPanel.tsx` | Slide-in snippet list | -| 2.15 | Connection overlay | `app/frontend/src/components/ConnectionOverlay.tsx` | Status + reconnect | -| 2.16 | Terminal toolbar | `app/frontend/src/components/TerminalToolbar.tsx` | Host info + actions | - -**WebSocket handler** (`ws/terminal.go`): -```go -package ws - -import ( - "encoding/json" - "log" - "time" - - "github.com/gofiber/contrib/websocket" - "git.tukangketik.id/swanadiva/hostkeeper/pkg/ssh" -) - -func HandleTerminal(c *websocket.Conn) { - hostID := c.Query("host_id") - cols := c.QueryInt("cols", 80) - rows := c.QueryInt("rows", 24) - - // Get host from storage - host, err := storage.GetHost(c.Context(), hostID) - if err != nil { - c.WriteJSON(fiber.Map{"type": "error", "message": "Host not found"}) - return - } - - // Create SSH client - client := ssh.NewClient(host, 30*time.Second) - if err := client.Connect(c.Context()); err != nil { - c.WriteJSON(fiber.Map{"type": "error", "message": err.Error()}) - return - } - defer client.Close() - - // Create session + PTY - session, err := client.GetClient().NewSession() - if err != nil { - c.WriteJSON(fiber.Map{"type": "error", "message": "Session failed"}) - return - } - defer session.Close() - - modes := ssh.TerminalModes{ssh.ECHO: 1} - session.RequestPty("xterm-256color", rows, cols, modes) - - stdin, _ := session.StdinPipe() - stdout, _ := session.StdoutPipe() - - if err := session.Shell(); err != nil { - c.WriteJSON(fiber.Map{"type": "error", "message": "Shell failed"}) - return - } - - c.WriteJSON(fiber.Map{"type": "status", "state": "connected"}) - - // Goroutine: SSH stdout -> WebSocket - go func() { - buf := make([]byte, 4096) - for { - n, err := stdout.Read(buf) - if err != nil { - break - } - if err := c.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil { - break - } - } - }() - - // Main loop: WebSocket -> SSH stdin - for { - _, msg, err := c.ReadMessage() - if err != nil { - break - } - - // Check for resize message - if len(msg) > 0 && msg[0] == '{' { - var ctrl struct { - Type string `json:"type"` - Cols int `json:"cols"` - Rows int `json:"rows"` - } - if json.Unmarshal(msg, &ctrl) == nil && ctrl.Type == "resize" { - session.WindowChange(ctrl.Rows, ctrl.Cols) - continue - } - } - - // Regular input - stdin.Write(msg) - } -} +internal/model/host.go +views/partials/dashboard.html +views/partials/host_list.html +views/partials/host_card.html +views/partials/host_modal.html +views/partials/toast.html +static/js/clipboard.js ← mulai buat ``` --- -## Sprint 3 — SFTP + Polish (Day 11-15) +## Sprint 2 — Snippets + Keychain -**Goal**: SFTP browser + security + settings. +**Goal**: Snippet library + credential keychain dengan CRUD. -### Day 11-12: SFTP Browser +### Tasks -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 3.1 | SFTP REST handlers | `app/backend/bridge/sftp.go` | All 7 endpoints work | -| 3.2 | SFTPScreen | `app/frontend/src/screens/SFTPScreen.tsx` | Dual pane visible | -| 3.3 | FileList component | `app/frontend/src/components/FileList.tsx` | Files listed with columns | -| 3.4 | LocalPane component | `app/frontend/src/components/LocalPane.tsx` | Local files | -| 3.5 | RemotePane component | `app/frontend/src/components/RemotePane.tsx` | Remote files | -| 3.6 | TransferQueue | `app/frontend/src/components/TransferQueue.tsx` | Transfer progress | -| 3.7 | SFTPToolbar | `app/frontend/src/components/SFTPToolbar.tsx` | Upload/download buttons | -| 3.8 | Context menu | `app/frontend/src/components/SFTPContextMenu.tsx` | File operations | +| # | Task | Files | +|---|------|-------| +| 2.1 | Model Snippet | `internal/model/snippet.go` | +| 2.2 | Model Keychain | `internal/model/keychain.go` | +| 2.3 | Snippets handler | `internal/handler/snippets.go` | +| 2.4 | Keychain handler | `internal/handler/keychain.go` | +| 2.5 | Snippets template | `views/partials/snippets.html` | +| 2.6 | Snippet grid partial | `views/partials/snippet_grid.html` | +| 2.7 | Snippet modal | `views/partials/snippet_modal.html` | +| 2.8 | Keychain template | `views/partials/keychain.html` | +| 2.9 | Key grid partial | `views/partials/key_grid.html` | +| 2.10 | Key modal | `views/partials/key_modal.html` | +| 2.11 | Clipboard JS | `static/js/clipboard.js` | +| 2.12 | Passphrase reveal + strength | `static/js/utils.js` | -### Day 13: Security + Vault - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 3.9 | VaultScreen | `app/frontend/src/screens/VaultScreen.tsx` | Password prompt | -| 3.10 | KnownHostDialog | `app/frontend/src/components/KnownHostDialog.tsx` | Fingerprint verify | -| 3.11 | PasswordPrompt | `app/frontend/src/components/PasswordPrompt.tsx` | Reusable prompt | -| 3.12 | Auto-lock logic | `app/frontend/src/hooks/useAutoLock.ts` | Locks after inactivity | - -### Day 14: Settings Screen - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 3.13 | SettingsScreen | `app/frontend/src/screens/SettingsScreen.tsx` | Tabbed settings | -| 3.14 | AppearanceSettings | `app/frontend/src/components/settings/Appearance.tsx` | Theme, font | -| 3.15 | TerminalSettings | `app/frontend/src/components/settings/Terminal.tsx` | Scrollback, cursor | -| 3.16 | ConnectionSettings | `app/frontend/src/components/settings/Connection.tsx` | Timeout, keepalive | -| 3.17 | VaultSettings | `app/frontend/src/components/settings/Vault.tsx` | Password, auto-lock | -| 3.18 | GeneralSettings | `app/frontend/src/components/settings/General.tsx` | About, data dir | - -### Day 15: Mobile Setup - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 3.19 | gomobile build script | `mobile/gomobile/build.sh` | Compiles to .aar/.xcframework | -| 3.20 | Android WebView app | `mobile/android/` | App runs on emulator | -| 3.21 | iOS WebView app | `mobile/ios/` | App runs on simulator | -| 3.22 | Mobile responsive CSS | `app/frontend/src/styles/mobile.css` | Touch-friendly | - ---- - -## Sprint 4 — Advanced Features (Day 16-20) - -**Goal**: Port forwarding + import/export + jump hosts. - -### Day 16-17: Port Forwarding - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 4.1 | Forward model + storage | `app/backend/bridge/portforward.go` | CRUD works | -| 4.2 | Local forwarding | `app/backend/ws/portforward.go` | Local tunnel works | -| 4.3 | Remote forwarding | `app/backend/ws/portforward.go` | Remote tunnel works | -| 4.4 | Dynamic forwarding | `app/backend/ws/portforward.go` | SOCKS5 proxy works | -| 4.5 | PortForwardScreen | `app/frontend/src/screens/PortForwardScreen.tsx` | List + add form | -| 4.6 | ForwardCard component | `app/frontend/src/components/ForwardCard.tsx` | Status + controls | - -### Day 18: Import/Export - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 4.7 | SSH config parser | `app/backend/bridge/import.go` | Parses ~/.ssh/config | -| 4.8 | CSV import | `app/backend/bridge/import.go` | Parses CSV | -| 4.9 | Export handler | `app/backend/bridge/export.go` | Encrypted JSON export | -| 4.10 | ImportScreen | `app/frontend/src/screens/ImportScreen.tsx` | Drag & drop UI | - -### Day 19-20: Jump Hosts + Proxy - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 4.11 | Jump host chain | `app/backend/ws/jump.go` | Multi-hop SSH works | -| 4.12 | SOCKS5 proxy | `app/backend/ws/proxy.go` | Proxy before SSH | -| 4.13 | AdvancedHostForm | `app/frontend/src/components/AdvancedHostForm.tsx` | Jump host selector | - ---- - -## Sprint 5 — Platform Polish (Day 21-25) - -**Goal**: Desktop polish + performance + mobile. - -### Day 21-22: Desktop Enhancements - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 5.1 | Native menu bar | `app/electron/menu.ts` | File/Edit/View/Help | -| 5.2 | System tray | `app/electron/tray.ts` | Tray icon + menu | -| 5.3 | Global shortcuts | `app/electron/shortcuts.ts` | Cmd+`, Cmd+N | -| 5.4 | Window state | `app/electron/state.ts` | Position/size saved | -| 5.5 | Auto-update | `app/electron/updater.ts` | Update prompt | -| 5.6 | OS Keychain | `app/electron/keychain.ts` | Store vault key | - -### Day 23: Performance Optimization - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 5.7 | SSH connection pool | `app/backend/ws/pool.go` | Reuse connections | -| 5.8 | Buffer pools | `app/backend/ws/buffers.go` | sync.Pool for buffers | -| 5.9 | Virtual scrolling | `app/frontend/src/components/VirtualList.tsx` | 1000+ hosts smooth | -| 5.10 | Code splitting | `app/frontend/src/router.tsx` | Lazy load screens | -| 5.11 | React.memo | Various components | No unnecessary re-renders | -| 5.12 | WebGL renderer | `app/frontend/src/components/XTermWrapper.tsx` | Fast terminal render | - -### Day 24-25: Mobile Polish - -| # | Task | Files to Create | Verify | -|---|------|----------------|--------| -| 5.13 | Android gomobile | `mobile/android/` | .aar compiled | -| 5.14 | iOS gomobile | `mobile/ios/` | .xcframework compiled | -| 5.15 | Mobile keyboard | `app/frontend/src/components/MobileKeyboard.tsx` | Terminal toolbar | -| 5.16 | Gesture navigation | `app/frontend/src/hooks/useGesture.ts` | Swipe to switch tabs | -| 5.17 | Landscape handling | `app/frontend/src/styles/mobile.css` | Responsive layout | -| 5.18 | Bluetooth keyboard | `app/frontend/src/hooks/useKeyboard.ts` | External keyboard | - ---- - -## Dependency Graph +### Files to Create (Sprint 2) ``` -Sprint 0 (Scaffolding) - │ - ├── Sprint 1 (Foundation) - │ │ - │ ├── Sprint 2 (Core Features) - │ │ │ - │ │ ├── Sprint 3 (SFTP + Polish) - │ │ │ │ - │ │ │ └── Sprint 5 (Platform Polish) - │ │ │ - │ │ └── Sprint 4 (Advanced Features) - │ │ │ - │ │ └── Sprint 5 (Platform Polish) +internal/model/snippet.go +internal/model/keychain.go +internal/handler/snippets.go +internal/handler/keychain.go +views/partials/snippets.html +views/partials/snippet_grid.html +views/partials/snippet_modal.html +views/partials/keychain.html +views/partials/key_grid.html +views/partials/key_modal.html +static/js/clipboard.js +static/js/utils.js ``` -**Critical path**: Sprint 0 → Sprint 1 → Sprint 2 → Sprint 5 - -**Parallel tracks**: -- Sprint 3 and Sprint 4 can be done in parallel -- Sprint 5 can start after Sprint 2 (desktop polish), Sprint 3 (SFTP mobile), Sprint 4 (advanced mobile) - --- -## File Creation Summary +## Sprint 3 — Settings + Brief Overlay -| Sprint | New Files | Directories | -|--------|-----------|-------------| -| Sprint 0 | 12 | 6 | -| Sprint 1 | 23 | 2 | -| Sprint 2 | 16 | 2 | -| Sprint 3 | 22 | 4 | -| Sprint 4 | 13 | 1 | -| Sprint 5 | 18 | 2 | -| **Total** | **104** | **17** | +**Goal**: Settings page with profile form, toggles, connected devices. + +### Tasks + +| # | Task | Files | +|---|------|-------| +| 3.1 | Model Device + Config | `internal/model/device.go`, `internal/model/config.go` | +| 3.2 | Settings handler | `internal/handler/settings.go` | +| 3.3 | Brief handler | `internal/handler/brief.go` | +| 3.4 | Settings template | `views/partials/settings.html` | +| 3.5 | Brief template | `views/partials/brief.html` | +| 3.6 | Toggle switch JS | `static/js/toggles.js` | +| 3.7 | Toggle CSS | In custom.css | +| 3.8 | Danger zone | `localStorage.clear()` handler | + +### Files to Create (Sprint 3) + +``` +internal/model/device.go +internal/model/config.go +internal/handler/settings.go +internal/handler/brief.go +views/partials/settings.html +views/partials/brief.html +static/js/toggles.js +``` + +--- + +## Sprint 4 — SFTP Dual-Pane + +**Goal**: File browser dual-pane dengan navigasi folder, upload/download. + +### Tasks + +| # | Task | Files | +|---|------|-------| +| 4.1 | Model FileItem | `internal/model/file.go` | +| 4.2 | Model Transfer | `internal/model/transfer.go` | +| 4.3 | SFTP handler | `internal/handler/sftp.go` | +| 4.4 | SFTP template | `views/partials/sftp.html` | +| 4.5 | SFTP pane partial | `views/partials/sftp_pane.html` | +| 4.6 | Breadcrumb navigation | In sftp.html | +| 4.7 | Folder create + delete | Modal Alpine + POST | +| 4.8 | Toast notification | Alpine `x-data` | + +### Files to Create (Sprint 4) + +``` +internal/model/file.go +internal/model/transfer.go +internal/handler/sftp.go +views/partials/sftp.html +views/partials/sftp_pane.html +``` + +--- + +## Sprint 5 — Terminal + xterm.js + +**Goal**: SSH terminal via WebSocket + xterm.js dengan multi-tab. + +### Tasks + +| # | Task | Files | +|---|------|-------| +| 5.1 | Download xterm.js | `static/xterm/xterm.js`, `static/xterm/xterm.css` | +| 5.2 | Terminal handler | `internal/handler/terminal.go` | +| 5.3 | WebSocket handler | `internal/handler/terminal.go` (WS upgrade) | +| 5.4 | Terminal template | `views/partials/terminal.html` | +| 5.5 | Terminal JS | `static/js/terminal.js` (xterm init + WS) | +| 5.6 | Tab management | Alpine `x-data="terminalTabs()"` | +| 5.7 | SSH pipe (mock dulu) | Go backend → SSH via v1/pkg/ssh | +| 5.8 | Multi-tab per host | Alpine state + multiple xterm instances | + +### Files to Create (Sprint 5) + +``` +internal/handler/terminal.go +views/partials/terminal.html +static/js/terminal.js +static/xterm/xterm.js +static/xterm/xterm.css +``` + +--- + +## Sprint 6 — Data Store Integration + +**Goal**: Ganti mock data dengan real storage dari v1/pkg/. Integrasi SSH, SFTP, crypto. + +### Tasks + +| # | Task | Files | +|---|------|-------| +| 6.1 | replace directive go.mod | `app/backend/go.mod` | +| 6.2 | Integrate v1/pkg/storage | Semua handler | +| 6.3 | Integrate v1/pkg/config | `handler/settings.go` | +| 6.4 | Integrate v1/pkg/crypto | `handler/keychain.go` | +| 6.5 | Integrate v1/pkg/knownhosts | `handler/terminal.go` | +| 6.6 | Real SSH via v1/pkg/ssh | `handler/terminal.go` WS | +| 6.7 | Real SFTP via v1/pkg/ssh | `handler/sftp.go` | +| 6.8 | Remove all mock data | Semua handler | + +--- + +## Total Files + +| Sprint | Go Files | HTML Templates | JS Files | CSS Files | +|--------|----------|---------------|----------|-----------| +| Sprint 0 | 4 | 3 | 2 | 2 | +| Sprint 1 | 2 | 5 | 1 | 0 | +| Sprint 2 | 4 | 6 | 2 | 0 | +| Sprint 3 | 4 | 2 | 1 | 0 | +| Sprint 4 | 3 | 2 | 0 | 0 | +| Sprint 5 | 1 | 1 | 1 | 0 | +| Sprint 6 | (modify existing) | 0 | 0 | 0 | +| **Total** | **18** | **19** | **7** | **2** | diff --git a/docs/TIMELINE.md b/docs/TIMELINE.md index ef2e047..0a87418 100644 --- a/docs/TIMELINE.md +++ b/docs/TIMELINE.md @@ -1,316 +1,129 @@ -# Hostkeeper V2 — Timeline & Milestones +# Hostkeeper V2 — Timeline & Milestones (HTMX) -> **Status**: V2 Planning Complete -> **Last Updated**: 2026-06-29 -> **Start Date**: 2026-06-30 (estimated) -> **Target MVP**: 2026-08-07 (estimated) +> **Status**: Updated untuk HTMX +> **Last Updated**: 2026-07-07 +> **Start Date**: 2026-07-07 +> **Target MVP**: 2026-08-01 (estimasi ~3.5 minggu) --- -## 1. Timeline Overview - -Total estimated: **25-30 working days** (~6 minggu calendar, 1-2 minggu buffer) +## Timeline Overview ``` -Minggu 1 │ Sprint 0 ─ Sprint 1 ─ │──── Foundation -Minggu 2 │ Sprint 2 ─────────────│──── Core (Terminal) -Minggu 3 │ Sprint 3 ─────────────│──── SFTP + Polish -Minggu 4 │ Sprint 4 ─────────────│──── Advanced -Minggu 5 │ Sprint 5 ── Buffer ───│──── Polish + Release +Minggu 1 │ Sprint 0 ─ Sprint 1 ─ │── Foundation + Dashboard +Minggu 2 │ Sprint 2 ─ Sprint 3 ─ │── Snippets + Keychain + Settings +Minggu 3 │ Sprint 4 ─────────────│── SFTP +Minggu 4 │ Sprint 5 ─ Sprint 6 ─ │── Terminal + Storage Integration └────────────────────────┘ - 26 Jun 10 Jul 24 Jul 7 Aug + 7 Jul 14 Jul 21 Jul 28 Jul ``` --- -## 2. Detailed Timeline +## Detailed Timeline -### Phase 0: Foundation (Sprint 0 + Sprint 1) -**6 working days — 30 Jun s.d. 7 Jul** +### Sprint 0 + Sprint 1 — Foundation + Dashboard +**5 hari — 7 Jul s.d. 11 Jul** -| Day | Date | Sprint | Deliverable | Milestone | -|-----|------|--------|-------------|-----------| -| 1 | 30 Jun | Sprint 0 | Go module, Electron shell, Vite scaffold | App starts, shows "Hostkeeper V2" | -| 2 | 1 Jul | Sprint 0 | Health endpoint, middleware, build script | `curl /api/health` returns OK | -| 3 | 2 Jul | Sprint 1 | Host CRUD API (6 endpoints) | `curl POST /api/hosts` returns 201 | -| 4 | 3 Jul | Sprint 1 | Key + Snippet CRUD API | All CRUD works via curl | -| 5 | 4 Jul | Sprint 1 | Groups + Config + Vault API | Group tree returned | -| 6 | 7 Jul | Sprint 1 | Web UI scaffold (layout, router, theme) | Navigation works, sidebar visible | +| Day | Date | Deliverable | +|-----|------|-------------| +| 1 | 7 Jul | Go module, Fiber server, static files, HTMX + Alpine | +| 2 | 8 Jul | Layout template, nav sidebar, health endpoint | +| 3 | 9 Jul | Host model, dashboard handler, host list template | +| 4 | 10 Jul | Search HTMX, filter, view toggle, add host modal | +| 5 | 11 Jul | Background transfers, toast notification, CSS final | -**Milestone M1 — API Complete (7 Jul)** -- All REST API endpoints functional -- Storage integration with V1 `pkg/storage` -- Web UI layout renders +**Milestone M1 — Dashboard Functional (11 Jul)** +- GoFiber server running on :1947 +- Layout dengan sidebar navigasi +- Dashboard dengan host list, search, filter, add host +- HTMX interaktivitas berfungsi ---- +### Sprint 2 — Snippets + Keychain +**3 hari — 14 Jul s.d. 16 Jul** -### Phase 1: Core Features (Sprint 2) -**5 working days — 8 Jul s.d. 14 Jul** +| Day | Date | Deliverable | +|-----|------|-------------| +| 6 | 14 Jul | Snippet model + handler + template + search/filter | +| 7 | 15 Jul | Add snippet modal, clipboard JS, tag filter | +| 8 | 16 Jul | Keychain model + handler + template + passphrase reveal | -| Day | Date | Deliverable | Milestone | -|-----|------|-------------|-----------| -| 7 | 8 Jul | Host List screen + HostForm UI | Add/edit host from browser | -| 8 | 9 Jul | HostForm: auth, tags, group picker | All form fields work | -| 9 | 10 Jul | WebSocket handler (Go) | Terminal WS connects | -| 10 | 11 Jul | XTermWrapper + terminal UI | Terminal renders, keystrokes work | -| 11 | 14 Jul | TabBar + multi-tab + snippet panel | Multi-tab terminal with snippets | +**Milestone M2 — Snippets + Keys CRUD (16 Jul)** +- Snippet library dengan search, tag filter, add modal +- Keychain dengan strength badge, passphrase reveal, copy -**Milestone M2 — Terminal Works (14 Jul)** -- SSH terminal via WebSocket +### Sprint 3 — Settings + Brief +**2 hari — 17 Jul s.d. 18 Jul** + +| Day | Date | Deliverable | +|-----|------|-------------| +| 9 | 17 Jul | Settings handler + template + toggle switches | +| 10 | 18 Jul | Brief overlay, connected devices, danger zone | + +**Milestone M3 — Settings Complete (18 Jul)** +- Profile form, toggle switches, connected devices +- Brief design system overlay + +### Sprint 4 — SFTP +**4 hari — 21 Jul s.d. 24 Jul** + +| Day | Date | Deliverable | +|-----|------|-------------| +| 11 | 21 Jul | FileItem + Transfer model, SFTP handler | +| 12 | 22 Jul | Dual-pane layout, breadcrumb navigation | +| 13 | 23 Jul | Folder create/delete, upload/download | +| 14 | 24 Jul | Search pane, toast notification, polish | + +**Milestone M4 — SFTP Works (24 Jul)** +- Dual-pane file browser dengan navigasi folder +- Upload/download, create/delete folder + +### Sprint 5 — Terminal +**3 hari — 25 Jul s.d. 28 Jul** + +| Day | Date | Deliverable | +|-----|------|-------------| +| 15 | 25 Jul | xterm.js setup, WebSocket handler | +| 16 | 26 Jul | SSH pipe via v1/pkg/ssh, terminal I/O | +| 17 | 28 Jul | Multi-tab Alpine, polish, error handling | + +**Milestone M5 — Terminal Works (28 Jul)** +- SSH terminal via WebSocket + xterm.js - Multi-tab support -- Snippet injection + +### Sprint 6 — Storage Integration + Polish +**3 hari — 29 Jul s.d. 31 Jul** + +| Day | Date | Deliverable | +|-----|------|-------------| +| 18 | 29 Jul | replace directive, v1/pkg/storage integration | +| 19 | 30 Jul | v1/pkg/crypto, config, knownhosts integration | +| 20 | 31 Jul | Remove mock data, final testing, bug fixes | + +**Milestone MVP — Hostkeeper V2 Ready (31 Jul)** +- All features functional with real data persistence +- Single Go binary, ready for Electron wrapping --- -### Phase 2: SFTP + Security (Sprint 3) -**5 working days — 15 Jul s.d. 21 Jul** +## Key Milestones -| Day | Date | Deliverable | Milestone | -|-----|------|-------------|-----------| -| 12 | 15 Jul | SFTP REST handlers (ls, upload, download) | SFTP API works via curl | -| 13 | 16 Jul | SFTP: mkdir, rm, rename, chmod | File operations work | -| 14 | 17 Jul | SFTPScreen + dual pane UI | SFTP browser functional | -| 15 | 18 Jul | Vault unlock + known hosts dialog | Security flow works | -| 16 | 21 Jul | Settings screen (all tabs) | Settings persist | - -**Milestone M3 — SFTP + Vault (21 Jul)** -- SFTP file browser with upload/download -- Vault lock/unlock flow -- Settings customization +| Milestone | Date | Deliverable | +|-----------|------|-------------| +| M1 | 11 Jul | Dashboard + search/filter + add host | +| M2 | 16 Jul | Snippets + Keychain CRUD | +| M3 | 18 Jul | Settings + Brief overlay | +| M4 | 24 Jul | SFTP dual-pane file browser | +| M5 | 28 Jul | Terminal SSH via WebSocket | +| MVP | 31 Jul | Full integration with real storage | --- -### Phase 3: Advanced Features (Sprint 4) -**5 working days — 22 Jul s.d. 28 Jul** +## Estimasi vs React (dulu) -| Day | Date | Deliverable | Milestone | -|-----|------|-------------|-----------| -| 17 | 22 Jul | PortForward CRUD + local tunnel | Local port forward works | -| 18 | 23 Jul | Remote + dynamic forwarding | All forward types work | -| 19 | 24 Jul | Import: SSH config + CSV parser | Bulk import works | -| 20 | 25 Jul | Export: all data encrypted | Export/import round-trip | -| 21 | 28 Jul | Jump host + proxy support | Multi-hop SSH works | - -**Milestone M4 — Advanced Complete (28 Jul)** -- Port forwarding (local/remote/dynamic) -- Import/export -- Jump hosts + proxy - ---- - -### Phase 4: Polish & Release (Sprint 5) -**5 working days — 29 Jul s.d. 4 Aug** - -| Day | Date | Deliverable | Milestone | -|-----|------|-------------|-----------| -| 22 | 29 Jul | Electron: menu, tray, shortcuts | Desktop polish complete | -| 23 | 30 Jul | OS Keychain + auto-update | Keychain integration | -| 24 | 31 Jul | Performance optimization | 1000 hosts smooth, memory < 200MB | -| 25 | 1 Aug | Android gomobile + WebView | Android app runs | -| 26 | 4 Aug | iOS gomobile + WKWebView | iOS app runs | - -**Milestone M5 — Release Candidate (4 Aug)** -- All features complete -- Desktop and mobile builds -- Performance targets met - ---- - -### Buffer: 3 days (5 Aug s.d. 7 Aug) - -| Day | Date | Activity | -|-----|------|----------| -| 27 | 5 Aug | Bug fixes from integration testing | -| 28 | 6 Aug | Documentation finalization | -| 29 | 7 Aug | Release v2.0.0 | - -**Milestone M6 — v2.0.0 Release (7 Aug)** -- All platforms built and signed -- Release notes complete -- Artifacts uploaded to GitHub - ---- - -## 3. Critical Path - -``` -Sprint 0 ── Sprint 1 ── Sprint 2 ── Sprint 5 ── Release - 1 day 5 days 5 days 5 days 3 days - ───────────────────────────────────────────────────────── - 19 days minimum (no parallel work) -``` - -### Parallel Work Opportunities - -| Sprint | Can run in parallel with | Notes | -|--------|-------------------------|-------| -| Sprint 1 | — | Must complete before Sprint 2 | -| Sprint 2 | — | Must complete before Sprint 3/4/5 | -| Sprint 3 | Sprint 4 | SFTP ≠ Port forwarding, different files | -| Sprint 4 | Sprint 3 | Independent features | -| Sprint 5 | Sprint 3, Sprint 4 | Desktop polish can start early | - -**Optimistic timeline** (with parallelism): 22 days -**Conservative timeline** (sequential): 29 days - ---- - -## 4. Dependency Graph - -``` -Sprint 0 (Scaffolding) - │ - ▼ -Sprint 1 (Foundation API) - │ - ▼ -Sprint 2 (SSH Terminal) ⭐ CRITICAL PATH - │ - ├────▶ Sprint 3 (SFTP + Security) ────▶ Sprint 5 (Desktop Polish) - │ │ - └────▶ Sprint 4 (Advanced) ────────────┘ - │ - ▼ - Release v2.0.0 -``` - -**Critical path**: Sprint 0 → Sprint 1 → Sprint 2 → Sprint 5 → Release - -**Fastest possible**: 22 hari (Sprint 3 + 4 done in parallel with Sprint 5) - ---- - -## 5. Milestone Summary - -| Milestone | Date | Deliverable | Dependency | -|-----------|------|-------------|------------| -| **M1** | 7 Jul | All REST API endpoints + Web UI scaffold | Sprint 0 + Sprint 1 | -| **M2** | 14 Jul | SSH terminal with multi-tab | M1 | -| **M3** | 21 Jul | SFTP browser + vault + settings | M2 | -| **M4** | 28 Jul | Port forwarding + import/export + jump hosts | M2 | -| **M5** | 4 Aug | Desktop polish + mobile apps | M2, M3, M4 | -| **M6** | 7 Aug | **v2.0.0 Release** | M5 | - ---- - -## 6. Weekly Breakdown - -### Week 1 (30 Jun - 4 Jul) — Foundation -``` -Mon 30: [Sprint 0] Go scaffold + Electron shell -Tue 1: [Sprint 0] Build script + middleware -Wed 2: [Sprint 1] Host CRUD API -Thu 3: [Sprint 1] Key + Snippet CRUD API -Fri 4: [Sprint 1] Groups + Config + Vault API -``` - -### Week 2 (7 Jul - 11 Jul) — API + UI Scaffold -``` -Mon 7: [Sprint 1] Web UI scaffold (layout, router, theme) ← M1 -Tue 8: [Sprint 2] Host List screen + HostForm -Wed 9: [Sprint 2] HostForm complete (auth, tags, groups) -Thu 10: [Sprint 2] WebSocket handler (Go) -Fri 11: [Sprint 2] XTermWrapper + terminal rendering -``` - -### Week 3 (14 Jul - 18 Jul) — Terminal + SFTP -``` -Mon 14: [Sprint 2] TabBar + multi-tab + snippet panel ← M2 -Tue 15: [Sprint 3] SFTP REST handlers -Wed 16: [Sprint 3] File operations (mkdir, rm, rename) -Thu 17: [Sprint 3] SFTPScreen + dual pane UI -Fri 18: [Sprint 3] Vault unlock + known hosts dialog -``` - -### Week 4 (21 Jul - 25 Jul) — SFTP Settings + Advanced -``` -Mon 21: [Sprint 3] Settings screen (all tabs) ← M3 -Tue 22: [Sprint 4] PortForward CRUD + local tunnel -Wed 23: [Sprint 4] Remote + dynamic forwarding -Thu 24: [Sprint 4] Import (SSH config, CSV) -Fri 25: [Sprint 4] Export + jump hosts -``` - -### Week 5 (28 Jul - 1 Aug) — Advanced + Polish -``` -Mon 28: [Sprint 4] Proxy support ← M4 -Tue 29: [Sprint 5] Electron: menu, tray, shortcuts -Wed 30: [Sprint 5] OS Keychain + auto-update -Thu 31: [Sprint 5] Performance optimization -Fri 1: [Sprint 5] Android gomobile + WebView -``` - -### Week 6 (4 Aug - 7 Aug) — Mobile + Release -``` -Mon 4: [Sprint 5] iOS gomobile + WKWebView ← M5 -Tue 5: [Buffer] Bug fixes -Wed 6: [Buffer] Documentation -Thu 7: [Release] v2.0.0 ← M6 -``` - ---- - -## 7. Risk Buffer - -| Risk | Impact | Probability | Mitigation | Buffer needed | -|------|--------|-------------|------------|---------------| -| Terminal WebSocket complexity | High | Medium | Use proven pattern from docs | +1 day | -| Mobile gomobile compilation | Medium | Medium | Start early, test on emulator | +1 day | -| Electron auto-update signing | Medium | Low | Prepare certificates early | — | -| Performance issues (1000 hosts) | Medium | Low | Virtual scrolling already planned | — | -| Unknown V1 package compatibility | Low | Low | V1 packages are framework-agnostic | — | -| UX polish takes longer | Medium | Medium | Prioritize function over beauty | +1 day | - -**Total buffer**: 3 days (included in timeline as 5-7 Aug) - ---- - -## 8. Effort by Technology - -| Technology | Days | % of total | Sprint | -|------------|------|------------|--------| -| Go (backend) | 10 | 34% | S0, S1, S2, S3, S4 | -| React/TypeScript (frontend) | 10 | 34% | S1, S2, S3, S4, S5 | -| Electron (desktop) | 3 | 10% | S0, S5 | -| Mobile (gomobile + WebView) | 2 | 7% | S3, S5 | -| DevOps (build, CI/CD) | 2 | 7% | S0, S5 | -| Testing + bug fixes | 2 | 7% | All | -| **Total** | **29** | **100%** | | - ---- - -## 9. What NOT in Scope (Post-MVP) - -| Feature | Reason | -|---------|--------| -| Cloud sync | Requires backend server, Phase 3 | -| Team vault | Requires multi-user auth, Phase 3 | -| Multiplayer terminal | Requires relay server, Phase 3 | -| AI command generation | LLM API integration, nice-to-have | -| Serial console | Niche use case, low priority | -| Mosh support | Requires additional library | -| Telnet support | Legacy protocol, low priority | - ---- - -## 10. Progress Tracking - -**How to update this timeline**: -1. At the end of each day, mark `[x]` for completed tasks -2. If a task takes longer, adjust subsequent dates -3. Update `docs/PROGRESS.md` with actual completion dates - -```markdown -### Week 1 Progress -[x] Mon 30 Jun: [Sprint 0] Go scaffold + Electron shell -[ ] Tue 1 Jul: [Sprint 0] Build script + middleware -[ ] Wed 2 Jul: [Sprint 1] Host CRUD API -[ ] Thu 3 Jul: [Sprint 1] Key + Snippet CRUD API -[ ] Fri 4 Jul: [Sprint 1] Groups + Config + Vault API -``` - ---- - -**Target**: v2.0.0 Release by **7 August 2026** -**Best case**: 22 July (parallel work, no blockers) -**Worst case**: 14 August (1 week buffer used + delays) +| Metrik | React (rencana lama) | HTMX (sekarang) | +|--------|---------------------|-----------------| +| Total waktu | ~6 minggu (30 Jun - 7 Aug) | ~3.5 minggu (7 Jul - 31 Jul) | +| Files | ~50 (Go + React + TS + Vite) | ~38 (Go + HTML + JS) | +| JS dependencies | 10+ library + npm | 3 file JS (HTMX, Alpine, xterm) | +| Build complexity | Vite + TypeScript + npm + Electron | CSS build + Go build | diff --git a/docs/UI_COMPONENTS.md b/docs/UI_COMPONENTS.md index 1b1dda3..fab23bb 100644 --- a/docs/UI_COMPONENTS.md +++ b/docs/UI_COMPONENTS.md @@ -1,1142 +1,510 @@ -# Hostkeeper V2 — React Component Tree +# Hostkeeper V2 — HTMX Components & Partials -> **Status**: V2 Planning Complete -> **Last Updated**: 2026-06-29 -> **Framework**: React 19, TypeScript, Zustand, TailwindCSS +> **Status**: Menggantikan React Component Tree +> **Last Updated**: 2026-07-07 +> **Framework**: Go HTML Template + HTMX + Alpine.js --- -## 1. App Component Tree +## 1. Template Structure ``` -App -├── # React Query (optional) -│ ├── # CSS variables theme provider -│ │ ├── # React Router -│ │ │ ├── # Route: / (if locked) -│ │ │ └── # Route: /* -│ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ │ ├── -│ │ │ │ │ │ └── -│ │ │ │ │ └── (recursive) -│ │ │ │ └── -│ │ │ ├── -│ │ │ │ ├── -│ │ │ │ └── -│ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ ├── -│ │ │ │ └── -│ │ │ └── -│ │ └── -│ │ ├── -│ │ ├── -│ │ ├── -│ │ ├── -│ │ └── -│ └── # Sonner toast notifications +views/ +├── layout.html ← Base layout (html, head, body, sidebar, header, scripts) +├── index.html ← Entry: {{template "layout" .}} + {{template "content" .}} +│ +└── partials/ ← HTMX-swappable partial fragments + ├── nav.html + ├── dashboard.html + ├── host_list.html + ├── host_card.html + ├── host_modal.html + ├── terminal.html + ├── sftp.html + ├── sftp_pane.html + ├── snippets.html + ├── snippet_grid.html + ├── snippet_modal.html + ├── keychain.html + ├── key_grid.html + ├── key_modal.html + ├── settings.html + ├── brief.html + └── toast.html ``` --- -## 2. Component Details +## 2. Komponen per Halaman -### 2.1 App Root +### 2.1 Navigasi (layout.html + nav.html) -```typescript -// app/frontend/src/App.tsx -function App() { - const { isLocked } = useVaultStore(); - - if (isLocked) { - return ; - } - - return ( - - - - - ); -} +```html + + + + + + + + + + + + Hostkeeper V2 + + +
+ {{template "nav" .}} +
+
+

{{.Title}}

+
+
+ {{block "content" .}}{{end}} +
+
+
+ + + + + ``` -**State**: `useVaultStore()` — `isLocked: boolean` +### 2.2 Dashboard (partials/dashboard.html) ---- +```html + +
+ +
+
...
+
...
+
...
+
-### 2.2 VaultScreen + +
+ + + + +
-Password prompt shown on app start (when vault is locked). - -```typescript -interface VaultScreenProps { - onUnlock: (password: string) => Promise; -} - -function VaultScreen({ onUnlock }: VaultScreenProps) { - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const [loading, setLoading] = useState(false); - - // UI: centered card with password input, unlock button - // Enter key submits, Esc clears -} + +
+ {{template "host_list" .Hosts}} +
+
+ {{template "host_list_table" .Hosts}} +
+
``` -**Layout**: Centered card on dark background. App logo + name above. Password input + Unlock button. Error message below input. +### 2.3 Terminal (partials/terminal.html) ---- +```html +
+ +
+ + +
-### 2.3 MainLayout + +
+
+
+
-The main application shell after vault unlock. + + +``` -```typescript -function MainLayout() { - const [sidebarOpen, setSidebarOpen] = useState(true); - const [sidebarWidth, setSidebarWidth] = useState(240); - const tabs = useTabStore(state => state.tabs); - - return ( -
- -
- - - -
+### 2.4 SFTP (partials/sftp.html) + +```html +
+
+ +
+
+ {{svg "hard-drive"}} + Local Workstation + +
+ +
+ {{template "sftp_pane" .LocalFiles}} +
+
+ + +
+ ... sama seperti local pane ... +
+
+ + +
Drop files here
+ + +
+
+
+``` + +### 2.5 Snippets (partials/snippets.html) + +```html +
+
+ +
+
+

Collections

+ All + Favorites + Recent +
+
+

Tags

+ {{range .AllTags}} + {{.}} + {{end}} +
+
+ + +
+
+ + +
+
+ {{template "snippet_grid" .Snippets}} +
+
+
+ + + +
+``` + +### 2.6 Keychain (partials/keychain.html) + +```html +
+
+ + +
+ +
+ {{range .Keys}} +
+
+
+ {{svg "key"}} + {{.Name}}
- ); -} -``` - -**Layout**: Horizontal split — sidebar (left) + main area (right). Main area splits vertically — tab bar (top) + content (middle) + status bar (bottom). - ---- - -### 2.4 Sidebar - -Left navigation panel with host tree and quick actions. - -```typescript -interface SidebarProps { - width: number; - onToggle: (open: boolean) => void; -} - -function Sidebar({ width, onToggle }: SidebarProps) { - const [searchQuery, setSearchQuery] = useState(''); - const groups = useHostStore(state => state.groups); - const hosts = useHostStore(state => state.hosts); - - return ( - - ); -} -``` - -**Width**: 240px default, resizable via drag handle (min 180px, max 400px). - ---- - -### 2.5 SearchBar - -```typescript -interface SearchBarProps { - value: string; - onChange: (value: string) => void; - placeholder?: string; -} - -function SearchBar({ value, onChange, placeholder = "Search hosts..." }: SearchBarProps) { - return ( -
- - onChange(e.target.value)} - placeholder={placeholder} - className="w-full pl-8 pr-3 py-1.5 rounded-md bg-surface text-sm" - /> - {value && ( - - )} + {{.Strength}} +
+
+
+ User + {{.User}}
- ); -} -``` - ---- - -### 2.6 GroupTree - -Recursive tree component for host groups. - -```typescript -interface GroupTreeProps { - groups: HostGroup[]; - hosts: Host[]; - filter: string; - parent_id?: string | null; - depth?: number; -} - -function GroupTree({ groups, hosts, filter, parent_id = null, depth = 0 }: GroupTreeProps) { - const childGroups = groups.filter(g => g.parent_id === parent_id); - const ungroupedHosts = hosts.filter(h => !h.group_id && !parent_id); - - return ( -
- {childGroups.map(group => ( - - ))} - {depth === 0 && } -
- ); -} - -interface GroupItemProps { - group: HostGroup; - groups: HostGroup[]; - hosts: Host[]; - filter: string; - depth: number; -} - -function GroupItem({ group, groups, hosts, filter, depth }: GroupItemProps) { - const [expanded, setExpanded] = useState(true); - const groupHosts = hosts.filter(h => h.group_id === group.id); - const color = group.color || '#888'; - - return ( -
-
setExpanded(!expanded)} - > - {expanded ? : } -
- {group.name} - - {groupHosts.length} - -
- {expanded && ( - - )} -
- ); -} -``` - -**Features**: -- Collapsible groups with chevron icon -- Color dot for group color -- Host count badge -- Drag-and-drop: drag host onto group to reassign -- Right-click: context menu (rename, delete, new sub-group) - ---- - -### 2.7 HostItem - -```typescript -interface HostItemProps { - host: Host; - isActive?: boolean; - onClick?: () => void; -} - -function HostItem({ host, isActive, onClick }: HostItemProps) { - const status = useTerminalStore(state => state.getSessionStatus(host.id)); - - return ( -
- - - {host.name} - {host.is_favorite && ( - - )} -
- ); -} - -function StatusDot({ status }: { status: string }) { - const color = { - connected: 'bg-green-500', - connecting: 'bg-yellow-500', - disconnected: 'bg-gray-500', - error: 'bg-red-500' - }[status] || 'bg-gray-500'; - - return
; -} -``` - ---- - -### 2.8 TabBar - -Horizontal tab bar for open terminals/SFTP sessions. - -```typescript -function TabBar() { - const { tabs, activeTab, addTab, closeTab, setActiveTab } = useTabStore(); - - return ( -
- {tabs.map(tab => ( - setActiveTab(tab.id)} - onClose={() => closeTab(tab.id)} - /> - ))} - addTab()} /> -
- ); -} - -interface TabProps { - tab: Tab; - isActive: boolean; - onClick: () => void; - onClose: () => void; -} - -function Tab({ tab, isActive, onClick, onClose }: TabProps) { - const status = useTerminalStore(state => state.getSessionStatus(tab.host_id)); - - return ( -
- - {tab.title} - + +
- ); -} -``` - -**Features**: -- Status dot (green=connected, red=error, gray=disconnected) -- Tab title (host name) -- Close button (X) -- Drag to reorder -- Middle-click to close -- Max 20 tabs - ---- - -### 2.9 MainContent - -Routes to the appropriate screen based on active tab type. - -```typescript -function MainContent() { - const activeTab = useTabStore(state => state.getActiveTab()); - - if (!activeTab) { - return ; - } - - switch (activeTab.type) { - case 'terminal': - return ; - case 'sftp': - return ; - default: - return ; - } -} -``` - ---- - -### 2.10 HostListScreen - -Default screen when no tab is active. - -```typescript -function HostListScreen() { - const [viewMode, setViewMode] = useState<'grid' | 'list'>('list'); - const hosts = useHostStore(state => state.hosts); - const selectedHost = useHostStore(state => state.selectedHost); - - if (hosts.length === 0) { - return ; - } - - return ( -
-
-

Hosts

-
- - - -
-
- {viewMode === 'list' ? ( - - ) : ( - - )} +
+ {{.Fingerprint}}
- ); -} +
+
+ {{end}} +
+
+``` -function EmptyState() { - return ( -
- -

No hosts yet

-

Add your first SSH host to get started

- +### 2.7 Settings (partials/settings.html) + +```html +
+
+ +
+

Profile

+
+
+
+ + +
+
+ + +
- ); -} -``` + +
+
---- - -### 2.11 HostForm (Add/Edit) - -```typescript -interface HostFormProps { - host?: Host; // undefined = add mode, defined = edit mode - onSave: (host: Host) => void; - onCancel: () => void; -} - -function HostForm({ host, onSave, onCancel }: HostFormProps) { - const [formData, setFormData] = useState({ - name: host?.name || '', - hostname: host?.hostname || '', - port: host?.port || 22, - username: host?.username || '', - auth_type: host?.auth?.type || 'key', - key_id: host?.auth?.key_id || '', - password: '', - group_id: host?.group_id || null, - tags: host?.tags || [], - is_favorite: host?.is_favorite || false, - notes: host?.notes || '', - }); - const [errors, setErrors] = useState>({}); - - return ( -
-

- {host ? 'Edit Host' : 'New Host'} -

- -
- {/* Basic Info */} - setFormData({ ...formData, name: v })} - error={errors.name} - placeholder="My Server" - /> - setFormData({ ...formData, hostname: v })} - error={errors.hostname} - placeholder="192.168.1.100 or example.com" - /> -
- setFormData({ ...formData, port: parseInt(v) })} - error={errors.port} - /> - setFormData({ ...formData, username: v })} - error={errors.username} - placeholder="root" - /> -
- - {/* Auth */} - setFormData({ ...formData, auth_type: v })} - onKeyChange={v => setFormData({ ...formData, key_id: v })} - onPasswordChange={v => setFormData({ ...formData, password: v })} - /> - - {/* Tags */} - setFormData({ ...formData, tags })} - /> - - {/* Group */} - setFormData({ ...formData, group_id: id })} - /> - - {/* Notes */} -