Files
HostKeeper/app/backend/views/terminal.html
T
swanadiva 8357d9d76e fix: UI overhaul — align all HTMX views with Lumina Design System (46 fixes)
- All 7 pages rewritten: Dashboard, Terminal, Snippets, Keychain, SFTP, Settings, Brief
- Apply consistent Lumina glassmorphic theme (rounded-2xl, shadows, borders, tracking)
- Fix SVG icons not rendering — return template.HTML instead of string
- Fix Alpine x-data scoping: terminal tabs, SFTP modals, snippet tag buttons
- Fix dashboard search sending literal ?q=...
- Rebuild CSS with all new Tailwind classes (62KB)
- Add .air.toml with hot-reload config, docs/PERBAIKANUI.md checklist
- Remove unused backup partials
2026-07-07 14:55:42 +07:00

126 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Terminal · Hostkeeper V2</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/output.css">
<link rel="stylesheet" href="/static/css/custom.css">
<link rel="stylesheet" href="/static/xterm/xterm.css">
<script src="/static/js/htmx.min.js"></script>
<script src="/static/js/alpine.min.js" defer></script>
</head>
<body class="bg-surface text-on-surface antialiased dot-grid min-h-screen">
<div class="flex min-h-screen">
{{template "nav" .}}
<div class="flex-1 flex flex-col overflow-hidden" x-data="terminalManager()" x-init="init()">
<header class="h-14 border-b border-outline-variant/30 flex items-center gap-2 px-6 bg-white/70 backdrop-blur-md">
<h1 class="text-xl font-bold tracking-tight text-on-surface">{{.Title}}</h1>
<span class="text-xs text-on-surface-variant mt-0.5">Interactive SSH shell with multi-tab session management</span>
<div class="flex-1"></div>
<button @click="addTab()"
class="p-1.5 rounded-lg bg-surface-container hover:bg-primary-container hover:text-on-primary-container text-on-surface-variant transition-all" title="New Tab">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
</button>
</header>
<main id="main-content" class="flex-1 flex flex-col overflow-auto p-6">
<div class="max-w-7xl mx-auto space-y-6 flex flex-col flex-1 min-h-0 w-full">
<!-- Session Tab Toolbar -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-outline-variant/30 pb-3">
<div class="flex items-center gap-2 overflow-x-auto no-scrollbar scroll-smooth pr-6">
<template x-for="(tab, i) in tabs" :key="tab.id">
<div @click="switchTab(i)"
:class="activeTab === i ? 'bg-white text-primary border-primary shadow-[0_-4px_12px_rgba(0,80,203,0.06)]' : 'bg-surface-container-low text-on-surface-variant hover:bg-surface-container border-transparent'"
class="flex items-center gap-2 px-3.5 py-2 rounded-t-xl text-xs font-bold font-mono transition-all cursor-pointer border-t-2">
<svg class="w-3.5 h-3.5" :class="activeTab === i ? 'text-primary' : 'text-outline'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 17V7c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v10c0 .621-.504 1.125-1.125 1.125h-2.25A1.125 1.125 0 014 17zm6 0V7c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v10c0 .621-.504 1.125-1.125 1.125h-2.25A1.125 1.125 0 0110 17zm6 0V7c0-.621.504-1.125 1.125-1.125h2.25C19.496 5.875 20 6.379 20 7v10c0 .621-.504 1.125-1.125 1.125h-2.25A1.125 1.125 0 0116 17z"/></svg>
<span class="truncate max-w-[120px]" x-text="tab.name"></span>
<button @click.stop="closeTab(i)" class="p-0.5 hover:bg-black/10 rounded transition-colors">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
</template>
<button @click="addTab()"
class="p-1.5 bg-surface-container hover:bg-primary-container hover:text-on-primary-container text-on-surface-variant rounded-lg transition-all" title="Open new terminal connection">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
</button>
</div>
<div class="flex items-center gap-4 text-xs font-mono shrink-0">
<div class="flex items-center gap-1.5 text-secondary">
<span class="w-2 h-2 rounded-full bg-secondary animate-pulse"></span>
<span>SECURE CRYPTO PATH</span>
</div>
<div class="text-outline">
HOST: <span class="text-on-surface font-semibold" x-text="tabs.length ? tabs[activeTab].host : '—'"></span>
</div>
</div>
</div>
<!-- Terminal shell frame -->
<div class="flex-1 bg-white border border-outline-variant rounded-2xl shadow-sm flex flex-col overflow-hidden min-h-0">
<!-- Terminal Header -->
<div class="px-4 py-2 bg-surface-container-low border-b border-outline-variant/30 flex justify-between items-center">
<div class="flex items-center gap-1.5">
<span class="w-3 h-3 rounded-full bg-error/80"></span>
<span class="w-3 h-3 rounded-full bg-primary-container/80"></span>
<span class="w-3 h-3 rounded-full bg-secondary/80"></span>
<span class="text-[10px] font-mono font-bold text-outline ml-2">ssh -i id_ed25519 root@<span x-text="tabs.length ? tabs[activeTab].host : '—'"></span></span>
</div>
<div class="flex items-center gap-3">
<button @click="clearBuffer()"
class="text-[10px] uppercase font-bold text-outline hover:text-primary transition-colors flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182"/></svg>
Clear Buffer
</button>
</div>
</div>
<!-- Console display -->
<div class="flex-1 overflow-y-auto min-h-0 font-mono text-sm leading-relaxed p-5 bg-white" id="terminal-output">
<template x-for="(line, idx) in history" :key="idx">
<div :class="line.type === 'input' ? 'text-on-surface font-bold' : line.type === 'error' ? 'text-error bg-error-container/20 px-2.5 py-1 rounded-md' : line.type === 'success' ? 'text-secondary bg-secondary-container/10 px-2.5 py-1 rounded-md font-semibold' : 'text-[#2244aa]'"
class="whitespace-pre-wrap" x-text="line.text"></div>
</template>
<div class="h-4"></div>
</div>
<!-- Input prompt -->
<form @submit.prevent="submitCommand()"
class="p-3 border-t border-outline-variant/40 bg-surface-container-low flex items-center gap-2.5">
<span class="font-mono text-xs font-bold text-primary pl-2 shrink-0 flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.75 17.25L12 21m0 0l-3.75-3.75M12 21V3"/></svg>
root@<span x-text="tabs.length ? tabs[activeTab].name : 'host'"></span>:~$
</span>
<input type="text" x-model="command" autofocus
placeholder='Type a command (e.g. "help", "ls", "docker ps", "ping google.com")...'
class="flex-1 bg-transparent border-none font-mono text-xs font-medium focus:ring-0 focus:outline-none text-on-surface p-0">
<button type="submit"
class="p-1.5 bg-primary text-white rounded-lg hover:bg-primary-container hover:text-on-primary-container transition-all" title="Execute Command">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/></svg>
</button>
</form>
</div>
<!-- Shortcut pills -->
<div class="flex flex-wrap items-center gap-2 text-[11px] font-mono font-bold text-on-surface-variant">
<span class="text-outline">SHORTCUT SUGGESTIONS:</span>
<button type="button" @click="insertCommand('help')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">help</button>
<button type="button" @click="insertCommand('ls')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">ls</button>
<button type="button" @click="insertCommand('docker ps')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">docker ps</button>
<button type="button" @click="insertCommand('uname -a')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">uname -a</button>
<button type="button" @click="insertCommand('ping 8.8.8.8')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">ping 8.8.8.8</button>
<button type="button" @click="insertCommand('cat server.js')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">cat server.js</button>
<button type="button" @click="insertCommand('keychain')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">keychain</button>
<button type="button" @click="insertCommand('clear')" class="px-2.5 py-1 rounded-md bg-surface-container border border-outline-variant/30 hover:border-primary hover:bg-primary-container/10 transition-colors">clear</button>
</div>
</div>
</main>
</div>
</div>
<script id="hosts-data" type="application/json">{{.HostsJSON}}</script>
<script src="/static/xterm/xterm.js"></script>
<script src="/static/js/terminal.js"></script>
</body>
</html>