Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4eedc77bb | ||
|
|
93123c5c60 | ||
|
|
3714205dab | ||
|
|
4818523b29 | ||
|
|
6f52873b69 |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mrterm",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mrterm",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mrterm",
|
||||
"productName": "MrTerm",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"description": "Moderner SSH-, SFTP- und RDP-Client",
|
||||
"main": "src/main/main.js",
|
||||
"author": "MrBlake",
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
'Send Enter automatically': 'Enter automatisch senden',
|
||||
'Command is missing.': 'Befehl fehlt.',
|
||||
'No open terminal': 'Kein offenes Terminal',
|
||||
'Upload files …': 'Dateien hochladen …',
|
||||
// Support-Assistent
|
||||
'Delete chat': 'Chat löschen',
|
||||
'Delete chat?': 'Chat löschen?',
|
||||
|
||||
@@ -408,6 +408,11 @@ handle('sftp:open', async (sessionId, hostRef) => {
|
||||
});
|
||||
handle('sftp:list', (id, dir) => ssh.sftpList(id, dir));
|
||||
handle('sftp:op', (id, op, a, b) => ssh.sftpOp(id, op, a, b));
|
||||
// Dateien für den SFTP-Upload auswählen (Mehrfachauswahl)
|
||||
handle('sftp:pickUpload', async () => {
|
||||
const r = await dialog.showOpenDialog(win, { title: i18n.t('Upload'), properties: ['openFile', 'multiSelections', 'showHiddenFiles'] });
|
||||
return r.canceled ? [] : r.filePaths.map((p) => ({ path: p, name: path.basename(p) }));
|
||||
});
|
||||
handle('sftp:transfer', async (id, dir, localPath, remotePath, transferId) => {
|
||||
let last = 0;
|
||||
await ssh.transfer(id, dir, localPath, remotePath, (done, total) => {
|
||||
|
||||
+12
-4
@@ -1887,7 +1887,8 @@ function initAi() {
|
||||
AI.chat = aiChats().find((c) => c.id === e.target.value) || null;
|
||||
aiRender();
|
||||
};
|
||||
p.addEventListener('click', async (e) => {
|
||||
// onclick statt addEventListener: initAi() läuft bei jedem reload(), sonst doppelte Handler (z. B. zwei Löschdialoge)
|
||||
p.onclick = async (e) => {
|
||||
const a = e.target.closest('[data-ai]')?.dataset.ai;
|
||||
if (a === 'new') { if (AI.busy) api.call('ai:stop', AI.chat?.id); AI.chat = null; aiRender(); ta.focus(); }
|
||||
if (a === 'del' && AI.chat && await confirmBox(T('Delete chat?'), `“${AI.chat.title}”`, T('Delete'))) {
|
||||
@@ -1907,7 +1908,7 @@ function initAi() {
|
||||
// Nur einfügen, nicht ausführen – der Nutzer prüft und bestätigt mit Enter
|
||||
if (b.dataset.cmd === 'paste' && t?.kind === 'ssh') { t.term.paste(code); t.term.focus(); }
|
||||
else { api.call('clipboard:write', code); toast(b.dataset.cmd === 'paste' ? T('Copied – no terminal tab is active') : T('Copied')); }
|
||||
});
|
||||
};
|
||||
if ($('#aiPanel').classList.contains('open')) aiRender();
|
||||
if (AI.init) return;
|
||||
AI.init = true;
|
||||
@@ -2041,7 +2042,7 @@ class FilePane {
|
||||
<input class="path" spellcheck="false"/>
|
||||
<button class="btn ghost sm" data-p="refresh" title="${T('Refresh')}">${ICONS.refresh}</button>
|
||||
<button class="btn ghost sm" data-p="mkdir" title="${T('New folder')}">${ICONS.newdir}</button>
|
||||
${WEB ? `<button class="btn ghost sm" data-p="upload" title="${T('Upload')}">${ICONS.upload}</button><button class="btn ghost sm" data-p="xfer" title="${T('Download')}">${ICONS.download}</button>` : `<button class="btn ghost sm" data-p="xfer" title="${side === 'local' ? T('Upload →') : T('← Download')}">${side === 'local' ? ICONS.arrowR : ICONS.arrowL}</button>`}</div>
|
||||
${WEB ? `<button class="btn ghost sm" data-p="upload" title="${T('Upload')}">${ICONS.upload}</button><button class="btn ghost sm" data-p="xfer" title="${T('Download')}">${ICONS.download}</button>` : `${side === 'remote' ? `<button class="btn ghost sm" data-p="upload" title="${T('Upload files …')}">${ICONS.upload}</button>` : ''}<button class="btn ghost sm" data-p="xfer" title="${side === 'local' ? T('Upload →') : T('← Download')}">${side === 'local' ? ICONS.arrowR : ICONS.arrowL}</button>`}</div>
|
||||
<div class="files"></div></div>`);
|
||||
this.el.addEventListener('click', (e) => {
|
||||
const p = e.target.closest('[data-p]')?.dataset.p;
|
||||
@@ -2068,7 +2069,14 @@ class FilePane {
|
||||
});
|
||||
}
|
||||
get other() { return this.side === 'local' ? this.sftp.remote : this.sftp.local; }
|
||||
pickUpload() {
|
||||
async pickUpload() {
|
||||
if (!WEB) {
|
||||
// Desktop: Dateiauswahl des Systems, Upload in den aktuellen Server-Ordner
|
||||
const files = await call('sftp:pickUpload');
|
||||
for (const f of files) await this.sftp.transfer('upload', f.path, await this.join(this.cwd, f.name), f.name);
|
||||
if (files.length) this.go(this.cwd);
|
||||
return;
|
||||
}
|
||||
const inp = h('<input type="file" multiple style="display:none"/>');
|
||||
inp.onchange = () => { this.uploadFiles([...inp.files]); inp.remove(); };
|
||||
document.body.append(inp);
|
||||
|
||||
@@ -361,7 +361,7 @@ kbd { background: var(--card); border: 1px solid var(--border); border-bottom-wi
|
||||
|
||||
/* Firewall */
|
||||
.seg.sm button { padding: 4px 10px; font-size: 12px; }
|
||||
.session.docker .sbar .seg { margin: 0 6px 0 0; flex: none; }
|
||||
.session .sbar .seg { margin: 0 6px 0 0; flex: none; }
|
||||
.docker-table .dot.none { background: transparent; border: 1px solid var(--faint); }
|
||||
.docker-table .badge, .tpl-it .badge { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 6px; background: var(--icon-bg); color: var(--muted); margin-left: 4px; vertical-align: middle; }
|
||||
.stack-bar { display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-bottom: 1px solid var(--border); flex-wrap: wrap; position: sticky; top: 0; background: var(--bg); z-index: 2; }
|
||||
|
||||
Reference in New Issue
Block a user