Skip to content

支持为key添加备注#265

Merged
tbphp merged 32 commits intotbphp:devfrom
paopaoandlingyia:feature-add-remark
Oct 13, 2025
Merged

支持为key添加备注#265
tbphp merged 32 commits intotbphp:devfrom
paopaoandlingyia:feature-add-remark

Conversation

@paopaoandlingyia
Copy link
Contributor

@paopaoandlingyia paopaoandlingyia commented Sep 10, 2025

关联 Issue / Related Issue

Closes #

变更内容 / Change Content

  • Bug 修复 / Bug fix
  • 新功能 / New feature
  • 其他改动 / Other changes

在 web\src\types\models.ts 模型中新增 notes字段 (varchar(255), 可选)
新增 PUT /api/keys/:id/notes API 端点用于更新密钥备注

优化密钥显示逻辑:有备注的密钥默认显示备注,无备注的密钥显示掩码
新增"备注"按钮,支持实时编辑密钥备注
现有密钥正常工作,备注字段为可选
不影响任何现有功能

有备注:默认显示备注,点击眼睛图标显示完整密钥
无备注:维持原有行为(显示掩码密钥)

每个密钥旁边都有备注按钮
备注存储在数据库中,与密钥一同管理

自查清单 / Checklist

  • 我已在本地测试过我的变更。 / I have tested my changes locally.
  • 我已更新了必要的文档。 / I have updated the necessary documentation.

Summary by CodeRabbit

  • New Features

    • Add per-key notes: create, edit, and save notes for API keys (up to 255 characters).
    • Notes appear in the key list and are prioritized in display when present.
    • New “Notes” action opens a modal with textarea and save/cancel controls.
    • Instant feedback on successful updates.
  • Chores

    • Added translations for notes-related UI in English, Japanese, and Chinese.

@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

Walkthrough

Introduces key notes functionality: adds Notes field to APIKey, a PUT /api/keys/:id/notes endpoint to update notes with validation, frontend API method to call it, UI in KeyTable.vue to edit/display notes, and localization strings in en-US, ja-JP, and zh-CN.

Changes

Cohort / File(s) Summary
Backend: Handler & Router
internal/handler/key_handler.go, internal/router/router.go
Adds UpdateKeyNotesRequest type and Server.UpdateKeyNotes handler; validates path id, binds JSON, trims/enforces <=255 runes, checks key existence, updates DB; wires route PUT /api/keys/:id/notes.
Backend: Model
internal/models/types.go
Adds Notes string field to APIKey with gorm tag type:varchar(255);default:'' and JSON tag notes.
Frontend: API & Types
web/src/api/keys.ts, web/src/types/models.ts
Adds keysApi.updateKeyNotes(keyId, notes): Promise; extends APIKey interface with optional notes?: string.
Frontend: UI
web/src/components/keys/KeyTable.vue
Adds notes editing modal and state; "Notes" action button; displays notes in key listing; saves via keysApi.updateKeyNotes and updates local state.
Localization
web/src/locales/en-US.ts, web/src/locales/ja-JP.ts, web/src/locales/zh-CN.ts
Adds strings: notesUpdated, editNotes, notes, editKeyNotes, enterNotes across three locales.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant V as KeyTable.vue
  participant A as keysApi
  participant S as Server (UpdateKeyNotes)
  participant D as DB

  U->>V: Click "Notes" and Save (notes text)
  V->>A: updateKeyNotes(keyId, notes)
  A->>S: PUT /api/keys/:id/notes { notes }
  S->>S: Validate id, bind JSON, trim, <=255 runes
  S->>D: Verify key exists
  D-->>S: Exists / not found
  alt Key exists
    S->>D: Update notes
    D-->>S: OK
    S-->>A: 200 No content
    A-->>V: Resolve
    V->>V: Update local key.notes, show success
  else Not found / invalid
    S-->>A: 4xx/5xx error
    A-->>V: Reject
    V->>V: Show error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A nibble of notes on a carrot key,
