Skip to content

Commit 4a4f3ae

Browse files
axpnetaeroftp[bot]clauderaelb
committed
feat(servers): add Disconnect entry to the server context menu when connected
Pairs with the pulsing health indicator from the previous commit and closes the other half of issue #222: the right-click menu on a saved server now offers a Disconnect entry, but only when the profile has at least one open session in the tab strip. When nothing is connected the entry simply does not appear, so the menu mirrors the actual connection state instead of showing a no-op. Disconnect closes every session whose savedServerId matches the selected profile (covers the multi-tab case where the same saved server has been opened more than once). It reuses the existing closeSession path, which already takes care of switching to another session or calling disconnectFromFtp when nothing is left. Wiring is symmetric with activeProfileIds: App.tsx exposes a disconnectProfile(profileId) callback, IntroHub forwards it, and MyServersPanel reads activeProfileIds + onDisconnectProfile from props to gate the entry. No new i18n key needed: common.disconnect is already translated in every locale. Co-Authored-By: aeroftp[bot] <aeroftp[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Rael Bauer <13108198+raelb@users.noreply.github.com>
1 parent 861cf7d commit 4a4f3ae

3 files changed

Lines changed: 40 additions & 3 deletions

File tree

src/App.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5085,6 +5085,19 @@ interface UpdateVerificationInfo {
50855085
setSessions(prev => prev.filter(s => s.id !== sessionId));
50865086
};
50875087

