feat: NPM remote mode for cross-machine deployments

- Add npm_remote setting: when enabled, proxy forwards to server_ip with
  published host ports instead of Docker container names
- Deployer looks up assigned host port via InspectContainerPort in remote mode
- Auto-remove stale containers with same name before creating new ones
- Add Remote NPM toggle with warning on NPM settings page
- DB migration + schema for npm_remote column
This commit is contained in:
2026-04-05 02:18:06 +03:00
parent f71f2275a2
commit 195ef3e7e5
10 changed files with 77 additions and 12 deletions
+20 -1
View File
@@ -2,6 +2,7 @@
import { getSettings, updateSettings, listNpmCertificates, testNpmConnection } from '$lib/api';
import type { EntityPickerItem } from '$lib/types';
import FormField from '$lib/components/FormField.svelte';
import ToggleSwitch from '$lib/components/ToggleSwitch.svelte';
import EntityPicker from '$lib/components/EntityPicker.svelte';
import Skeleton from '$lib/components/Skeleton.svelte';
import { toasts } from '$lib/stores/toast';
@@ -15,6 +16,7 @@
let npmEmail = $state('');
let npmPassword = $state('');
let npmHasCredentials = $state(false);
let npmRemote = $state(false);
let editingNpm = $state(false);
let errors = $state<Record<string, string>>({});
@@ -42,6 +44,7 @@
npmHasCredentials = !!(settings.npm_url && settings.npm_email);
npmPassword = '';
sslCertificateId = settings.ssl_certificate_id ?? 0;
npmRemote = settings.npm_remote ?? false;
if (sslCertificateId > 0) sslCertName = `Certificate #${sslCertificateId}`;
} catch (err) { toasts.error(err instanceof Error ? err.message : $t('settingsCredentials.loadFailed')); } finally { loading = false; }
}
@@ -77,7 +80,7 @@
return;
}
try {
const payload: Record<string, unknown> = { npm_url: npmUrl.trim(), npm_email: npmEmail.trim() };
const payload: Record<string, unknown> = { npm_url: npmUrl.trim(), npm_email: npmEmail.trim(), npm_remote: npmRemote };
if (npmPassword.trim()) payload.npm_password = npmPassword.trim();
await updateSettings(payload);
npmHasCredentials = true;
@@ -195,6 +198,22 @@
{/if}
</div>
<!-- NPM Mode -->
<div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]">
<div class="flex items-center gap-3">
<ToggleSwitch bind:checked={npmRemote} label={$t('settingsNpm.remoteMode')} />
<div>
<span class="text-sm font-medium text-[var(--text-primary)]">{$t('settingsNpm.remoteMode')}</span>
<p class="text-xs text-[var(--text-tertiary)]">{$t('settingsNpm.remoteModeHelp')}</p>
</div>
</div>
{#if npmRemote}
<div class="mt-3 rounded-lg border border-amber-300 bg-amber-50 dark:border-amber-700 dark:bg-amber-950/30 p-3">
<p class="text-sm text-amber-800 dark:text-amber-300">{$t('settingsNpm.remoteModeWarning')}</p>
</div>
{/if}
</div>
<!-- SSL Certificate -->
<div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]">
<div class="flex items-start gap-3">