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} - +