From 76398c3de6ddca952a286e5619564508273d83ed Mon Sep 17 00:00:00 2001 From: jigoong Date: Tue, 9 Jun 2026 00:41:36 +0700 Subject: [PATCH] feat(apiservice): add edit client/key functionality in API Management page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PATCH /admin/api-keys/clients/{id} — update client name and is_active - PATCH /admin/api-keys/{id} — update key name and permissions - Edit Client modal with name field and active/inactive toggle - Edit Key modal with name field and permissions JSON textarea (pre-filled) - Fix JS syntax error: use data-* attributes instead of inline JSON in onclick --- 03-apiservice/app/routes/admin_api_keys.py | 55 ++++++++ .../app/templates/api_management.html | 129 ++++++++++++++++++ 2 files changed, 184 insertions(+) diff --git a/03-apiservice/app/routes/admin_api_keys.py b/03-apiservice/app/routes/admin_api_keys.py index 0c375e0..a8f3098 100644 --- a/03-apiservice/app/routes/admin_api_keys.py +++ b/03-apiservice/app/routes/admin_api_keys.py @@ -45,12 +45,22 @@ class ApiClientCreateSchema(BaseModel): name: str +class ApiClientUpdateSchema(BaseModel): + name: str | None = None + is_active: bool | None = None + + class ApiKeyCreateSchema(BaseModel): client_id: int name: str | None = None permissions: list[str] = [] +class ApiKeyUpdateSchema(BaseModel): + name: str | None = None + permissions: list[str] | None = None + + @router.get("/clients", response_model=List[ApiClientSchema]) async def list_clients( db: Session = Depends(get_db), @@ -60,6 +70,30 @@ async def list_clients( return db.query(ApiClient).order_by(ApiClient.id).all() +@router.patch("/clients/{client_id}", response_model=ApiClientSchema) +async def update_client( + client_id: int, + data: ApiClientUpdateSchema, + db: Session = Depends(get_db), + current_user: dict = Depends(require_role(Roles.ADMIN)), +): + """Update API client name or active status (Admin only)""" + client = db.get(ApiClient, client_id) + if not client: + raise HTTPException(status_code=404, detail="Client not found") + if data.name is not None: + existing = db.query(ApiClient).filter(ApiClient.name == data.name, ApiClient.id != client_id).first() + if existing: + raise HTTPException(status_code=400, detail="Client name already exists") + client.name = data.name + if data.is_active is not None: + client.is_active = data.is_active + db.commit() + db.refresh(client) + logger.info(f"Admin {current_user.get('username')} updated client {client_id}") + return client + + @router.post("/clients", response_model=ApiClientSchema) async def create_client( data: ApiClientCreateSchema, @@ -126,6 +160,27 @@ async def regenerate_key( return {"key_id": api_key.id, "api_key": plain_key, "key_prefix": api_key.key_prefix, "permissions": api_key.permissions} +@router.patch("/{key_id}", response_model=ApiKeySchema) +async def update_key( + key_id: int, + data: ApiKeyUpdateSchema, + db: Session = Depends(get_db), + current_user: dict = Depends(require_role(Roles.ADMIN)), +): + """Update API key name or permissions (Admin only)""" + api_key = db.get(ApiKey, key_id) + if not api_key: + raise HTTPException(status_code=404, detail="API Key not found") + if data.name is not None: + api_key.name = data.name + if data.permissions is not None: + api_key.permissions = data.permissions + db.commit() + db.refresh(api_key) + logger.info(f"Admin {current_user.get('username')} updated API key {key_id}") + return api_key + + @router.patch("/{key_id}/toggle") async def toggle_key( key_id: int, diff --git a/03-apiservice/app/templates/api_management.html b/03-apiservice/app/templates/api_management.html index f072126..0459ea8 100644 --- a/03-apiservice/app/templates/api_management.html +++ b/03-apiservice/app/templates/api_management.html @@ -207,6 +207,21 @@ } .loading { text-align: center; padding: 40px; color: #666; } + + .toggle-row { display: flex; align-items: center; gap: 10px; } + .toggle-switch { + position: relative; width: 44px; height: 24px; cursor: pointer; + } + .toggle-switch input { opacity: 0; width: 0; height: 0; } + .toggle-slider { + position: absolute; inset: 0; background: #ccc; border-radius: 24px; transition: 0.3s; + } + .toggle-slider:before { + content: ''; position: absolute; width: 18px; height: 18px; + left: 3px; top: 3px; background: white; border-radius: 50%; transition: 0.3s; + } + .toggle-switch input:checked + .toggle-slider { background: #51cf66; } + .toggle-switch input:checked + .toggle-slider:before { transform: translateX(20px); } @@ -291,6 +306,53 @@ + + + + + +