From 2e160cdd1488016ce766f2f95a689ca19998c8ca Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 18 Apr 2026 17:19:13 +0200 Subject: [PATCH] implement instance editing capability and move user creation to a modal --- main.py | 30 ++++++++++++++++++ static/app.js | 77 ++++++++++++++++++++++++++++++++++++----------- static/index.html | 51 ++++++++++++++++++++----------- 3 files changed, 123 insertions(+), 35 deletions(-) diff --git a/main.py b/main.py index ba0eae5..f7aea04 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ import httpx import base64 import secrets import urllib.parse +from typing import Optional from fastapi import FastAPI, Request, Depends, HTTPException from fastapi.responses import Response, FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -328,6 +329,35 @@ def create_instance(inst: InstanceCreate, request: Request): conn.close() return {"status": "ok"} +@app.put("/api/instances/{id}", dependencies=[Depends(is_authenticated)]) +def update_instance(id: int, inst: InstanceUpdate, request: Request): + user_id = request.session.get("user_id") + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + + # Verify ownership + c.execute('SELECT id FROM instances WHERE id=? AND user_id=?', (id, user_id)) + if not c.fetchone(): + conn.close() + raise HTTPException(status_code=404, detail="Instance not found") + + if inst.secret: + cleaned = clean_secret(inst.secret) + encrypted = fernet.encrypt(cleaned.encode()).decode() + c.execute(''' + UPDATE instances SET name=?, ip=?, port=?, encrypted_secret=? + WHERE id=? AND user_id=? + ''', (inst.name, inst.ip, inst.port, encrypted, id, user_id)) + else: + c.execute(''' + UPDATE instances SET name=?, ip=?, port=? + WHERE id=? AND user_id=? + ''', (inst.name, inst.ip, inst.port, id, user_id)) + + conn.commit() + conn.close() + return {"status": "ok"} + @app.get("/api/config", dependencies=[Depends(is_authenticated)]) def get_config(): return {"firewall_host_ip": FIREWALL_HOST_IP} diff --git a/static/app.js b/static/app.js index 22792a9..04e7963 100644 --- a/static/app.js +++ b/static/app.js @@ -19,12 +19,25 @@ document.addEventListener('DOMContentLoaded', () => { const helpModal = document.getElementById('help-modal'); const addInstanceModal = document.getElementById('add-instance-modal'); const showAddBtn = document.getElementById('show-add-btn'); + const showAddUserBtn = document.getElementById('show-add-user-btn'); + const addUserModal = document.getElementById('add-user-modal'); + const modalTitle = document.querySelector('#add-instance-modal h2'); + const secretInput = document.getElementById('secret'); let config = { firewall_host_ip: null }; let currentUser = null; + let editId = null; + let instancesData = []; const openModal = (modal) => modal.classList.add('active'); - const closeModal = (modal) => modal.classList.remove('active'); + const closeModal = (modal) => { + modal.classList.remove('active'); + editId = null; + addForm.reset(); + modalTitle.textContent = 'Add Instance'; + secretInput.placeholder = 'Base32 Secret or otpauth:// URL'; + secretInput.required = true; + }; document.querySelectorAll('.btn-close').forEach(btn => { btn.addEventListener('click', () => { @@ -37,7 +50,14 @@ document.addEventListener('DOMContentLoaded', () => { }); helpBtn.addEventListener('click', () => openModal(helpModal)); - showAddBtn.addEventListener('click', () => openModal(addInstanceModal)); + showAddBtn.addEventListener('click', () => { + editId = null; + modalTitle.textContent = 'Add Instance'; + secretInput.placeholder = 'Base32 Secret or otpauth:// URL'; + secretInput.required = true; + openModal(addInstanceModal); + }); + showAddUserBtn.addEventListener('click', () => openModal(addUserModal)); const showLogin = () => { loginOverlay.style.display = 'flex'; @@ -89,8 +109,8 @@ document.addEventListener('DOMContentLoaded', () => { try { const res = await fetch('/api/instances'); if (res.status === 401) return showLogin(); - const data = await res.json(); - renderInstances(data); + instancesData = await res.json(); + renderInstances(instancesData); } catch (e) { console.error('Failed to fetch instances:', e); } @@ -133,12 +153,17 @@ document.addEventListener('DOMContentLoaded', () => { Last checked: ${timeAgo} - +
+ + +
`; }).join(''); @@ -169,27 +194,44 @@ document.addEventListener('DOMContentLoaded', () => { const port = parseInt(document.getElementById('port').value, 10); const secret = document.getElementById('secret').value; + const url = editId ? `/api/instances/${editId}` : '/api/instances'; + const method = editId ? 'PUT' : 'POST'; + const body = { name, ip, port }; + if (secret) body.secret = secret; + try { - const res = await fetch('/api/instances', { - method: 'POST', + const res = await fetch(url, { + method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, ip, port, secret }) + body: JSON.stringify(body) }); if (res.ok) { - addForm.reset(); - document.getElementById('port').value = "4646"; closeModal(addInstanceModal); fetchInstances(); } else if (res.status === 401) { showLogin(); } else { - alert('Failed to add instance'); + alert(`Failed to ${editId ? 'update' : 'add'} instance`); } } catch (e) { - console.error('Error adding instance', e); + console.error('Error saving instance', e); } }); + window.editInstance = (id) => { + const inst = instancesData.find(i => i.id === id); + if (!inst) return; + editId = id; + document.getElementById('name').value = inst.name; + document.getElementById('ip').value = inst.ip; + document.getElementById('port').value = inst.port; + secretInput.value = ''; + secretInput.required = false; + secretInput.placeholder = 'Leave blank to keep current secret'; + modalTitle.textContent = 'Edit Instance'; + openModal(addInstanceModal); + }; + loginForm.addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('login-username').value; @@ -248,6 +290,7 @@ document.addEventListener('DOMContentLoaded', () => { }); if (res.ok) { createUserForm.reset(); + closeModal(addUserModal); fetchUsers(); } else { const data = await res.json(); diff --git a/static/index.html b/static/index.html index 601d658..8104271 100644 --- a/static/index.html +++ b/static/index.html @@ -68,24 +68,13 @@
-

User Management

-
-
- - -
-
- - -
-
- -
- -
+
+

User Management

+ +
@@ -140,6 +129,32 @@
+ +