Update
This commit is contained in:
+27
-63
@@ -238,9 +238,8 @@ const DEFAULT_PORTAINER_SSH_CONFIG = {
|
|||||||
host: '',
|
host: '',
|
||||||
port: 22,
|
port: 22,
|
||||||
username: '',
|
username: '',
|
||||||
extraSshArgs: [],
|
password: '',
|
||||||
privateKey: '',
|
extraSshArgs: []
|
||||||
privateKeyPassphrase: ''
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeString = (value, fallback = '') => {
|
const normalizeString = (value, fallback = '') => {
|
||||||
@@ -283,14 +282,7 @@ const normalizeExtraArgs = (value, fallback = []) => {
|
|||||||
return Array.isArray(fallback) ? [...fallback] : [];
|
return Array.isArray(fallback) ? [...fallback] : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizePrivateKey = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.privateKey) => {
|
const normalizePassword = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.password) => {
|
||||||
if (value === undefined) return fallback;
|
|
||||||
if (value === null) return '';
|
|
||||||
if (typeof value !== 'string') return String(value);
|
|
||||||
return value.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizePrivateKeyPassphrase = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.privateKeyPassphrase) => {
|
|
||||||
if (value === undefined) return fallback;
|
if (value === undefined) return fallback;
|
||||||
if (value === null) return '';
|
if (value === null) return '';
|
||||||
return String(value);
|
return String(value);
|
||||||
@@ -303,14 +295,14 @@ const getPortainerSshConfig = () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = stored.value ? JSON.parse(stored.value) : {};
|
const parsed = stored.value ? JSON.parse(stored.value) : {};
|
||||||
const decryptedKey = parsed.privateKeyEncrypted ? decryptSensitive(parsed.privateKeyEncrypted) : '';
|
const decryptedPassword = parsed.passwordEncrypted ? decryptSensitive(parsed.passwordEncrypted) : null;
|
||||||
|
const fallbackPassword = parsed.password !== undefined ? parsed.password : null;
|
||||||
return {
|
return {
|
||||||
host: normalizeString(parsed.host),
|
host: normalizeString(parsed.host),
|
||||||
port: normalizePort(parsed.port),
|
port: normalizePort(parsed.port),
|
||||||
username: normalizeString(parsed.username),
|
username: normalizeString(parsed.username),
|
||||||
extraSshArgs: normalizeExtraArgs(parsed.extraSshArgs),
|
password: normalizePassword(decryptedPassword ?? fallbackPassword),
|
||||||
privateKey: decryptedKey,
|
extraSshArgs: normalizeExtraArgs(parsed.extraSshArgs)
|
||||||
privateKeyPassphrase: parsed.privateKeyPassphraseEncrypted ? decryptSensitive(parsed.privateKeyPassphraseEncrypted) : ''
|
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('⚠️ [Maintenance] Konnte Portainer SSH Konfiguration nicht parsen:', err.message);
|
console.warn('⚠️ [Maintenance] Konnte Portainer SSH Konfiguration nicht parsen:', err.message);
|
||||||
@@ -324,8 +316,7 @@ const persistPortainerSshConfig = (config) => {
|
|||||||
port: config.port,
|
port: config.port,
|
||||||
username: config.username,
|
username: config.username,
|
||||||
extraSshArgs: config.extraSshArgs,
|
extraSshArgs: config.extraSshArgs,
|
||||||
privateKeyEncrypted: config.privateKey ? encryptSensitive(config.privateKey) : null,
|
passwordEncrypted: config.password ? encryptSensitive(config.password) : null
|
||||||
privateKeyPassphraseEncrypted: config.privateKeyPassphrase ? encryptSensitive(config.privateKeyPassphrase) : null
|
|
||||||
};
|
};
|
||||||
setSetting(PORTAINER_SSH_CONFIG_KEY, JSON.stringify(payload));
|
setSetting(PORTAINER_SSH_CONFIG_KEY, JSON.stringify(payload));
|
||||||
};
|
};
|
||||||
@@ -334,9 +325,8 @@ const mergeSshConfig = (base, overrides = {}) => ({
|
|||||||
host: normalizeString(overrides.host, base.host),
|
host: normalizeString(overrides.host, base.host),
|
||||||
port: normalizePort(overrides.port, base.port),
|
port: normalizePort(overrides.port, base.port),
|
||||||
username: normalizeString(overrides.username, base.username),
|
username: normalizeString(overrides.username, base.username),
|
||||||
extraSshArgs: normalizeExtraArgs(overrides.extraSshArgs, base.extraSshArgs),
|
password: normalizePassword(overrides.password, base.password),
|
||||||
privateKey: normalizePrivateKey(overrides.privateKey, base.privateKey),
|
extraSshArgs: normalizeExtraArgs(overrides.extraSshArgs, base.extraSshArgs)
|
||||||
privateKeyPassphrase: normalizePrivateKeyPassphrase(overrides.privateKeyPassphrase, base.privateKeyPassphrase)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const savePortainerSshConfig = (payload = {}) => {
|
const savePortainerSshConfig = (payload = {}) => {
|
||||||
@@ -351,36 +341,17 @@ const deletePortainerSshConfig = () => {
|
|||||||
return { ...DEFAULT_PORTAINER_SSH_CONFIG };
|
return { ...DEFAULT_PORTAINER_SSH_CONFIG };
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTempPrivateKeyFile = (privateKey) => {
|
const createTempAskPassScript = (secret) => {
|
||||||
const normalized = normalizePrivateKey(privateKey, '');
|
|
||||||
const content = normalized.endsWith('\n') ? normalized : `${normalized}\n`;
|
|
||||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stackpulse-ssh-'));
|
|
||||||
const filePath = path.join(tmpDir, 'id_rsa');
|
|
||||||
fs.writeFileSync(filePath, content, { mode: 0o600 });
|
|
||||||
return {
|
|
||||||
filePath,
|
|
||||||
cleanup: () => {
|
|
||||||
try {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
} catch (err) { }
|
|
||||||
try {
|
|
||||||
fs.rmdirSync(tmpDir);
|
|
||||||
} catch (err) { }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTempAskPassScript = (passphrase) => {
|
|
||||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stackpulse-askpass-'));
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stackpulse-askpass-'));
|
||||||
const scriptPath = path.join(tmpDir, 'askpass.sh');
|
const scriptPath = path.join(tmpDir, 'askpass.sh');
|
||||||
const scriptContent = "#!/bin/sh\nprintf '%s\\n' \"$STACKPULSE_SSH_PASS\"\n";
|
const scriptContent = "#!/bin/sh\nprintf '%s\n' \"$STACKPULSE_SSH_PASS\"\n";
|
||||||
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o700 });
|
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o700 });
|
||||||
return {
|
return {
|
||||||
env: {
|
env: {
|
||||||
SSH_ASKPASS: scriptPath,
|
SSH_ASKPASS: scriptPath,
|
||||||
SSH_ASKPASS_REQUIRE: 'force',
|
SSH_ASKPASS_REQUIRE: 'force',
|
||||||
DISPLAY: process.env.DISPLAY || ':9999',
|
DISPLAY: process.env.DISPLAY || ':9999',
|
||||||
STACKPULSE_SSH_PASS: passphrase
|
STACKPULSE_SSH_PASS: secret
|
||||||
},
|
},
|
||||||
cleanup: () => {
|
cleanup: () => {
|
||||||
try {
|
try {
|
||||||
@@ -404,7 +375,10 @@ const buildSshCommandArgs = (config) => {
|
|||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
'-p', String(sshConfig.port),
|
'-p', String(sshConfig.port),
|
||||||
'-o', 'StrictHostKeyChecking=no'
|
'-o', 'StrictHostKeyChecking=no',
|
||||||
|
'-o', 'PreferredAuthentications=password',
|
||||||
|
'-o', 'PubkeyAuthentication=no',
|
||||||
|
'-o', 'NumberOfPasswordPrompts=1'
|
||||||
];
|
];
|
||||||
|
|
||||||
const cleanupTasks = [];
|
const cleanupTasks = [];
|
||||||
@@ -414,14 +388,14 @@ const buildSshCommandArgs = (config) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sshConfig.privateKeyPassphrase) {
|
let envOverrides = {};
|
||||||
args.push('-o', 'BatchMode=yes');
|
if (sshConfig.password) {
|
||||||
}
|
const { env, cleanup: dispose } = createTempAskPassScript(sshConfig.password);
|
||||||
|
envOverrides = { ...envOverrides, ...env };
|
||||||
if (sshConfig.privateKey) {
|
|
||||||
const { filePath, cleanup: dispose } = createTempPrivateKeyFile(sshConfig.privateKey);
|
|
||||||
registerCleanup(dispose);
|
registerCleanup(dispose);
|
||||||
args.push('-i', filePath);
|
args.push('-o', 'BatchMode=no');
|
||||||
|
} else {
|
||||||
|
args.push('-o', 'BatchMode=yes');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sshConfig.extraSshArgs.length) {
|
if (sshConfig.extraSshArgs.length) {
|
||||||
@@ -431,13 +405,6 @@ const buildSshCommandArgs = (config) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let envOverrides = {};
|
|
||||||
if (sshConfig.privateKeyPassphrase) {
|
|
||||||
const { env, cleanup: dispose } = createTempAskPassScript(sshConfig.privateKeyPassphrase);
|
|
||||||
envOverrides = { ...envOverrides, ...env };
|
|
||||||
registerCleanup(dispose);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(`${sshConfig.username}@${sshConfig.host}`);
|
args.push(`${sshConfig.username}@${sshConfig.host}`);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
@@ -1237,8 +1204,7 @@ app.get('/api/maintenance/config', (req, res) => {
|
|||||||
port: ssh.port,
|
port: ssh.port,
|
||||||
username: ssh.username,
|
username: ssh.username,
|
||||||
extraSshArgs: ssh.extraSshArgs,
|
extraSshArgs: ssh.extraSshArgs,
|
||||||
privateKeyStored: Boolean(ssh.privateKey),
|
passwordStored: Boolean(ssh.password)
|
||||||
privateKeyPassphraseStored: Boolean(ssh.privateKeyPassphrase)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1253,8 +1219,7 @@ app.put('/api/maintenance/ssh-config', (req, res) => {
|
|||||||
port: config.port,
|
port: config.port,
|
||||||
username: config.username,
|
username: config.username,
|
||||||
extraSshArgs: config.extraSshArgs,
|
extraSshArgs: config.extraSshArgs,
|
||||||
privateKeyStored: Boolean(config.privateKey),
|
passwordStored: Boolean(config.password)
|
||||||
privateKeyPassphraseStored: Boolean(config.privateKeyPassphrase)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1272,8 +1237,7 @@ app.delete('/api/maintenance/ssh-config', (req, res) => {
|
|||||||
port: config.port,
|
port: config.port,
|
||||||
username: config.username,
|
username: config.username,
|
||||||
extraSshArgs: config.extraSshArgs,
|
extraSshArgs: config.extraSshArgs,
|
||||||
privateKeyStored: false,
|
passwordStored: false
|
||||||
privateKeyPassphraseStored: false
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ const createEmptySshDraft = () => ({
|
|||||||
host: '',
|
host: '',
|
||||||
port: '22',
|
port: '22',
|
||||||
username: '',
|
username: '',
|
||||||
privateKey: '',
|
password: '',
|
||||||
privateKeyPassphrase: '',
|
|
||||||
extraSshArgs: ''
|
extraSshArgs: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,10 +126,8 @@ export default function Maintenance() {
|
|||||||
const [updateActionError, setUpdateActionError] = useState("");
|
const [updateActionError, setUpdateActionError] = useState("");
|
||||||
|
|
||||||
const [sshDraft, setSshDraft] = useState(() => createEmptySshDraft());
|
const [sshDraft, setSshDraft] = useState(() => createEmptySshDraft());
|
||||||
const [sshKeyStored, setSshKeyStored] = useState(false);
|
const [sshPasswordStored, setSshPasswordStored] = useState(false);
|
||||||
const [sshPassphraseStored, setSshPassphraseStored] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showPrivateKey, setShowPrivateKey] = useState(false);
|
|
||||||
const [showPassphrase, setShowPassphrase] = useState(false);
|
|
||||||
const [sshSaving, setSshSaving] = useState(false);
|
const [sshSaving, setSshSaving] = useState(false);
|
||||||
const [sshTesting, setSshTesting] = useState(false);
|
const [sshTesting, setSshTesting] = useState(false);
|
||||||
const [sshDeleting, setSshDeleting] = useState(false);
|
const [sshDeleting, setSshDeleting] = useState(false);
|
||||||
@@ -145,10 +142,8 @@ export default function Maintenance() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sshConfig) {
|
if (!sshConfig) {
|
||||||
setSshDraft(createEmptySshDraft());
|
setSshDraft(createEmptySshDraft());
|
||||||
setSshKeyStored(false);
|
setSshPasswordStored(false);
|
||||||
setSshPassphraseStored(false);
|
setShowPassword(false);
|
||||||
setShowPrivateKey(false);
|
|
||||||
setShowPassphrase(false);
|
|
||||||
setSshTestResult(null);
|
setSshTestResult(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,14 +151,11 @@ export default function Maintenance() {
|
|||||||
host: sshConfig.host ?? '',
|
host: sshConfig.host ?? '',
|
||||||
port: String(sshConfig.port ?? '22'),
|
port: String(sshConfig.port ?? '22'),
|
||||||
username: sshConfig.username ?? '',
|
username: sshConfig.username ?? '',
|
||||||
privateKey: '',
|
password: '',
|
||||||
privateKeyPassphrase: '',
|
|
||||||
extraSshArgs: Array.isArray(sshConfig.extraSshArgs) ? sshConfig.extraSshArgs.join('\n') : ''
|
extraSshArgs: Array.isArray(sshConfig.extraSshArgs) ? sshConfig.extraSshArgs.join('\n') : ''
|
||||||
});
|
});
|
||||||
setSshKeyStored(Boolean(sshConfig.privateKeyStored));
|
setSshPasswordStored(Boolean(sshConfig.passwordStored));
|
||||||
setSshPassphraseStored(Boolean(sshConfig.privateKeyPassphraseStored));
|
setShowPassword(false);
|
||||||
setShowPrivateKey(false);
|
|
||||||
setShowPassphrase(false);
|
|
||||||
setSshTestResult(null);
|
setSshTestResult(null);
|
||||||
}, [sshConfig]);
|
}, [sshConfig]);
|
||||||
|
|
||||||
@@ -273,11 +265,8 @@ export default function Maintenance() {
|
|||||||
|
|
||||||
const handleSshDraftChange = useCallback((field, value) => {
|
const handleSshDraftChange = useCallback((field, value) => {
|
||||||
setSshDraft((prev) => ({ ...prev, [field]: value }));
|
setSshDraft((prev) => ({ ...prev, [field]: value }));
|
||||||
if (field === 'privateKey') {
|
if (field === 'password') {
|
||||||
setSshKeyStored(false);
|
setSshPasswordStored(false);
|
||||||
}
|
|
||||||
if (field === 'privateKeyPassphrase') {
|
|
||||||
setSshPassphraseStored(false);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -286,29 +275,21 @@ export default function Maintenance() {
|
|||||||
host: sshDraft.host.trim(),
|
host: sshDraft.host.trim(),
|
||||||
port: Number.parseInt(sshDraft.port, 10) || 22,
|
port: Number.parseInt(sshDraft.port, 10) || 22,
|
||||||
username: sshDraft.username.trim(),
|
username: sshDraft.username.trim(),
|
||||||
extraSshArgs: sshDraft.extraSshArgs
|
extraSshArgs: (sshDraft.extraSshArgs || '')
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.map((entry) => entry.trim())
|
.map((entry) => entry.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
};
|
};
|
||||||
|
|
||||||
const rawKey = (sshDraft.privateKey ?? '');
|
const rawPassword = sshDraft.password ?? '';
|
||||||
const sanitizedKey = rawKey.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
if (rawPassword) {
|
||||||
if (sanitizedKey) {
|
normalized.password = rawPassword;
|
||||||
normalized.privateKey = sanitizedKey;
|
} else if (!sshPasswordStored) {
|
||||||
} else if (!sshKeyStored) {
|
normalized.password = '';
|
||||||
normalized.privateKey = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawPassphrase = sshDraft.privateKeyPassphrase ?? '';
|
|
||||||
if (rawPassphrase) {
|
|
||||||
normalized.privateKeyPassphrase = rawPassphrase;
|
|
||||||
} else if (!sshPassphraseStored) {
|
|
||||||
normalized.privateKeyPassphrase = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalized;
|
return normalized;
|
||||||
}, [sshDraft, sshKeyStored, sshPassphraseStored]);
|
}, [sshDraft, sshPasswordStored]);
|
||||||
|
|
||||||
const handleScriptSave = useCallback(async () => {
|
const handleScriptSave = useCallback(async () => {
|
||||||
if (!scriptConfig) return;
|
if (!scriptConfig) return;
|
||||||
@@ -350,8 +331,7 @@ export default function Maintenance() {
|
|||||||
setSshSaving(true);
|
setSshSaving(true);
|
||||||
await saveSshConfig(normalizedSshDraft);
|
await saveSshConfig(normalizedSshDraft);
|
||||||
setSshTestResult(null);
|
setSshTestResult(null);
|
||||||
setShowPrivateKey(false);
|
setShowPassword(false);
|
||||||
setShowPassphrase(false);
|
|
||||||
showToast({
|
showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: "SSH-Konfiguration gespeichert",
|
title: "SSH-Konfiguration gespeichert",
|
||||||
@@ -389,10 +369,8 @@ export default function Maintenance() {
|
|||||||
setSshDeleting(true);
|
setSshDeleting(true);
|
||||||
await deleteSshConfig();
|
await deleteSshConfig();
|
||||||
setSshDraft(createEmptySshDraft());
|
setSshDraft(createEmptySshDraft());
|
||||||
setSshKeyStored(false);
|
setSshPasswordStored(false);
|
||||||
setSshPassphraseStored(false);
|
setShowPassword(false);
|
||||||
setShowPrivateKey(false);
|
|
||||||
setShowPassphrase(false);
|
|
||||||
setSshTestResult(null);
|
setSshTestResult(null);
|
||||||
showToast({
|
showToast({
|
||||||
variant: "info",
|
variant: "info",
|
||||||
@@ -766,60 +744,30 @@ export default function Maintenance() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
<div className="flex items-center justify-between text-xs uppercase tracking-wide text-gray-400">
|
<div className="flex items-center justify-between text-xs uppercase tracking-wide text-gray-400">
|
||||||
<label htmlFor="maintenance-ssh-private-key" className="cursor-pointer">Privater SSH-Key</label>
|
<label htmlFor="maintenance-ssh-password" className="cursor-pointer">Passwort</label>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPrivateKey((prev) => !prev)}
|
onClick={() => setShowPassword((prev) => !prev)}
|
||||||
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
||||||
className="text-[11px] font-medium text-purple-300 transition hover:text-purple-200 disabled:opacity-50"
|
className="text-[11px] font-medium text-purple-300 transition hover:text-purple-200 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{showPrivateKey ? 'Verbergen' : 'Anzeigen'}
|
{showPassword ? 'Verbergen' : 'Anzeigen'}
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
id="maintenance-ssh-private-key"
|
|
||||||
rows={6}
|
|
||||||
value={sshDraft.privateKey}
|
|
||||||
onChange={(event) => handleSshDraftChange('privateKey', event.target.value)}
|
|
||||||
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
|
||||||
className="w-full rounded-md border border-gray-700 bg-gray-950/70 px-3 py-2 font-mono text-xs text-gray-100 focus:border-purple-500 focus:outline-none"
|
|
||||||
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
|
|
||||||
autoComplete="off"
|
|
||||||
spellCheck={false}
|
|
||||||
style={showPrivateKey ? undefined : { WebkitTextSecurity: 'disc', MozTextSecurity: 'disc' }}
|
|
||||||
/>
|
|
||||||
{sshKeyStored && !sshDraft.privateKey && (
|
|
||||||
<span className="text-[11px] text-gray-400">
|
|
||||||
Ein privater Schlüssel ist gespeichert. Neuer Inhalt ersetzt ihn oder lösche die Konfiguration unten.
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<div className="flex items-center justify-between text-xs uppercase tracking-wide text-gray-400">
|
|
||||||
<label htmlFor="maintenance-ssh-passphrase" className="cursor-pointer">Passphrase</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassphrase((prev) => !prev)}
|
|
||||||
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
|
||||||
className="text-[11px] font-medium text-purple-300 transition hover:text-purple-200 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{showPassphrase ? 'Verbergen' : 'Anzeigen'}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="maintenance-ssh-passphrase"
|
id="maintenance-ssh-password"
|
||||||
type={showPassphrase ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={sshDraft.privateKeyPassphrase}
|
value={sshDraft.password}
|
||||||
onChange={(event) => handleSshDraftChange('privateKeyPassphrase', event.target.value)}
|
onChange={(event) => handleSshDraftChange('password', event.target.value)}
|
||||||
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
disabled={sshSaving || sshTesting || sshDeleting || updateRunning}
|
||||||
className="w-full rounded-md border border-gray-700 bg-gray-950/70 px-3 py-2 text-sm text-gray-100 focus:border-purple-500 focus:outline-none"
|
className="w-full rounded-md border border-gray-700 bg-gray-950/70 px-3 py-2 text-sm text-gray-100 focus:border-purple-500 focus:outline-none"
|
||||||
placeholder="optional"
|
placeholder="Passwort für den SSH-Benutzer"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
{sshPassphraseStored && !sshDraft.privateKeyPassphrase && (
|
{sshPasswordStored && !sshDraft.password && (
|
||||||
<span className="text-[11px] text-gray-400">
|
<span className="text-[11px] text-gray-400">
|
||||||
Eine Passphrase ist gespeichert. Neuer Wert ersetzt sie oder lösche die Konfiguration unten.
|
Ein Passwort ist gespeichert. Neuer Inhalt ersetzt es oder lösche die Konfiguration unten.
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user