I scribble in fields where bytes run free.
Hop to the modal, tap-tap—save!
The PUT path echoes: brave, concise, brave.
Now keys hum softly, with thoughts in tow—
A burrow of comments where secrets grow. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description generally follows the repository template but leaves the Related Issue section as the placeholder 'Closes #' without specifying an actual issue number, which is required by the template. The Change Content and Checklist sections are detailed and clear, but the missing issue link makes the description incomplete. Please update the Related Issue section with a valid issue number or remove it if no issue is associated, and confirm or update the documentation checklist item if any documentation changes are needed.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title '支持为key添加备注' succinctly captures the primary change in this pull request by indicating support for adding notes to keys and is both concise and specific enough for teammates to understand its purpose. It directly reflects the main functionality introduced without unnecessary details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96d3b3d and 886c070.

📒 Files selected for processing (5)
  • internal/handler/key_handler.go (2 hunks)
  • web/src/components/keys/KeyTable.vue (6 hunks)
  • web/src/locales/en-US.ts (1 hunks)
  • web/src/locales/ja-JP.ts (1 hunks)
  • web/src/locales/zh-CN.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/src/locales/en-US.ts
  • web/src/locales/ja-JP.ts
  • web/src/locales/zh-CN.ts
  • web/src/components/keys/KeyTable.vue
  • internal/handler/key_handler.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
internal/models/types.go (1)

84-84: Notes column added — consider nullability consistency and DB default

Current choice uses empty string as default. If you intend “optional,” consider either:

  • Keep string but add not null explicitly for clarity; or
  • Switch to *string to represent nulls consistently across DB and JSON (omitempty).

Minimal GORM tag tweak (if staying non-null) for explicitness:

- Notes        string     `gorm:"type:varchar(255);default:''" json:"notes"`
+ Notes        string     `gorm:"type:varchar(255);not null;default:''" json:"notes"`
web/src/types/models.ts (1)

16-16: Align TS type with backend payload

Backend always returns a string (often empty). Making notes non-optional avoids undefined checks in the UI.

-  notes?: string;
+  notes: string;
web/src/api/keys.ts (1)

109-112: Trim and bound notes client-side to reduce server rejects

Minor UX polish: trim and enforce 255 chars before sending.

   // 更新密钥备注
   async updateKeyNotes(keyId: number, notes: string): Promise<void> {
-    await http.put(`/keys/${keyId}/notes`, { notes });
+    const payload = (notes ?? "").trim().slice(0, 255);
+    await http.put(`/keys/${keyId}/notes`, { notes: payload });
   },
internal/handler/key_handler.go (1)

452-485: Optional: audit trail

Consider logging who changed notes and when (if you have user identity), to aid operational traceability.

web/src/components/keys/KeyTable.vue (4)

34-36: Type safety: make is_visible optional or initialize it

Data from API lacks is_visible. Either mark it optional:

-interface KeyRow extends APIKey {
-  is_visible: boolean;
-}
+interface KeyRow extends APIKey {
+  is_visible?: boolean;
+}

or initialize when loading (see next comment).


190-201: Initialize is_visible to avoid undefined at runtime

Set a default when mapping response items.

-    keys.value = result.items as KeyRow[];
+    keys.value = (result.items as APIKey[]).map(k => ({ ...k, is_visible: false })) as KeyRow[];

302-313: Trim notes before save for consistent UX

Aligns with API trimming suggestion.

 async function saveKeyNotes() {
   if (!editingKey.value) return;
 
   try {
-    await keysApi.updateKeyNotes(editingKey.value.id, editingNotes.value);
-    editingKey.value.notes = editingNotes.value;
+    const payload = (editingNotes.value ?? "").trim().slice(0, 255);
+    await keysApi.updateKeyNotes(editingKey.value.id, payload);
+    editingKey.value.notes = payload;
     window.$message.success("备注已更新");
     notesDialogShow.value = false;
   } catch (error) {
     window.$message.error("备注更新失败");
   }
 }

1082-1091: Unused style block

.key-notes isn’t referenced in the template. Remove or apply it where intended to avoid dead CSS.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6c676 and 821b5d2.

