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:
@@ -373,7 +373,10 @@
|
||||
"testing": "Testing...",
|
||||
"testSuccess": "NPM connection successful",
|
||||
"testFailed": "NPM connection failed",
|
||||
"saveFailedConnection": "Cannot save \u2014 connection test failed"
|
||||
"saveFailedConnection": "Cannot save \u2014 connection test failed",
|
||||
"remoteMode": "Remote NPM",
|
||||
"remoteModeHelp": "Enable when NPM runs on a different machine than Docker. Forwards to Server IP with published host ports.",
|
||||
"remoteModeWarning": "Requires Server IP in General settings. Ports are auto-mapped to random host ports."
|
||||
},
|
||||
"settingsCredentials": {
|
||||
"title": "Credentials",
|
||||
|
||||
@@ -373,7 +373,10 @@
|
||||
"testing": "Проверка...",
|
||||
"testSuccess": "Подключение к NPM успешно",
|
||||
"testFailed": "Не удалось подключиться к NPM",
|
||||
"saveFailedConnection": "Невозможно сохранить — проверка соединения не пройдена"
|
||||
"saveFailedConnection": "Невозможно сохранить — проверка соединения не пройдена",
|
||||
"remoteMode": "Удалённый NPM",
|
||||
"remoteModeHelp": "Включите, если NPM работает на другой машине. Перенаправление на IP сервера с опубликованными портами.",
|
||||
"remoteModeWarning": "Требуется IP сервера в общих настройках. Порты автоматически привязываются к случайным портам хоста."
|
||||
},
|
||||
"settingsCredentials": {
|
||||
"title": "Учётные данные",
|
||||
|
||||
@@ -106,6 +106,7 @@ export interface Settings {
|
||||
has_npm_password: boolean;
|
||||
/** Sent on PUT to update the password; never returned by GET. */
|
||||
npm_password?: string;
|
||||
npm_remote: boolean;
|
||||
polling_interval: string;
|
||||
base_volume_path: string;
|
||||
ssl_certificate_id: number;
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user