From 7accb86203a33a3fe0a521549baa1c9f4ba26e07 Mon Sep 17 00:00:00 2001 From: swanadiva Date: Thu, 9 Jul 2026 18:27:47 +0700 Subject: [PATCH] feat: add transfer panel to dashboard + remove PERBAIKANUI.md - Add Background Transfers section with mock download/upload rows - Progress bars with green glow shadow, source/destination layout - Empty state when no transfers active - Delete PERBAIKANUI.md (all UI tasks complete) --- app/backend/internal/handler/dashboard.go | 17 +- app/backend/static/css/output.css | 98 +++++- app/backend/views/index.html | 59 ++++ docs/PERBAIKANUI.md | 344 ---------------------- 4 files changed, 155 insertions(+), 363 deletions(-) delete mode 100644 docs/PERBAIKANUI.md diff --git a/app/backend/internal/handler/dashboard.go b/app/backend/internal/handler/dashboard.go index 0e861bc..58fe5c0 100644 --- a/app/backend/internal/handler/dashboard.go +++ b/app/backend/internal/handler/dashboard.go @@ -21,13 +21,18 @@ var navItems = []fiber.Map{ func Dashboard(c *fiber.Ctx) error { hosts := hostStore.All() + transfers := []model.Transfer{ + {ID: "1", FileName: "backup_v4.tar.gz", Source: "production-api-01", Destination: "Local Machine", Progress: 82, Speed: "12.4 MB/s", Type: "download", Status: "transferring"}, + {ID: "2", FileName: "assets_deploy.zip", Source: "Local Storage", Destination: "web-frontend-02", Progress: 35, Speed: "4.8 MB/s", Type: "upload", Status: "transferring"}, + } return c.Render("index", fiber.Map{ - "View": "hosts", - "Title": "Hosts", - "Hosts": hosts, - "Active": countByStatus(hosts, "active"), - "Offline": countByStatus(hosts, "offline"), - "NavItems": navItems, + "View": "hosts", + "Title": "Hosts", + "Hosts": hosts, + "Active": countByStatus(hosts, "active"), + "Offline": countByStatus(hosts, "offline"), + "NavItems": navItems, + "Transfers": transfers, }) } diff --git a/app/backend/static/css/output.css b/app/backend/static/css/output.css index ea7bce5..a7b82ef 100644 --- a/app/backend/static/css/output.css +++ b/app/backend/static/css/output.css @@ -69,6 +69,7 @@ --color-on-surface: #191c1e; --color-surface-container-low: #f0f2f5; --color-surface-container: #eaecf0; + --color-surface-container-high: #e2e5e9; --color-on-surface-variant: #424656; --color-outline: #727686; --color-outline-variant: #c2c6d8; @@ -294,6 +295,9 @@ .z-\[180\] { z-index: 180; } + .z-\[200\] { + z-index: 200; + } .col-span-full { grid-column: 1 / -1; } @@ -312,6 +316,9 @@ .mt-3 { margin-top: calc(var(--spacing) * 3); } + .mt-8 { + margin-top: calc(var(--spacing) * 8); + } .mr-1 { margin-right: var(--spacing); } @@ -324,6 +331,9 @@ .mb-2 { margin-bottom: calc(var(--spacing) * 2); } + .mb-3 { + margin-bottom: calc(var(--spacing) * 3); + } .mb-4 { margin-bottom: calc(var(--spacing) * 4); } @@ -390,6 +400,9 @@ .h-11 { height: calc(var(--spacing) * 11); } + .h-12 { + height: calc(var(--spacing) * 12); + } .h-14 { height: calc(var(--spacing) * 14); } @@ -408,12 +421,24 @@ .h-full { height: 100%; } + .h-px { + height: 1px; + } + .max-h-32 { + max-height: calc(var(--spacing) * 32); + } + .max-h-\[90vh\] { + max-height: 90vh; + } .min-h-0 { min-height: 0; } .min-h-\[200px\] { min-height: 200px; } + .min-h-full { + min-height: 100%; + } .min-h-screen { min-height: 100vh; } @@ -453,6 +478,9 @@ .w-11 { width: calc(var(--spacing) * 11); } + .w-12 { + width: calc(var(--spacing) * 12); + } .w-16 { width: calc(var(--spacing) * 16); } @@ -483,9 +511,6 @@ .max-w-lg { max-width: var(--container-lg); } - .max-w-md { - max-width: var(--container-md); - } .max-w-sm { max-width: var(--container-sm); } @@ -526,6 +551,9 @@ .resize { resize: both; } + .resize-none { + resize: none; + } .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } @@ -589,6 +617,9 @@ .gap-8 { gap: calc(var(--spacing) * 8); } + .gap-12 { + gap: calc(var(--spacing) * 12); + } .space-y-0\.5 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -868,9 +899,21 @@ .bg-outline { background-color: var(--color-outline); } + .bg-outline-variant\/30 { + background-color: color-mix(in srgb, #c2c6d8 30%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-outline-variant) 30%, transparent); + } + } .bg-primary { background-color: var(--color-primary); } + .bg-primary-container\/10 { + background-color: color-mix(in srgb, #d6e3ff 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-primary-container) 10%, transparent); + } + } .bg-primary-container\/15 { background-color: color-mix(in srgb, #d6e3ff 15%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -946,6 +989,9 @@ .bg-surface-container { background-color: var(--color-surface-container); } + .bg-surface-container-high { + background-color: var(--color-surface-container-high); + } .bg-surface-container-low { background-color: var(--color-surface-container-low); } @@ -955,6 +1001,12 @@ background-color: color-mix(in oklab, var(--color-surface-container-low) 50%, transparent); } } + .bg-tertiary\/10 { + background-color: color-mix(in srgb, #7e23cc 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-tertiary) 10%, transparent); + } + } .bg-tertiary\/20 { background-color: color-mix(in srgb, #7e23cc 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -979,6 +1031,12 @@ background-color: color-mix(in oklab, var(--color-white) 40%, transparent); } } + .bg-white\/50 { + background-color: color-mix(in srgb, #fff 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 50%, transparent); + } + } .bg-white\/70 { background-color: color-mix(in srgb, #fff 70%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1257,6 +1315,12 @@ .text-outline-variant { color: var(--color-outline-variant); } + .text-outline\/30 { + color: color-mix(in srgb, #727686 30%, transparent); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-outline) 30%, transparent); + } + } .text-outline\/40 { color: color-mix(in srgb, #727686 40%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -1338,6 +1402,10 @@ --tw-shadow: 0 -4px 12px var(--tw-shadow-color, rgba(0,80,203,0.06)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .shadow-\[0_0_8px_rgba\(0\,110\,47\,0\.3\)\] { + --tw-shadow: 0 0 8px var(--tw-shadow-color, rgba(0,110,47,0.3)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .shadow-lg { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -1420,6 +1488,10 @@ .delay-200 { transition-delay: 200ms; } + .duration-500 { + --tw-duration: 500ms; + transition-duration: 500ms; + } .select-all { -webkit-user-select: all; user-select: all; @@ -1697,16 +1769,16 @@ grid-column: span 2 / span 2; } } - .sm\:block { - @media (width >= 40rem) { - display: block; - } - } .sm\:grid { @media (width >= 40rem) { display: grid; } } + .sm\:w-48 { + @media (width >= 40rem) { + width: calc(var(--spacing) * 48); + } + } .sm\:w-60 { @media (width >= 40rem) { width: calc(var(--spacing) * 60); @@ -1717,11 +1789,6 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } } - .sm\:grid-cols-3 { - @media (width >= 40rem) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - } .sm\:grid-cols-4 { @media (width >= 40rem) { grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -2077,6 +2144,10 @@ syntax: "*"; inherits: false; } +@property --tw-duration { + syntax: "*"; + inherits: false; +} @property --tw-scale-x { syntax: "*"; inherits: false; @@ -2170,6 +2241,7 @@ --tw-backdrop-opacity: initial; --tw-backdrop-saturate: initial; --tw-backdrop-sepia: initial; + --tw-duration: initial; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scale-z: 1; diff --git a/app/backend/views/index.html b/app/backend/views/index.html index 67f9505..d6643cb 100644 --- a/app/backend/views/index.html +++ b/app/backend/views/index.html @@ -105,6 +105,65 @@ {{template "host_list" .}} + + +
+
+

Background Transfers

+
+
+ +
+
+
+ Source & File + Destination +
+ Progress +
+ +
+ {{range .Transfers}} +
+
+
+ +
+
+
+

{{.FileName}}

+ {{.Source}} +
+
+ + {{.Destination}} +
+
+
+ +
+
+ {{.Progress}}% + {{.Speed}} +
+
+
+
+
+
+ {{end}} + + {{if not .Transfers}} +
+ +

No active transfers

+
+ {{end}} +
+
+
+ {{template "host_modal" .}} diff --git a/docs/PERBAIKANUI.md b/docs/PERBAIKANUI.md deleted file mode 100644 index 94e3ce7..0000000 --- a/docs/PERBAIKANUI.md +++ /dev/null @@ -1,344 +0,0 @@ -# πŸ” Hostkeeper V2 β€” UI/UX Audit & Perbaikan Checklist - -> **Acuan perbaikan UI** β€” Bandingkan HTMX implementation dengan React template di `template/src/components/` -> **Target**: Menyamakan visual dengan Lumina Design System hingga level piksel -> **Last Updated**: 2026-07-07 - ---- - -## 🎨 Design System Reference - -| Token | Value | Status | -|-------|-------|--------| -| Primary | `#0050cb` | βœ… OK | -| Secondary | `#006e2f` | βœ… OK | -| Tertiary | `#7e23cc` | βœ… OK | -| Surface | `#f7f9fb` | βœ… OK | -| On Surface | `#191c1e` | βœ… OK | -| On Surface Variant | `#424656` | βœ… OK | -| Outline | `#727686` | βœ… OK | -| Outline Variant | `#c2c6d8` | βœ… OK | -| Error | `#b3261e` | βœ… OK | -| Surface Container Low | `#f0f2f5` | βœ… OK | -| Surface Container | `#eaecf0` | βœ… OK | -| Font Sans | Inter (300–900) | βœ… OK | -| Font Mono | JetBrains Mono (300–700) | βœ… OK | -| Dot Grid | `radial-gradient(#e2e8f0 1.5px, transparent 1.5px) 16px` | βœ… OK | -| Sidebar Width | `260px` | βœ… OK | - ---- - -## πŸ”΄ P0 β€” DASHBOARD (index.html + host_list.html + host_modal.html) - -### ~~Task #1: Banner Stats~~ βœ… Selesai - -### ~~Task #2: Status Filter~~ βœ… Selesai - -### ~~Task #3: Search Bar~~ βœ… Selesai - -### ~~Task #4: View Toggle~~ βœ… Selesai - -### ~~Task #5: Section Title~~ βœ… Selesai - -### ~~Task #6: Host Card Grid~~ βœ… Selesai - -### ~~Task #7: Add Host Card~~ βœ… Selesai - -### ~~Task #8: Host Card List~~ βœ… Selesai - ---- - -### Task #9: Transfer Panel - -**Referensi**: `template/src/components/DashboardView.tsx:306-356` - -Template punya section "Background Transfers" dengan: -- Line separator + judul -- Table header: Source & File | Destination | Progress -- Tiap row: icon (download=tertiary/upload=primary), nama file, source, destination panah, progress bar (h-1.5, bg-secondary, shadow), percentage + speed - -HTMX: Hanya "No active transfers" di sidebar ❌ - -**Files**: `app/backend/views/index.html` + `app/backend/views/host_list.html` - ---- - -### ~~Task #10: Add Host Modal~~ βœ… Selesai - ---- - -## πŸ”΄ P0 β€” TERMINAL (terminal.html + terminal.js) - -### Task #11: Ubah theme dari dark ke light (Lumina) - -**Referensi**: `template/src/components/TerminalView.tsx:208-284` - -| Detail | Template (wajib) | HTMX Saat Ini | -|--------|----------|---------------| -| Background terminal | `bg-white` | `bg-[#0d1117]` ❌ | -| Tab bar | `bg-surface-container-low` | `bg-[#161b22]` ❌ | -| Tab border | `border-b border-outline-variant/30` | `border-[#30363d]` ❌ | -| Tab active | `bg-white text-primary border-primary shadow` | `bg-[#0d1117] text-white border-t-2 border-[#58a6ff]` ❌ | -| Tab inactive | `bg-surface-container-low text-on-surface-variant` | `bg-[#161b22] text-[#8b949e]` ❌ | -| Tab close | `hover:bg-black/10 rounded` | `hover:bg-[#30363d]` ❌ | -| New tab button | `bg-surface-container hover:bg-primary-container hover:text-on-primary-container` | styling dark ❌ | -| Terminal header | `bg-surface-container-low border-b border-outline-variant/30` | tidak ada | - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #12: Traffic Light Dots - -**Referensi**: `template/src/components/TerminalView.tsx:212-216` - -Tambah 3 dot (red `#b3261e`, yellow `#d6e3ff` / kuning, green `#006e2f`) di kiri terminal header. - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #13: SSH Connection String - -**Referensi**: `template/src/components/TerminalView.tsx:216` - -Tampilkan teks: `ssh -i id_ed25519 root@{host.ip}` dengan `text-[10px] font-mono font-bold text-outline` - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #14: "Clear Buffer" Button - -**Referensi**: `template/src/components/TerminalView.tsx:219-232` - -Button `text-[10px] uppercase font-bold text-outline hover:text-primary` dengan `RefreshCw` icon. - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #15: Terminal Output Styles - -**Referensi**: `template/src/components/TerminalView.tsx:241-255` - -| Type | Template | HTMX | -|------|----------|------| -| input | `text-on-surface font-bold` | (xterm.js) | -| error | `text-error bg-error-container/20 px-2.5 py-1 rounded-md` | (xterm.js) | -| success | `text-[#006e2f] bg-secondary-container/10 px-2.5 py-1 rounded-md font-semibold` | (xterm.js) | -| output | `text-[#2244aa]` | (xterm.js) | - -Ini perlu disesuaikan di terminal.js / xterm theme. - -**Files**: `app/backend/static/js/terminal.js`, `app/backend/views/terminal.html` - ---- - -### Task #16: Input Prompt Bar - -**Referensi**: `template/src/components/TerminalView.tsx:259-283` - -| Detail | Template | HTMX | -|--------|----------|------| -| Background | `bg-surface-container-low` | dark ❌ | -| Prompt | `root@{host}:~$` dengan icon `CornerDownLeft` | (di mock WebSocket) | -| Input | `bg-transparent font-mono text-xs focus:ring-0` | (xterm.js) | -| Execute button | `bg-primary text-white rounded-lg hover:bg-primary-container` | tidak ada | - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #17: Shortcut Pills - -**Referensi**: `template/src/components/TerminalView.tsx:287-301` - -Tambah rail shortcut di bawah terminal: help, ls, docker ps, uname -a, ping 8.8.8.8, cat server.js, keychain, clear. - -| Detail | Template | -|--------|----------| -| Container | `flex flex-wrap gap-2 text-[11px] font-mono font-bold text-on-surface-variant` | -| Label | "SHORTCUT SUGGESTIONS:" | -| Pill | `px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10` | - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #18: Status Info (Security + Host IP) - -**Referensi**: `template/src/components/TerminalView.tsx:197-205` - -| Detail | Template | -|--------|----------| -| Kiri | "SECURE CRYPTO PATH" dengan green dot + animate-pulse | -| Kanan | "HOST: {host.ip}" | - -**Files**: `app/backend/views/terminal.html` - ---- - -### Task #19: Sync Mock Commands dengan Template - -**Referensi**: `template/src/components/TerminalView.tsx:61-129` - -Template punya command list: help, ls, docker ps, uname -a, ping 8.8.8.8, cat server.js, keychain, clear. - -HTMX di Go handler punya: clear, exit, pwd, whoami, hostname, date, uptime, uname, ls, cd, cat, echo, ssh, help. - -Output di tiap command juga harus disamakan dengan template (docker ps output, ls output, dll). - -**Files**: `app/backend/internal/handler/terminal.go` - ---- - -## πŸ”΄ P0 β€” SNIPPETS (snippets.html + snippet_grid.html) βœ… - -### ~~Task #20: Layout β€” Ubah ke 12-col grid (sidebar + content)~~ βœ… Selesai - -### ~~Task #21: Sidebar β€” Collections~~ βœ… Selesai - -### ~~Task #22: Sidebar β€” Tag Categories~~ βœ… Selesai - -### ~~Task #23: Snippet Card β€” Stacked Layout~~ βœ… Selesai - -### ~~Task #24: Headers & Searching~~ βœ… Selesai - -### ~~Task #25: Add Snippet Modal (style)~~ βœ… Selesai - -### ~~Task #26: Data Model β€” Tambah field Snippet~~ βœ… Selesai - ---- - -## 🟑 P1 β€” KEYCHAIN (keychain.html + key_grid.html) βœ… - -### ~~Task #27: Layout β€” 2 col~~ βœ… Selesai - -### ~~Task #28: Key Card β€” Style Upgrade~~ βœ… Selesai - -### ~~Task #29: Add Key Modal (style)~~ βœ… Selesai - -### ~~Task #30: Security Advisory~~ βœ… Selesai - ---- - -## 🟑 P1 β€” SFTP (sftp.html + sftp_pane.html) βœ… - -### ~~Task #31: Header β€” Tambah subtitle~~ βœ… Selesai - -### ~~Task #32: File Pane β€” Table Structure~~ βœ… Selesai -### ~~Task #33: Drop Zone β€” Style~~ βœ… Selesai - -### ~~Task #34: Toast Notification~~ βœ… Selesai - ---- - -## 🟑 P1 β€” SETTINGS (settings.html) βœ… - -### ~~Task #35: Layout β€” 2-column grid~~ βœ… Selesai - -### ~~Task #36: Profile β€” Editable Form~~ βœ… Selesai - -### ~~Task #37: Toggles β€” iOS-Style~~ βœ… Selesai - -### ~~Task #38: Billing Card (Kanan)~~ βœ… Selesai - -### ~~Task #39: Danger Zone β€” Style~~ βœ… Selesai - ---- - -## 🟑 P1 β€” BRIEF (brief.html) - -### Task #40: Ubah jadi Overlay Panel βœ… - -**Referensi**: `template/src/components/BriefOverlay.tsx` - -Styling updated β€” color palette visual, typography samples, elevation examples, keyboard shortcuts. Halaman stilistik mirror template content. - -**Files**: `app/backend/views/brief.html` - ---- - -## 🟑 GLOBAL ISSUES βœ… - -### Task #41: SVG Icons β€” Stroke-width - -Periksa semua SVG inline, pastikan: -- Icon kecil (w-3.5, w-4): `stroke-width="1.5"` -- Icon besar (w-5, w-6, w-8+): `stroke-width="2"` - -**Files**: Semua view files - ---- - -### Task #42: Border Radius β€” `rounded-2xl` - -Ubah dari `rounded-xl` ke `rounded-2xl` di: -- Host cards (host_list.html) -- Snippet cards (snippet_grid.html) -- Key cards (key_grid.html) -- Settings cards (settings.html) -- Modal containers (host_modal.html, snippets.html, keychain.html) - ---- - -### Task #43: Shadows - -| Element | Target | -|---------|--------| -| Host card | `shadow-sm` normal, `shadow-lg` hover | -| Snippet card | `shadow-sm` | -| Key card | `shadow-sm` normal, `shadow-lg` hover | -| Modal | `shadow-2xl` | -| Settings card | `shadow-sm` | - ---- - -### Task #44: Text Styles - -| Element | Target | -|---------|--------| -| Page title | `text-xl font-bold tracking-tight` | -| Subtitle | `text-xs text-on-surface-variant mt-0.5` | -| Card title | `font-bold text-base` | -| Label form | `text-xs font-bold text-on-surface-variant uppercase tracking-wider` | - ---- - -### Task #45: Search Input β€” `rounded-full` - -Semua search input: `rounded-full` bukan `rounded-lg`. - -**Files**: index.html, snippets.html, keychain.html, sftp.html - ---- - -### Task #46: Nav β€” Icons - -Periksa semua icon di nav (nav.html) apakah SVG sesuai dengan lucide equivalents: -- server β†’ ok -- terminal β†’ `TerminalSquare` -- folder-sync β†’ `ArrowLeftRight` -- code2 β†’ `Code` -- key-round β†’ `Key` -- settings2 β†’ `Settings` - -**Files**: `app/backend/views/nav.html` - ---- - -## πŸ—ΊοΈ Execution Order - -``` -1. Dashboard (P0) β†’ index.html, host_list.html, host_modal.html -2. Terminal (P0) β†’ terminal.html, terminal.js -3. Snippets (P0) β†’ snippets.html, snippet_grid.html, model/handler -4. Keychain (P1) β†’ keychain.html, key_grid.html -5. SFTP (P1) β†’ sftp.html, sftp_pane.html -6. Settings (P1) β†’ settings.html -7. Brief (P1) β†’ brief.html, nav.html -8. Global (P1) β†’ semua files -``` - -> **⚠️ Checklist selesai**: Setiap task di atas harus dihapus dari file ini setelah selesai dikerjakan.