diff --git a/Data/Server/WebUI/src/Dialogs.jsx b/Data/Server/WebUI/src/Dialogs.jsx
index d28529e..68e3cd2 100644
--- a/Data/Server/WebUI/src/Dialogs.jsx
+++ b/Data/Server/WebUI/src/Dialogs.jsx
@@ -479,3 +479,36 @@ export function CreateSiteDialog({ open, onCancel, onCreate }) {
);
}
+
+export function RenameSiteDialog({ open, value, onChange, onCancel, onSave }) {
+ return (
+
+ );
+}
diff --git a/Data/Server/WebUI/src/Sites/Site_List.jsx b/Data/Server/WebUI/src/Sites/Site_List.jsx
index 21c5990..478a7b2 100644
--- a/Data/Server/WebUI/src/Sites/Site_List.jsx
+++ b/Data/Server/WebUI/src/Sites/Site_List.jsx
@@ -18,9 +18,10 @@ import {
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/DeleteOutline";
+import EditIcon from "@mui/icons-material/Edit";
import FilterListIcon from "@mui/icons-material/FilterList";
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
-import { CreateSiteDialog, ConfirmDeleteDialog } from "../Dialogs.jsx";
+import { CreateSiteDialog, ConfirmDeleteDialog, RenameSiteDialog } from "../Dialogs.jsx";
export default function SiteList({ onOpenDevicesForSite }) {
const [rows, setRows] = useState([]); // {id, name, description, device_count}
@@ -51,6 +52,8 @@ export default function SiteList({ onOpenDevicesForSite }) {
const [createOpen, setCreateOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
+ const [renameOpen, setRenameOpen] = useState(false);
+ const [renameValue, setRenameValue] = useState("");
const fetchSites = useCallback(async () => {
try {
@@ -148,6 +151,24 @@ export default function SiteList({ onOpenDevicesForSite }) {
Sites
+ }
+ disabled={selectedIds.size !== 1}
+ onClick={() => {
+ // Prefill with the currently selected site's name
+ const selId = selectedIds.size === 1 ? Array.from(selectedIds)[0] : null;
+ if (selId != null) {
+ const site = rows.find((r) => r.id === selId);
+ setRenameValue(site?.name || "");
+ setRenameOpen(true);
+ }
+ }}
+ sx={{ color: selectedIds.size === 1 ? '#58a6ff' : '#666', borderColor: selectedIds.size === 1 ? '#58a6ff' : '#333', textTransform: 'none' }}
+ >
+ Rename
+
+
+ {/* Rename site dialog */}
+ setRenameOpen(false)}
+ onSave={async () => {
+ const newName = (renameValue || '').trim();
+ if (!newName) return;
+ const selId = selectedIds.size === 1 ? Array.from(selectedIds)[0] : null;
+ if (selId == null) return;
+ try {
+ const res = await fetch('/api/sites/rename', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ id: selId, new_name: newName })
+ });
+ if (!res.ok) {
+ // Keep dialog open on error; optionally log
+ try { const err = await res.json(); console.warn('Rename failed', err); } catch {}
+ return;
+ }
+ setRenameOpen(false);
+ await fetchSites();
+ } catch (e) {
+ console.warn('Rename error', e);
+ }
+ }}
+ />
);
}
diff --git a/Data/Server/server.py b/Data/Server/server.py
index c10d7b6..86a80d5 100644
--- a/Data/Server/server.py
+++ b/Data/Server/server.py
@@ -1427,6 +1427,49 @@ def assign_devices_to_site():
return jsonify({"error": str(e)}), 500
+# Rename a site (update name only)
+@app.route("/api/sites/rename", methods=["POST"])
+def rename_site():
+ payload = request.get_json(silent=True) or {}
+ site_id = payload.get("id")
+ new_name = (payload.get("new_name") or "").strip()
+ try:
+ site_id = int(site_id)
+ except Exception:
+ return jsonify({"error": "invalid id"}), 400
+ if not new_name:
+ return jsonify({"error": "new_name is required"}), 400
+ try:
+ conn = _db_conn()
+ cur = conn.cursor()
+ cur.execute("UPDATE sites SET name = ? WHERE id = ?", (new_name, site_id))
+ if cur.rowcount == 0:
+ conn.close()
+ return jsonify({"error": "site not found"}), 404
+ conn.commit()
+ cur.execute(
+ """
+ SELECT s.id, s.name, s.description, s.created_at,
+ COALESCE(ds.cnt, 0) AS device_count
+ FROM sites s
+ LEFT JOIN (
+ SELECT site_id, COUNT(*) AS cnt
+ FROM device_sites
+ GROUP BY site_id
+ ) ds ON ds.site_id = s.id
+ WHERE s.id = ?
+ """,
+ (site_id,)
+ )
+ row = cur.fetchone()
+ conn.close()
+ return jsonify(_row_to_site(row))
+ except sqlite3.IntegrityError:
+ return jsonify({"error": "name already exists"}), 409
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+
# ---------------------------------------------
# Global Search (suggestions)
# ---------------------------------------------