fix: credential picker in host modal, fix xterm dimensions crash
- Added credential dropdown to New Host modal (loads from vault API) - Fixed xterm.js "Cannot read dimensions" crash by guarding fitAddon.fit() with requestAnimationFrame and container dimension checks - Added WebGL context loss handler - credentialId now passed when creating hosts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7e347ce378
commit
1f32ce4620
@ -33,13 +33,23 @@ export function useTerminal() {
|
||||
term.open(container)
|
||||
|
||||
try {
|
||||
term.loadAddon(new WebglAddon())
|
||||
const webgl = new WebglAddon()
|
||||
webgl.onContextLoss(() => { webgl.dispose() })
|
||||
term.loadAddon(webgl)
|
||||
} catch {
|
||||
// WebGL not available, fall back to canvas
|
||||
}
|
||||
|
||||
fitAddon.fit()
|
||||
const resizeObserver = new ResizeObserver(() => fitAddon.fit())
|
||||
// Delay fit until container has layout dimensions
|
||||
const safeFit = () => {
|
||||
try {
|
||||
if (container.offsetWidth > 0 && container.offsetHeight > 0) {
|
||||
fitAddon.fit()
|
||||
}
|
||||
} catch { /* ignore fit errors on unmounted containers */ }
|
||||
}
|
||||
requestAnimationFrame(safeFit)
|
||||
const resizeObserver = new ResizeObserver(() => safeFit())
|
||||
resizeObserver.observe(container)
|
||||
|
||||
return { term, fitAddon, searchAddon, resizeObserver }
|
||||
|
||||
@ -17,6 +17,10 @@ const selectedHost = ref<any>(null)
|
||||
const hostCredential = ref<any>(null)
|
||||
const loadingCredential = ref(false)
|
||||
|
||||
// Credentials list for host modal dropdown
|
||||
const allCredentials = ref<any[]>([])
|
||||
const allSshKeys = ref<any[]>([])
|
||||
|
||||
// Terminal composable for connect-on-click
|
||||
const { createTerminal, connectToHost } = useTerminal()
|
||||
|
||||
@ -24,9 +28,18 @@ onMounted(async () => {
|
||||
await Promise.all([connections.fetchHosts(), connections.fetchTree()])
|
||||
})
|
||||
|
||||
function openNewHost(groupId?: number) {
|
||||
async function openNewHost(groupId?: number) {
|
||||
editingHost.value = groupId ? { groupId } : null
|
||||
inlineHost.value = { name: '', hostname: '', port: 22, protocol: 'ssh', groupId: groupId || null, tags: '' }
|
||||
inlineHost.value = { name: '', hostname: '', port: 22, protocol: 'ssh', groupId: groupId || null, tags: '', credentialId: null }
|
||||
// Load credentials for dropdown
|
||||
try {
|
||||
const [creds, keys] = await Promise.all([
|
||||
vault.listCredentials() as Promise<any[]>,
|
||||
vault.listKeys() as Promise<any[]>,
|
||||
])
|
||||
allCredentials.value = creds
|
||||
allSshKeys.value = keys
|
||||
} catch { /* ignore */ }
|
||||
showHostDialog.value = true
|
||||
}
|
||||
|
||||
@ -113,7 +126,7 @@ function dismissSavePrompt() {
|
||||
}
|
||||
|
||||
// Inline modal state
|
||||
const inlineHost = ref({ name: '', hostname: '', port: 22, protocol: 'ssh' as 'ssh' | 'rdp', groupId: null as number | null, tags: '' })
|
||||
const inlineHost = ref({ name: '', hostname: '', port: 22, protocol: 'ssh' as 'ssh' | 'rdp', groupId: null as number | null, tags: '', credentialId: null as number | null })
|
||||
|
||||
async function createGroupInline() {
|
||||
const nameEl = document.getElementById('grp-name') as HTMLInputElement
|
||||
@ -134,10 +147,11 @@ async function createHostInline() {
|
||||
port: inlineHost.value.port,
|
||||
protocol: inlineHost.value.protocol,
|
||||
groupId: inlineHost.value.groupId,
|
||||
credentialId: inlineHost.value.credentialId,
|
||||
tags,
|
||||
})
|
||||
showHostDialog.value = false
|
||||
inlineHost.value = { name: '', hostname: '', port: 22, protocol: 'ssh', groupId: null, tags: '' }
|
||||
inlineHost.value = { name: '', hostname: '', port: 22, protocol: 'ssh', groupId: null, tags: '', credentialId: null }
|
||||
await connections.fetchTree()
|
||||
}
|
||||
|
||||
@ -419,6 +433,15 @@ async function deleteSelectedHost() {
|
||||
<option value="rdp">RDP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-400 mb-1">Credential</label>
|
||||
<select v-model="inlineHost.credentialId" class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded text-white">
|
||||
<option :value="null">— None —</option>
|
||||
<option v-for="cred in allCredentials" :key="cred.id" :value="cred.id">
|
||||
{{ cred.name }} ({{ cred.username || cred.type }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-400 mb-1">Tags <span class="text-gray-600">(comma separated)</span></label>
|
||||
<input v-model="inlineHost.tags" type="text" placeholder="ssh, prod, web"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user