📒 Files selected for processing (7)
  • internal/handler/key_handler.go (1 hunks)
  • internal/models/types.go (1 hunks)
  • internal/router/router.go (1 hunks)
  • internal/services/key_service.go (1 hunks)
  • web/src/api/keys.ts (1 hunks)
  • web/src/components/keys/KeyTable.vue (7 hunks)
  • web/src/types/models.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/handler/key_handler.go (4)
internal/handler/handler.go (1)
  • Server (20-33)
internal/response/response.go (2)
  • Error (34-39)
  • Success (25-31)
internal/errors/errors.go (5)
  • NewAPIError (44-50)
  • ErrBadRequest (27-27)
  • ErrInvalidJSON (28-28)
  • ErrResourceNotFound (31-31)
  • ParseDBError (62-91)
internal/models/types.go (1)
  • APIKey (78-90)
🔇 Additional comments (5)
internal/router/router.go (2)

133-133: Route wiring LGTM

PUT /api/keys/:id/notes correctly scoped under the protected keys group.


133-133: Confirm authorization model for cross-group access

Handler updates by key ID only. If you enforce group- or role-based access, verify the auth layer prevents modifying keys across unauthorized groups. If needed, add a group check.

Would you like a quick repo scan to locate existing group-ownership checks to mirror here?

internal/services/key_service.go (1)

133-134: Explicit initialization is fine

Setting Notes: "" on creation matches the DB default and frontend expectations.

web/src/components/keys/KeyTable.vue (2)

287-293: Display logic LGTM

Notes shown when hidden; full key when visible; masked otherwise. Matches spec.


830-843: Modal UX is solid

Textarea with maxlength and counter matches backend constraints.

@tbphp tbphp added the enhancement New feature or request label Sep 12, 2025
@tbphp
Copy link
Owner

tbphp commented Sep 12, 2025

感谢你的贡献,之后会审查此PR。


Thank you for your contribution, this PR will be reviewed later.

@tbphp tbphp changed the base branch from main to dev September 12, 2025 13:21
@tbphp
Copy link
Owner

tbphp commented Oct 10, 2025

感谢你的贡献!

如果方便,希望能帮忙协助完成以下内容:

  • 合并最新的dev代码并测试功能是否正常(最近的1.3版本改动太多)
  • 前端需要国际化翻译 web/src/locales

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
internal/handler/key_handler.go (2)

443-446: Add validation tag to prevent DB constraint violations.

The Notes field lacks server-side validation that could prevent values exceeding the database's varchar(255) constraint, potentially causing 500 errors.

This issue was previously flagged. As suggested in the earlier review, add binding:"max=255" to the struct field.


448-481: Trim input and validate length explicitly before DB update.

Without trimming and explicit length validation, oversized or untrimmed notes can hit the database and cause errors or inconsistent data.

This issue was previously flagged. As suggested in the earlier review, trim the input with strings.TrimSpace after binding and validate the length before the DB operation.

🧹 Nitpick comments (2)
web/src/components/keys/KeyTable.vue (2)

309-313: Validate and trim notes before API call.

The function should validate the input before making the API call to provide better UX and catch issues client-side.

Apply this diff:

 async function saveKeyNotes() {
   if (!editingKey.value) return;
 
+  // Trim and validate
+  const trimmedNotes = editingNotes.value.trim();
+  if (trimmedNotes.length > 255) {
+    window.$message.error(t("keys.notesTooLong"));
+    return;
+  }
+
   try {
-    await keysApi.updateKeyNotes(editingKey.value.id, editingNotes.value);
-    editingKey.value.notes = editingNotes.value;
+    await keysApi.updateKeyNotes(editingKey.value.id, trimmedNotes);
+    editingKey.value.notes = trimmedNotes;
     window.$message.success(t("keys.notesUpdated"));
     notesDialogShow.value = false;
   } catch (error) {
     window.$message.error(t("keys.notesUpdateFailed"));
   }
 }

1096-1105: Remove unused CSS class.

The key-notes class is defined but never used in the template. Consider removing it to reduce code clutter.

Apply this diff to remove the unused class:

