Compare commits

...
2 Commits
4 changed files with 24 additions and 16 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mrterm", "name": "mrterm",
"version": "0.10.1", "version": "0.10.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mrterm", "name": "mrterm",
"version": "0.10.1", "version": "0.10.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mrterm", "name": "mrterm",
"productName": "MrTerm", "productName": "MrTerm",
"version": "0.10.1", "version": "0.10.2",
"description": "Moderner SSH-, SFTP- und RDP-Client", "description": "Moderner SSH-, SFTP- und RDP-Client",
"main": "src/main/main.js", "main": "src/main/main.js",
"author": "MrBlake", "author": "MrBlake",
+13 -10
View File
@@ -149,8 +149,10 @@ class NetworkConfigManager {
} }
watch(id, s) { watch(id, s) {
s.conn.on('close', () => { if (this.sessions.get(id) === s && !s.replacing) { this.close(id); s.onClose(); } }); const conn = s.conn;
s.conn.on('error', () => {}); // Nur die aktuelle Verbindung darf die Sitzung beenden (nach apply wird sie ersetzt)
conn.on('close', () => { if (this.sessions.get(id) === s && s.conn === conn) { this.close(id); s.onClose(); } });
conn.on('error', () => {});
} }
async read(id) { async read(id) {
@@ -226,7 +228,7 @@ class NetworkConfigManager {
const be = BACKENDS[backendName || s.backend]; const be = BACKENDS[backendName || s.backend];
if (!be) throw new Error(i18n.t('Editing is not supported for this network configuration ({backend}).', { backend: s.backend })); if (!be) throw new Error(i18n.t('Editing is not supported for this network configuration ({backend}).', { backend: s.backend }));
const script = `export PATH=$PATH:/usr/sbin:/sbin const script = `export PATH=$PATH:/usr/sbin:/sbin
mkdir -p ${STATE} || exit 1; rm -f ${STATE}/pending ${STATE}/rolled-back mkdir -p ${STATE} || exit 1; rm -f ${STATE}/pending ${STATE}/applied ${STATE}/rolled-back
cd / && tar czf ${STATE}/backup.tgz $(for p in ${be.paths.join(' ')}; do [ -e "$p" ] && echo "$p"; done) || exit 1 cd / && tar czf ${STATE}/backup.tgz $(for p in ${be.paths.join(' ')}; do [ -e "$p" ] && echo "$p"; done) || exit 1
# Schreiben in einer Subshell: bei einem Fehler sofort den alten Stand wiederherstellen # Schreiben in einer Subshell: bei einem Fehler sofort den alten Stand wiederherstellen
if ! ( set -e if ! ( set -e
@@ -234,7 +236,7 @@ ${writes.join('\n')}
) >${STATE}/write.log 2>&1; then ${be.restore}; cat ${STATE}/write.log >&2; exit 5; fi ) >${STATE}/write.log 2>&1; then ${be.restore}; cat ${STATE}/write.log >&2; exit 5; fi
if ! (${be.validate}) >${STATE}/validate.log 2>&1; then ${be.restore}; cat ${STATE}/validate.log >&2; exit 4; fi if ! (${be.validate}) >${STATE}/validate.log 2>&1; then ${be.restore}; cat ${STATE}/validate.log >&2; exit 4; fi
touch ${STATE}/pending touch ${STATE}/pending
nohup setsid sh -c 'export PATH=$PATH:/usr/sbin:/sbin; sleep 2; (${be.apply}) >${STATE}/apply.log 2>&1; sleep ${ROLLBACK_SECONDS}; if [ -f ${STATE}/pending ]; then ${be.restore}; (${be.apply}) >>${STATE}/apply.log 2>&1; rm -f ${STATE}/pending; date "+%Y-%m-%d %H:%M:%S" > ${STATE}/rolled-back; fi' >/dev/null 2>&1 & nohup setsid sh -c 'export PATH=$PATH:/usr/sbin:/sbin; sleep 2; (${be.apply}) >${STATE}/apply.log 2>&1; touch ${STATE}/applied; sleep ${ROLLBACK_SECONDS}; if [ -f ${STATE}/pending ]; then ${be.restore}; (${be.apply}) >>${STATE}/apply.log 2>&1; rm -f ${STATE}/pending; date "+%Y-%m-%d %H:%M:%S" > ${STATE}/rolled-back; fi' >/dev/null 2>&1 &
echo started`; echo started`;
const r = await this.root(s, script); const r = await this.root(s, script);
if (r.code === 4) throw new Error(i18n.t('The new configuration is invalid and was not applied: {err}', { err: lastLine(r.err) })); if (r.code === 4) throw new Error(i18n.t('The new configuration is invalid and was not applied: {err}', { err: lastLine(r.err) }));
@@ -245,13 +247,14 @@ echo started`;
while (Date.now() < deadline) { while (Date.now() < deadline) {
try { try {
const { conn, jumps } = await this.ssh.connect(target, id); const { conn, jumps } = await this.ssh.connect(target, id);
const c = await this.root(s, `rm -f ${STATE}/pending`, conn); // Erst bestätigen, wenn das Anwenden fertig ist: sonst reißt z. B. ein Bond-Neustart die neue Verbindung wieder ab
if (c.code) { conn.end(); throw new Error(lastLine(c.err)); } const c = await this.root(s, `[ -f ${STATE}/applied ] || exit 7; rm -f ${STATE}/pending`, conn);
if (c.code) { conn.on('error', () => {}); conn.end(); jumps?.forEach((x) => { try { x.end(); } catch {} }); throw new Error(lastLine(c.err) || 'not applied yet'); }
// Neue Verbindung übernimmt die Sitzung // Neue Verbindung übernimmt die Sitzung
s.replacing = true; const old = { conn: s.conn, jumps: s.jumps };
try { s.conn.end(); } catch {} Object.assign(s, { conn, jumps });
s.jumps?.forEach((x) => { try { x.end(); } catch {} }); // Alte Verbindung ist nach dem IP-Wechsel meist tot: Fehler (z. B. Keepalive-Timeout) still verwerfen
Object.assign(s, { conn, jumps, replacing: false }); for (const c of [old.conn, ...(old.jumps || [])]) { c.on('error', () => {}); try { c.end(); } catch {} }
if (verifyAddress) s.host = target; if (verifyAddress) s.host = target;
this.watch(id, s); this.watch(id, s);
return { confirmed: true }; return { confirmed: true };
+8 -3
View File
@@ -2185,8 +2185,13 @@ class NetworkSession {
this.draw(); this.draw();
} }
async refresh() { async refresh(tries = 1) {
try { this.apply(await api.call('network:read', this.id)); } catch (e) { toast(e.message, 'error'); } for (let i = 1; ; i++) {
try { return this.apply(await api.call('network:read', this.id)); } catch (e) {
if (i >= tries) return toast(e.message, 'error');
await new Promise((r) => setTimeout(r, 3000));
}
}
} }
cfg(name) { return this.data.config.find((c) => c.name === name); } cfg(name) { return this.data.config.find((c) => c.name === name); }
@@ -2258,7 +2263,7 @@ class NetworkSession {
try { try {
const r = await fn(); const r = await fn();
ov.remove(); ov.remove();
if (r?.confirmed) { toast(r.direct ? T('Saved') : T('Network change applied and confirmed'), 'ok'); await this.refresh(); } if (r?.confirmed) { toast(r.direct ? T('Saved') : T('Network change applied and confirmed'), 'ok'); await this.refresh(4); }
else { else {
setTabState(this, 'err'); setTabState(this, 'err');
this.body.innerHTML = `<div class="pane-empty"><div style="color:var(--orange);max-width:560px;text-align:center">${T('MrTerm could not reconnect after the change. The server restores the previous configuration automatically within 90 seconds.')}</div></div>`; this.body.innerHTML = `<div class="pane-empty"><div style="color:var(--orange);max-width:560px;text-align:center">${T('MrTerm could not reconnect after the change. The server restores the previous configuration automatically within 90 seconds.')}</div></div>`;