rds_ssm_connect 1.7.13 → 1.7.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configLoader.js +86 -0
- package/connect.js +189 -23
- package/gui-adapter.js +72 -55
- package/package.json +1 -1
- package/scripts/generate-update-json.js +10 -0
- package/src/App.svelte +60 -3
- package/src/lib/SavedConnections.svelte +76 -11
- package/src/lib/Settings.svelte +433 -28
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/lib.rs +130 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/test/configLoader.test.js +150 -0
- package/envPortMapping.js +0 -54
- package/latest.json +0 -27
package/src/App.svelte
CHANGED
|
@@ -32,6 +32,7 @@ let showSavePrompt = $state(false)
|
|
|
32
32
|
let lastConnectedConfig = $state(null)
|
|
33
33
|
let saveConnectionName = $state('')
|
|
34
34
|
let showDeleteConfirm = $state(null)
|
|
35
|
+
let showCloseConfirm = $state(false)
|
|
35
36
|
let isCheckingUpdates = $state(false)
|
|
36
37
|
let updateCheckMessage = $state('')
|
|
37
38
|
|
|
@@ -42,10 +43,12 @@ let showSettings = $state(false)
|
|
|
42
43
|
|
|
43
44
|
let invoke = null
|
|
44
45
|
let listen = null
|
|
46
|
+
let appWindow = null
|
|
45
47
|
|
|
46
48
|
// Cleanup references
|
|
47
49
|
let cancelUpdateMsgTimeout = null
|
|
48
50
|
let unlistenSidecar = null
|
|
51
|
+
let unlistenCloseRequested = null
|
|
49
52
|
|
|
50
53
|
// Global keyboard shortcuts
|
|
51
54
|
function handleGlobalKeydown(e) {
|
|
@@ -83,21 +86,31 @@ async function initApp() {
|
|
|
83
86
|
loadingProjects = true
|
|
84
87
|
|
|
85
88
|
try {
|
|
86
|
-
const [core, event] = await withTimeout(
|
|
89
|
+
const [core, event, windowModule] = await withTimeout(
|
|
87
90
|
Promise.all([
|
|
88
91
|
import('@tauri-apps/api/core'),
|
|
89
92
|
import('@tauri-apps/api/event'),
|
|
93
|
+
import('@tauri-apps/api/window'),
|
|
90
94
|
]),
|
|
91
95
|
5000,
|
|
92
96
|
)
|
|
93
97
|
invoke = core.invoke
|
|
94
98
|
listen = event.listen
|
|
99
|
+
appWindow = windowModule.getCurrentWindow()
|
|
95
100
|
} catch (err) {
|
|
96
101
|
errorMessage = `Failed to load Tauri API: ${err}`
|
|
97
102
|
loadingProjects = false
|
|
98
103
|
return
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
// Intercept window close — prompt if there are active connections
|
|
107
|
+
unlistenCloseRequested = await appWindow.onCloseRequested(async (event) => {
|
|
108
|
+
if (activeConnections.length > 0) {
|
|
109
|
+
event.preventDefault()
|
|
110
|
+
showCloseConfirm = true
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
101
114
|
// Set up sidecar listener (non-blocking)
|
|
102
115
|
listen('sidecar-event', (ev) => {
|
|
103
116
|
const data = ev.payload
|
|
@@ -159,8 +172,28 @@ function retryInit() {
|
|
|
159
172
|
onDestroy(() => {
|
|
160
173
|
cancelUpdateMsgTimeout?.()
|
|
161
174
|
unlistenSidecar?.()
|
|
175
|
+
unlistenCloseRequested?.()
|
|
162
176
|
})
|
|
163
177
|
|
|
178
|
+
async function confirmClose() {
|
|
179
|
+
showCloseConfirm = false
|
|
180
|
+
try {
|
|
181
|
+
await invoke('disconnect_all')
|
|
182
|
+
} catch (_err) {
|
|
183
|
+
// Best-effort disconnect before closing
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await invoke('quit_app')
|
|
187
|
+
} catch (_err) {
|
|
188
|
+
// If quit_app fails, force close via window API
|
|
189
|
+
appWindow?.close()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function cancelClose() {
|
|
194
|
+
showCloseConfirm = false
|
|
195
|
+
}
|
|
196
|
+
|
|
164
197
|
async function checkForUpdates() {
|
|
165
198
|
if (isCheckingUpdates) return
|
|
166
199
|
isCheckingUpdates = true
|
|
@@ -213,6 +246,14 @@ async function loadProfiles() {
|
|
|
213
246
|
}
|
|
214
247
|
}
|
|
215
248
|
|
|
249
|
+
async function refreshProjects() {
|
|
250
|
+
try {
|
|
251
|
+
projects = await invoke('list_projects')
|
|
252
|
+
} catch (err) {
|
|
253
|
+
errorMessage = `Failed to refresh projects: ${err}`
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
216
257
|
async function handleConnect() {
|
|
217
258
|
if (!selectedProject || !selectedProfile) return
|
|
218
259
|
|
|
@@ -392,8 +433,10 @@ async function handleInstallUpdate() {
|
|
|
392
433
|
|
|
393
434
|
try {
|
|
394
435
|
await invoke('install_update')
|
|
395
|
-
|
|
396
|
-
|
|
436
|
+
// App should auto-restart and never reach here, but just in case:
|
|
437
|
+
isUpdating = false
|
|
438
|
+
showUpdateBanner = false
|
|
439
|
+
statusMessage = 'Update installed successfully.'
|
|
397
440
|
} catch (err) {
|
|
398
441
|
errorMessage = `Update failed: ${err}`
|
|
399
442
|
isUpdating = false
|
|
@@ -490,6 +533,7 @@ const isAlreadySaved = $derived(
|
|
|
490
533
|
{savedConnections}
|
|
491
534
|
{activeConnections}
|
|
492
535
|
{projects}
|
|
536
|
+
{connectingId}
|
|
493
537
|
onConnect={handleSavedConnectionConnect}
|
|
494
538
|
onDisconnect={handleDisconnectOne}
|
|
495
539
|
onDelete={handleDeleteSavedConnection}
|
|
@@ -586,6 +630,18 @@ const isAlreadySaved = $derived(
|
|
|
586
630
|
/>
|
|
587
631
|
{/if}
|
|
588
632
|
|
|
633
|
+
{#if showCloseConfirm}
|
|
634
|
+
<ConfirmDialog
|
|
635
|
+
title="Close Application"
|
|
636
|
+
message="All active connections will be closed. Are you sure you want to quit?"
|
|
637
|
+
confirmLabel="Quit"
|
|
638
|
+
cancelLabel="Cancel"
|
|
639
|
+
destructive={true}
|
|
640
|
+
onConfirm={confirmClose}
|
|
641
|
+
onCancel={cancelClose}
|
|
642
|
+
/>
|
|
643
|
+
{/if}
|
|
644
|
+
|
|
589
645
|
{#if showPrerequisites}
|
|
590
646
|
<PrerequisitesCheck
|
|
591
647
|
prerequisites={prerequisitesData}
|
|
@@ -598,6 +654,7 @@ const isAlreadySaved = $derived(
|
|
|
598
654
|
<Settings
|
|
599
655
|
onClose={() => showSettings = false}
|
|
600
656
|
{invoke}
|
|
657
|
+
onProjectsChanged={refreshProjects}
|
|
601
658
|
/>
|
|
602
659
|
{/if}
|
|
603
660
|
{/if}
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
savedConnections = [],
|
|
7
7
|
activeConnections = [],
|
|
8
8
|
projects = [],
|
|
9
|
+
connectingId = null,
|
|
9
10
|
onConnect,
|
|
10
11
|
onDisconnect,
|
|
11
12
|
onDelete,
|
|
@@ -85,7 +86,8 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
85
86
|
<div class="connections-list">
|
|
86
87
|
{#each savedConnections as connection (connection.id)}
|
|
87
88
|
{@const activeConn = getActiveConnection(connection)}
|
|
88
|
-
|
|
89
|
+
{@const isConnecting = connectingId === connection.id}
|
|
90
|
+
<div class="connection-item" class:active={activeConn} class:connecting={isConnecting} class:expanded={expandedId === connection.id}>
|
|
89
91
|
<div
|
|
90
92
|
class="connection-header"
|
|
91
93
|
role="button"
|
|
@@ -93,7 +95,11 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
93
95
|
onclick={() => activeConn && toggleExpand(connection.id)}
|
|
94
96
|
onkeydown={(e) => handleHeaderKeydown(e, activeConn, connection.id)}
|
|
95
97
|
>
|
|
96
|
-
{#if
|
|
98
|
+
{#if isConnecting}
|
|
99
|
+
<div class="connection-status">
|
|
100
|
+
<span class="connecting-spinner"></span>
|
|
101
|
+
</div>
|
|
102
|
+
{:else if activeConn}
|
|
97
103
|
<div class="connection-status">
|
|
98
104
|
<span class="status-dot"></span>
|
|
99
105
|
</div>
|
|
@@ -105,19 +111,24 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
105
111
|
<span class="connection-port">:{activeConn.localPort}</span>
|
|
106
112
|
{/if}
|
|
107
113
|
</div>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Last used: {formatLastUsed(connection.lastUsedAt)}
|
|
114
|
+
{#if isConnecting}
|
|
115
|
+
<span class="connecting-text">Connecting...</span>
|
|
116
|
+
{:else}
|
|
117
|
+
<span class="connection-meta">
|
|
118
|
+
{getProjectName(connection.projectKey)} / {connection.profile}
|
|
114
119
|
</span>
|
|
120
|
+
{#if !activeConn}
|
|
121
|
+
<span class="connection-last-used">
|
|
122
|
+
Last used: {formatLastUsed(connection.lastUsedAt)}
|
|
123
|
+
</span>
|
|
124
|
+
{/if}
|
|
115
125
|
{/if}
|
|
116
126
|
</div>
|
|
117
127
|
<div class="connection-actions">
|
|
118
128
|
{#if activeConn}
|
|
119
129
|
<button
|
|
120
130
|
class="btn-expand"
|
|
131
|
+
disabled={!!connectingId}
|
|
121
132
|
aria-label={expandedId === connection.id ? 'Collapse credentials' : 'Show credentials'}
|
|
122
133
|
>
|
|
123
134
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" class:rotated={expandedId === connection.id}>
|
|
@@ -126,6 +137,7 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
126
137
|
</button>
|
|
127
138
|
<button
|
|
128
139
|
class="btn-disconnect"
|
|
140
|
+
disabled={!!connectingId}
|
|
129
141
|
onclick={(e) => { e.stopPropagation(); handleDisconnect(activeConn); }}
|
|
130
142
|
aria-label="Disconnect {connection.name}"
|
|
131
143
|
>
|
|
@@ -136,15 +148,21 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
136
148
|
{:else}
|
|
137
149
|
<button
|
|
138
150
|
class="btn-connect"
|
|
151
|
+
disabled={!!connectingId}
|
|
139
152
|
onclick={(e) => { e.stopPropagation(); handleConnect(connection); }}
|
|
140
153
|
aria-label="Connect to {connection.name}"
|
|
141
154
|
>
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
155
|
+
{#if isConnecting}
|
|
156
|
+
<span class="btn-spinner"></span>
|
|
157
|
+
{:else}
|
|
158
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
159
|
+
<path d="M5 3l8 5-8 5V3z" fill="currentColor"/>
|
|
160
|
+
</svg>
|
|
161
|
+
{/if}
|
|
145
162
|
</button>
|
|
146
163
|
<button
|
|
147
164
|
class="btn-delete"
|
|
165
|
+
disabled={!!connectingId}
|
|
148
166
|
onclick={(e) => { e.stopPropagation(); handleDelete(connection); }}
|
|
149
167
|
aria-label="Delete {connection.name}"
|
|
150
168
|
>
|
|
@@ -255,6 +273,10 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
255
273
|
border: 1px solid rgba(52, 211, 153, 0.2);
|
|
256
274
|
}
|
|
257
275
|
|
|
276
|
+
.connection-item.connecting {
|
|
277
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
278
|
+
}
|
|
279
|
+
|
|
258
280
|
.connection-header {
|
|
259
281
|
display: flex;
|
|
260
282
|
align-items: center;
|
|
@@ -398,6 +420,49 @@ function handleHeaderKeydown(e, activeConn, connectionId) {
|
|
|
398
420
|
transform: rotate(180deg);
|
|
399
421
|
}
|
|
400
422
|
|
|
423
|
+
.btn-connect:disabled, .btn-delete:disabled, .btn-disconnect:disabled, .btn-expand:disabled {
|
|
424
|
+
opacity: 0.4;
|
|
425
|
+
cursor: not-allowed;
|
|
426
|
+
pointer-events: none;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.connecting-spinner {
|
|
430
|
+
display: block;
|
|
431
|
+
width: 8px;
|
|
432
|
+
height: 8px;
|
|
433
|
+
border: 1.5px solid rgba(99, 102, 241, 0.3);
|
|
434
|
+
border-top-color: #6366f1;
|
|
435
|
+
border-radius: 50%;
|
|
436
|
+
animation: spin 0.8s linear infinite;
|
|
437
|
+
will-change: transform;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.connecting-text {
|
|
441
|
+
font-size: 0.75rem;
|
|
442
|
+
color: #a5b4fc;
|
|
443
|
+
animation: fadeIn 0.3s ease-out;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.btn-spinner {
|
|
447
|
+
display: inline-block;
|
|
448
|
+
width: 12px;
|
|
449
|
+
height: 12px;
|
|
450
|
+
border: 1.5px solid rgba(52, 211, 153, 0.3);
|
|
451
|
+
border-top-color: #34d399;
|
|
452
|
+
border-radius: 50%;
|
|
453
|
+
animation: spin 0.8s linear infinite;
|
|
454
|
+
will-change: transform;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@keyframes spin {
|
|
458
|
+
to { transform: rotate(360deg); }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@keyframes fadeIn {
|
|
462
|
+
from { opacity: 0; }
|
|
463
|
+
to { opacity: 1; }
|
|
464
|
+
}
|
|
465
|
+
|
|
401
466
|
.connection-details {
|
|
402
467
|
padding: 0 16px 16px;
|
|
403
468
|
animation: slideDown 0.2s ease-out;
|