What happens when you adopt: Dockhand will track this compose file, letting you edit, start, and stop the stack from the UI. Your files stay in their current location.
diff --git a/src/routes/stacks/RedeployPopover.svelte b/src/routes/stacks/RedeployPopover.svelte
new file mode 100644
index 0000000..dc652fe
--- /dev/null
+++ b/src/routes/stacks/RedeployPopover.svelte
@@ -0,0 +1,99 @@
+
+
+
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
+
Redeploy stack
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/terminal/+page.svelte b/src/routes/terminal/+page.svelte
index 2ca1cf9..046b02b 100644
--- a/src/routes/terminal/+page.svelte
+++ b/src/routes/terminal/+page.svelte
@@ -15,7 +15,7 @@
import { currentEnvironment, environments, appendEnvParam } from '$lib/stores/environment';
import Terminal from './Terminal.svelte';
import { NoEnvironment } from '$lib/components/ui/empty-state';
- import { detectShells, getBestShell, hasAvailableShell, USER_OPTIONS, type ShellInfo, type ShellDetectionResult } from '$lib/utils/shell-detection';
+ import { detectShells, getBestShell, hasAvailableShell, USER_OPTIONS, getSavedUser, saveUserForContainer, getCustomUsers, removeCustomUser, type ShellInfo, type ShellDetectionResult } from '$lib/utils/shell-detection';
// Track if we've handled the initial container from URL
let initialContainerHandled = $state(false);
@@ -35,6 +35,8 @@
// Shell/user options
let selectedShell = $state('/bin/bash');
let selectedUser = $state('root');
+ let customUserInput = $state('');
+ let customUsers = $state
([]);
let terminalFontSize = $state(14);
// Track previous shell/user for reconnection
@@ -115,6 +117,16 @@
if (bestShell && bestShell !== selectedShell) {
selectedShell = bestShell;
}
+
+ // Restore saved user for this container
+ const savedUser = getSavedUser(container.id);
+ if (savedUser !== null) {
+ selectedUser = savedUser;
+ committedUser = savedUser;
+ } else {
+ selectedUser = 'root';
+ committedUser = 'root';
+ }
} catch (error) {
console.error('Failed to detect shells:', error);
} finally {
@@ -151,16 +163,42 @@
}
}
+ // Committed user: only updates when a preset is selected or custom input is confirmed
+ let committedUser = $state('root');
+
+ function commitUser(user: string) {
+ committedUser = user;
+ if (selectedContainer) {
+ saveUserForContainer(selectedContainer.id, user);
+ customUsers = getCustomUsers();
+ }
+ }
+
+ // When a user is selected from dropdown, commit immediately
+ function onUserSelectChange(value: string) {
+ commitUser(value);
+ }
+
+ // Confirm custom user on Enter
+ function onCustomUserKeydown(e: KeyboardEvent) {
+ e.stopPropagation();
+ if (e.key === 'Enter' && customUserInput.trim()) {
+ const newUser = customUserInput.trim();
+ selectedUser = newUser;
+ commitUser(newUser);
+ customUserInput = '';
+ }
+ }
+
// Watch for shell/user changes while connected and trigger reconnect
$effect(() => {
if (selectedContainer && connected && terminalComponent) {
- if (selectedShell !== prevShell || selectedUser !== prevUser) {
- // Reconnect with new shell/user
+ if (selectedShell !== prevShell || committedUser !== prevUser) {
terminalComponent.reconnect();
}
}
prevShell = selectedShell;
- prevUser = selectedUser;
+ prevUser = committedUser;
});
// Change font size
@@ -175,6 +213,7 @@
}
onMount(async () => {
+ customUsers = getCustomUsers();
await fetchContainers();
// Check for container ID in URL query parameter
@@ -336,10 +375,10 @@
-
+
- {USER_OPTIONS.find(o => o.value === selectedUser)?.label || 'Select'}
+ {USER_OPTIONS.find(o => o.value === selectedUser)?.label || selectedUser || 'Select'}
{#each USER_OPTIONS as option}
@@ -348,6 +387,36 @@
{option.label}
{/each}
+ {#if customUsers.length > 0}
+
+ {#each customUsers as cu}
+
+
+
+ {cu}
+
+
+
+ {/each}
+ {/if}
+
+
+
+ e.stopPropagation()}
+ />
+
@@ -428,13 +497,13 @@