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}}
+
+
+
+
+
+
+
+ {{.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.