-/* 备注信息行 */
-.key-notes {
-  font-size: 12px;
-  color: var(--text-secondary);
-  margin-top: 8px;
-  padding: 4px 6px;
-  background: var(--bg-secondary);
-  border-radius: 4px;
-  line-height: 1.4;
-}
-
 /* 底部统计和按钮行 */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 821b5d2 and 94bd3c1.

📒 Files selected for processing (6)
  • internal/handler/key_handler.go (1 hunks)
  • internal/models/types.go (1 hunks)
  • internal/router/router.go (1 hunks)
  • web/src/api/keys.ts (1 hunks)
  • web/src/components/keys/KeyTable.vue (7 hunks)
  • web/src/types/models.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/models/types.go
  • web/src/types/models.ts
  • internal/router/router.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/handler/key_handler.go (4)
internal/handler/handler.go (1)
  • Server (21-36)
internal/response/response.go (2)
  • Error (36-41)
  • Success (26-33)
internal/errors/errors.go (5)
  • NewAPIError (44-50)
  • ErrBadRequest (27-27)
  • ErrInvalidJSON (28-28)
  • ErrResourceNotFound (31-31)
  • ParseDBError (62-91)
internal/models/types.go (1)
  • APIKey (110-122)
🔇 Additional comments (3)
web/src/api/keys.ts (1)

119-122: LGTM! API method follows existing patterns.

The implementation is clean and consistent with other methods in the file. The method signature correctly matches the backend endpoint structure.

web/src/components/keys/KeyTable.vue (2)

95-98: LGTM! State management setup is clean.

The reactive variables for notes editing are well-organized and follow Vue 3 composition API conventions.


293-299: LGTM! Centralized display logic improves maintainability.

The getDisplayValue function properly implements the requirement: "有备注的密钥默认显示备注;无备注的密钥仍显示掩码" (keys with notes show notes by default; keys without notes show masked values).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
web/src/components/keys/KeyTable.vue (3)

293-299: Clarify the notes display priority logic.

The function shows notes when they exist AND the key is hidden (!key.is_visible). When the key is visible (is_visible=true), it always shows the actual key value instead of notes. This means users must toggle visibility to switch between viewing notes and the actual key.

Consider documenting this behavior with a comment, or evaluate if notes should be displayed regardless of visibility state:

 // 获取要显示的值(备注优先,否则显示密钥)
 function getDisplayValue(key: KeyRow): string {
+  // Notes are shown only when the key is hidden; when visible, always show actual key
   if (key.notes && !key.is_visible) {
     return key.notes;
   }
   return key.is_visible ? key.key_value : maskKey(key.key_value);
 }

309-320: Improve error handling with logging.

The error handler in saveKeyNotes shows a generic error message but doesn't log the error details. This makes debugging production issues difficult.

Apply this diff:

   try {
     await keysApi.updateKeyNotes(editingKey.value.id, editingNotes.value);
     editingKey.value.notes = editingNotes.value;
     window.$message.success(t("keys.notesUpdated"));
     notesDialogShow.value = false;
   } catch (error) {
+    console.error("Failed to update key notes:", error);
     window.$message.error(t("keys.notesUpdateFailed"));
   }

1096-1105: Remove unused CSS class.

The .key-notes CSS class is defined but never used in the template. Notes are displayed through the getDisplayValue() function within the existing .key-text input field, not as a separate element with the .key-notes class.

Apply this diff to remove the unused CSS:

-/* 备注信息行 */
-.key-notes {
-  font-size: 12px;
-  color: var(--text-secondary);
-  margin-top: 8px;
-  padding: 4px 6px;
-  background: var(--bg-secondary);
-  border-radius: 4px;
-  line-height: 1.4;
-}
-
 /* 底部统计和按钮行 */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 821b5d2 and f464b1b.

📒 Files selected for processing (9)
  • internal/handler/key_handler.go (1 hunks)
  • internal/models/types.go (1 hunks)
  • internal/router/router.go (1 hunks)
  • web/src/api/keys.ts (1 hunks)
  • web/src/components/keys/KeyTable.vue (7 hunks)
  • web/src/locales/en-US.ts (1 hunks)
  • web/src/locales/ja-JP.ts (1 hunks)
  • web/src/locales/zh-CN.ts (1 hunks)
  • web/src/types/models.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/router/router.go
  • internal/handler/key_handler.go
  • internal/models/types.go
  • web/src/api/keys.ts