5088+
// Issue #222: close every open session whose savedServerId matches the
5089+
// given profile id. Reached from the server card context menu when the
5090+
// profile is in the activeProfileIds set, so the entry only shows for
5091+
// saved servers that actually have a live session.
5092+
const disconnectProfile = useCallback(async (profileId: string) => {
5093+
const targetIds = sessions
5094+
.filter(s => s.savedServerId === profileId)
5095+
.map(s => s.id);
5096+
for (const id of targetIds) {
5097+
await closeSession(id);
5098+
}
5099+
}, [sessions]);
5100+
50885101
const closeAllSessions = async () => {
50895102
for (const s of sessions) {
50905103
const overlaySessionId = s.aeroVaultOverlaySession?.sessionId;
@@ -11791,6 +11804,7 @@ interface UpdateVerificationInfo {
1179111804
onOpenCloudPanel={() => setShowCloudPanel(true)}
1179211805
hasExistingSessions={sessions.length > 0}
1179311806
activeProfileIds={activeProfileIds}
11807+
onDisconnectProfile={disconnectProfile}
1179411808
serversRefreshKey={serversRefreshKey}
1179511809
onServersChanged={() => setServersRefreshKey(k => k + 1)}
1179611810
onAeroCloud={() => {

src/components/IntroHub/IntroHub.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export interface IntroHubProps {
5656
* Pulses the per-server health indicator so users can tell at a glance
5757
* which saved server they are currently connected to. Issue #222. */
5858
activeProfileIds?: ReadonlySet<string>;
59+
/** Close every open session for the given saved profile. Surfaces as the
60+
* Disconnect entry in the server card context menu, gated on the profile
61+
* being present in `activeProfileIds`. Issue #222. */
62+
onDisconnectProfile?: (profileId: string) => void | Promise<void>;
5963
}
6064

6165
function generateTabId(): string {
@@ -84,6 +88,7 @@ export function IntroHub(props: IntroHubProps) {
8488
serversRefreshKey,
8589
onServersChanged,
8690
activeProfileIds,
91+
onDisconnectProfile,
8792
} = props;
8893

8994
const t = useTranslation();
@@ -387,6 +392,7 @@ export function IntroHub(props: IntroHubProps) {
387392
onOpenCrossProfile={onOpenCrossProfile}
388393
onOpenMountManager={onOpenMountManager}
389394
activeProfileIds={activeProfileIds}
395+
onDisconnectProfile={onDisconnectProfile}
390396
/>
391397
</div>
392398

src/components/IntroHub/MyServersPanel.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
33
import { invoke } from '@tauri-apps/api/core';
4-
import { Plus, Server as ServerIcon, Play, Edit2, Copy, Trash2, Activity, Star, PencilLine, ArrowUpRight, ArrowDownLeft, Database, Globe, Cloud, Camera, Code, Gauge, HardDrive } from 'lucide-react';
4+
import { Plus, Server as ServerIcon, Play, Edit2, Copy, Trash2, Activity, Star, PencilLine, ArrowUpRight, ArrowDownLeft, Database, Globe, Cloud, Camera, Code, Gauge, HardDrive, LogOut } from 'lucide-react';
55
import { ServerProfile, ConnectionParams, ProviderType, getE2EBits, getProtocolClass, isOAuthProvider, isFourSharedProvider, isNativeApiProtocol } from '../../types';
66
import { MyServersViewMode, MyServersFilterBy, FILTER_CHIPS, CatalogCategoryId } from '../../types/catalog';
77
import { MyServersToolbar } from './MyServersToolbar';
@@ -240,6 +240,9 @@ interface MyServersPanelProps {
240240
/** Profile ids that have at least one open session in the tab strip.
241241
* Drives the pulsing health indicator on cards / rows (issue #222). */
242242
activeProfileIds?: ReadonlySet<string>;
243+
/** Close every open session for the given saved profile. Wired to the
244+
* Disconnect context-menu entry, gated on `activeProfileIds`. #222. */
245+
onDisconnectProfile?: (profileId: string) => void | Promise<void>;
243246
}
244247

245248
const EMPTY_STATE_CATEGORIES: { id: CatalogCategoryId; labelKey: string; icon: React.ReactNode; iconColor: string }[] = [
@@ -262,6 +265,7 @@ export function MyServersPanel({
262265
onOpenCrossProfile,
263266
onOpenMountManager,
264267
activeProfileIds,
268+
onDisconnectProfile,
265269
}: MyServersPanelProps) {
266270
const t = useTranslation();
267271
// Lazy init: read localStorage synchronously on first mount so the panel
@@ -1058,13 +1062,26 @@ export function MyServersPanel({
10581062

10591063
const handleContextMenu = useCallback((e: React.MouseEvent, server: ServerProfile) => {
10601064
const isFav = favorites.has(server.id);
1065+
const hasActiveSession = activeProfileIds?.has(server.id) ?? false;
10611066
const items: ContextMenuItem[] = [
10621067
{ label: t('common.connect'), icon: <Play size={14} />, action: () => handleConnect(server) },
1068+
];
1069+
if (hasActiveSession && onDisconnectProfile) {
1070+
// Issue #222: Disconnect entry surfaces only when at least one
1071+
// session is open for this saved profile, so the menu mirrors
1072+
// the actual connection state instead of showing a no-op entry.
1073+
items.push({
1074+
label: t('common.disconnect'),
1075+
icon: <LogOut size={14} className="text-amber-500" />,
1076+
action: () => { void onDisconnectProfile(server.id); },
1077+
});
1078+
}
1079+
items.push(
10631080
{ label: t('common.edit'), icon: <Edit2 size={14} />, action: () => onEdit(server) },
10641081
{ label: t('introHub.renameWithHotkey'), icon: <PencilLine size={14} />, action: () => handleRenameStart(server) },
10651082
{ label: t('common.copy'), icon: <Copy size={14} />, action: () => handleDuplicate(server) },
10661083
{ label: isFav ? t('introHub.removeFavorite') : t('introHub.addFavorite'), icon: <Star size={14} />, action: () => toggleFavorite(server.id) },
1067-
];
1084+
);
10681085
if (onOpenCrossProfile && servers.length > 1) {
10691086
items.push({
10701087
label: t('introHub.setAsCrossProfileSource'),
@@ -1090,7 +1107,7 @@ export function MyServersPanel({
10901107
{ label: t('common.delete'), icon: <Trash2 size={14} />, action: () => handleDelete(server), danger: true },
10911108
);
10921109
showContextMenu(e, items);
1093-
}, [t, handleConnect, onEdit, handleDuplicate, handleDelete, handleRenameStart, toggleFavorite, favorites, showContextMenu, onOpenCrossProfile, setAsCrossProfileSource, setAsCrossProfileDestination, servers.length, handleOpenMount]);
1110+
}, [t, handleConnect, onEdit, handleDuplicate, handleDelete, handleRenameStart, toggleFavorite, favorites, showContextMenu, onOpenCrossProfile, setAsCrossProfileSource, setAsCrossProfileDestination, servers.length, handleOpenMount, activeProfileIds, onDisconnectProfile]);
10941111

10951112
return (
10961113
<div className="h-full flex flex-col" onClick={handlePanelBlankClick}>

0 commit comments

Comments
 (0)