feat(gui): inline peer rename
Click on peer name in the table to edit it inline. Enter to save, Escape to cancel, blur also saves. Saved via POST /peers/<id>/rename without page reload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
@@ -625,6 +625,19 @@ def peer_delete_by_key():
|
|||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/peers/<int:peer_id>/rename")
|
||||||
|
def peer_rename(peer_id: int):
|
||||||
|
name = (request.form.get("name") or "").strip()
|
||||||
|
if not name:
|
||||||
|
flash("Имя не может быть пустым", "error")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
with db_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("UPDATE peers SET name=? WHERE id=?", (name, peer_id))
|
||||||
|
conn.commit()
|
||||||
|
return ("", 204)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/scripts")
|
@app.route("/scripts")
|
||||||
def scripts():
|
def scripts():
|
||||||
commands = {
|
commands = {
|
||||||
|
|||||||
@@ -179,6 +179,22 @@ tbody tr.row-disabled td { opacity: 0.45; }
|
|||||||
.pubkey { font-family: var(--mono); font-size: 11px; color: var(--text-muted); cursor: default; }
|
.pubkey { font-family: var(--mono); font-size: 11px; color: var(--text-muted); cursor: default; }
|
||||||
.hostname { font-size: 11px; color: var(--accent); font-weight: 500; }
|
.hostname { font-size: 11px; color: var(--accent); font-weight: 500; }
|
||||||
|
|
||||||
|
.editable { cursor: text; border-bottom: 1px dashed var(--border); }
|
||||||
|
.editable:hover { border-bottom-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
.rename-input {
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
outline: none;
|
||||||
|
width: 140px;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59,110,246,.12);
|
||||||
|
}
|
||||||
|
|
||||||
.empty { text-align: center; padding: 40px !important; color: var(--text-muted); }
|
.empty { text-align: center; padding: 40px !important; color: var(--text-muted); }
|
||||||
.empty a { color: var(--accent); text-decoration: none; }
|
.empty a { color: var(--accent); text-decoration: none; }
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for p in peers %}
|
{% for p in peers %}
|
||||||
<tr class="{{ 'row-disabled' if not p.enabled else '' }}">
|
<tr class="{{ 'row-disabled' if not p.enabled else '' }}">
|
||||||
<td><span class="peer-name">{{ p.name }}</span></td>
|
<td>
|
||||||
|
{% if p.id %}
|
||||||
|
<span class="peer-name editable" onclick="startRename(this, {{ p.id }}, '{{ csrf_token() }}')" title="Нажмите чтобы переименовать">{{ p.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="peer-name">{{ p.name }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge {{ p.status }}" title="{{ p.handshake_ago }}">
|
<span class="badge {{ p.status }}" title="{{ p.handshake_ago }}">
|
||||||
<i class="dot"></i>
|
<i class="dot"></i>
|
||||||
@@ -90,4 +96,36 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function startRename(el, peerId, csrf) {
|
||||||
|
if (el.querySelector('input')) return;
|
||||||
|
const oldName = el.textContent.trim();
|
||||||
|
const inp = document.createElement('input');
|
||||||
|
inp.className = 'rename-input';
|
||||||
|
inp.value = oldName;
|
||||||
|
el.textContent = '';
|
||||||
|
el.appendChild(inp);
|
||||||
|
inp.focus();
|
||||||
|
inp.select();
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const name = inp.value.trim();
|
||||||
|
if (!name || name === oldName) { el.textContent = oldName; return; }
|
||||||
|
fetch(`/peers/${peerId}/rename`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
|
body: `name=${encodeURIComponent(name)}&csrf_token=${encodeURIComponent(csrf)}`
|
||||||
|
}).then(r => {
|
||||||
|
el.textContent = r.ok ? name : oldName;
|
||||||
|
}).catch(() => { el.textContent = oldName; });
|
||||||
|
}
|
||||||
|
|
||||||
|
inp.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') { e.preventDefault(); save(); }
|
||||||
|
if (e.key === 'Escape') { el.textContent = oldName; }
|
||||||
|
});
|
||||||
|
inp.addEventListener('blur', save);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user