🔇 Additional comments (5)
web/src/types/models.ts (1)

22-22: LGTM! Clean addition of optional notes field.

The optional notes field integrates cleanly with the existing APIKey interface and maintains backward compatibility.

web/src/components/keys/KeyTable.vue (4)

95-98: LGTM! Notes editing state properly initialized.

The reactive state for notes editing is well-structured and follows Vue 3 composition API best practices.


301-320: LGTM! I18n implementation is now correct.

The previous issue with hard-coded Chinese strings has been properly resolved. All user-facing text now uses the i18n translation function t().


745-754: LGTM! I18n implementation is now correct.

The Notes button now properly uses translated strings via t() for both the button text and title attribute.


843-857: LGTM! I18n implementation is now correct.

The modal dialog now properly uses translated strings for the title, placeholder, and button labels.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/src/locales/ja-JP.ts (1)

401-406: Consider using "備考" instead of "注釈" for better UX alignment.

The current translations use "注釈" (annotation), which is more formal/academic. Since the PR title references "备注" (remark/memo in Chinese), consider using "備考" (bikō) in Japanese, which is more commonly used in business and UI contexts for user notes/remarks.

Apply this diff:

-    notesUpdated: "注釈が更新されました",
-    notesUpdateFailed: "注釈の更新に失敗しました",
-    editNotes: "注釈を編集",
-    notes: "注釈",
-    editKeyNotes: "キー注釈を編集",
-    enterNotes: "注釈を入力してください...",
+    notesUpdated: "備考が更新されました",
+    notesUpdateFailed: "備考の更新に失敗しました",
+    editNotes: "備考を編集",
+    notes: "備考",
+    editKeyNotes: "キー備考を編集",
+    enterNotes: "備考を入力してください...",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f464b1b and 983a66f.

📒 Files selected for processing (3)
  • web/src/locales/en-US.ts (1 hunks)
  • web/src/locales/ja-JP.ts (1 hunks)
  • web/src/locales/zh-CN.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/locales/zh-CN.ts
  • web/src/locales/en-US.ts

@paopaoandlingyia
Copy link
Contributor Author

感谢你的贡献!

如果方便,希望能帮忙协助完成以下内容:

  • 合并最新的dev代码并测试功能是否正常(最近的1.3版本改动太多)
  • 前端需要国际化翻译 web/src/locales

好的,本地测试已经正常,并且已添加国际化翻译

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/src/components/keys/KeyTable.vue (1)

308-322: Add loading state and improve error handling.

The save operation lacks a loading state, which could lead to duplicate submissions if users click the save button multiple times. Additionally, the error is not logged, making debugging difficult in production.

Consider this enhancement:

