diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 1a31ddaf..548c3190 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -362,6 +362,7 @@ erDiagram
string name
string key
string machineId
+ boolean isActive
}
USAGE_ENTRY {
diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
index 5ab015b2..9f725c43 100644
--- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
+++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js
@@ -272,6 +272,21 @@ export default function APIPageClient({ machineId }) {
}
};
+ const handleToggleKey = async (id, isActive) => {
+ try {
+ const res = await fetch(`/api/keys/${id}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ isActive }),
+ });
+ if (res.ok) {
+ setKeys(prev => prev.map(k => k.id === id ? { ...k, isActive } : k));
+ }
+ } catch (error) {
+ console.log("Error toggling key:", error);
+ }
+ };
+
const [baseUrl, setBaseUrl] = useState("/v1");
const cloudEndpointNew = cloudUrl ? `${cloudUrl}/v1` : "";
@@ -405,7 +420,7 @@ export default function APIPageClient({ machineId }) {
{keys.map((key) => (
{key.name}
@@ -423,13 +438,32 @@ export default function APIPageClient({ machineId }) {
Created {new Date(key.createdAt).toLocaleDateString()}
+ {key.isActive === false && (
+
Paused
+ )}
+
+
+ {
+ if (key.isActive && !checked) {
+ if (confirm(`Pause API key "${key.name}"?\n\nThis key will stop working immediately but can be resumed later.`)) {
+ handleToggleKey(key.id, checked);
+ }
+ } else {
+ handleToggleKey(key.id, checked);
+ }
+ }}
+ title={key.isActive ? "Pause key" : "Resume key"}
+ />
+
-
))}
diff --git a/src/app/api/keys/[id]/route.js b/src/app/api/keys/[id]/route.js
index 825e2e78..1cfc0ff5 100644
--- a/src/app/api/keys/[id]/route.js
+++ b/src/app/api/keys/[id]/route.js
@@ -1,8 +1,48 @@
import { NextResponse } from "next/server";
-import { deleteApiKey, isCloudEnabled } from "@/lib/localDb";
+import { deleteApiKey, getApiKeyById, updateApiKey, isCloudEnabled } from "@/lib/localDb";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/app/api/sync/cloud/route";
+// GET /api/keys/[id] - Get single key
+export async function GET(request, { params }) {
+ try {
+ const { id } = await params;
+ const key = await getApiKeyById(id);
+ if (!key) {
+ return NextResponse.json({ error: "Key not found" }, { status: 404 });
+ }
+ return NextResponse.json({ key });
+ } catch (error) {
+ console.log("Error fetching key:", error);
+ return NextResponse.json({ error: "Failed to fetch key" }, { status: 500 });
+ }
+}
+
+// PUT /api/keys/[id] - Update key
+export async function PUT(request, { params }) {
+ try {
+ const { id } = await params;
+ const body = await request.json();
+ const { isActive } = body;
+
+ const existing = await getApiKeyById(id);
+ if (!existing) {
+ return NextResponse.json({ error: "Key not found" }, { status: 404 });
+ }
+
+ const updateData = {};
+ if (isActive !== undefined) updateData.isActive = isActive;
+
+ const updated = await updateApiKey(id, updateData);
+ await syncKeysToCloudIfEnabled();
+
+ return NextResponse.json({ key: updated });
+ } catch (error) {
+ console.log("Error updating key:", error);
+ return NextResponse.json({ error: "Failed to update key" }, { status: 500 });
+ }
+}
+
// DELETE /api/keys/[id] - Delete API key
export async function DELETE(request, { params }) {
try {
diff --git a/src/lib/localDb.js b/src/lib/localDb.js
index e7f675f7..34227a8c 100644
--- a/src/lib/localDb.js
+++ b/src/lib/localDb.js
@@ -115,6 +115,16 @@ function ensureDbShape(data) {
}
}
}
+
+ // Migrate existing API keys to have isActive
+ if (key === "apiKeys" && Array.isArray(next.apiKeys)) {
+ for (const apiKey of next.apiKeys) {
+ if (apiKey.isActive === undefined || apiKey.isActive === null) {
+ apiKey.isActive = true;
+ changed = true;
+ }
+ }
+ }
}
return { data: next, changed };
@@ -649,6 +659,7 @@ export async function createApiKey(name, machineId) {
name: name,
key: result.key,
machineId: machineId,
+ isActive: true,
createdAt: now,
};
@@ -673,12 +684,36 @@ export async function deleteApiKey(id) {
return true;
}
+/**
+ * Get API key by ID
+ */
+export async function getApiKeyById(id) {
+ const db = await getDb();
+ return db.data.apiKeys.find(k => k.id === id) || null;
+}
+
+/**
+ * Update API key
+ */
+export async function updateApiKey(id, data) {
+ const db = await getDb();
+ const index = db.data.apiKeys.findIndex(k => k.id === id);
+ if (index === -1) return null;
+ db.data.apiKeys[index] = {
+ ...db.data.apiKeys[index],
+ ...data,
+ };
+ await db.write();
+ return db.data.apiKeys[index];
+}
+
/**
* Validate API key
*/
export async function validateApiKey(key) {
const db = await getDb();
- return db.data.apiKeys.some(k => k.key === key);
+ const found = db.data.apiKeys.find(k => k.key === key);
+ return found && found.isActive !== false;
}
// ============ Data Cleanup ============