fix: agent update runs as root when it can (sudo without a password otherwise), checks the download, restarts reliably and reports its result; one-colour file icons; zip and delete on one line on phones; PIN buttons alike; no key fingerprint in the SSH status; corner plus clears split panes
This commit is contained in:
1 parent
467bd3cb4f
commit
5eca9ca215
8 files changed
+184
-40
No files matched your search
@@ -94,7 +94,8 @@
|
||||
|
||||
<div class="card">
|
||||
<h2>Версия клиента</h2>
|
||||
{{if .Updated}}<div class="err" style="background:#1d3a24;border-color:#2b6b3a;color:#b3ffcb">Команда обновления отправлена. Клиент скачает свежий бинарник и перезапустится.</div>{{end}}
|
||||
{{if .Updated}}<div class="err" style="background:#1d3a24;border-color:#2b6b3a;color:#b3ffcb">Клиент {{if .UpdateMsg}}{{.UpdateMsg}}{{else}}обновлён и перезапускается{{end}}. Через несколько секунд он вернётся с новой версией.</div>{{end}}
|
||||
{{if .UpdateErr}}<div class="err">Обновление не вышло: {{.UpdateErr}}</div>{{end}}
|
||||
<p class="muted">
|
||||
Клиент: <code>{{if .Agent.Version}}{{.Agent.Version}}{{else}}неизвестно{{end}}</code>
|
||||
· Сервер: <code>{{.ServerVersion}}</code>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
}
|
||||
/* A button with an icon before its text. */
|
||||
.fm-bar .btn.ico { gap:6px; }
|
||||
.fm-bar .spacer { flex:1; }
|
||||
input.fm-path { font-family:ui-monospace,Menlo,monospace; font-size:13px; color:var(--muted);
|
||||
background:#0c0f13; border:1px solid var(--border); border-radius:8px; padding:8px 12px;
|
||||
flex:1; min-width:200px; height:34px; box-sizing:border-box; }
|
||||
@@ -58,7 +59,9 @@
|
||||
table.fm td, table.fm th { padding:7px 8px; }
|
||||
.fm .nm { cursor:pointer; }
|
||||
.fm .nm:hover { color:var(--accent); }
|
||||
.ic { display:inline-block; width:18px; text-align:center; margin-right:6px; }
|
||||
.ic { display:inline-flex; vertical-align:-3px; width:18px; justify-content:center; margin-right:6px; color:var(--muted); }
|
||||
.ic.dir { color:#7aa7d9; }
|
||||
.ic.dir svg { fill:rgba(122,167,217,.16); }
|
||||
.sz { color:var(--muted); font-variant-numeric:tabular-nums; }
|
||||
.act { display:flex; gap:6px; justify-content:flex-end; }
|
||||
/* Sortable headers: a click sorts by the column, a second one turns it round. */
|
||||
@@ -76,6 +79,8 @@
|
||||
table.fm td, table.fm th { padding:7px 4px; }
|
||||
.ic { margin-right:4px; }
|
||||
.fm-bar .fm-pathbox { flex:1 1 100%; order:-1; }
|
||||
/* The selection's buttons go together on a line of their own. */
|
||||
.fm-bar .spacer { flex:1 1 100%; height:0; }
|
||||
}
|
||||
dialog { background:var(--panel); color:var(--text); border:1px solid var(--border);
|
||||
border-radius:12px; padding:0; max-width:900px; width:92vw; }
|
||||
@@ -304,7 +309,7 @@
|
||||
<button class="btn small ico" id="mkdir"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><path d="M12 11v6M9 14h6"/></svg>Папка</button>
|
||||
<button class="btn small ico" id="newfile"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M12 12v6M9 15h6"/></svg>Файл</button>
|
||||
<label class="btn small ico" style="cursor:pointer"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12M6 10l6 6 6-6M5 21h14"/></svg>Загрузить<input type="file" id="upl" multiple hidden></label>
|
||||
<span class="spacer" style="flex:1"></span>
|
||||
<span class="spacer"></span>
|
||||
<span id="selinfo" class="muted" style="font-size:13px"></span>
|
||||
<button class="btn ghost small" id="zipsel" disabled>В zip</button>
|
||||
<button class="btn danger small" id="delsel" disabled>Удалить выбранное</button>
|
||||
@@ -482,6 +487,30 @@
|
||||
const ICON_MV = SVG + '<polyline points="5 9 2 12 5 15"/><polyline points="9 5 12 2 15 5"/><polyline points="15 19 12 22 9 19"/><polyline points="19 9 22 12 19 15"/><line x1="2" y1="12" x2="22" y2="12"/><line x1="12" y1="2" x2="12" y2="22"/></svg>';
|
||||
const ICON_UNZIP = SVG + '<rect x="2" y="3" width="20" height="5" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h2"/><path d="M20 8v11a2 2 0 0 1-2 2h-2"/><path d="m9 15 3-3 3 3"/><path d="M12 12v9"/></svg>';
|
||||
const ICON_DEL = SVG + '<path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';
|
||||
// File icons: one colour, the line style of the buttons; a few kinds by
|
||||
// the name's ending.
|
||||
const FI = '<svg class="fi" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">';
|
||||
const FICONS = {
|
||||
dir: FI + '<path d="M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',
|
||||
file: FI + '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>',
|
||||
text: FI + '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M9 13h6M9 17h4"/></svg>',
|
||||
code: FI + '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M10 12l-2 2.5 2 2.5M14 12l2 2.5-2 2.5"/></svg>',
|
||||
img: FI + '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="m21 16-5-5-8 9"/></svg>',
|
||||
arc: FI + '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M11 4v2M11 8v2M11 12v2"/><rect x="9.5" y="15" width="3" height="3" rx=".6"/></svg>',
|
||||
media:FI + '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m10 9 5 3-5 3z"/></svg>',
|
||||
link: FI + '<path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"/></svg>'
|
||||
};
|
||||
function fileIcon(e) {
|
||||
let k = 'file';
|
||||
if (e.is_dir) k = 'dir';
|
||||
else if (/^L/.test(e.mode || '')) k = 'link';
|
||||
else if (/\.(png|jpe?g|gif|webp|bmp|ico|svg|avif|heic)$/i.test(e.name)) k = 'img';
|
||||
else if (/\.(zip|tar|gz|tgz|bz2|xz|zst|7z|rar|deb|rpm)$/i.test(e.name)) k = 'arc';
|
||||
else if (/\.(mp4|m4v|webm|mov|mkv|avi|mp3|wav|ogg|flac|m4a|aac|opus)$/i.test(e.name)) k = 'media';
|
||||
else if (/\.(js|ts|go|py|rb|rs|c|h|cpp|java|kt|php|sh|bash|zsh|json|ya?ml|toml|html?|css|sql|lua|pl|swift|vue|jsx|tsx|ini|conf|xml)$/i.test(e.name)) k = 'code';
|
||||
else if (/\.(txt|md|log|csv|rst|cfg|env)$|^(readme|license|makefile|dockerfile)/i.test(e.name)) k = 'text';
|
||||
return '<span class="ic' + (k === 'dir' ? ' dir' : '') + '">' + FICONS[k] + '</span>';
|
||||
}
|
||||
let cwd = '/';
|
||||
let entries = [];
|
||||
const selected = new Set();
|
||||
@@ -564,7 +593,7 @@
|
||||
const dlHref = '/api/panel/agents/' + encodeURIComponent(agentID) + '/fs/download?path=' + encodeURIComponent(join(cwd, e.name));
|
||||
tr.innerHTML =
|
||||
'<td><input type="checkbox" class="ck" data-n="'+encodeURIComponent(e.name)+'"></td>' +
|
||||
'<td class="nm"><span class="ic">'+(e.is_dir?'📁':'📄')+'</span>'+esc(e.name)+'</td>' +
|
||||
'<td class="nm">'+fileIcon(e)+esc(e.name)+'</td>' +
|
||||
'<td class="sz c-sz">'+(e.is_dir?'':fmtSize(e.size))+'</td>' +
|
||||
'<td class="sz mt">'+fmtTime(e.mtime)+'</td>' +
|
||||
'<td><div class="act">' +
|
||||
|
||||
@@ -82,7 +82,9 @@
|
||||
body.multi .area { margin-top:0; border-top-left-radius:0; }
|
||||
.float-add { position:absolute; top:8px; right:10px; z-index:6; width:28px; height:28px; padding:0; display:inline-flex;
|
||||
align-items:center; justify-content:center; font-size:17px; line-height:1; color:var(--text);
|
||||
background:transparent; border:1px solid var(--border); opacity:.28; transition:opacity .25s ease, background .25s ease; }
|
||||
background:transparent; border:1px solid var(--border); opacity:.28; transition:opacity .25s ease, background .25s ease, top .25s ease; }
|
||||
/* One tab split in panes: below the panes' title bars, clear of their close buttons. */
|
||||
body.split1 .float-add { top:42px; }
|
||||
.float-add:hover, .float-add:focus-visible { opacity:1; background:var(--panel); }
|
||||
body.multi .float-add { opacity:0; pointer-events:none; }
|
||||
.tabview { position:absolute; inset:8px; opacity:0; visibility:hidden; transform:translateY(4px) scale(.995);
|
||||
|
||||
Reference in new issue
Block a user