+const savingNotes = ref(false);
+
 // 保存备注
 async function saveKeyNotes() {
-  if (!editingKey.value) {
+  if (!editingKey.value || savingNotes.value) {
     return;
   }
 
+  savingNotes.value = true;
   try {
     await keysApi.updateKeyNotes(editingKey.value.id, editingNotes.value);
     editingKey.value.notes = editingNotes.value;
     window.$message.success(t("keys.notesUpdated"));
     notesDialogShow.value = false;
-  } catch (_error) {
+  } catch (error) {
+    console.error("Failed to update notes:", error);
     window.$message.error(t("keys.notesUpdateFailed"));
+  } finally {
+    savingNotes.value = false;
   }
 }

Then update the save button to show loading state:

-    <n-button type="primary" @click="saveKeyNotes">{{ t("common.save") }}</n-button>
+    <n-button type="primary" @click="saveKeyNotes" :loading="savingNotes">{{ t("common.save") }}</n-button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9633967 and 46e4bf9.

📒 Files selected for processing (1)
  • web/src/components/keys/KeyTable.vue (6 hunks)
🔇 Additional comments (2)
web/src/components/keys/KeyTable.vue (2)

293-299: LGTM! Smart display logic.

The function correctly prioritizes showing notes when available and the key is hidden, providing a clean UX for the notes feature.


845-859: LGTM! Well-implemented modal.

The modal implementation is solid:

  • Properly internationalized throughout
  • maxlength="255" matches the backend constraint
  • show-count provides helpful UX feedback

@tbphp
Copy link
Owner

tbphp commented Oct 11, 2025

bug:
前端没有trim去除两边空白,后端正常去除。导致保存时前端会保留空格,刷新之后就没有空格。

期望:
前端保存时也去除note两侧空白,保持和后端一样的处理逻辑。

@tbphp
Copy link
Owner

tbphp commented Oct 11, 2025

CleanShot 2025-10-11 at 10 25 52

失效状态的密钥,会同时存在4个操作,导致前面的文字显示遮挡。

把下面按钮放在上面改为icon,这种样式应该会好点:
CleanShot 2025-10-11 at 10 30 10

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
web/src/components/keys/KeyTable.vue (2)

309-324: Consider adding a loading state to prevent double-submission.

While the current implementation handles errors correctly, there's no loading state to prevent users from clicking the save button multiple times while the API call is in progress. This could lead to duplicate requests.

Consider adding a loading state:

+const isSavingNotes = ref(false);
+
 async function saveKeyNotes() {
-  if (!editingKey.value) {
+  if (!editingKey.value || isSavingNotes.value) {
     return;
   }
 
+  isSavingNotes.value = true;
   try {
     const trimmed = editingNotes.value.trim();
     await keysApi.updateKeyNotes(editingKey.value.id, trimmed);
     editingKey.value.notes = trimmed;
     window.$message.success(t("keys.notesUpdated"));
     notesDialogShow.value = false;
   } catch (_error) {
     window.$message.error(t("keys.notesUpdateFailed"));
+  } finally {
+    isSavingNotes.value = false;
   }
 }

Then disable the save button while loading:

-    <n-button type="primary" @click="saveKeyNotes">{{ t("common.save") }}</n-button>
+    <n-button type="primary" @click="saveKeyNotes" :loading="isSavingNotes">{{ t("common.save") }}</n-button>

743-790: Consider consolidating action buttons to improve layout for invalid keys.

The maintainer reported that for invalid status keys, multiple action buttons in the bottom row cause text overlap with the statistics display. The maintainer suggested moving bottom buttons to the top row and using icon-only buttons to improve the layout and prevent text from being obscured.

Based on PR comments reporting UI layout issues with button overlap.

Current layout:

  • Top row (lines 713-739): Icon-only buttons for Notes, Show/Hide, Copy
  • Bottom row (lines 758-789): Text buttons for Test, Restore (conditional), Delete

Possible improvements:

  1. Move Restore and Delete buttons to the top row as icon-only buttons and keep only the Test button (or a combined "More actions" dropdown) at the bottom
  2. Use a dropdown menu for less-frequent actions (Restore, Delete) to reduce button count
  3. Reduce button padding or use smaller button sizes for the bottom row to prevent overlap

The maintainer referenced screenshots showing the overlap issue. Would you like me to generate a specific UI refactor proposal based on one of these approaches?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46e4bf9 and 721464b.

⛔ Files ignored due to path filters (1)
  • image.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • web/src/components/keys/KeyTable.vue (6 hunks)
🔇 Additional comments (3)
web/src/components/keys/KeyTable.vue (3)

294-300: LGTM! Display logic correctly prioritizes notes.

The function correctly prioritizes notes when the key is hidden, and falls back to showing the actual or masked key value. The truthy check on key.notes properly handles empty strings by falling through to display the masked key.


309-324: Trim implementation correctly addresses the maintainer's bug report.

The function properly trims whitespace from notes before saving (line 316) and updates both the API and local state with the trimmed value. This ensures consistency between frontend and backend, resolving the issue where the frontend preserved whitespace but the backend removed it.

Based on PR comments requesting trim implementation to match backend behavior.


847-861: LGTM! Modal implementation is complete and user-friendly.

The notes editing dialog properly uses i18n translations for all text elements, enforces the 255-character limit matching the database constraint, and provides a character counter for good UX.

@paopaoandlingyia
Copy link
Contributor Author

CleanShot 2025-10-11 at 10 25 52 失效状态的密钥,会同时存在4个操作,导致前面的文字显示遮挡。

把下面按钮放在上面改为icon,这种样式应该会好点: CleanShot 2025-10-11 at 10 30 10

已优化,看看效果怎么样

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/src/components/keys/KeyTable.vue (1)

847-861: LGTM! Modal implementation is correct.

The notes editing modal properly uses:

  • v-model:show for dialog visibility control
  • Internationalized strings for title, placeholder, and buttons
  • maxlength="255" matching the database field size
  • show-count for user feedback
  • Proper action buttons (Cancel/Save) with correct handlers

Optional: Consider adding a loading state to the Save button during the API call to provide visual feedback and prevent double-clicks.

Apply this diff to add loading state:

+const isSavingNotes = ref(false);
+
 // 保存备注
 async function saveKeyNotes() {
   if (!editingKey.value) {
     return;
   }
 
   try {
+    isSavingNotes.value = true;
     const trimmed = editingNotes.value.trim();
     await keysApi.updateKeyNotes(editingKey.value.id, trimmed);
     editingKey.value.notes = trimmed;
     window.$message.success(t("keys.notesUpdated"));
     notesDialogShow.value = false;
   } catch (_error) {
     window.$message.error(t("keys.notesUpdateFailed"));
+  } finally {
+    isSavingNotes.value = false;
   }
 }

Then update the Save button:

-    <n-button type="primary" @click="saveKeyNotes">{{ t("common.save") }}</n-button>
+    <n-button type="primary" :loading="isSavingNotes" @click="saveKeyNotes">{{ t("common.save") }}</n-button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 721464b and 2d14bd6.

📒 Files selected for processing (1)
  • web/src/components/keys/KeyTable.vue (6 hunks)
🔇 Additional comments (6)
web/src/components/keys/KeyTable.vue (6)

15-15: LGTM! Imports are appropriate.

The Pencil icon and NModal component imports are correctly added to support the notes editing feature.

Also applies to: 24-24


96-99: LGTM! State management is clean.

The three refs (notesDialogShow, editingKey, editingNotes) properly manage the modal visibility, the key being edited, and the notes content. The state is well-scoped and minimal.


294-300: LGTM! Display logic correctly prioritizes notes.

The getDisplayValue function implements the requirement: when notes exist and the key is hidden, display notes; otherwise display the key value (visible or masked). This allows users to toggle between notes and the actual key using the eye icon.


302-307: LGTM! Edit initialization is straightforward.

The editKeyNotes function correctly sets up the editing state, initializing editingNotes with the current notes value (or empty string) and opening the modal dialog.


309-324: LGTM! Save function correctly addresses trim requirement.

The saveKeyNotes function properly addresses the maintainer's concern by trimming the notes on line 316 before saving. Both the API call (line 317) and the local state update (line 318) use the trimmed value, ensuring consistency between frontend and backend. Error handling keeps the dialog open on failure, allowing users to retry.

Note: Empty notes after trimming will be saved, which appears intentional to allow clearing notes.

Based on PR comments requesting trim on frontend save to match backend behavior.


714-723: LGTM! Icon-only edit button improves space efficiency.

The notes edit button is implemented as an icon-only button with a tooltip (:title="t('keys.editNotes')"), which saves space in the quick-actions area while remaining accessible. This design choice aligns with the other quick-action buttons (toggle visibility, copy).

Copy link
Owner

@tbphp tbphp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code reviewed.

@tbphp tbphp merged commit f57d371 into tbphp:dev Oct 13, 2025
1 check passed
@tbphp tbphp added this to the v1.3.1 milestone Oct 13, 2025
@tbphp
Copy link
Owner

tbphp commented Oct 13, 2025

感谢你的贡献!!!


This PR got merged and will be out in version v1.3.1. Big thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants