Skip to content

Commit 75e0dfb

Browse files
authored
Merge pull request #16 from luizomf/feat/config-enhancements
feat(config): ProxyJump visualization, drag reorder, order hint
2 parents ee50447 + 4f27f38 commit 75e0dfb

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

src/pages/config.astro

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ import Footer from '../components/Footer.astro';
5959
</div>
6060
</div>
6161

62+
<!-- Host order hint -->
63+
<p class="order-hint text-muted" id="order-hint" hidden>
64+
Arraste os cards para reordenar. A ordem importa — o SSH usa o primeiro Host que combinar.
65+
</p>
66+
6267
<!-- Host entries container -->
6368
<section id="hosts-container"></section>
6469

@@ -294,6 +299,59 @@ import Footer from '../components/Footer.astro';
294299
gap: 0.5rem;
295300
}
296301

302+
/* Order hint */
303+
.order-hint {
304+
font-size: 0.75rem;
305+
margin-bottom: 0.5rem;
306+
}
307+
308+
/* Drag handle */
309+
.host-card__drag {
310+
cursor: grab;
311+
color: var(--text-muted);
312+
font-size: 0.9rem;
313+
padding: 0 0.25rem;
314+
flex-shrink: 0;
315+
user-select: none;
316+
}
317+
318+
.host-card__drag:active {
319+
cursor: grabbing;
320+
}
321+
322+
.host-card--dragging {
323+
opacity: 0.4;
324+
border-style: dashed;
325+
}
326+
327+
.host-card--drag-over {
328+
border-color: var(--accent);
329+
box-shadow: var(--glow-sm);
330+
}
331+
332+
/* ProxyJump chain */
333+
.proxy-chain {
334+
display: flex;
335+
align-items: center;
336+
gap: 0.35rem;
337+
flex-wrap: wrap;
338+
margin-top: 0.5rem;
339+
padding: 0.4rem 0.6rem;
340+
background: var(--bg-secondary);
341+
border: 1px solid var(--border);
342+
border-radius: var(--radius);
343+
font-size: 0.75rem;
344+
}
345+
346+
.proxy-chain__node {
347+
color: var(--accent);
348+
font-weight: 700;
349+
}
350+
351+
.proxy-chain__arrow {
352+
color: var(--text-muted);
353+
}
354+
297355
@media (max-width: 640px) {
298356
.actions-bar {
299357
flex-direction: column;
@@ -328,12 +386,14 @@ import Footer from '../components/Footer.astro';
328386
hostsContainer.innerHTML = '';
329387
emptyState.hidden = hosts.length > 0;
330388
outputSection.hidden = hosts.length === 0;
389+
$<HTMLElement>('order-hint').hidden = hosts.length < 2;
331390

332391
for (const host of hosts) {
333392
hostsContainer.appendChild(createHostCard(host, false));
334393
}
335394

336395
updateOutput();
396+
updateProxyChains();
337397
}
338398

339399
function updateOutput() {
@@ -350,9 +410,12 @@ import Footer from '../components/Footer.astro';
350410
const title = entry.host || 'novo-host';
351411
const subtitle = [entry.user, entry.hostName].filter(Boolean).join('@') || '';
352412

413+
card.draggable = true;
414+
353415
card.innerHTML = `
354416
<div class="host-card__header">
355417
<div class="host-card__header-left">
418+
<span class="host-card__drag" title="Arraste para reordenar">&#x2630;</span>
356419
<span class="host-card__chevron">&#9654;</span>
357420
<span class="host-card__title">Host ${title}</span>
358421
<span class="host-card__subtitle">${esc(subtitle)}</span>
@@ -386,6 +449,7 @@ import Footer from '../components/Footer.astro';
386449
<input type="text" data-field="proxyJump" value="${esc(entry.proxyJump)}" placeholder="bastion" />
387450
</div>
388451
</div>
452+
<div class="proxy-chain" data-proxy-chain="${entry.id}" hidden></div>
389453
<button class="advanced-toggle" data-toggle="${entry.id}">+ Opções avançadas</button>
390454
<div class="advanced-fields form-grid" hidden data-advanced="${entry.id}">
391455
<div class="form-group">
@@ -471,6 +535,7 @@ import Footer from '../components/Footer.astro';
471535
titleEl.textContent = `Host ${entry.host || 'novo-host'}`;
472536
subtitleEl.textContent = [entry.user, entry.hostName].filter(Boolean).join('@');
473537
updateOutput();
538+
if (field === 'proxyJump') updateProxyChains();
474539
};
475540
input.addEventListener('input', handler);
476541
input.addEventListener('change', handler);
@@ -522,6 +587,90 @@ import Footer from '../components/Footer.astro';
522587
return value.replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
523588
}
524589

590+
// --- ProxyJump chain resolver ---
591+
function resolveChain(entry: SshHostEntry): string[] {
592+
const chain: string[] = ['Você'];
593+
const visited = new Set<string>();
594+
let jump = entry.proxyJump;
595+
596+
while (jump) {
597+
if (visited.has(jump)) break; // circular
598+
visited.add(jump);
599+
chain.push(jump);
600+
const jumpHost = hosts.find(h => h.host === jump);
601+
jump = jumpHost?.proxyJump;
602+
}
603+
604+
chain.push(entry.host || 'destino');
605+
return chain;
606+
}
607+
608+
function updateProxyChains() {
609+
for (const entry of hosts) {
610+
const el = document.querySelector(`[data-proxy-chain="${entry.id}"]`) as HTMLElement | null;
611+
if (!el) continue;
612+
613+
if (!entry.proxyJump) {
614+
el.hidden = true;
615+
continue;
616+
}
617+
618+
const chain = resolveChain(entry);
619+
el.hidden = false;
620+
el.innerHTML = chain
621+
.map((node, i) => {
622+
const nodeHtml = `<span class="proxy-chain__node">${esc(node)}</span>`;
623+
return i < chain.length - 1
624+
? `${nodeHtml} <span class="proxy-chain__arrow">&rarr;</span>`
625+
: nodeHtml;
626+
})
627+
.join(' ');
628+
}
629+
}
630+
631+
// --- Drag and drop ---
632+
let draggedId: string | null = null;
633+
634+
hostsContainer.addEventListener('dragstart', (e) => {
635+
const card = (e.target as HTMLElement).closest('.host-card') as HTMLElement | null;
636+
if (!card) return;
637+
draggedId = card.dataset.id!;
638+
card.classList.add('host-card--dragging');
639+
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move';
640+
});
641+
642+
hostsContainer.addEventListener('dragend', (e) => {
643+
const card = (e.target as HTMLElement).closest('.host-card') as HTMLElement | null;
644+
if (card) card.classList.remove('host-card--dragging');
645+
hostsContainer.querySelectorAll('.host-card--drag-over').forEach(c => c.classList.remove('host-card--drag-over'));
646+
draggedId = null;
647+
});
648+
649+
hostsContainer.addEventListener('dragover', (e) => {
650+
e.preventDefault();
651+
const card = (e.target as HTMLElement).closest('.host-card') as HTMLElement | null;
652+
if (!card || card.dataset.id === draggedId) return;
653+
hostsContainer.querySelectorAll('.host-card--drag-over').forEach(c => c.classList.remove('host-card--drag-over'));
654+
card.classList.add('host-card--drag-over');
655+
});
656+
657+
hostsContainer.addEventListener('drop', (e) => {
658+
e.preventDefault();
659+
const targetCard = (e.target as HTMLElement).closest('.host-card') as HTMLElement | null;
660+
if (!targetCard || !draggedId) return;
661+
662+
const targetId = targetCard.dataset.id!;
663+
if (targetId === draggedId) return;
664+
665+
const fromIdx = hosts.findIndex(h => h.id === draggedId);
666+
const toIdx = hosts.findIndex(h => h.id === targetId);
667+
if (fromIdx === -1 || toIdx === -1) return;
668+
669+
const [moved] = hosts.splice(fromIdx, 1);
670+
hosts.splice(toIdx, 0, moved);
671+
render();
672+
});
673+
525674
// --- Add Host ---
526675
$<HTMLButtonElement>('btn-add-host').addEventListener('click', () => {
527676
const newHost = createEmptyHost();

0 commit comments

Comments
 (0)