feat: implement batch selection and deletion for apps#673
Conversation
RX 9070 + Adrenalin 26.5.x freezes after ~2min on H.264/HEVC streaming because Sunshine's direct-AMF integration hardcodes aggressive properties (LOWLATENCY_MODE=true, INPUT_QUEUE_SIZE=1, HIGH_MOTION_QUALITY_BOOST=true, AV1 ENCODING_LATENCY_MODE=LOWEST_LATENCY) that force less-traveled VCN paths. AV1 is unaffected because users typically don't hit those props through other means. Mirror FFmpeg amfenc convention: every aggressive opt is std::optional, default = nullopt = skip SetProperty = use AMD driver default. Streaming- required props kept hardcoded (QUERY_TIMEOUT=1, IDR_PERIOD/GOP_SIZE=0, B_PIC_PATTERN=0, HEVC NUM_GOPS_PER_IDR=1 + IDR_ALIGNED, AV1 NO_RESTRICTIONS). Exposed to WebUI under new 'AMF Advanced (Driver Workarounds)' accordion with empty-string = driver default round-trip through optional<>. Affected props: - LOWLATENCY_MODE (H.264/HEVC) - INPUT_QUEUE_SIZE (H.264/HEVC/AV1) - HIGH_MOTION_QUALITY_BOOST_ENABLE (H.264/HEVC) - AV1 ENCODING_LATENCY_MODE Refs: #666
) Hand-ported subset of upstream LizardByte/Sunshine 738ac93: adds check_content_type() helper and calls it at the start of POST handlers for /api/apps, /api/apps/close, /api/apps/<id> (DELETE), /api/clients/unpair, /api/clients/unpair-all, /api/config, /api/covers/upload, /api/password, /api/pin, /api/restart, and /api/reset-display-device-persistence. AlkaidLab does not have upstreams bad_request() helper, so the bad-request response is inlined inside check_content_type() as a local lambda; the behavior matches upstream (HTTP 400 + JSON body with status_code/status/error). AlkaidLab-specific POST endpoints (AI/LLM proxy at /api/ai/config and /api/ai/chat/completions, QR pairing at /api/qr-pair{,/cancel}, client rename at /api/clients/rename, /api/apps/test-menu-cmd, /api/logout) are intentionally NOT covered by this PR; reviewer may extend the same check to those if desired. HTML-side Content-Type-on-fetch changes from upstream are skipped: AlkaidLab UI is heavily restructured for Chinese localization + AI UI and using a different request framework. (cherry picked from commit 738ac93) Signed-off-by: dkgkdfg65 <219107372+dkgkdfg65@users.noreply.github.com> Co-authored-by: dkgkdfg65 <219107372+dkgkdfg65@users.noreply.github.com>
Summary by CodeRabbit
Walkthrough新增后端Content-Type校验与批量删除HTTP处理器;前端新增多选/批量删除状态与方法、API 客户端、UI 选择与确认对话框、样式调整,并为多语言资源添加批量操作文案键。 变更概览批量删除应用功能
序列图sequenceDiagram
participant User as 用户
participant UI as Apps.vue
participant Composable as useApps
participant Service as AppService
participant Backend as 后端API
User->>UI: 点击切换多选模式
UI->>Composable: toggleSelectionMode()
Composable->>UI: selectionMode=true
User->>UI: 勾选应用复选框
UI->>Composable: toggleAppSelection(index)
Composable->>UI: selectedIndices更新
User->>UI: 点击批量删除
UI->>Composable: askBatchDelete()
Composable->>UI: batchDeleteConfirm=true
UI->>User: 显示确认对话框
User->>UI: 确认删除
UI->>Composable: confirmBatchDelete()
Composable->>Service: batchDeleteApps(indices数组)
Service->>Backend: POST /api/apps/batch-delete
Backend->>Service: {deleted, remaining}
Service->>Composable: 返回删除结果
Composable->>UI: 刷新应用列表,清空选择
Composable->>UI: 显示成功或失败消息
UI->>User: 展示删除结果提示
🎯 3 (Moderate) | ⏱️ ~25 minutes 可能相关的 PR:
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src_assets/common/assets/web/styles/apps.less (1)
554-575: ⚡ Quick win为选择复选框补充键盘焦点可见态
.app-select-checkbox目前只有:hover态;键盘导航时焦点反馈不够明确,建议补充:focus-visible,避免无鼠标场景下难以定位当前控件。♿ 建议修改
.app-select-checkbox { position: absolute; @@ &:hover { background: rgba(99, 102, 241, 0.9); } + + &:focus-visible { + outline: 2px solid var(--bs-primary, `#6366f1`); + outline-offset: 2px; + } &--list {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src_assets/common/assets/web/styles/apps.less` around lines 554 - 575, The .app-select-checkbox lacks keyboard-focused styling; add a :focus-visible rule for .app-select-checkbox to provide a clear, accessible focus indicator (e.g., a visible outline or box-shadow and maintain contrast) so keyboard users can identify the control; update the selector .app-select-checkbox:focus-visible to set an accessible outline/box-shadow, ensure pointer-events and background interactions remain consistent with :hover, and keep transition behavior in the relevant CSS block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src_assets/common/assets/web/views/Apps.vue`:
- Around line 50-57: The toggle button for batch selection is missing
accessibility state semantics; update the button element that uses selectionMode
and toggleSelectionMode to include aria-pressed bound to selectionMode (e.g.,
:aria-pressed="selectionMode") and add an explicit aria-label that reflects the
action/state (e.g., :aria-label="$t('apps.batch_select_toggle') + (selectionMode
? ' ' + $t('on') : ' ' + $t('off'))" or a concise localized label), ensuring the
same button that currently has :class and :title is the one updated so screen
readers get the toggle role and current state.
- Around line 137-145: The custom checkbox divs with role="checkbox" (the block
controlled by selectionMode and using isAppSelected(index) and
toggleAppSelection(index) in Apps.vue) are not keyboard-focusable or operable;
make them accessible by adding tabindex="0" so they are tabbable, and add a
keyboard handler (e.g., `@keydown`) that listens for Space and Enter and invokes
toggleAppSelection(index) (with appropriate .stop/.prevent) so keyboard users
can toggle selection; keep the existing `@click` behavior and ensure aria-checked
remains in place.
In `@src/confighttp.cpp`:
- Around line 1017-1020: 返回空删除请求时缺少与其他成功响应一致的 remaining 字段:在处理
indices_to_remove.empty() 分支中(同一函数使用到变量 indices_to_remove 和 outputTree),在设置
outputTree.put("status", "true") 和 outputTree.put("deleted", 0") 后也写入
outputTree.put("remaining", <当前 apps 数量>);获取当前 apps
数量可以复用后续计数路径或读取保存应用列表/容器的变量/方法以获得一致的值,保证该分支与其它成功响应字段契约一致。
- Around line 975-980: The error branches currently only populate outputTree and
call response->write(data.str()) inside the fail_guard, which leaves the HTTP
status at 200; introduce a status_code variable (default 200) alongside
outputTree and g, update each error branch that builds an error response to set
status_code to the appropriate value (e.g., 400, 409), and before writing the
JSON in the fail_guard set the response HTTP status using the response object
(e.g., response->set_status(status_code) or response->setStatus(status_code)) so
the written body and status code are consistent; apply this pattern for the
blocks around outputTree/g/response->write and the other listed ranges (991-995,
997-1001, 1010-1015, 1033-1037, 1057-1061).
---
Nitpick comments:
In `@src_assets/common/assets/web/styles/apps.less`:
- Around line 554-575: The .app-select-checkbox lacks keyboard-focused styling;
add a :focus-visible rule for .app-select-checkbox to provide a clear,
accessible focus indicator (e.g., a visible outline or box-shadow and maintain
contrast) so keyboard users can identify the control; update the selector
.app-select-checkbox:focus-visible to set an accessible outline/box-shadow,
ensure pointer-events and background interactions remain consistent with :hover,
and keep transition behavior in the relevant CSS block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 43eb6626-2482-408c-86da-29f96faeae6d
📒 Files selected for processing (26)
src/confighttp.cppsrc_assets/common/assets/web/composables/useApps.jssrc_assets/common/assets/web/public/assets/locale/bg.jsonsrc_assets/common/assets/web/public/assets/locale/cs.jsonsrc_assets/common/assets/web/public/assets/locale/de.jsonsrc_assets/common/assets/web/public/assets/locale/en.jsonsrc_assets/common/assets/web/public/assets/locale/en_GB.jsonsrc_assets/common/assets/web/public/assets/locale/en_US.jsonsrc_assets/common/assets/web/public/assets/locale/es.jsonsrc_assets/common/assets/web/public/assets/locale/fr.jsonsrc_assets/common/assets/web/public/assets/locale/it.jsonsrc_assets/common/assets/web/public/assets/locale/ja.jsonsrc_assets/common/assets/web/public/assets/locale/ko.jsonsrc_assets/common/assets/web/public/assets/locale/pl.jsonsrc_assets/common/assets/web/public/assets/locale/pt.jsonsrc_assets/common/assets/web/public/assets/locale/pt_BR.jsonsrc_assets/common/assets/web/public/assets/locale/ru.jsonsrc_assets/common/assets/web/public/assets/locale/sv.jsonsrc_assets/common/assets/web/public/assets/locale/tr.jsonsrc_assets/common/assets/web/public/assets/locale/uk.jsonsrc_assets/common/assets/web/public/assets/locale/zh.jsonsrc_assets/common/assets/web/public/assets/locale/zh_TW.jsonsrc_assets/common/assets/web/services/appService.jssrc_assets/common/assets/web/styles/apps.lesssrc_assets/common/assets/web/utils/constants.jssrc_assets/common/assets/web/views/Apps.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (2)
src_assets/**/*.{vue,js,html}
⚙️ CodeRabbit configuration file
src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。
Files:
src_assets/common/assets/web/services/appService.jssrc_assets/common/assets/web/utils/constants.jssrc_assets/common/assets/web/composables/useApps.jssrc_assets/common/assets/web/views/Apps.vue
src/**/*.{cpp,c,h}
⚙️ CodeRabbit configuration file
src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。
Files:
src/confighttp.cpp
🔇 Additional comments (25)
src_assets/common/assets/web/public/assets/locale/bg.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/cs.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/de.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/en.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/en_GB.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/en_US.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/es.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/fr.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/it.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/ja.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/ko.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/pl.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/pt.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/pt_BR.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/ru.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/sv.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/tr.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/uk.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/zh.json (1)
57-64: LGTM!src_assets/common/assets/web/public/assets/locale/zh_TW.json (1)
57-64: LGTM!src/confighttp.cpp (1)
2963-2963: LGTM!src_assets/common/assets/web/composables/useApps.js (1)
39-42: LGTM!Also applies to: 73-77, 175-241, 615-618, 639-646
src_assets/common/assets/web/services/appService.js (1)
83-103: LGTM!src_assets/common/assets/web/utils/constants.js (1)
85-86: LGTM!src_assets/common/assets/web/views/Apps.vue (1)
148-153: ⚡ Quick win多选模式下需禁用拖拽与单项删除,避免 selectedIndices 基于索引导致误删
在
src_assets/common/assets/web/views/Apps.vue的selectionMode下,列表仍可能发生重排/结构变更(对应draggable :disabled="false"),同时单项删除仍绑定@delete="showDeleteForm(index)"(也影响 218-222、251-253)。若selectedIndices按“索引”存储,重排后索引可能漂移,导致后续批量删除删除到错误应用。建议修复(示例)
- :disabled="false" + :disabled="selectionMode" ... - :disabled="false" + :disabled="selectionMode"- `@delete`="showDeleteForm(index)" + `@delete`="selectionMode ? undefined : showDeleteForm(index)" ... - `@delete`="showDeleteForm(index)" + `@delete`="selectionMode ? undefined : showDeleteForm(index)" ... - `@delete`="showDeleteForm(getOriginalIndex(app, index))" + `@delete`="selectionMode ? undefined : showDeleteForm(getOriginalIndex(app, index))"
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/confighttp.cpp (1)
1028-1048: 💤 Low value文件读取与索引验证逻辑清晰,但建议增加防御性检查
当前逻辑在读取 JSON 后直接调用
get_child("apps"s)(Line 1041),如果配置文件存在但缺少apps数组,会抛出ptree_bad_path异常且未被捕获,导致 fail_guard 返回空响应体{}。虽然这与
deleteApp/saveApp的现有模式一致,但建议增加防御性检查:🛡️ 建议增加防御性检查
} catch (std::exception &e) { BOOST_LOG(warning) << "BatchDeleteApps: "sv << e.what(); outputTree.put("status", "false"); outputTree.put("error", "Invalid File JSON"); return; } + auto apps_node_opt = fileTree.get_child_optional("apps"s); + if (!apps_node_opt) { + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON: missing 'apps' array"); + return; + } + // Validate every index against the single snapshot we just loaded BEFORE // any write, so a partially invalid request fails atomically. - const int apps_count = static_cast<int>(fileTree.get_child("apps"s).size()); + const int apps_count = static_cast<int>(apps_node_opt->size());🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/confighttp.cpp` around lines 1028 - 1048, The code assumes the JSON contains an "apps" child and calls fileTree.get_child("apps"s) which can throw ptree_bad_path; update the logic around pt::ptree fileTree / pt::read_json and where apps_count is computed so you first verify the presence and type of "apps" (e.g. use get_child_optional or count/find) before calling get_child("apps"s), and if missing or not an array set outputTree.put("status","false") and outputTree.put("error","Invalid or missing apps array") and return; adjust the validation loop that uses indices_to_remove and apps_count accordingly to avoid uncaught exceptions from get_child.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src_assets/common/assets/web/views/Apps.vue`:
- Line 146: 在 Apps.vue 中四个复选框(网格/列表 + 搜索/非搜索)当前都使用相同的
aria-label="$t('apps.batch_select_toggle')",导致读屏用户无法区分项;请把每个复选框的 aria-label
改为包含对应应用名(使用 app.name),例如通过将 $t 调用传入 name 参数或拼接 app.name,确保每个复选框的 aria-label
唯一且包含 app.name(定位参考 symbol: aria-label, $t('apps.batch_select_toggle'),
app.name,在文件 Apps.vue 的四个复选框块里同步修改)。
---
Nitpick comments:
In `@src/confighttp.cpp`:
- Around line 1028-1048: The code assumes the JSON contains an "apps" child and
calls fileTree.get_child("apps"s) which can throw ptree_bad_path; update the
logic around pt::ptree fileTree / pt::read_json and where apps_count is computed
so you first verify the presence and type of "apps" (e.g. use get_child_optional
or count/find) before calling get_child("apps"s), and if missing or not an array
set outputTree.put("status","false") and outputTree.put("error","Invalid or
missing apps array") and return; adjust the validation loop that uses
indices_to_remove and apps_count accordingly to avoid uncaught exceptions from
get_child.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a9e20687-b9b1-491f-a92f-d32fc1fb9e4b
📒 Files selected for processing (3)
src/confighttp.cppsrc_assets/common/assets/web/styles/apps.lesssrc_assets/common/assets/web/views/Apps.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Windows
🧰 Additional context used
📓 Path-based instructions (2)
src_assets/**/*.{vue,js,html}
⚙️ CodeRabbit configuration file
src_assets/**/*.{vue,js,html}: 基于 Vue.js 的 Web 配置面板。审查 XSS/CSRF 安全性、 组件设计、状态管理和可访问性。
Files:
src_assets/common/assets/web/views/Apps.vue
src/**/*.{cpp,c,h}
⚙️ CodeRabbit configuration file
src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。
Files:
src/confighttp.cpp
🔇 Additional comments (3)
src/confighttp.cpp (3)
1050-1057: 已修复:空删除请求现在正确返回remaining字段此更改解决了先前评审中指出的"空删除请求成功返回里缺少
remaining字段"问题,确保了与非空路径的响应契约一致。
949-956: LGTM!
981-991: 状态码处理逻辑已修复,符合uploadCover()的约定新增的 fail_guard 逻辑检查
error字段来决定返回 400 还是 200,解决了先前评审中指出的"错误分支返回 200"问题。一个小建议:当前所有错误(包括文件 I/O 失败)都返回 400,而文件写入失败实际上是服务端错误(5xx)。不过这与
uploadCover()的模式一致,属于现有约定。
| role="checkbox" | ||
| tabindex="0" | ||
| :aria-checked="isAppSelected(index)" | ||
| :aria-label="$t('apps.batch_select_toggle')" |
There was a problem hiding this comment.
每个应用复选框应包含应用名,避免读屏标签重复
Line 146、Line 181、Line 224、Line 258 当前都使用同一个 aria-label,读屏时难以区分正在操作哪一个应用。建议把 app.name 拼入标签。
♿ 建议修改
-:aria-label="$t('apps.batch_select_toggle')"
+:aria-label="`${$t('apps.batch_select_toggle')}: ${app.name}`"同样改动请同步到 4 个复选框块(网格/列表 + 搜索/非搜索)。
As per coding guidelines, src_assets/**/*.{vue,js,html} 需要重点审查“可访问性”。
Also applies to: 181-181, 224-224, 258-258
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src_assets/common/assets/web/views/Apps.vue` at line 146, 在 Apps.vue
中四个复选框(网格/列表 + 搜索/非搜索)当前都使用相同的
aria-label="$t('apps.batch_select_toggle')",导致读屏用户无法区分项;请把每个复选框的 aria-label
改为包含对应应用名(使用 app.name),例如通过将 $t 调用传入 name 参数或拼接 app.name,确保每个复选框的 aria-label
唯一且包含 app.name(定位参考 symbol: aria-label, $t('apps.batch_select_toggle'),
app.name,在文件 Apps.vue 的四个复选框块里同步修改)。
No description provided.