-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add rclone #1362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add rclone #1362
Conversation
Warning Rate limit exceeded@elibosley has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 31 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
WalkthroughThis update introduces comprehensive RClone remote management and flash backup functionality across backend and frontend. It adds new GraphQL types, resolvers, and services for RClone operations, implements schema-driven multi-step forms for remote configuration, and provides a Vue UI for managing remotes. Supporting utilities, tests, and build scripts are included, with improved logging and sensitive data handling. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebApp (Vue)
participant Apollo Client
participant API (GraphQL)
participant RClone Service
User->>WebApp (Vue): Open RClone Config or Overview
WebApp (Vue)->>Apollo Client: Query RClone config/remotes
Apollo Client->>API (GraphQL): rclone { configForm, remotes }
API (GraphQL)->>RClone Service: getFormSchemas(), getRemoteDetails()
RClone Service->>API (GraphQL): Return config schema & remotes
API (GraphQL)->>Apollo Client: Return data
Apollo Client->>WebApp (Vue): Provide config schema & remotes
User->>WebApp (Vue): Submit new remote config
WebApp (Vue)->>Apollo Client: Mutation: createRCloneRemote
Apollo Client->>API (GraphQL): rclone { createRCloneRemote(input) }
API (GraphQL)->>RClone Service: createRemote(input)
RClone Service->>API (GraphQL): Return created remote
API (GraphQL)->>Apollo Client: Mutation result
Apollo Client->>WebApp (Vue): Notify of success/failure
Possibly related PRs
Suggested reviewers
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
plugin/plugins/dynamix.unraid.net.plg (1)
178-198
: Consider adding uninstall handling for RcloneThe plugin manifest includes removal operations for other components but lacks specific commands to uninstall Rclone when the plugin is removed.
Consider adding Rclone removal to the existing removal script around line 206-222 or line 284-292:
<FILE Run="/bin/bash" Method="remove"> <INLINE> <![CDATA[ version= # shellcheck disable=SC1091 source /etc/unraid-version +# Remove Rclone binary when plugin is uninstalled +if [ -f /usr/local/bin/rclone ]; then + echo "Removing Rclone binary" + rm -f /usr/local/bin/rclone +fi + # Undo some activation / partner setup source /usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugin/plugins/dynamix.unraid.net.plg
(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: Build Web App
- GitHub Check: Test API
- GitHub Check: Build API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (2)
plugin/plugins/dynamix.unraid.net.plg (2)
34-38
: Good definition of Rclone entities with appropriate valuesThe XML entity declarations for Rclone follow the same pattern as the existing Node.js declarations, with well-structured version, download URL, checksum, filename, and installation directory.
129-132
: Appropriate file entry for downloading RcloneThe file entry correctly references the defined Rclone entities and includes proper SHA256 verification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 21
🔭 Outside diff range comments (1)
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts (1)
1-26
: 🛠️ Refactor suggestionFix import formatting issues
This file has several formatting issues flagged by static analysis.
import { Inject, Logger } from '@nestjs/common'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; - - -import { - FlashBackupStatus, - InitiateFlashBackupInput, -} from './flash-backup.model.js'; +import { RCloneService } from '@app/unraid-api/graph/resolvers/rclone/rclone.service.js'; + +import { + FlashBackupStatus, + InitiateFlashBackupInput, +} from '@app/unraid-api/graph/resolvers/flash-backup/flash-backup.model.js';🧰 Tools
🪛 GitHub Check: Build API
[failure] 4-4:
Replace⏎import·{⏎····FlashBackupStatus,⏎····InitiateFlashBackupInput,⏎}·from·'./flash-backup.model.js';⏎import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service
withimport·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service.js';⏎⏎import·{·FlashBackupStatus,·InitiateFlashBackupInput·}·from·'./flash-backup.model
[failure] 4-4:
More than 1 blank line not allowed
[failure] 5-5:
import statements should have an absolute path🪛 GitHub Check: Test API
[failure] 4-4:
Replace⏎import·{⏎····FlashBackupStatus,⏎····InitiateFlashBackupInput,⏎}·from·'./flash-backup.model.js';⏎import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service
withimport·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service.js';⏎⏎import·{·FlashBackupStatus,·InitiateFlashBackupInput·}·from·'./flash-backup.model
[failure] 4-4:
More than 1 blank line not allowed
[failure] 5-5:
import statements should have an absolute path
🧹 Nitpick comments (15)
web/pages/flashbackup.vue (1)
19-24
: Add error handling and loading statesThe component renders RCloneConfig but doesn't handle potential loading states or errors.
Consider adding error handling and loading indicators:
<template> <div> {{ serverState.connectPluginInstalled }} - <RCloneConfig /> + <div v-if="loading">Loading...</div> + <div v-else-if="error">Error: {{ error }}</div> + <RCloneConfig v-else /> </div> </template>api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts (2)
48-57
: Consider improving error handling with specific error typesThe current error handling pattern catches all errors and returns an empty array. Consider handling specific error types differently.
async drives(@Parent() _parent: RCloneBackupSettings): Promise<RCloneDrive[]> { try { const providers = await this.rcloneApiService.getProviders(); return Object.entries(providers).map(([name, options]) => ({ name, options: options as Record<string, unknown>, })); } catch (error) { - this.logger.error(`Error getting providers: ${error}`); + if (error instanceof Error) { + this.logger.error(`Error getting providers: ${error.message}`, error.stack); + } else { + this.logger.error(`Error getting providers: ${error}`); + } return []; } }
63-67
: Apply consistent error handling across resolver methodsUpdate the remotes method to use the same improved error handling pattern as suggested for the drives method.
async remotes(@Parent() _parent: RCloneBackupSettings): Promise<string[]> { try { return await this.rcloneApiService.listRemotes(); } catch (error) { - this.logger.error(`Error listing remotes: ${error}`); + if (error instanceof Error) { + this.logger.error(`Error listing remotes: ${error.message}`, error.stack); + } else { + this.logger.error(`Error listing remotes: ${error}`); + } return []; } }api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (1)
73-81
: Default settings
Returning default config values is fine. You might consider whether additional defaults (e.g.,type
,name
) are needed for the UI flow.web/components/RClone/RCloneConfig.vue (1)
128-133
: Add loading state for remote creation.The form shows a loading state for fetching the form, but no visual indication during remote creation.
Add a loading indicator when creating a remote:
- <h2 class="text-xl font-medium mb-4">Configure RClone Remote</h2> + <div class="flex justify-between items-center mb-4"> + <h2 class="text-xl font-medium">Configure RClone Remote</h2> + <div v-if="isCreating" class="text-sm text-blue-600">Creating remote...</div> + </div>api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
19-19
: Remove unnecessary empty constructor.The empty constructor serves no purpose and should be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 19-19: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
163-165
: Replaceany
return type with proper type definition.The method uses
any
as the return type, which loses type safety.- async getRemoteConfig(name: string): Promise<any> { + async getRemoteConfig(name: string): Promise<Record<string, unknown>> { return this.callRcloneApi('config/get', { name }); }api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (2)
126-137
: Remove sample data comment.The code contains a block comment with sample data that should be moved to documentation.
-/** - * { - Name: 'jottacloud', - Description: 'Jottacloud', - Prefix: 'jottacloud', - Options: [Array], - CommandHelp: null, - Aliases: null, - Hide: false, - MetadataInfo: [Object] - }, - */
28-37
: Add JSDoc comments to interface definitions.The interface definitions would benefit from JSDoc comments explaining their purpose and usage.
+/** + * Represents the raw provider data returned from the RClone API + * @interface RCloneProviderResponse + */ export interface RCloneProviderResponse { Name: string; Description: string; Prefix: string; Options: RCloneProviderOptionResponse[]; CommandHelp?: string | null; Aliases?: string[] | null; Hide?: boolean; MetadataInfo?: Record<string, unknown>; }api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
1-1
: Consider splitting import statements for better readability.A build check suggests reorganizing the imports to separate type-only imports from concrete imports. For example:
-import { RuleEffect, type SchemaBasedCondition, type JsonSchema, JsonSchema7 } from '@jsonforms/core'; +import type { JsonSchema, SchemaBasedCondition } from '@jsonforms/core'; +import { JsonSchema7, RuleEffect } from '@jsonforms/core';This ensures consistency with recommended TypeScript import style and resolves the lint/build warning.
🧰 Tools
🪛 GitHub Check: Build API
[failure] 1-1:
Replace{·RuleEffect,·type·SchemaBasedCondition,·type·JsonSchema,·JsonSchema7·}·from·'@jsonforms/core';
withtype·{·JsonSchema,·SchemaBasedCondition·}·from·'@jsonforms/core';⏎import·{·JsonSchema7,·RuleEffect·}·from·'@jsonforms/core';⏎
🪛 GitHub Check: Test API
[failure] 1-1:
Replace{·RuleEffect,·type·SchemaBasedCondition,·type·JsonSchema,·JsonSchema7·}·from·'@jsonforms/core';
withtype·{·JsonSchema,·SchemaBasedCondition·}·from·'@jsonforms/core';⏎import·{·JsonSchema7,·RuleEffect·}·from·'@jsonforms/core';⏎
334-337
: Remove redundant case clauses.The
case 'string':
andcase 'text':
fall through to thedefault
block, which returns the same"string"
type. You can safely remove these case labels to simplify the switch statement, as reported by the static analysis.- case 'string': - case 'text': default: return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 334-334: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 335-335: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/generated-schema.graphql (4)
37-42
: Document or sanitize AccessUrlInput.The new
AccessUrlInput
can incorporate validation or documentation clarifying optional vs. mandatory fields (e.g.,ipv4
,ipv6
). Ensure the backend uses it safely and consistently.
573-576
: Document shape of JSON fields.The new JSON fields (
labels
,mounts
, etc.) are flexible, but remind users that shape or structure is not enforced at the schema level. Consider docstrings or custom scalars if strict structure is desired.
652-659
: Add test coverage for FlashBackupStatus.The new
FlashBackupStatus
type includes ajobId
andstatus
. Ensure that you have integration tests or mocks covering how the client interprets these fields.
431-435
: CreateRCloneRemoteInput shape is good.The new input structure neatly organizes config data. Consider adding server-side checks to validate the
config
JSON if certain keys are mandatory for RClone.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (28)
api/.env.development
(1 hunks)api/dev/Unraid.net/myservers.cfg
(1 hunks)api/generated-schema.graphql
(6 hunks)api/src/store/modules/paths.ts
(2 hunks)api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/docker/docker.model.ts
(5 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/resolvers.module.ts
(1 hunks)api/src/unraid-api/rest/rest.controller.ts
(2 hunks)api/src/unraid-api/rest/rest.module.ts
(1 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/graphql/settings.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(22 hunks)web/helpers/create-apollo-client.ts
(1 hunks)web/pages/flashbackup.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (9)
- api/.env.development
- api/dev/Unraid.net/myservers.cfg
- web/helpers/create-apollo-client.ts
- api/src/unraid-api/graph/resolvers/resolvers.module.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- api/src/unraid-api/graph/resolvers/docker/docker.model.ts
- web/components/RClone/graphql/settings.query.ts
- api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
🧰 Additional context used
🧠 Learnings (1)
api/src/unraid-api/auth/auth.service.ts (1)
Learnt from: mdatelle
PR: unraid/api#1219
File: api/src/unraid-api/auth/cookie.strategy.ts:19-20
Timestamp: 2025-03-10T17:24:06.914Z
Learning: In the auth system, CSRF token validation and cookie validation have been unified in the `validateCookiesCasbin()` method in the AuthService class, which takes the entire FastifyRequest object and performs both validations sequentially.
🧬 Code Graph Analysis (5)
api/src/unraid-api/rest/rest.module.ts (2)
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts (1)
Module
(6-11)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts (1)
Module
(9-20)
api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts (1)
Injectable
(17-118)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
Injectable
(11-272)
web/composables/gql/gql.ts (1)
api/src/graphql/generated/client/gql.ts (1)
graphql
(54-56)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (2)
web/composables/gql/graphql.ts (5)
RCloneDrive
(1171-1177)RCloneBackupConfigForm
(1157-1162)RCloneBackupSettings
(1164-1169)RCloneRemote
(1179-1184)CreateRCloneRemoteInput
(431-435)api/src/unraid-api/types/json-forms.ts (1)
DataSlice
(16-16)
web/composables/gql/graphql.ts (2)
api/src/unraid-api/graph/directives/use-permissions.directive.ts (2)
AuthActionVerb
(14-14)AuthPossession
(14-14)web/types/server.ts (1)
Server
(70-121)
🪛 GitHub Check: Build API
api/src/unraid-api/auth/auth.service.ts
[failure] 55-55:
Replace request.method·!==·'GET'·&&·!request.url.startsWith('/graphql/api/rclone-webgui/')·&&·(!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token))
with ⏎················request.method·!==·'GET'·&&⏎················!request.url.startsWith('/graphql/api/rclone-webgui/')·&&⏎················!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token)⏎············
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
[failure] 4-4:
Replace ⏎import·{⏎····FlashBackupStatus,⏎····InitiateFlashBackupInput,⏎}·from·'./flash-backup.model.js';⏎import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service
with import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service.js';⏎⏎import·{·FlashBackupStatus,·InitiateFlashBackupInput·}·from·'./flash-backup.model
[failure] 4-4:
More than 1 blank line not allowed
[failure] 5-5:
import statements should have an absolute path
api/src/store/modules/paths.ts
[failure] 13-13:
Replace ⏎········process.env.PATHS_RCLONE_SOCKET·??·('/var/run/rclone.socket'·as·const)⏎····
with process.env.PATHS_RCLONE_SOCKET·??·('/var/run/rclone.socket'·as·const)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 1-1:
Replace {·RuleEffect,·type·SchemaBasedCondition,·type·JsonSchema,·JsonSchema7·}·from·'@jsonforms/core';
with type·{·JsonSchema,·SchemaBasedCondition·}·from·'@jsonforms/core';⏎import·{·JsonSchema7,·RuleEffect·}·from·'@jsonforms/core';⏎
api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
[failure] 99-99:
Insert ····
[failure] 100-100:
Insert ····
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
[failure] 3-3:
import statements should have an absolute path
[failure] 3-3:
Replace FlashBackupResolver·}·from·'./flash-backup.resolver.js';⏎import·{·RCloneModule·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.module
with RCloneModule·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.module.js';⏎⏎import·{·FlashBackupResolver·}·from·'./flash-backup.resolver
🪛 GitHub Check: Test API
api/src/unraid-api/auth/auth.service.ts
[failure] 55-55:
Replace request.method·!==·'GET'·&&·!request.url.startsWith('/graphql/api/rclone-webgui/')·&&·(!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token))
with ⏎················request.method·!==·'GET'·&&⏎················!request.url.startsWith('/graphql/api/rclone-webgui/')·&&⏎················!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token)⏎············
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
[failure] 4-4:
Replace ⏎import·{⏎····FlashBackupStatus,⏎····InitiateFlashBackupInput,⏎}·from·'./flash-backup.model.js';⏎import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service
with import·{·RCloneService·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.service.js';⏎⏎import·{·FlashBackupStatus,·InitiateFlashBackupInput·}·from·'./flash-backup.model
[failure] 4-4:
More than 1 blank line not allowed
[failure] 5-5:
import statements should have an absolute path
api/src/store/modules/paths.ts
[failure] 13-13:
Replace ⏎········process.env.PATHS_RCLONE_SOCKET·??·('/var/run/rclone.socket'·as·const)⏎····
with process.env.PATHS_RCLONE_SOCKET·??·('/var/run/rclone.socket'·as·const)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 1-1:
Replace {·RuleEffect,·type·SchemaBasedCondition,·type·JsonSchema,·JsonSchema7·}·from·'@jsonforms/core';
with type·{·JsonSchema,·SchemaBasedCondition·}·from·'@jsonforms/core';⏎import·{·JsonSchema7,·RuleEffect·}·from·'@jsonforms/core';⏎
api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
[failure] 99-99:
Insert ····
[failure] 100-100:
Insert ····
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
[failure] 3-3:
import statements should have an absolute path
[failure] 3-3:
Replace FlashBackupResolver·}·from·'./flash-backup.resolver.js';⏎import·{·RCloneModule·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.module
with RCloneModule·}·from·'@app/unraid-api/graph/resolvers/rclone/rclone.module.js';⏎⏎import·{·FlashBackupResolver·}·from·'./flash-backup.resolver
🪛 GitHub Actions: CI - Main (API)
api/src/store/modules/paths.ts
[error] 13-13: Prettier formatting error: Replace multiline expression with a single line as per prettier/prettier rule.
web/components/RClone/RCloneConfig.vue
[error] 14-14: ESLint: 'RCloneRemote' is defined but never used. Allowed unused vars must match /^_/u (@typescript-eslint/no-unused-vars)
🪛 GitHub Check: Build Web App
web/components/RClone/RCloneConfig.vue
[failure] 14-14:
'RCloneRemote' is defined but never used. Allowed unused vars must match /^_/u
[failure] 15-15:
'offlineError' is assigned a value but never used. Allowed unused vars must match /^_/u
[failure] 15-15:
'unraidApiStatus' is assigned a value but never used. Allowed unused vars must match /^_/u
[failure] 31-31:
'result' is assigned a value but never used. Allowed unused vars must match /^_/u
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 19-19: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 334-334: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 335-335: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (30)
api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts (1)
99-100
: Indentation fix improves clarity.The corrected indentation for the closing brace and
return true;
makes the control flow clearer. No logic is changed.🧰 Tools
🪛 GitHub Check: Build API
[failure] 99-99:
Insert····
[failure] 100-100:
Insert····
🪛 GitHub Check: Test API
[failure] 99-99:
Insert····
[failure] 100-100:
Insert····
api/src/store/modules/paths.ts (1)
67-68
: Improved configuration flexibility with environment variablesGood enhancement to prioritize environment variables for log paths, which allows for easier configuration across different environments.
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts (1)
6-10
: Module structure looks goodThe module correctly imports RCloneModule and provides FlashBackupResolver, following NestJS module patterns.
web/pages/flashbackup.vue (2)
14-16
: Hardcoded session cookie may be a security concernSetting a fixed mock session cookie suggests this might be intended for development purposes only.
Is this intended for production use or should there be conditional logic to only set this in development environments?
7-8
: Consider alternatives to dummy storeUsing a dummy server store might indicate incomplete implementation.
Verify whether this is the intended approach for production or if it should be replaced with the actual server store later.
api/src/unraid-api/rest/rest.module.ts (1)
5-7
: Good integration of RCloneModuleThe import and inclusion of RCloneModule in the REST module makes RClone functionality available to the REST API context, which is consistent with the feature being implemented.
api/src/unraid-api/auth/auth.service.ts (1)
55-57
:❓ Verification inconclusive
Security consideration for CSRF bypass
The code bypasses CSRF token validation for the rclone web GUI paths. While this may be necessary for the rclone interface to work properly, ensure this path is adequately protected through other means.
Run this script to check if the rclone-webgui routes have proper permission checks:
🏁 Script executed:
#!/bin/bash # Check for permission checks on rclone-webgui routes rg -A 5 "rclone-webgui" --include="*.ts"Length of output: 482
Below is a revised review comment considering the shell script error. Note that the original command produced an argument error, so please re-run the following revised command to verify that the rclone-webgui routes include the necessary permission checks. If this verification does not yield any output, a manual check of those endpoints is advised.
Security Notice: Verify Permissions for rclone-webgui Endpoints
The CSRF token validation bypass for/graphql/api/rclone-webgui/
is intentional for supporting the rclone web GUI; however, ensure that these paths are protected by robust permission checks or alternative security measures.
- Action Items:
- Re-run this updated script to search for permission checks in TypeScript files:
# # Re-run script with corrected argument ordering to check for permission checks on rclone-webgui routes rg --include='*.ts' -A 5 "rclone-webgui"- If no output is produced, please manually verify that appropriate security mechanisms (e.g., access controls) are in place for these endpoints.
File:
api/src/unraid-api/auth/auth.service.ts
(Lines 55-57)
Below is an updated review comment with additional verification steps. Please re-run the following script to search for nearby permission checks on the rclone-webgui routes in all TypeScript files. If the output still does not show evidence of permission validation, manually confirm that these endpoints are adequately secured (for example, via role-based access or other middleware):
Action Step:
- Run this script to search for "rclone-webgui" content with surrounding context:
#!/bin/bash # Search within TypeScript files for rclone-webgui entries and their nearby code (to locate permission checks) rg --include='*.ts' -n -C 7 "rclone-webgui"Next:
- If there’s no output showing permission validations, please perform a manual review of the endpoints to ensure that alternative security measures are in place.
File:
api/src/unraid-api/auth/auth.service.ts
(Lines 55-57)Security Notice: Verify Permissions for rclone-webgui Endpoints
Security Reminder: Confirm Permissions for rclone-webgui Endpoints
The current bypass of CSRF validation for/graphql/api/rclone-webgui/
is acceptable only if other robust permission checks are enforced. Please manually verify that these endpoints implement proper access controls (such as role-based permissions or equivalent security measures), as the automated check did not yield visible results.File:
api/src/unraid-api/auth/auth.service.ts
(Lines 55-57)🧰 Tools
🪛 GitHub Check: Build API
[failure] 55-55:
Replacerequest.method·!==·'GET'·&&·!request.url.startsWith('/graphql/api/rclone-webgui/')·&&·(!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token))
with⏎················request.method·!==·'GET'·&&⏎················!request.url.startsWith('/graphql/api/rclone-webgui/')·&&⏎················!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token)⏎············
🪛 GitHub Check: Test API
[failure] 55-55:
Replacerequest.method·!==·'GET'·&&·!request.url.startsWith('/graphql/api/rclone-webgui/')·&&·(!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token))
with⏎················request.method·!==·'GET'·&&⏎················!request.url.startsWith('/graphql/api/rclone-webgui/')·&&⏎················!this.validateCsrfToken(request.headers['x-csrf-token']·||·request.query.csrf_token)⏎············
api/src/unraid-api/rest/rest.controller.ts (4)
1-1
: No issue with additional imports
The newly added imports forReq
andAll
are consistent with NestJS best practices.
5-5
: Typed Fastify imports
Usingtype { FastifyReply, FastifyRequest }
improves clarity for request/response types.
9-9
: Usage of 'got'
Employing the 'got' library is acceptable for making HTTP calls. Keep in mind that fetch-based approaches or 'axios' are also common alternatives if you need advanced features.
14-16
: Minor constructor formatting
The constructor signature change looks fine and matches NestJS dependency injection patterns.api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (5)
1-18
: Interface and imports
DefiningRcloneBackupConfigValues
upfront is a good practice for a clean contract. The imports are straightforward and appropriate.
19-29
: Service definition
The class is well annotated with@Injectable()
, and the logger instantiation is clear.
30-45
: Getter properties
Providing get-only properties for_providerTypes
and_providerOptions
enhances clarity.
47-53
: onModuleInit implementation
InvokingloadProviderInfo()
at module initialization and logging errors is a robust approach.
83-89
: Listing configured remotes
Delegating torcloneApiService.listRemotes()
is straightforward and keeps this service lightweight.api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts (6)
1-6
: Imports
All essential imports are properly declared. No issues found.
7-13
: RClone JSON Forms config
ReferencinggetRcloneConfigFormSchema
andgetRcloneConfigSlice
is consistent with the approach for dynamic form builds.
14-46
: Class definition and provider loading
The constructor andloadProviderInfo()
method correctly retrieve and cache providers from the API.
48-65
: buildSettingsSchema
Merging the base slice with provider-specific slices is an efficient way to compose JSON Forms schemas.
70-91
: dataSchema
The method gracefully attempts to load provider info if missing, then returns a valid JSON schema object.
93-118
: uiSchema
Similarly, the UI schema retrieval ensures provider info is loaded first, then merges layout details. This approach is clean and cohesive.web/composables/gql/gql.ts (1)
33-35
:✅ Verification successful
Ensure GraphQL operations match component usage.
The GraphQL operations defined here need to match those imported in the RCloneConfig component.
Verify that the GraphQL query definitions in
~/components/RClone/graphql/settings.query.ts
match these definitions:
🏁 Script executed:
#!/bin/bash # Check if the GraphQL query definitions match fd "settings.query.ts" --exec cat {} \; | grep -A 20 "GET_RCLONE_CONFIG_FORM"Length of output: 446
GraphQL operations consistency confirmed.
- The
GET_RCLONE_CONFIG_FORM
query inweb/composables/gql/gql.ts
matches the definition in~/components/RClone/graphql/settings.query.ts
.- The
LIST_RCLONE_REMOTES
(exported asLIST_REMOTES
) also aligns with the component’s expected usage.- No discrepancies were found in the operations used by the RCloneConfig component.
api/generated-schema.graphql (5)
23-24
: Validate the new Port scalar usage.Defining a custom scalar
Port
is helpful. Make sure to perform appropriate runtime validation in resolvers or at the schema layer so that invalid port values don’t slip through.
407-408
: Check for port collisions.New container port fields
privatePort
andpublicPort
use thePort
scalar. If the system enforces port uniqueness, ensure higher-level logic prevents collisions or handles them gracefully.
555-563
: No concerns about skipCache argument.The optional
skipCache
parameter is straightforward. Just confirm that other calls referencingdocker.containers
ordocker.networks
handle this new argument properly.
605-605
: Confirm backward compatibility.Introducing JSON fields (
configFrom
,containers
,ipam
,labels
,options
) can affect existing clients. Check that older clients gracefully handle these new or updated fields.Also applies to: 607-607, 614-614, 615-615, 617-617
736-745
: InitiateFlashBackupInput usage.
InitiateFlashBackupInput
referencessourcePath
anddestinationPath
. Confirm that user-provided paths are validated or sanitized to avoid unexpected filesystem operations.web/composables/gql/graphql.ts (2)
1858-1861
: Ensure dynamic UI references the RClone config form.The new
GetRCloneConfigFormQuery
supports a dynamic schema-based form. Verify that your frontend code handles empty or malformed configurations gracefully.
1866-1867
: Lightweight approach for listing RClone remotes.Listing remotes via
ListRCloneRemotesQuery
is straightforward. Double-check any caching or incremental updates to reflect changes in real time.
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
web/components/RClone/RCloneConfig.vue (1)
149-151
: 🛠️ Refactor suggestionAdd validations for provider-specific parameters.
Step 2 always returnstrue
, which allows progression even if the selected provider requires additional parameters. Consider adding validation to ensure all required fields for the chosen provider are complete.if (formState.value.configStep === 1) { - return true; + // Example branching for provider validations: + // if (formState.value.type === 'SomeProvider') { + // return isValidSomeProviderParams(formState.value.parameters); + // } + return true; // or false if missing required fieldsapi/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts (1)
129-130
: Preserve original error context.
Consider preserving more details (e.g.,error.stack
) when throwing a new error. This was previously mentioned in an outdated commit.catch (error) { this.logger.error(`Error creating remote: ${error}`); - throw new Error(`Failed to create remote: ${error}`); + if (error instanceof Error) { + this.logger.error(`Error creating remote '${input.name}':`, error); + throw new Error(`Failed to create remote: ${error.message}`); + } + throw new Error('Failed to create remote due to an unknown error'); }web/composables/gql/graphql.ts (1)
431-435
: Check for collisions or duplicates before remote creation
CreateRCloneRemoteInput
is straightforward. However, consider gracefully handling the scenario where a user tries to create a remote with an existing name.Also applies to: 1915-1921
🧹 Nitpick comments (3)
web/components/RClone/RCloneConfig.vue (1)
17-17
: Remove unused store references if not needed.
The variables_offlineError
and_unraidApiStatus
are never utilized. If you have no plans to use them, removing them can help maintain cleaner code.-const { offlineError: _offlineError, unraidApiStatus: _unraidApiStatus } = useUnraidApiStore();
api/generated-schema.graphql (1)
1658-1662
: Optional: Provide stronger typing for parameters.
Theparameters
field is of typeJSON!
. If the structure is partially known, consider introducing a more specific input type to help catch mistakes at compile time.web/composables/gql/graphql.ts (1)
1223-1228
: Consider adding validation for the remote nameThe
RCloneRemote
type looks good, but consider adding validation rules for thename
field (e.g., character restrictions, length limits) to prevent issues when used with the underlying rclone tool.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
api/generated-schema.graphql
(6 hunks)api/package.json
(0 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-config.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/graphql/settings.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(22 hunks)
💤 Files with no reviewable changes (1)
- api/package.json
🚧 Files skipped from review as they are similar to previous changes (4)
- web/components/RClone/graphql/settings.query.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- web/composables/gql/gql.ts
🧰 Additional context used
🪛 GitHub Check: Build Web App
web/components/RClone/RCloneConfig.vue
[failure] 162-162:
'existingRemotes' is assigned a value but never used. Allowed unused vars must match /^_/u
🪛 GitHub Actions: CI - Main (API)
web/components/RClone/RCloneConfig.vue
[error] 162-162: ESLint: 'existingRemotes' is assigned a value but never used. Allowed unused vars must match /^_/u (@typescript-eslint/no-unused-vars)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (16)
web/components/RClone/RCloneConfig.vue (1)
221-230
: Form submission logic looks good!
The logic for submitting the remote configuration is straightforward, and the button is correctly disabled until all requirements are met.api/generated-schema.graphql (1)
1519-1519
: New mutation aligns with rclone logic.
ThecreateRCloneRemote
mutation is consistent with your newly introducedRCloneRemote
type and overall schema design.web/composables/gql/graphql.ts (14)
23-24
: Good addition of the Port scalar typeThe new
Port
scalar type with a clear range constraint (0-65535) improves type safety for TCP port fields throughout the schema.
37-42
: The AccessUrlInput type looks goodThis input type properly mirrors the AccessUrl output type, ensuring consistency between input and output structures.
262-275
: Well-structured authentication enumsThe
AuthActionVerb
andAuthPossession
enums provide clear semantics for permission controls. The documentation comments clarify their intended use.
407-408
: Good conversion to specialized Port scalarUpdating
ContainerPort
fields to use the newPort
scalar improves type safety and semantic clarity.
555-562
: Good addition of cache control for Docker queriesThe
skipCache
parameter gives clients more control over data freshness, which is particularly useful when container states might have changed.
573-577
: Proper standardization to JSON scalarConverting Docker-related fields from the deprecated
JSONObject
to standardJSON
scalar improves schema consistency and follows GraphQL best practices.Also applies to: 605-617
652-658
: Well-structured FlashBackupStatus typeThe new
FlashBackupStatus
type provides a clear response format for backup operations, including the importantjobId
for tracking asynchronous processes.
736-745
: Comprehensive backup input parametersThe
InitiateFlashBackupInput
type covers all necessary parameters for configuring a backup operation, with appropriate optional fields for advanced configuration.
827-828
: Good organization of mutation fieldsAdding
initiateFlashBackup
and organizingparityCheck
as a grouped mutations field improves the schema structure.
1057-1074
: Clear mutation structure for parity check operationsThe
ParityCheckMutations
type properly groups related parity check operations, making the API more intuitive.
1157-1187
: Well-structured RClone configuration typesThe RClone configuration form types provide a flexible approach for dynamic form generation based on provider type and parameters.
1197-1221
: Comprehensive provider option typesThe
RCloneProviderOption
type includes all necessary fields for rendering and validating provider-specific configuration options.
1698-1749
: Good consolidation of VM mutation operationsGrouping VM operations under a single
VmMutations
type improves API organization and maintainability.
1902-1987
: Complete set of RClone GraphQL operationsThe RClone-related operations (queries and mutations) provide a comprehensive API for the frontend to interact with RClone functionality.
I recommend adding a comment in the GraphQL operations to clarify that these operations work with the corresponding backend resolvers in
api/src/unraid-api/graph/resolvers/rclone/
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (10)
web/components/RClone/RCloneConfig.vue (3)
82-94
: 🛠️ Refactor suggestionAdd user feedback for failed remote creation.
The current implementation only logs errors to the console without providing feedback to the user.
const submitForm = async () => { try { await createRemote({ input: { name: formState.value.name, type: formState.value.type, parameters: formState.value.parameters, } as CreateRCloneRemoteInput, }); } catch (error) { console.error('Error creating remote:', error); + if (window.toast) { + window.toast.error('Remote Creation Failed', { + description: error instanceof Error ? error.message : 'Failed to create remote', + }); + } } };
154-157
: 🛠️ Refactor suggestionImprove validation for provider-specific parameters.
Currently, step 2 always allows proceeding regardless of required provider-specific parameters.
if (formState.value.configStep === 1) { // Step 2: Provider-specific validation could go here - return true; + // Check if all required parameters for the selected provider are filled + const parameters = formState.value.parameters || {}; + const requiredKeys = Object.keys(parameters).filter(key => + parameters[key]?.required === true + ); + return requiredKeys.every(key => !!parameters[key]?.value); }
168-172
:⚠️ Potential issueRemove unused computed property.
The
existingRemotes
computed property is defined but never used in the template.-// Existing remotes - safely extract from query result -const existingRemotes = computed(() => { - // Type assertion needed for the codegen types - const result = remotesResult.value as RemotesResult | undefined; - return result?.rcloneBackup?.remotes || []; -});Alternatively, you could use this computed property in the template instead of directly accessing
remotesResult?.rcloneBackup?.remotes
.🧰 Tools
🪛 GitHub Check: Build Web App
[failure] 168-168:
'existingRemotes' is assigned a value but never used. Allowed unused vars must match /^_/uapi/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts (1)
37-39
: Return a structured object instead of an empty castThis is consistent with a previous review comment requesting a more complete default object.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (5)
48-50
: Fix incorrect service name in error logs.The error log references "FlashBackupService" but the class is named "RCloneApiService".
- this.logger.error(`Error initializing FlashBackupService: ${error}`); + this.logger.error(`Error initializing RCloneApiService: ${error}`); this.isInitialized = false;
57-58
: Fix incorrect service name in destruction log.Similar to the initialization error, the log message uses the wrong service name.
- this.logger.log('FlashBackupService module destroyed'); + this.logger.log('RCloneApiService module destroyed');
96-110
: Improve socket checking logic.The current implementation doesn't properly verify if the RClone daemon is running on the specific socket path.
The check for whether the socket is running doesn't actually validate that the daemon is running on the specified socket path. Consider this improved implementation:
private async checkRcloneSocket(socketPath: string): Promise<boolean> { const socketExists = existsSync(socketPath); if (!socketExists) { this.logger.warn(`RClone socket does not exist at: ${socketPath}`); return false; } try { - const socketIsRunning = await execa('rclone', ['about']); - return socketIsRunning.exitCode === 0; + // Test the socket by making a simple API call + const client = got.extend({ + prefixUrl: `http://unix:${socketPath}:/`, + enableUnixSockets: true, + }); + + await client.get('rc/noop'); + this.logger.log(`RClone socket is running at: ${socketPath}`); + return true; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Error checking RClone socket: ${errorMessage}`); return false; } }
165-175
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call.Add validation to ensure the required parameters are provided:
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Combine the required parameters for the create request const params = { name, type, ...parameters, }; this.logger.log(`Creating new remote: ${name} of type: ${type}`); return this.callRcloneApi('config/create', params); }
256-266
: Enhance security for web GUI credentials.The
serveWebGui
method returns username and password in plain text, which could pose a security risk.Consider adding logging for credential access attempts and potentially using temporary credentials with limited lifetime:
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }web/composables/gql/graphql.ts (1)
1915-1921
: Check for collisions or duplicates before remote creation.
CreateRCloneRemoteMutation
is straightforward. However, consider gracefully handling the scenario where a user tries to create a remote with an existing name.This could be implemented on the server side in the service or resolver that handles this mutation.
🧹 Nitpick comments (11)
web/components/RClone/RCloneConfig.vue (2)
242-257
: Consider using the existingRemotes computed property.If keeping the
existingRemotes
computed property, use it here instead of directly accessingremotesResult?.rcloneBackup?.remotes
.- <div v-if="remotesResult?.rcloneBackup?.remotes?.length ?? 0 > 0" class="mt-10"> + <div v-if="existingRemotes.length > 0" class="mt-10"> <h3 class="text-lg font-medium mb-4">Configured Remotes</h3> <div class="space-y-4"> <div - v-for="remote in remotesResult?.rcloneBackup?.remotes ?? []" + v-for="remote in existingRemotes" :key="remote" class="p-4 border border-gray-200 rounded-md" >
241-241
: Add loading state for existing remotes.Consider showing a loading indicator when fetching the list of remotes.
+ <!-- Loading state for remotes --> + <div v-if="remotesResult === undefined" class="mt-10 py-4 text-center text-gray-500"> + Loading configured remotes... + </div> + <!-- Existing remotes list -->api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts (1)
60-78
: Avoid leaking internal error detailsCurrently, the entire error is rethrown, revealing internal details. Consider simplifying the thrown message for security.
- throw new Error(`Failed to create remote: ${error}`); + throw new Error('Failed to create remote. Please check logs for more details.');api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts (1)
234-234
: Remove console.log in production codeUse the Nest logger or remove the statement to maintain consistency.
- console.log('paramProperties', paramProperties); + this.logger.debug(`paramProperties: ${JSON.stringify(paramProperties)}`);api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
174-174
: Use const instead of letThis variable is never reassigned, so converting to const is recommended.
- let configElements: UIElement[] = [ + const configElements: UIElement[] = [🧰 Tools
🪛 GitHub Check: Test API
[failure] 174-174:
'configElements' is never reassigned. Use 'const' instead🪛 GitHub Check: Build API
[failure] 174-174:
'configElements' is never reassigned. Use 'const' instead
268-270
: Clean up redundant case clausesThese case labels are overshadowed by the default clause in each switch. Consider removing them for clarity.
--- a/getJsonSchemaType() case 'bool': return 'boolean'; -case 'string': -case 'text': -case 'password': default: return 'string'; --- a/getJsonFormElementForType() case 'bool': return 'checkbox'; -case 'string': default: return undefined;Also applies to: 308-308
🧰 Tools
🪛 Biome (1.9.4)
[error] 268-268: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 269-269: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 270-270: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
28-28
: Remove this unnecessary constructor.The empty constructor doesn't add any value and can be completely removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 28-28: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/generated-schema.graphql (2)
1496-1497
: Ensure error handling for failure cases in mutations.The mutations for flash backup and RClone remotes should provide clear feedback about failures.
Consider enhancing the return types to include success/failure status fields and error messages, which would provide more explicit error handling than relying solely on GraphQL's error mechanism.
1620-1634
: Consider adding validation options for flash backup input.For a critical operation like flash backup, you might want to add options for validation or confirmation.
Consider adding a field like
validateOnly: Boolean
that would check the backup configuration without actually performing the backup, or aconfirmOverwrite: Boolean
for existing backups.web/composables/gql/graphql.ts (2)
1224-1228
: Consider exposing remote deletion functionality.The code includes functionality to create remotes but there doesn't appear to be a corresponding mutation for deleting them.
Consider adding a
deleteRCloneRemote
mutation to provide a complete API for remote management. This would complement the existing create operation and provide users with a way to clean up unused or misconfigured remotes.
736-745
: Add documentation for flash backup options.The
options
field inInitiateFlashBackupInput
could benefit from clearer documentation.Consider adding examples of valid options in the field description to guide developers on proper usage, especially for complex options like transfer configurations or filtering rules.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (15)
api/generated-schema.graphql
(6 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(2 hunks)unraid-ui/src/forms/renderers.ts
(2 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/graphql/settings.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(22 hunks)web/layouts/default.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- web/layouts/default.vue
🚧 Files skipped from review as they are similar to previous changes (4)
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- web/components/RClone/graphql/settings.query.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- web/composables/gql/gql.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
unraid-ui/src/forms/renderers.ts (1)
unraid-ui/src/forms/renderer-entries.ts (1)
inputFieldEntry
(44-47)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(34-50)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
providerTypes
(33-35)providerOptions
(40-42)api/src/unraid-api/types/json-forms.ts (4)
SettingSlice
(35-43)mergeSettingSlices
(64-64)UIElement
(21-21)DataSlice
(16-16)
🪛 GitHub Actions: CI - Main (API)
unraid-ui/src/forms/renderer-entries.ts
[error] 1-1: Prettier formatting error: Insert import inputFieldRenderer from '@/forms/InputField.vue';
web/components/RClone/RCloneConfig.vue
[error] 5-5: ESLint: 'Input' is defined but never used. Allowed unused vars must match /^_/u (@typescript-eslint/no-unused-vars)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 268-268: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 269-269: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 270-270: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 308-308: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 28-28: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 174-174:
'configElements' is never reassigned. Use 'const' instead
🪛 GitHub Check: Build API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 174-174:
'configElements' is never reassigned. Use 'const' instead
🪛 GitHub Check: Build Web App
web/components/RClone/RCloneConfig.vue
[failure] 5-5:
'Input' is defined but never used. Allowed unused vars must match /^_/u
[failure] 168-168:
'existingRemotes' is assigned a value but never used. Allowed unused vars must match /^_/u
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (14)
unraid-ui/src/forms/renderers.ts (2)
4-4
: Import added for new input field renderer.The addition of
inputFieldEntry
to the imports establishes the foundation for the new input field renderer in the JSONForms system.
22-22
: New input field renderer registered.Good addition of the
inputFieldEntry
to the JSON Forms renderers array, making it available for use in forms throughout the application.unraid-ui/src/forms/renderer-entries.ts (3)
6-6
: Import added for new input field component.The import of
inputFieldRenderer
correctly references the new Vue component that implements the renderer.
14-14
: Added import for string control type checking.The
isStringControl
import is appropriately added to support the tester function for the new renderer.
44-47
: New JSON Forms renderer for string inputs.This implementation correctly defines a new renderer entry with appropriate priority (rank 3) for string controls.
unraid-ui/src/forms/InputField.vue (2)
1-24
: Well-structured input field component with proper JSON Forms integration.The component correctly implements:
- Two-way data binding with computed properties
- Dynamic class application based on UI schema options
- Default value handling
The implementation follows established patterns for JSON Forms renderers.
26-36
: Clean template with appropriate conditional rendering.The template correctly:
- Renders only when the control is visible
- Uses the ControlLayout wrapper for consistent styling
- Passes all necessary props to the Input component
web/components/RClone/RCloneConfig.vue (1)
17-17
: Good use of underscore prefix for unused variables.You've correctly prefixed unused store variables with underscores to avoid linting errors.
api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts (4)
1-19
: Imports and service injections look fineThese imports and initial setup appear valid.
21-36
: Resolver class definitionNo issues found in the decorator or constructor usage.
41-59
: Dynamic form resolverThe configForm logic is straightforward and properly delegates schema creation to the form service.
80-88
: Remotes field resolverThe remotes listing with fallback to an empty array is acceptable for error handling.
api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts (2)
1-233
: Core form service implementationThe logic for loading providers, processing options, and building schemas is coherent and well-organized.
235-305
: Remaining methodsValidation and merging of setting slices appear consistent and thorough.
There was a problem hiding this 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 (11)
unraid-ui/src/forms/MissingRenderer.vue (1)
8-19
: Improve MissingRenderer implementation.The MissingRenderer is a helpful debugging component, but it could be improved:
- The explicit import of
defineProps
is unnecessary in<script setup>
- Props should have proper typing for better type safety
Consider these improvements:
<script setup lang="ts"> -import { defineProps } from 'vue'; +// No imports needed -const props = defineProps({ - // Define props based on what JsonForms passes to renderers - // Typically includes 'schema', 'uischema', 'path', 'data', 'errors', etc. - // Add specific props as needed, or leave it generic for now -}); +const props = defineProps<{ + schema?: Record<string, any>; + uischema?: Record<string, any>; + path?: string; + data?: any; + errors?: any[]; + // Add other JsonForms renderer props as needed +}>(); // eslint-disable-next-line no-console console.warn('Missing renderer used for:', props);unraid-ui/src/components/form/combobox/ComboboxAnchor.vue (1)
18-25
: Consider making the width configurable.The component has a fixed width of 200px, which may limit its flexibility in different layouts.
Consider making the width configurable:
<template> <ComboboxAnchor v-bind="forwarded" - :class="cn('w-[200px]', props.class)" + :class="cn('', props.class)" > <slot /> </ComboboxAnchor> </template>Or add a
width
prop to allow customization:const props = defineProps<ComboboxAnchorProps & { class?: HTMLAttributes['class']; + width?: string; }>(); <template> <ComboboxAnchor v-bind="forwarded" - :class="cn('w-[200px]', props.class)" + :class="cn(props.width ? `w-[${props.width}]` : 'w-[200px]', props.class)" > <slot /> </ComboboxAnchor> </template>unraid-ui/src/forms/ComboBoxField.vue (3)
26-32
: Improve null/undefined handling in handleInput.The function only checks for null values but should also handle undefined cases.
const handleInput = (value: AcceptableValue) => { - if (value === null) return + if (value === null || value === undefined) return const stringValue = String(value) handleChange(control.value.path, stringValue) }
33-40
: Enhance value checking in handleSelect.The function should more robustly check if the value exists before attempting conversion.
const handleSelect = (event: CustomEvent<{ value?: AcceptableValue }>) => { - if (!event.detail.value) return + if (event.detail.value === undefined || event.detail.value === null) return const stringValue = String(event.detail.value) handleChange(control.value.path, stringValue) isOpen.value = false }
41-47
: Optimize watcher to prevent unnecessary updates.The watcher logic can be improved to avoid calling handleChange when not needed.
watch(() => control.value.data, (newValue) => { if (newValue !== selected.value) { handleChange(control.value.path, newValue || '') } })unraid-ui/src/forms/CategorizationAccordionRenderer.vue (1)
33-71
: Consider using consistent API style and handling category visibility changes.The component mixes options API and composition API. It also doesn't handle the case where a selected category becomes invisible.
Consider:
- Converting fully to the composition API for consistency
- Adding a watcher to update the selected index if the current category becomes invisible
- Adding keyboard navigation support for accessibility
setup(props: RendererProps<Layout>) { - return useVanillaLayout(useJsonFormsCategorization(props)); + const { layout, styles, categories } = useVanillaLayout(useJsonFormsCategorization(props)); + const selected = ref(0); + + // Watch for visibility changes in the selected category + watch(() => categories.value[selected.value]?.value.visible, (isVisible) => { + if (isVisible === false) { + // Find the first visible category + const visibleIndex = categories.value.findIndex(c => c.value.visible); + if (visibleIndex >= 0) { + selected.value = visibleIndex; + } + } + }); + + return { layout, styles, categories, selected }; }, - data() { - return { - selected: 0, - }; - },unraid-ui/src/forms/renderer-entries.ts (1)
68-71
: Consider lowering rank for the missing renderer.
This fallback renderer also uses rank 3, which may cause collisions with other specialized renderers. Consider a lower rank if you want it truly as a last resort.unraid-ui/src/forms/SteppedLayout.vue (2)
28-75
: Graceful fallback when step path is invalid.
Returning 0 is safe, but consider logging or otherwise notifying to help diagnose path issues.
86-155
: Well-structured multi-step template.
This layout is intuitive for forms. If needed, you could integrate validation checks before advancing steps for additional robustness.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
363-382
: Refine Redundant Return CasesIn
getJsonSchemaType()
, thecase 'string'
,case 'text'
, andcase 'password'
produce the same result as thedefault
clause. You can remove these cases to streamline the switch statement.switch (rcloneType?.toLowerCase()) { case 'int': return 'integer'; case 'size': case 'number': return 'number'; case 'sizesuffix': case 'duration': return 'string'; case 'bool': return 'boolean'; - case 'string': - case 'text': - case 'password': default: return 'string'; }🧰 Tools
🪛 Biome (1.9.4)
[error] 375-376: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 376-376: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 377-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
400-424
: Eliminate Redundant Cases in UI Format SwitchSimilarly, in
getJsonFormElementForType()
, some cases (e.g.,'string'
) return the same as thedefault
block. For cleaner code, consider removing these redundant case labels.🧰 Tools
🪛 Biome (1.9.4)
[error] 420-421: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)unraid-ui/package.json
(1 hunks)unraid-ui/src/components/form/combobox/Combobox.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxGroup.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxItem.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
(1 hunks)unraid-ui/src/components/form/combobox/index.ts
(1 hunks)unraid-ui/src/forms/CategorizationAccordionRenderer.vue
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/MissingRenderer.vue
(1 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(4 hunks)unraid-ui/src/forms/renderers.ts
(2 hunks)unraid-ui/src/forms/stepped-layout.renderer.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (8)
- unraid-ui/package.json
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- unraid-ui/src/forms/stepped-layout.renderer.ts
- unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
- unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- unraid-ui/src/components/form/combobox/index.ts
- unraid-ui/src/components/form/combobox/ComboboxItem.vue
🚧 Files skipped from review as they are similar to previous changes (2)
- unraid-ui/src/forms/renderers.ts
- api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
🧰 Additional context used
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
[failure] 5-5:
import statements should have an absolute path
🪛 GitHub Check: Build API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
[failure] 5-5:
import statements should have an absolute path
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 375-376: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 376-376: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 377-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 420-421: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
unraid-ui/src/components/form/combobox/Combobox.vue
[failure] 2-2:
Replace ·ComboboxRoot,·type·ComboboxRootEmits,·type·ComboboxRootProps,·useForwardPropsEmits·}·from·'reka-ui'
with ⏎··ComboboxRoot,⏎··useForwardPropsEmits,⏎··type·ComboboxRootEmits,⏎··type·ComboboxRootProps,⏎}·from·'reka-ui';
[failure] 4-4:
Insert ;
[failure] 5-5:
Insert ;
[failure] 7-7:
Insert ;
unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
[failure] 2-2:
Replace type·{·ComboboxAnchorProps·}·from·'reka-ui'⏎import·{·cn·}·from·'@/lib/utils'
with {·cn·}·from·'@/lib/utils';⏎import·type·{·ComboboxAnchorProps·}·from·'reka-ui';
[failure] 4-4:
Insert ;
[failure] 5-5:
Insert ;
[failure] 7-7:
Insert ;
[failure] 10-10:
Insert ;
[failure] 12-12:
Insert ;
🪛 GitHub Actions: CI - Main (API)
unraid-ui/src/components/form/combobox/Combobox.vue
[error] 2-2: Prettier formatting error: Replace ComboboxRoot, type ComboboxRootEmits, type ComboboxRootProps, useForwardPropsEmits } from 'reka-ui'
with a properly formatted multiline import statement.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (28)
unraid-ui/src/components/form/combobox/ComboboxTrigger.vue (1)
1-26
: LGTM: ComboboxTrigger implementation is well-structured.The component properly wraps the ComboboxTrigger from reka-ui, correctly handles class forwarding, and sets an appropriate tabindex for accessibility.
unraid-ui/src/components/form/combobox/ComboboxGroup.vue (2)
1-17
: Well-structured Vue 3 composition API with TypeScript integration.The component correctly extends
ComboboxGroupProps
from reka-ui and adds optional class and heading properties. The computed delegatedProps function properly filters out the class property to avoid duplication in the template.
19-29
: Good implementation of the template with conditional rendering.The template correctly uses the
cn
utility for class composition and conditionally renders the ComboboxLabel when a heading is provided. The component properly forwards all props to the underlying ComboboxGroup component.unraid-ui/src/components/form/combobox/ComboboxList.vue (2)
1-21
: Well-implemented prop and emit forwarding.The component correctly sets default values for position, align, and sideOffset, and uses the useForwardPropsEmits utility to handle prop and emit forwarding. The delegatedProps computation appropriately excludes the class property.
23-34
: Effective portal implementation with proper class composition.The template correctly uses ComboboxPortal to handle positioning and ComboboxViewport for content rendering. The cn utility is used to apply a comprehensive set of styling and animation classes.
unraid-ui/src/forms/ComboBoxField.vue (2)
1-25
: Good JSON Forms integration with combobox component.The component properly integrates with the JSON Forms ecosystem through useJsonFormsControl and effectively defines the Suggestion interface. The suggestions computation correctly handles the optional nature of the control's options.
49-98
: Well-structured template with appropriate styling and error handling.The template handles visibility conditions, properly displays labels and errors, and uses conditional classes for styling. The ComboboxList implementation correctly handles empty states and iterates through suggestions.
unraid-ui/src/forms/renderer-entries.ts (5)
1-4
: Imports look good.
All imported components and utilities appear valid and consistently named.
13-13
: Consistent usage of JSON Forms predicates.
IncludingisCategorization
andisStringControl
aligns well with JSON Forms best practices.Also applies to: 18-18
43-46
: Introducing a combo box renderer is a neat addition.
The rank of 4 keeps it higher than a basic enum but not too high to override more specialized renderers. Looks good.
53-56
: Input field renderer is well-structured.
A rank of 3 ensures it does not overshadow higher-ranked renderers.
73-76
: Accordion-based categorization renderer looks good.
A rank of 5 ensures it takes precedence over the default categorization.unraid-ui/src/forms/SteppedLayout.vue (3)
1-27
: Imports and setup are clean.
Using the injected JSON Forms core and dispatch is a straightforward approach. No issues found.
76-83
: Step state logic is concise.
The default'inactive' | 'active' | 'completed'
states effectively track progress.
158-160
: Scoped style block is straightforward.
No issues or conflicts detected.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (12)
1-8
: Check import path rule at line 5.
Static analysis recommends absolute imports; confirm your project’s conventions to see if'../rclone.model.js'
should become an absolute path.🧰 Tools
🪛 GitHub Check: Test API
[failure] 5-5:
import statements should have an absolute path🪛 GitHub Check: Build API
[failure] 5-5:
import statements should have an absolute path
9-43
: Helper function for mock config is clear.
It correctly transforms raw mock data into a uniform format.
45-48
: Destructuring mock config data is efficient.
No issues found.
50-66
: Test coverage for base config slice is sound.
Verifies presence and absence of expected properties.
69-86
: Verifies schema generation with no provider.
Covers a key edge case thoroughly.
88-106
: Test coverage for empty provider options is good.
Ensures no erroneous categories are created.
108-154
: Categorization test is comprehensive.
Includes standard and advanced categories with visibility rules.
156-180
: Diverse option types tested with "sftp".
Covers string vs. password vs. boolean.
182-194
: Enum/examples coverage for drive scopes is well-handled.
Enum usage and format hint are validated.
196-218
: SizeSuffix/Duration types are tested thoroughly.
Pattern checks confirm correct input formats.
220-235
: Default values for multiple types are verified.
Good demonstration of coverage for string, number, and boolean.
237-239
: Optional direct tests recognized.
Adding more specialized coverage is a good future enhancement.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
1-7
: Overall Good StructureThe file exhibits a well-structured approach to dynamically building JSON Forms schemas and UI layouts. The slicing mechanism for basic, standard, and advanced configuration steps is composed in a clearly organized workflow.
<template> | ||
<div :class="styles.categorization.root"> | ||
<div :class="styles.categorization.category"> | ||
<template | ||
v-for="(category, index) in categories" | ||
:key="`category-${index}`" | ||
> | ||
<div v-if="category.value.visible" @click="selected = index"> | ||
<button | ||
:class="[selected === index ? styles.categorization.selected : '']" | ||
:disabled="!category.value.enabled" | ||
> | ||
<label>{{ category.value.label }}</label> | ||
</button> | ||
</div> | ||
</template> | ||
</div> | ||
|
||
<div :class="styles.categorization.panel"> | ||
<DispatchRenderer | ||
v-if="categories[selected]" | ||
:schema="layout.schema" | ||
:uischema="categories[selected].value.uischema" | ||
:path="layout.path" | ||
:enabled="layout.enabled" | ||
:renderers="layout.renderers" | ||
:cells="layout.cells" | ||
/> | ||
</div> | ||
</div> | ||
</template> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add handling for empty categories case.
The template doesn't handle the scenario where categories array is empty, which could lead to errors when referencing categories[selected].
<div :class="styles.categorization.panel">
<DispatchRenderer
- v-if="categories[selected]"
+ v-if="categories.length > 0 && categories[selected]"
:schema="layout.schema"
:uischema="categories[selected].value.uischema"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
+ <div v-else class="text-sm text-muted-foreground">
+ No categories available
+ </div>
</div>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<template> | |
<div :class="styles.categorization.root"> | |
<div :class="styles.categorization.category"> | |
<template | |
v-for="(category, index) in categories" | |
:key="`category-${index}`" | |
> | |
<div v-if="category.value.visible" @click="selected = index"> | |
<button | |
:class="[selected === index ? styles.categorization.selected : '']" | |
:disabled="!category.value.enabled" | |
> | |
<label>{{ category.value.label }}</label> | |
</button> | |
</div> | |
</template> | |
</div> | |
<div :class="styles.categorization.panel"> | |
<DispatchRenderer | |
v-if="categories[selected]" | |
:schema="layout.schema" | |
:uischema="categories[selected].value.uischema" | |
:path="layout.path" | |
:enabled="layout.enabled" | |
:renderers="layout.renderers" | |
:cells="layout.cells" | |
/> | |
</div> | |
</div> | |
</template> | |
<template> | |
<div :class="styles.categorization.root"> | |
<div :class="styles.categorization.category"> | |
<template | |
v-for="(category, index) in categories" | |
:key="`category-${index}`" | |
> | |
<div v-if="category.value.visible" @click="selected = index"> | |
<button | |
:class="[selected === index ? styles.categorization.selected : '']" | |
:disabled="!category.value.enabled" | |
> | |
<label>{{ category.value.label }}</label> | |
</button> | |
</div> | |
</template> | |
</div> | |
<div :class="styles.categorization.panel"> | |
<DispatchRenderer | |
v-if="categories.length > 0 && categories[selected]" | |
:schema="layout.schema" | |
:uischema="categories[selected].value.uischema" | |
:path="layout.path" | |
:enabled="layout.enabled" | |
:renderers="layout.renderers" | |
:cells="layout.cells" | |
/> | |
<div v-else class="text-sm text-muted-foreground"> | |
No categories available | |
</div> | |
</div> | |
</div> | |
</template> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🔭 Outside diff range comments (1)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php (1)
115-139
: 🛠️ Refactor suggestionClarify usage of the global
$cooldown
WhilecleanupFails
references$cooldown
as a global, consider passing$cooldown
as an argument for better isolation and testability.
🧹 Nitpick comments (20)
api/src/unraid-api/graph/resolvers/docker/docker.service.ts (1)
63-64
: Fix indentation in the try block.The lines inside the try block need proper indentation to match the codebase style. This was flagged by the static analysis tools.
try { - await this.getContainers({ skipCache: true }); - await this.getNetworks({ skipCache: true }); + await this.getContainers({ skipCache: true }); + await this.getNetworks({ skipCache: true }); this.logger.debug('Docker cache warming complete.');🧰 Tools
🪛 GitHub Check: Build API
[failure] 63-63:
Insert····
[failure] 64-64:
Insert····
🪛 GitHub Check: Test API
[failure] 63-63:
Insert····
[failure] 64-64:
Insert····
unraid-ui/src/forms/MissingRenderer.vue (1)
16-23
: Improve visibility of debugging informationThe styles are good, but the debugging information could be more structured for better visibility.
Enhance the styles to make debugging information more readable:
div { border: 1px dashed red; padding: 1em; margin: 1em 0; + background-color: rgba(255, 0, 0, 0.05); +} + +pre { + background-color: #f5f5f5; + padding: 0.5em; + border-radius: 4px; + overflow: auto; + max-height: 200px; }unraid-ui/src/register.ts (2)
71-72
: Improve type safety by avoiding 'any' type assertionUsing
as any
bypasses TypeScript's type checking system. This could lead to runtime errors if the component doesn't match the expected shape.Consider using a more type-safe approach:
// Use appropriate casting for defineCustomElement -customElements.define(elementName, defineCustomElement(component as any)); +customElements.define(elementName, defineCustomElement(component as unknown as ComponentOptions));Where
ComponentOptions
is imported from Vue or defined to match the expected input type ofdefineCustomElement
.
73-74
: Enhance error logging with structured informationThe current error logging is basic. Adding more context would make debugging easier.
Improve error logging with more structured information:
} catch (error) { - console.error(`[register components] Error registering component ${name}:`, error); + console.error( + `[register components] Error registering component ${name}:`, + { + componentName: name, + elementName, + errorMessage: error instanceof Error ? error.message : String(error), + errorStack: error instanceof Error ? error.stack : undefined + } + ); }unraid-ui/src/forms/ComboBoxField.vue (1)
24-25
: Initialize dropdown state based on data availabilityThe dropdown is always initialized as closed, but it might be better to open it automatically when no value is selected.
Consider initializing the dropdown state based on whether data is available:
const selected = computed(() => control.value.data); -const isOpen = ref(false); +const isOpen = ref(!control.value.data); +// Optionally, add a method to toggle the dropdown +const toggleDropdown = () => { + isOpen.value = !isOpen.value; +};web/components/RClone/RCloneConfig.vue (1)
106-112
: Consider adding form validation before submissionThe component doesn't validate form data before submission, which could lead to errors if required fields are missing.
Add form validation logic:
// Set up JSONForms config const jsonFormsConfig = { restrict: false, trim: false, }; const renderers = [...jsonFormsRenderers]; + +// Add form validation +const isFormValid = computed(() => { + // Basic validation + if (!formState.value.name || !formState.value.type) { + return false; + } + + // Additional validation based on step + if (formState.value.configStep === 1) { + // Validate provider-specific parameters if needed + // Return false if validation fails + } + + return true; +});unraid-ui/eslint.config.ts (2)
9-37
: Consider re-enabling 'no-unused-vars' checks.
Turning off this rule hides potential coding oversights. Setting it to 'warn' or 'error' can help catch forgotten variables.
70-82
: Redundant language option.
Defining both'ecmaVersion': 'latest'
and'es2022': true
can be confusing. You might remove'es2022'
to keep things clearer.unraid-ui/src/index.ts (1)
5-5
: Consider potential naming collisions from wildcard exports.
Usingexport *
from@/components
is convenient but might lead to naming collisions or confusion if multiple modules export similarly named symbols.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
3-3
: Unused import.
filter
from 'rxjs' is never used. Consider removing it to keep the code clean.
292-293
: Consider removing console logs or wrapping them in debug mode.
Excessive logs can clutter production output.- console.log('paramProperties', option.Name, paramProperties[option.Name]); + // console.debug('paramProperties', option.Name, paramProperties[option.Name]);
327-347
: Clean up redundant switch cases.
Cases'string'
,'text'
,'password'
lead to the same outcome asdefault
. Removing them reduces clutter:- case 'string': - case 'text': - case 'password': default: return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 340-341: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 341-341: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 342-342: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php (3)
71-83
: Check for file not found or fopen errors
WhilereadFromFile
handles the absence of data, consider more explicit error handling or logging iffopen
fails, as@$text = fread(...)
might mask runtime issues.
96-105
: Validate write operation success
Similar to other file operations, check iffopen
orfwrite
returns errors (e.g., permission issues, full disk) to avoid silent failures.
190-192
: Wrap session start in a helper
Multiple code blocks start sessions. Consider centralizing session logic in a shared helper if used repeatedly across different files/functions.api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php (4)
20-24
: Handle file read edge cases
Whenfopen
fails,$fp
could be null. Consider logging or returning more descriptive info to simplify debugging.
45-47
: Surface write errors
As with reading and appending, handlefopen
orfwrite
errors more explicitly to prevent silent fails.
64-65
: Improve clarity with function docstring
cleanupFails
modifies a file of login failures. A brief docstring or in-code comment clarifying the function’s usage would help maintainers.
139-141
: Duplicate session logic
Again, consider centralizing the session start to avoid maintenance overhead and misalignment across the codebase.api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch (1)
77-83
: SSO snippet inclusion
Includingsso-login.php
adds clarity to the code’s flow. Consider adding comments about how the included file extends or overrides existing login logic to help future readers.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (45)
api/src/__test__/store/modules/paths.test.ts
(1 hunks)api/src/unraid-api/graph/resolvers/docker/docker.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php
(14 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DefaultPageLayout.php
(0 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DefaultPageLayout.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/Notifications.page.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/auth-request.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php
(14 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php
(0 hunks)api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch
(4 hunks)api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch
(1 hunks)unraid-ui/.storybook/main.ts
(1 hunks)unraid-ui/.storybook/tsconfig.json
(0 hunks)unraid-ui/eslint.config.ts
(1 hunks)unraid-ui/package.json
(4 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/dropdown-menu/index.ts
(1 hunks)unraid-ui/src/components/form/combobox/Combobox.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxGroup.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxItem.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
(1 hunks)unraid-ui/src/components/form/combobox/index.ts
(1 hunks)unraid-ui/src/components/index.ts
(0 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/MissingRenderer.vue
(1 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(0 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/forms/vertical-layout.renderer.ts
(0 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/register.ts
(1 hunks)unraid-ui/stories/components/common/DropdownMenu.stories.ts
(1 hunks)unraid-ui/tsconfig.json
(2 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)
💤 Files with no reviewable changes (6)
- unraid-ui/.storybook/tsconfig.json
- api/src/unraid-api/unraid-file-modifier/modifications/test/snapshots/DefaultPageLayout.php.modified.snapshot.php
- unraid-ui/src/forms/vertical-layout.renderer.ts
- unraid-ui/src/components/index.ts
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/DefaultPageLayout.php
- unraid-ui/src/forms/renderer-entries.ts
✅ Files skipped from review due to trivial changes (7)
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/Notifications.page.last-download-time
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/auth-request.php.last-download-time
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/DefaultPageLayout.php.last-download-time
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/.login.php.last-download-time
- unraid-ui/stories/components/common/DropdownMenu.stories.ts
- api/src/test/store/modules/paths.test.ts
- unraid-ui/src/components.ts
🚧 Files skipped from review as they are similar to previous changes (13)
- unraid-ui/src/components/form/combobox/Combobox.vue
- unraid-ui/src/components/form/combobox/ComboboxGroup.vue
- unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
- unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
- unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- unraid-ui/package.json
- unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
- unraid-ui/src/components/form/combobox/ComboboxItem.vue
- unraid-ui/src/components/form/combobox/ComboboxList.vue
- unraid-ui/src/components/form/combobox/index.ts
- unraid-ui/src/forms/SteppedLayout.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
🧰 Additional context used
🧠 Learnings (2)
api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch (3)
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch is used to remove the old jGrowl notification system from Unraid pages, as notifications are handled by a new system implemented on a different page.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:6-20
Timestamp: 2025-01-31T22:01:41.842Z
Learning: The default-page-layout.patch removes the old jGrowl notification system and is complemented by the unraid-toaster component implementation. The new system is added through the DefaultPageLayout modification which inserts the toaster component with proper position configuration based on user preferences.
Learnt from: elibosley
PR: unraid/api#1101
File: api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch:30-54
Timestamp: 2025-01-31T22:01:02.725Z
Learning: The removal of jGrowl notifications from DefaultPageLayout.php is intentional as notifications are now handled on a separate page as part of the architectural design to override existing Unraid pages.
unraid-ui/src/register.ts (1)
Learnt from: pujitm
PR: unraid/api#1075
File: unraid-ui/src/register.ts:15-34
Timestamp: 2025-01-30T19:38:02.478Z
Learning: In the web components registration process for unraid-ui, use a soft-fail approach (logging warnings/errors) instead of throwing errors, to ensure other components can still register successfully even if one component fails.
🧬 Code Graph Analysis (4)
api/src/unraid-api/graph/resolvers/docker/docker.service.ts (1)
api/src/core/pubsub.ts (1)
pubsub
(25-25)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php (2)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php (6)
readFromFile
(20-32)appendToFile
(34-43)writeToFile
(45-54)cleanupFails
(64-88)isValidTimeStamp
(57-62)verifyUsernamePassword
(90-102)plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php (1)
_
(108-108)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php (2)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php (6)
readFromFile
(71-83)appendToFile
(85-94)writeToFile
(96-105)cleanupFails
(115-139)isValidTimeStamp
(108-113)verifyUsernamePassword
(141-153)plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php (1)
_
(108-108)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (4)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(34-50)api/src/unraid-api/types/json-forms.ts (4)
SettingSlice
(35-43)UIElement
(21-21)DataSlice
(16-16)mergeSettingSlices
(64-64)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
providerTypes
(33-35)providerOptions
(40-42)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts (2)
dataSchema
(40-46)uiSchema
(49-55)
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
unraid-ui/src/forms/VerticalLayout.vue
[failure] 24-24:
'console' is not defined
unraid-ui/src/components/common/dropdown-menu/index.ts
[failure] 1-1:
import statements should have an absolute path
unraid-ui/src/forms/ComboBoxField.vue
[failure] 40-40:
'CustomEvent' is not defined
unraid-ui/src/forms/MissingRenderer.vue
[failure] 13-13:
'console' is not defined
🪛 GitHub Check: Build API
api/src/unraid-api/graph/resolvers/docker/docker.service.ts
[failure] 63-63:
Insert ····
[failure] 64-64:
Insert ····
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/docker/docker.service.ts
[failure] 63-63:
Insert ····
[failure] 64-64:
Insert ····
🪛 Biome (1.9.4)
unraid-ui/src/register.ts
[error] 16-16: Don't use 'Function' as a type.
Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.
(lint/complexity/noBannedTypes)
[error] 17-17: Don't use 'Function' as a type.
Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.
(lint/complexity/noBannedTypes)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 340-341: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 341-341: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 342-342: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 385-386: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🪛 GitHub Check: Build Web App
web/components/RClone/RCloneConfig.vue
[failure] 72-72:
'submitForm' is assigned a value but never used. Allowed unused vars must match /^_/u
🪛 GitHub Actions: CI - Main (API)
web/components/RClone/RCloneConfig.vue
[error] 72-72: ESLint: 'submitForm' is assigned a value but never used. Allowed unused vars must match /^_/u (@typescript-eslint/no-unused-vars)
unraid-ui/src/components/common/dropdown-menu/index.ts
[error] 1-1: ESLint: import statements should have an absolute path (no-relative-import-paths/no-relative-import-paths)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (39)
api/src/unraid-api/graph/resolvers/docker/docker.service.ts (1)
62-70
: Improved error handling for Docker cache warming during initialization.The addition of a try-catch block around the cache warming logic is a good improvement. This prevents any exceptions during startup from crashing the application, while still logging any errors that occur.
I also like the addition of the completion log message and publishing app info after cache warming is complete.
🧰 Tools
🪛 GitHub Check: Build API
[failure] 63-63:
Insert····
[failure] 64-64:
Insert····
🪛 GitHub Check: Test API
[failure] 63-63:
Insert····
[failure] 64-64:
Insert····
unraid-ui/tsconfig.json (1)
47-50
: Expanded TypeScript scope to include story filesGood addition of story files to the TypeScript compiler scope. This ensures proper type checking for the Storybook components that support the RClone configuration UI.
unraid-ui/src/forms/VerticalLayout.vue (1)
30-30
: Updated layout structureThe change from grid layout to flex column is appropriate given the removal of label rendering (now handled by a separate LabelRenderer component).
unraid-ui/.storybook/main.ts (1)
25-48
: Improved Storybook configurationGood refactoring of the viteFinal function to avoid directly mutating the input config object. The addition of the tailwind-rem-to-rem dependency to optimizeDeps will improve build performance.
web/components/RClone/RCloneConfig.vue (1)
16-16
: LGTM: Properly prefixed unused variablesGood job prefixing unused variables with underscores to avoid linting errors.
unraid-ui/src/forms/LabelRenderer.vue (1)
32-40
: Well-structured label formatting.
The switch statement clarifies the styling approach for each label format.unraid-ui/src/forms/renderers.ts (1)
85-88
: Potential mismatch with local LabelRenderer.
The summary indicates thatLabelRenderer.vue
was introduced locally, yet this entry usesLabelRenderer
from '@jsonforms/vue-vanilla'. Verify whether you intended to import your localLabelRenderer.vue
instead.Likely an incorrect or invalid review comment.
unraid-ui/eslint.config.ts (2)
39-68
: Vue-specific rules look comprehensive.
These settings enforce consistent naming, version compatibility, and best practices for Vue 3.
83-139
: Well-structured ESLint config.
Separating the configurations by file type makes the linter setup clearer and more maintainable.unraid-ui/src/index.ts (6)
3-3
: No functional changes detected.
7-8
: Consistent wildcard exports.
Continuing the pattern of wildcard exports across modules helps maintain a concise single entry point for your UI library.
11-11
: Maintain consistency in wildcard exports.
This mirrors your approach in the other lines. No issues here.
14-14
: Good practice: default export for Tailwind config.
Exporting your Tailwind config as default can simplify imports and avoid naming collisions. Great job.
16-17
: Centralized composable export.
ExportinguseTeleport
here improves discoverability by gathering key composables in one place.
19-19
: No functional changes detected.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (2)
3-4
: Ensure import path aliases and .js extensions are fully supported.
Double-check that your TS config and bundler handle.js
extensions correctly, especially if you're mixing ESM and TS paths.
1-305
: Robust test coverage.
This test suite thoroughly verifies standard vs. advanced slices, provider filters, and duplicates within RClone options. Great job ensuring variety of scenarios!api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch (5)
5-24
: Removal of jGrowl inopenNotifier()
is consistent with the new system.This change streamlines the notification flow by dropping jGrowl references in favor of a more modern system. Looks good!
33-36
: Removing the navigation user bell aligns with the updated notification approach.Eliminating the DOM elements associated with jGrowl is consistent with the removal of that notification mechanism.
47-58
: Clean removal of jGrowl logic in the second usage block.Everything associated with jGrowl is properly deleted. Good job keeping it concise.
60-64
: Elimination of#bell
references is consistent with the new toaster approach.No more color-coded orb logic. This further ensures the old notification code is fully removed.
79-79
:❓ Verification inconclusive
Verify the new
<uui-toaster>
integration with the rest of the codebase.Ensure all jGrowl references are removed, and confirm the new toaster works as intended.
🏁 Script executed:
#!/bin/bash # Searching the codebase for any lingering jGrowl references and #bell usage. rg '\$\.jGrowl' rg '#bell'Length of output: 5028
New
<uui-toaster>
Integration Verification
- The patch file (
api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch
) shows that the production code now adds the new<uui-toaster>
component and removes the jGrowl calls and the associated#bell
manipulation.- The grep output confirms that jGrowl and
#bell
references have been removed from the patch file and transformation script, which is as expected.- However, several test snapshot files (e.g., in
modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php
) still include jGrowl references. Please verify whether these snapshots should be updated to mirror the new toaster integration or if they’re intentionally left unchanged.api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php (8)
56-61
: Verify session initialization logic carefully
Starting the session solely based on the presence of$_COOKIE['unraid_'.md5($server_name)]
could inadvertently start sessions in undesired scenarios. Confirm that this cookie check is sufficiently restrictive.
85-94
: Ensure atomic appends
Appending to a file withappendToFile
is safe under the lock, but verify that the function gracefully handles edge cases (e.g., no write permissions).
141-153
: Double-check shadow file reliability
verifyUsernamePassword
depends on/usr/bin/getent shadow $username
. Ifgetent
or/etc/shadow
is misconfigured, this will always fail. Might need fallback logic or more explicit error logging.
176-179
: Keep logger usage consistent
The logger call for “Ignoring login attempts” helps with diagnosing repeated failures. Ensure logs remain consistent in naming and structure for easy parsing.
184-186
: New function usage
Switching toverifyUsernamePasswordAndSSO
is consistent with the new SSO approach. Confirm that potential backward compatibility issues are addressed where only password validation was expected.
255-256
: Theme consistency
The dynamic background and color for dark mode looks correct. No concerns here.
340-340
: UI background update
This second background color check for dark mode ensures consistency across sections. Looks good.
372-372
: Minor spacing fix
margin-top: 1rem;
is a small but helpful improvement for a cleaner UI layout.api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php (8)
5-10
: Session initialization based on cookies
Lines 5–10 add logic to parse$_SERVER['HTTP_HOST']
and, if a matching cookie exists, conditionally start a session. Check that no unintended sessions are started for non-root
users or other hosts.
34-36
: Appending concurrency
appendToFile
is correct in acquiring an exclusive lock. Validate that subsequent readers also handle partial writes if a crash or interruption occurs.
76-77
: Validate numeric input
EnsureisValidTimeStamp($value)
covers boundary cases. Trimming or ignoring empty strings in$fails
lines can prevent extraneous entries.
126-128
: Continuity in logging
“Ignoring login attempts” is consistent with the approach in the other file. Good for forensics. No issues here.
133-135
: Reusing the simpler password-only check
Ensure all references have migrated to the new SSO function if SSO is indeed required. This code still usesverifyUsernamePassword
, so confirm consistent usage across the application.
204-205
: Dark mode color scheme
Small UI improvement. Ensure that any text or icons remain legible for visually impaired users on dark backgrounds.
289-289
: Consistent container background
Applying the updated background style is consistent with the existing approach, no concerns.
433-433
: Mobile background fix
Using the same dark background logic ensures mobile devices align with the rest of the theme.api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch (1)
9-57
: ImplementingverifyUsernamePasswordAndSSO
This function elegantly merges password verification with optional SSO token validation. Good approach, but ensure token length threshold (800 chars) is well-documented and tested for edge cases like borderline-sized tokens or malicious input.
unraid-ui/src/register.ts
Outdated
type CustomElementComponent = { | ||
styles?: string[]; | ||
render?: Function; | ||
setup?: Function; | ||
[key: string]: any; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve type safety by replacing generic 'Function' type
Using Function
as a type is too generic and can lead to type safety issues.
Replace with more specific function signatures:
// Type for our simplified Vue component representation
type CustomElementComponent = {
styles?: string[];
- render?: Function;
- setup?: Function;
+ render?: (...args: any[]) => any;
+ setup?: (props: Record<string, any>, ctx: any) => any;
[key: string]: any;
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
type CustomElementComponent = { | |
styles?: string[]; | |
render?: Function; | |
setup?: Function; | |
[key: string]: any; | |
}; | |
type CustomElementComponent = { | |
styles?: string[]; | |
render?: (...args: any[]) => any; | |
setup?: (props: Record<string, any>, ctx: any) => any; | |
[key: string]: any; | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 16-16: Don't use 'Function' as a type.
Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.
(lint/complexity/noBannedTypes)
[error] 17-17: Don't use 'Function' as a type.
Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.
(lint/complexity/noBannedTypes)
// Ensure any HTML is sanitized if it comes from untrusted sources. | ||
// Assuming the documentation link is safe here. | ||
const allowHtml = computed(() => labelFormat.value === 'documentation'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider sanitizing HTML content.
Even though the code comment mentions trust in the source, it’s safer to sanitize the HTML or thoroughly validate its origin to avoid potential XSS vulnerabilities.
...d-api/unraid-file-modifier/modifications/__test__/snapshots/.login.php.modified.snapshot.php
Outdated
Show resolved
Hide resolved
...rc/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php
Outdated
Show resolved
Hide resolved
...rc/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php
Outdated
Show resolved
Hide resolved
api/src/unraid-api/unraid-file-modifier/modifications/patches/sso.patch
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 (1)
unraid-ui/src/composables/useTeleport.ts (1)
7-9
: Remove console.log statements for production.The debug logging statements should be removed before going to production. Consider using a debug flag to conditionally log only in development environments.
- const myModalsComponent = document.querySelector('unraid-modals') || document.querySelector('uui-modals'); - console.log('myModalsComponent', myModalsComponent, 'has shadowRoot', myModalsComponent?.shadowRoot); + const myModalsComponent = document.querySelector('unraid-modals') || document.querySelector('uui-modals'); if (!myModalsComponent?.shadowRoot) return; const potentialTarget = myModalsComponent.shadowRoot.querySelector('#modals'); if (!potentialTarget) return; - console.log('potentialTarget', potentialTarget); - teleportTarget.value = potentialTarget as HTMLElement;Also applies to: 14-15
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
unraid-ui/.gitignore
(1 hunks)unraid-ui/.storybook/main.ts
(2 hunks)unraid-ui/.storybook/preview.ts
(2 hunks)unraid-ui/package.json
(5 hunks)unraid-ui/scripts/build-style.mjs
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/modals/ModalTarget.vue
(1 hunks)unraid-ui/src/components/modals/index.ts
(1 hunks)unraid-ui/src/composables/useTeleport.ts
(1 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/vite-env.d.ts
(1 hunks)unraid-ui/stories/components/form/Combobox.stories.ts
(1 hunks)unraid-ui/stories/components/form/Select.stories.ts
(1 hunks)web/composables/useTeleport.ts
(0 hunks)
💤 Files with no reviewable changes (1)
- web/composables/useTeleport.ts
✅ Files skipped from review due to trivial changes (6)
- unraid-ui/.gitignore
- unraid-ui/stories/components/form/Select.stories.ts
- unraid-ui/src/components/modals/index.ts
- unraid-ui/src/vite-env.d.ts
- unraid-ui/scripts/build-style.mjs
- unraid-ui/stories/components/form/Combobox.stories.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- unraid-ui/.storybook/main.ts
- unraid-ui/package.json
- unraid-ui/src/index.ts
- unraid-ui/src/components/form/combobox/ComboboxList.vue
🧰 Additional context used
🧬 Code Graph Analysis (1)
unraid-ui/.storybook/preview.ts (1)
unraid-ui/src/register.ts (1)
registerAllComponents
(21-77)
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
unraid-ui/src/components.ts
[failure] 21-21:
Insert ⏎
[failure] 21-21:
Newline required at end of file but not found
unraid-ui/src/components/modals/ModalTarget.vue
[failure] 1-1:
Delete ⏎
🪛 GitHub Actions: CI - Main (API)
unraid-ui/src/components.ts
[error] 21-21: Prettier formatting error: Insert newline (prettier/prettier)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
unraid-ui/src/components.ts (1)
1-21
: Good approach for centralizing component exports.This new file centralizes exports from multiple UI component submodules, making imports cleaner throughout the codebase.
🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 21-21:
Insert⏎
[failure] 21-21:
Newline required at end of file but not found🪛 GitHub Actions: CI - Main (API)
[error] 21-21: Prettier formatting error: Insert newline (prettier/prettier)
unraid-ui/src/composables/useTeleport.ts (1)
7-7
: Support for both modal components is good.Adding support for both 'unraid-modals' and 'uui-modals' elements ensures compatibility as the UI components evolve.
unraid-ui/src/components/modals/ModalTarget.vue (1)
1-5
: Simple and effective modal target component.This component provides a clean target for teleporting modal content, which works well with the updated useTeleport composable.
🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 1-1:
Delete⏎
unraid-ui/.storybook/preview.ts (2)
3-7
: Good CSS configuration for Storybook.Using the specific CSS import and providing the shared CSS path to registerAllComponents ensures consistent styling in Storybook.
26-26
: Good use of custom element.Replacing the plain div with the custom uui-modals element provides consistency between the application and Storybook environments.
There was a problem hiding this 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 (6)
unraid-ui/src/components.ts (2)
2-2
: Remove duplicate export of '@/components/brand'.The
@/components/brand
module is exported twice - once on line 2 and again on line 20. Remove one of these duplicate exports.Also applies to: 20-20
20-20
: Add newline at end of file.Add a newline at the end of the file to fix the formatting error reported by Prettier and make the CI pipeline pass.
export * from '@/components/modals'; +
🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 20-20:
Insert⏎
[failure] 20-20:
Newline required at end of file but not found🪛 GitHub Actions: CI - Main (API)
[error] 20-20: Prettier formatting error: Insert newline (prettier/prettier)
unraid-ui/src/forms/ComboBoxField.vue (2)
44-44
:⚠️ Potential issueAdd TypeScript type definition for CustomEvent.
The
CustomEvent
type is not recognized in the TypeScript context, which could lead to compilation errors.import { computed, ref, watch } from 'vue'; +// Add this import to ensure CustomEvent is recognized in TypeScript +type CustomSelectEvent = CustomEvent<{ value?: AcceptableValue }>; // Later in the code: -const handleSelect = (event: CustomEvent<{ value?: AcceptableValue }>) => { +const handleSelect = (event: CustomSelectEvent) => {
44-50
: 🛠️ Refactor suggestionImprove input handling for non-string values.
The current implementation always converts values to strings, which might not be appropriate for all data types.
-const handleSelect = (event: CustomEvent<{ value?: AcceptableValue }>) => { +const handleSelect = (event: CustomEvent<{ value?: AcceptableValue }>) => { if (event.detail.value === undefined || event.detail.value === null) return; - const stringValue = String(event.detail.value); - inputValue.value = stringValue; // Update local input state - handleChange(control.value.path, stringValue); // Update jsonforms state + const value = event.detail.value; + // Preserve the original type if possible but ensure display consistency + inputValue.value = String(value); // Update local input state for display + handleChange(control.value.path, value); // Update jsonforms state with original value isOpen.value = false; };api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
270-280
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call.Apply this diff to add validation:
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Combine the required parameters for the create request const params = { name, type, ...parameters, }; this.logger.log(`Creating new remote: ${name} of type: ${type}`); return this.callRcloneApi('config/create', params); }
361-371
: Enhance security for web GUI credentials.The
serveWebGui
method returns username and password in plain text, which could pose a security risk.Consider adding logging for credential access attempts and potentially using temporary credentials with limited lifetime:
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }
🧹 Nitpick comments (10)
unraid-ui/src/lib/utils.ts (1)
29-53
: Consider converting static-only class to standalone functions.The static analyzer correctly points out that this class only contains static methods. While the class approach provides a nice encapsulation, it would be more aligned with TypeScript best practices to use standalone functions.
-// eslint-disable-next-line @typescript-eslint/no-extraneous-class -export class Markdown { - private static instance = Markdown.create(); +const createMarkedInstance = (...args: Parameters<Marked['use']>) => { + return new Marked(defaultMarkedExtension, ...args); +}; +const markdownInstance = createMarkedInstance(); +/** + * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. + * + * @param markdownContent string of markdown content + * @returns safe, sanitized html content + */ +export const parseMarkdown = async (markdownContent: string): Promise<string> => { + return markdownInstance.parse(markdownContent); +}; - /** - * Creates a `Marked` instance with default MarkedExtension's already added. - * - * - * Default behaviors: - * - Sanitizes html after parsing - * - * - * @param args any number of Marked Extensions - * @returns Marked parser instance - */ - static create(...args: Parameters<Marked['use']>) { - return new Marked(defaultMarkedExtension, ...args); - } - - /** - * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. - * - * - * @param markdownContent string of markdown content - * @returns safe, sanitized html content - */ - static async parse(markdownContent: string): Promise<string> { - return Markdown.instance.parse(markdownContent); - } -}🧰 Tools
🪛 Biome (1.9.4)
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
unraid-ui/src/forms/LabelRenderer.vue (1)
32-47
: Consider adding debounce for markdown parsing.For longer markdown content, parsing on every change could impact performance. Consider adding a debounce mechanism to the watchEffect.
+import { debounce } from 'lodash-es'; // --- Parsed Description --- const parsedDescription = ref<string | null>(null); -watchEffect(async () => { +const parseDescription = debounce(async () => { // console.log('descriptionText', descriptionText.value); // Removed const desc = descriptionText.value; if (desc) { try { parsedDescription.value = await Markdown.parse(desc); // console.log('parsedDescription after parse:', parsedDescription.value); // Removed } catch (error) { console.error('Error parsing markdown in LabelRenderer:', error); // Fallback to plain text if parsing fails parsedDescription.value = desc; } } else { parsedDescription.value = null; } -}); +}, 250); + +watchEffect(() => { + parseDescription(); +});unraid-ui/src/forms/renderers.ts (1)
70-73
: Consider a lower rank for the missing renderer.Since the missing renderer is a fallback, it should have the lowest rank to ensure it only catches controls that other renderers don't handle.
const missingRendererEntry: JsonFormsRendererRegistryEntry = { renderer: MissingRenderer, - tester: rankWith(3, isControl), + tester: rankWith(1, isControl), };api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
44-44
: Remove unnecessary constructor.The empty constructor doesn't serve any purpose and can be removed.
-constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
139-139
: Improve daemon readiness detection.Using a fixed timeout isn't reliable for ensuring the daemon is ready. The daemon might take longer on slower systems or under load.
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Small delay + // Implement exponential backoff with reasonable timeout + let attempts = 0; + const maxAttempts = 10; + const initialDelay = 100; + + while (attempts < maxAttempts) { + const isRunning = await this.checkRcloneSocketRunning(); + if (isRunning) break; + + attempts++; + const delay = initialDelay * Math.pow(1.5, attempts); + await new Promise((resolve) => setTimeout(resolve, delay)); + }api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (5)
281-281
: Remove console.log statement.Production code should not include console.log statements. Use a proper logger instead.
- console.log('paramProperties', option.Name, paramProperties[option.Name]);
329-332
: Remove useless case clauses.These case clauses fall through to the default clause and don't add any value.
- case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 329-330: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 330-330: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 331-331: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
374-375
: Remove useless case clause.This case clause falls through to the default clause and doesn't add any value.
- case 'string': default: // Use combobox for string fields with examples if (examples && examples.length > 0) { return 'combobox'; } return undefined; // Use default InputField (via isStringControl)
🧰 Tools
🪛 Biome (1.9.4)
[error] 374-375: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
402-410
: Remove commented-out code.Commented-out code adds noise and can become outdated. If it's no longer needed, it should be removed.
- // Define the step control property - REMOVED as SteppedLayout uses local state - // const stepControlProperty: Record<string, JsonSchema7> = { - // configStep: { - // type: 'number', - // minimum: 0, - // maximum: 2, // 3 steps: 0, 1, 2 - // default: 0, - // }, - // };
188-189
: Use a logger instead of console.warn.Use a proper logger consistent with the rest of the codebase instead of console.warn.
- console.warn(`Duplicate RClone option name skipped in ${type} slice: ${current.Name}`); + // Consider injecting or importing a logger + // logger.warn(`Duplicate RClone option name skipped in ${type} slice: ${current.Name}`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/types/json-forms.test.ts
(1 hunks)api/src/unraid-api/types/json-forms.ts
(2 hunks)unraid-ui/package.json
(5 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/tooltip/TooltipContent.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/Select.vue
(3 hunks)unraid-ui/src/forms/VerticalLayout.vue
(2 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/lib/utils.ts
(1 hunks)web/pages/flashbackup.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- unraid-ui/src/components/common/tooltip/TooltipContent.vue
🚧 Files skipped from review as they are similar to previous changes (6)
- unraid-ui/src/forms/InputField.vue
- web/pages/flashbackup.vue
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
- unraid-ui/src/index.ts
- unraid-ui/package.json
🧰 Additional context used
🧬 Code Graph Analysis (2)
api/src/unraid-api/types/json-forms.test.ts (1)
api/src/unraid-api/types/json-forms.ts (5)
SettingSlice
(36-44)createEmptySettingSlice
(46-48)mergeSettingSlices
(67-67)UIElement
(22-22)DataSlice
(17-17)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (4)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(34-50)api/src/unraid-api/types/json-forms.ts (4)
SettingSlice
(36-44)UIElement
(22-22)DataSlice
(17-17)mergeSettingSlices
(67-67)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
providerTypes
(42-44)providerOptions
(49-51)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts (2)
dataSchema
(40-46)uiSchema
(49-55)
🪛 Biome (1.9.4)
unraid-ui/src/lib/utils.ts
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 329-330: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 330-330: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 331-331: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 374-375: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
unraid-ui/src/components.ts
[failure] 20-20:
Insert ⏎
[failure] 20-20:
Newline required at end of file but not found
🪛 GitHub Actions: CI - Main (API)
unraid-ui/src/components.ts
[error] 20-20: Prettier formatting error: Insert newline (prettier/prettier)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (14)
unraid-ui/src/components.ts (1)
1-20
: Good organization of component exports.The file provides a well-structured centralization of component exports, grouped logically by component types (common, form, layout, etc.), making it easier to manage and import components throughout the application.
🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 20-20:
Insert⏎
[failure] 20-20:
Newline required at end of file but not found🪛 GitHub Actions: CI - Main (API)
[error] 20-20: Prettier formatting error: Insert newline (prettier/prettier)
api/src/unraid-api/types/json-forms.ts (2)
12-12
: Added lodash-es import for deep property mergingGood choice using lodash's merge function to handle deep merging of nested properties rather than Object.assign.
56-58
: Improved property merging with deep mergeThe switch from shallow to deep object merging is an excellent improvement. This ensures that nested properties in JSON Forms schemas will be properly merged rather than overwritten at the top level.
unraid-ui/src/forms/VerticalLayout.vue (1)
29-29
: Simplified layout using flex columnThe change from a more complex grid layout to a simpler flex column layout makes the component more maintainable while still achieving the vertical layout requirement.
unraid-ui/src/forms/Select.vue (3)
2-2
: Added tooltip component importsGood inclusion of tooltip components to support the new option tooltip feature.
24-29
: Added tooltip support to select optionsNice enhancement to extract tooltip information from UI schema options and map them to the appropriate options.
60-76
: Implemented tooltip UI for select optionsWell-structured conditional rendering that only wraps options with tooltip providers when tooltips are present. The side positioning and offset provide good user experience.
api/src/unraid-api/types/json-forms.test.ts (1)
1-90
: Well-structured tests for mergeSettingSlices functionExcellent test coverage for the mergeSettingSlices function. The tests are comprehensive, covering:
- Empty array of slices
- Single slice merging
- Multiple slices with nested properties
- Slices with only properties or only elements
The test assertions properly validate the deep merging behavior implemented in the json-forms.ts file.
unraid-ui/src/lib/utils.ts (1)
2-3
: Well-implemented HTML sanitization using DOMPurify.The implementation properly uses DOMPurify to sanitize HTML output from markdown parsing, with appropriate restrictions on
style
tags and attributes to prevent XSS vulnerabilities.Also applies to: 10-20
unraid-ui/src/forms/LabelRenderer.vue (2)
37-38
: Good implementation of markdown parsing with proper sanitization.The code now correctly uses the
Markdown.parse
method which includes sanitization via DOMPurify, addressing previous XSS vulnerability concerns.
50-59
: Well-organized styling approach with format-based conditional classes.The computed properties for styling provide clear, format-specific styling options that follow a consistent pattern.
Also applies to: 61-68
unraid-ui/src/forms/renderers.ts (2)
29-33
: Good helper function for specialized schema detection.The
isStringArray
helper provides a clean way to detect and handle string array schemas in JSON Forms.
45-48
: Well-structured renderer registry with clear prioritization.The added renderer entries follow a consistent pattern with appropriate rank prioritization. The label renderer, combobox, and stepped layout are excellent additions to enhance form capabilities.
Also applies to: 80-88, 90-102
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
165-171
: 🛠️ Refactor suggestionAdd early return validation for selectedProvider.
When the selectedProvider is empty or invalid, the method should return early with an appropriate message.
+ // Early validation for selectedProvider + if (!selectedProvider) { + return { + properties: {}, + elements: [], + }; + } + if (!selectedProvider || !providerOptions || providerOptions.length === 0) { return { properties: configProperties, elements: [], // Return empty elements if no provider or options }; }Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🔭 Outside diff range comments (1)
unraid-ui/src/forms/NumberField.vue (1)
45-46
: 🛠️ Refactor suggestionRemove debug/test attributes.
The attributes
blah="true"
and:blah-2="true"
appear to be leftover debug or test properties and should be removed.:class="classOverride" :disabled="!control.enabled" :required="control.required" - blah="true" - :blah-2="true" >
♻️ Duplicate comments (3)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (3)
272-282
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call, which could lead to errors or unexpected behavior.async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Combine the required parameters for the create request const params = { name, type, ...parameters, }; this.logger.log(`Creating new remote: ${name} of type: ${type}`); return this.callRcloneApi('config/create', params); }
344-345
: Enhance parameter security in callRcloneApi.The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
363-373
: Enhance security for web GUI credentials.The
serveWebGui
method returns username and password in plain text, which could pose a security risk.async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }
🧹 Nitpick comments (16)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
44-44
: Remove unnecessary empty constructor.The empty constructor declaration doesn't add any functionality and can be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (7)
69-69
: Fix unnecessary escape character in regex pattern.The
\d
escape is unnecessary in a character class in JavaScript regex.- schema.pattern = '^(off|(\d+([KMGTPE]i?B?)?)+)$'; + schema.pattern = '^(off|(\\d+([KMGTPE]i?B?)?)+)$';🧰 Tools
🪛 GitHub Check: Test API
[failure] 69-69:
Unnecessary escape character: \d🪛 GitHub Check: Build API
[failure] 69-69:
Unnecessary escape character: \d
75-75
: Fix unnecessary escape characters in regex pattern.Several escape characters are unnecessary in this regex pattern.
- schema.pattern = '^(off|(\d+(\.\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; // µs is µs + schema.pattern = '^(off|(\\d+(\\.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; // µs is µs🧰 Tools
🪛 GitHub Check: Test API
[failure] 75-75:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: .
[failure] 75-75:
Unnecessary escape character: \d🪛 GitHub Check: Build API
[failure] 75-75:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: .
[failure] 75-75:
Unnecessary escape character: \d
177-177
: Fix string quote consistency.Use double quotes for strings containing single quotes to avoid escaping.
- description: 'This field should only be visible when the name is not \'hide_me\'', + description: "This field should only be visible when the name is not 'hide_me'",🧰 Tools
🪛 GitHub Check: Test API
[failure] 177-177:
Replace'This·field·should·only·be·visible·when·the·name·is·not·\'hide_me\''
with"This·field·should·only·be·visible·when·the·name·is·not·'hide_me'"
🪛 GitHub Check: Build API
[failure] 177-177:
Replace'This·field·should·only·be·visible·when·the·name·is·not·\'hide_me\''
with"This·field·should·only·be·visible·when·the·name·is·not·'hide_me'"
335-345
: Improve type safety by avoiding 'as any' casting.Using explicit types instead of 'as any' casting would improve type safety.
- if (!configProperties.parameters) { - configProperties.parameters = { type: 'object', properties: {} } as any; - } else if (!(configProperties.parameters as any).properties) { - (configProperties.parameters as any).properties = {}; - } - // Merge the new paramProperties into the existing parameters.properties - (configProperties.parameters as any).properties = { - ...(configProperties.parameters as any).properties, - ...paramProperties, - }; + if (!configProperties.parameters) { + configProperties.parameters = { + type: 'object', + properties: {} as Record<string, JsonSchema7> + }; + } else if (!configProperties.parameters.properties) { + configProperties.parameters.properties = {} as Record<string, JsonSchema7>; + } + // Merge the new paramProperties into the existing parameters.properties + configProperties.parameters.properties = { + ...configProperties.parameters.properties, + ...paramProperties, + };
449-458
: Remove commented out code.Commented out code should be removed if it's no longer needed.
- // Define the step control property - REMOVED as SteppedLayout uses local state - // const stepControlProperty: Record<string, JsonSchema7> = { - // configStep: { - // type: 'number', - // minimum: 0, - // maximum: 2, // 3 steps: 0, 1, 2 - // default: 0, - // }, - // };
376-378
: Remove redundant case clauses with default present.These case clauses are redundant since they fall through to the default case.
- case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 376-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 377-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 378-378: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
421-422
: Remove redundant case clause with default present.This case clause is redundant since it falls through to the default case.
- case 'password': - return 'password'; // Explicit format for password managers etc. - case 'string': default: // Use combobox for string fields with examples if (examples && examples.length > 0) { return 'combobox'; } return undefined; // Use default InputField (via isStringControl)🧰 Tools
🪛 Biome (1.9.4)
[error] 421-422: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
unraid-ui/src/forms/NumberField.vue (1)
49-51
: Fix indentation.The closing tags have inconsistent indentation.
<NumberFieldDecrement v-if="stepperEnabled" /> <NumberFieldInput /> - <NumberFieldIncrement v-if="stepperEnabled" /> - </NumberField> + <NumberFieldIncrement v-if="stepperEnabled" /> + </NumberField>unraid-ui/src/forms/composables/useJsonFormsVisibility.ts (1)
1-27
: Well-structured composable with clear interface definitions.The component effectively encapsulates visibility logic for JSON Forms layouts with proper TypeScript typing and reactive properties.
Remove the commented console.log statement on line 19 as it appears to be debug code.
- // console.log('[useJsonFormsVisibility] isVisible computed. layout.layout.value.visible:', layout.layout.value.visible);
unraid-ui/src/forms/UnraidSettingsLayout.vue (1)
36-47
: Avoid using array index as key – can cause stale DOM nodes.
index
does not survive insertion / deletion of schema elements, leading to Vue re‑using the wrong component instance.
Prefer something deterministic, e.g. the element’sscope
orid
:-<template v-for="(element, index) in elements" :key="index"> +<template v-for="element in elements" :key="element.scope ?? element.type">unraid-ui/src/forms/HorizontalLayout.vue (1)
34-47
: Stability & semantics of keys.Re‑use the recommendation from the previous file—replace
index
with a stable identifier to prevent Vue diffing glitches when the UI‑schema mutates.unraid-ui/src/forms/SteppedLayout.vue (3)
40-42
: Remove debugconsole.log
before shipping.Leaving console noise in production hampers DX and leaks internals to browsers.
- console.log('[SteppedLayout] currentStep computed. core.data.configStep:', core?.data?.configStep);
Repeat for all other logs (lines 61‑63 & 121‑124).
87-97
: Click handler fires even when the step is disabled.
StepperTrigger
’s@click
runs regardless of:disabled
.
Guard inside the handler to avoid jumping to untouched steps:-<StepperTrigger @click="updateStep(index)"> +<StepperTrigger @click="getStepState(index)!=='inactive' && updateStep(index)">
126-137
: Rendering large blocks inside a singleDispatchRenderer
loop can affect performance.If the current step contains many controls, re‑rendering the entire
div
on every keystroke is costly.
Consider splitting each element into its own wrapper component or usingv-memo
(Vue 3.3+) to memoise unchanged renderers.unraid-ui/src/forms/renderers.ts (1)
65-83
: Add explicit tester for string formats (e.g.,password
,Currently every plain string falls to
inputFieldRenderer
.
If you plan specialised controls later, inserting them after this entry will never trigger.
Reserve a lower rank here or introduce finer‑grained testers now to future‑proof the registry.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (35)
api/generated-schema.graphql
(5 hunks)api/src/store/modules/paths.ts
(2 hunks)api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/docker/docker.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/resolvers.module.ts
(3 hunks)api/src/unraid-api/rest/rest.controller.ts
(2 hunks)api/src/unraid-api/rest/rest.module.ts
(1 hunks)api/src/unraid-api/types/json-forms.test.ts
(1 hunks)unraid-ui/src/composables/useTeleport.ts
(1 hunks)unraid-ui/src/forms/ControlLayout.vue
(0 hunks)unraid-ui/src/forms/ControlWrapper.vue
(1 hunks)unraid-ui/src/forms/FormErrors.vue
(1 hunks)unraid-ui/src/forms/HorizontalLayout.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/NumberField.vue
(1 hunks)unraid-ui/src/forms/PreconditionsLabel.vue
(0 hunks)unraid-ui/src/forms/Select.vue
(3 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/StringArrayField.vue
(0 hunks)unraid-ui/src/forms/Switch.vue
(1 hunks)unraid-ui/src/forms/UnraidSettingsLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
(1 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)
💤 Files with no reviewable changes (3)
- unraid-ui/src/forms/StringArrayField.vue
- unraid-ui/src/forms/PreconditionsLabel.vue
- unraid-ui/src/forms/ControlLayout.vue
✅ Files skipped from review due to trivial changes (1)
- api/src/unraid-api/graph/resolvers/resolvers.module.ts
🚧 Files skipped from review as they are similar to previous changes (19)
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
- unraid-ui/src/composables/useTeleport.ts
- api/src/unraid-api/rest/rest.module.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- api/src/unraid-api/types/json-forms.test.ts
- unraid-ui/src/forms/VerticalLayout.vue
- api/src/store/modules/paths.ts
- unraid-ui/src/forms/Select.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- api/src/unraid-api/auth/auth.service.ts
- api/src/unraid-api/graph/resolvers/docker/docker.service.ts
- web/components/RClone/RCloneConfig.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- api/src/unraid-api/rest/rest.controller.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
- api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- api/generated-schema.graphql
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (4)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(34-50)api/src/unraid-api/types/json-forms.ts (4)
SettingSlice
(36-44)UIElement
(22-22)DataSlice
(17-17)mergeSettingSlices
(67-67)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
providerTypes
(34-36)providerOptions
(41-43)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts (2)
dataSchema
(40-46)uiSchema
(49-55)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 376-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 377-377: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 378-378: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 421-422: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 1-1:
Replace Layout,·SchemaBasedCondition,·ControlElement,·LabelElement
with ControlElement,·LabelElement,·Layout,·SchemaBasedCondition
[failure] 69-69:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: .
[failure] 75-75:
Unnecessary escape character: \d
[failure] 109-109:
Insert ⏎···························
[failure] 177-177:
Replace 'This·field·should·only·be·visible·when·the·name·is·not·\'hide_me\''
with "This·field·should·only·be·visible·when·the·name·is·not·'hide_me'"
🪛 GitHub Check: Build API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[failure] 1-1:
Replace Layout,·SchemaBasedCondition,·ControlElement,·LabelElement
with ControlElement,·LabelElement,·Layout,·SchemaBasedCondition
[failure] 69-69:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: \d
[failure] 75-75:
Unnecessary escape character: .
[failure] 75-75:
Unnecessary escape character: \d
[failure] 109-109:
Insert ⏎···························
[failure] 177-177:
Replace 'This·field·should·only·be·visible·when·the·name·is·not·\'hide_me\''
with "This·field·should·only·be·visible·when·the·name·is·not·'hide_me'"
🪛 GitHub Actions: CI - Main (API)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 1-1: Prettier formatting error: Replace Layout, SchemaBasedCondition, ControlElement, LabelElement
with ControlElement, LabelElement, Layout, SchemaBasedCondition
(prettier/prettier)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
unraid-ui/src/forms/NumberField.vue (1)
50-51
:❓ Verification inconclusive
Visibility condition removed with ControlLayout.
Similar to Switch.vue, the NumberField no longer checks
control.visible
before rendering. Ensure this visibility check is handled elsewhere.
🏁 Script executed:
#!/bin/bash # Check where control visibility is now managed # Look for JsonForms renderer registration to see how controls are wrapped echo "Checking JsonForms renderer registration..." rg "registerRenderer" --type tsLength of output: 128
#!/bin/bash # Locate the ControlLayout Vue component file=$(rg -l "ControlLayout" --type vue | head -n1) if [ -z "$file" ]; then echo "ControlLayout.vue not found." exit 1 fi echo "Found ControlLayout at: $file" echo # Show lines where control.visible is referenced echo "Occurrences of control.visible in ControlLayout.vue:" rg -n "control.visible" "$file" || echo " (none)" echo # Extract the <template> block to inspect visibility handling echo "Template section of ControlLayout.vue:" sed -n '1,200p' "$file" | sed -n '/<template/,/<\/template>/!d'
#!/usr/bin/env bash # Locate ControlLayout.vue file=$(rg -l "ControlLayout" --type vue | grep "ControlLayout.vue" | head -n1) if [ -z "$file" ]; then echo "ControlLayout.vue not found." exit 1 fi echo "Found ControlLayout.vue at: $file" echo # Search for control.visible references echo "Occurrences of control.visible in ControlLayout.vue:" rg -n "control\.visible" "$file" || echo " (none)"
Ensure ControlLayout handles
control.visible
before renderingThe NumberField no longer applies
control.visible
itself (similar to Switch.vue). The wrapper component—ControlLayout—must filter out invisible controls. Please confirm that ControlLayout.vue usescontrol.visible
to conditionally render its slot content.• File to check: unraid-ui/src/layouts/ControlLayout.vue
• Verify that the template or render function early‑returns (or v‑if’s) oncontrol.visible === false
unraid-ui/src/forms/FormErrors.vue (1)
1-18
: Clean, reusable error display component.This component is well-structured with appropriate error normalization logic. The conditional rendering prevents empty containers, and the styling is consistent with form validation patterns.
unraid-ui/src/forms/ControlWrapper.vue (1)
1-23
: Good separation of concerns for control rendering.This wrapper component effectively handles visibility logic and error display separately from the actual control UI, creating a clean abstraction layer.
unraid-ui/src/forms/InputField.vue (3)
13-16
: Properly implemented two-way binding with JSONForms.The computed property correctly handles default values and undefined states while maintaining the connection to the JSON Forms data model.
19-27
: Good dynamic configuration based on schema properties.The component intelligently determines input type and styling based on schema properties, enhancing flexibility and reuse.
30-39
: Complete input field implementation with appropriate attributes.All necessary attributes are properly bound to the Input component, including accessibility considerations like required state and placeholder text.
unraid-ui/src/forms/SteppedLayout.vue (1)
52-53
: Potential JSON‑Pointer mismatch when updatingconfigStep
.
Actions.update
expects a JSON‑pointer path (e.g.'/configStep'
) or a dot‑notation path depending on how the data is registered.
If the form is not anchored at the root,'configStep'
will update the wrong location or silently no‑op.
Please double‑check the data structure and adjust the path accordingly.- dispatch(Actions.update('configStep', () => newStep)); + dispatch(Actions.update('/configStep', () => newStep));
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (7)
unraid-ui/src/forms/renderers.ts (2)
36-42
: Preserve component name & props inwithErrorWrapper
Returning an anonymous functional component strips component names (hurting DevTools) and omits prop validation.
Wrap with
defineComponent
to forward all options:-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });
108-110
: Fallback tester rank collides with other rank 3 testersWith identical ranks, the first matching entry wins, meaning
MissingRenderer
might pre-emptLabelRenderer
or custom low-rank controls.Lower the fallback rank to
0
(or even-1
) to guarantee it's chosen only when nothing else applies.- tester: rankWith(3, isControl), + tester: rankWith(0, isControl),api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (4)
272-287
: Add input validation for createRemote method.The method lacks validation for required parameters and name format before making the API call.
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key };
314-327
: Add input validation for startBackup method.Similar to the createRemote method, this lacks validation for required parameters.
async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, };
349-351
: Enhance parameter security in callRcloneApi logging.The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
422-432
: Enhance security for web GUI credentials.The serveWebGui method returns username and password in plain text without any audit logging.
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, }; }api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
387-388
: Remove debug console.log statement.Debug logging statements should be removed from production code.
- console.log('paramProperties', option.Name, paramProperties[option.Name]);
🧹 Nitpick comments (9)
unraid-ui/src/lib/utils.ts (1)
29-54
: Consider converting this static-only class to pure functionsThe
Markdown
class contains only static methods, which is a common anti-pattern in TypeScript/JavaScript. This is also flagged by static analysis. Consider converting this to standalone functions for better maintainability.-export class Markdown { - private static instance = Markdown.create(); - - /** - * Creates a `Marked` instance with default MarkedExtension's already added. - * - * - * Default behaviors: - * - Sanitizes html after parsing - * - * - * @param args any number of Marked Extensions - * @returns Marked parser instance - */ - static create(...args: Parameters<Marked['use']>) { - return new Marked(defaultMarkedExtension, ...args); - } - - /** - * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. - * - * - * @param markdownContent string of markdown content - * @returns safe, sanitized html content - */ - static async parse(markdownContent: string): Promise<string> { - return Markdown.instance.parse(markdownContent); - } -} +// Private singleton instance +const markdownInstance = createMarkdown(); + +/** + * Creates a `Marked` instance with default MarkedExtension's already added. + * + * + * Default behaviors: + * - Sanitizes html after parsing + * + * + * @param args any number of Marked Extensions + * @returns Marked parser instance + */ +export function createMarkdown(...args: Parameters<Marked['use']>) { + return new Marked(defaultMarkedExtension, ...args); +} + +/** + * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. + * + * + * @param markdownContent string of markdown content + * @returns safe, sanitized html content + */ +export async function parseMarkdown(markdownContent: string): Promise<string> { + return markdownInstance.parse(markdownContent); +}🧰 Tools
🪛 Biome (1.9.4)
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
unraid-ui/src/forms/SteppedLayout.vue (3)
36-39
: Split null checks for better error messagesThe current check combines both
jsonforms
anddispatch
in one condition, making it harder to identify which one is missing when an error occurs.-if (!jsonforms || !dispatch) { - throw new Error("'jsonforms' or 'dispatch' context wasn't provided. Are you within JsonForms?"); +if (!jsonforms) { + throw new Error("'jsonforms' context wasn't provided. Are you within JsonForms?"); +} +if (!dispatch) { + throw new Error("'dispatch' context wasn't provided. Are you within JsonForms?"); }
182-184
: Remove empty style block if not neededThe style block is empty with just a comment. If no specific styling is needed, consider removing it entirely.
-<style scoped> -/* Add any specific styling needed */ -</style>
29-34
: Consider enhancing the default submitForm behaviorThe default implementation only logs a warning without providing any user feedback when not properly injected.
const submitForm = inject<() => Promise<void>>('submitForm', () => { console.warn('SteppedLayout: submitForm function not provided'); + // Show a toast or alert to the user + alert('Configuration cannot be submitted: form submission handler not provided'); return Promise.resolve(); // Provide a default no-op function });api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
44-44
: Remove unnecessary empty constructor.The empty constructor doesn't provide any functionality and can be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
378-378
: Use optional chaining for errorBody.error check.Simplify the conditional check using optional chaining syntax.
- if (errorBody && errorBody.error) { + if (errorBody?.error) {🧰 Tools
🪛 Biome (1.9.4)
[error] 378-378: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
435-437
: Remove redundant case clauses that fall through to default.These case clauses are unnecessary since there's a default clause that will handle them.
case 'bool': return 'boolean'; - case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 435-435: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 436-436: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 437-437: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
460-477
: Improve type checking implementation for better maintainability.The current switch statement contains multiple return paths that lead to undefined or the same values. Consider simplifying by grouping related types.
switch (rcloneType?.toLowerCase()) { case 'int': case 'size': - // Schema type 'integer'/'number' is sufficient. - // UI framework should infer NumberField from schema type. - return undefined; case 'sizesuffix': - return undefined; // Use default InputField (via isStringControl) case 'duration': - return undefined; // Use default InputField (via isStringControl) + // For all these types, schema type is sufficient - UI framework will infer the right control + return undefined; case 'bool': // ALWAYS use checkbox/toggle for boolean fields, regardless of examples. // RClone examples ("true"/"false") don't map well to UI boolean controls. return 'toggle'; case 'text': - // Consider 'textarea' format later if needed - return undefined; // Use default InputField (via isStringControl) case 'password': return 'password'; // Explicit format for password managers etc.
479-479
: Remove redundant password case clause.This case clause for 'password' is redundant since the isPassword check on line 456 already handles passwords.
case 'text': // Consider 'textarea' format later if needed return undefined; // Use default InputField (via isStringControl) - case 'password': - return 'password'; // Explicit format for password managers etc. case 'string': default:🧰 Tools
🪛 Biome (1.9.4)
[error] 479-479: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (35)
api/generated-schema.graphql
(5 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)unraid-ui/eslint.config.ts
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/dropdown-menu/index.ts
(1 hunks)unraid-ui/src/components/common/sheet/SheetFooter.vue
(1 hunks)unraid-ui/src/components/modals/ModalTarget.vue
(1 hunks)unraid-ui/src/components/modals/index.ts
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/ControlWrapper.vue
(1 hunks)unraid-ui/src/forms/FormErrors.vue
(1 hunks)unraid-ui/src/forms/HorizontalLayout.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/NumberField.vue
(1 hunks)unraid-ui/src/forms/PreconditionsLabel.vue
(1 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/StringArrayField.vue
(1 hunks)unraid-ui/src/forms/Switch.vue
(1 hunks)unraid-ui/src/forms/UnraidSettingsLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
(1 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/lib/utils.ts
(1 hunks)unraid-ui/src/register.ts
(1 hunks)unraid-ui/src/vite-env.d.ts
(1 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/graphql/settings.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(22 hunks)
✅ Files skipped from review due to trivial changes (2)
- unraid-ui/src/components/common/sheet/SheetFooter.vue
- unraid-ui/src/forms/FormErrors.vue
🚧 Files skipped from review as they are similar to previous changes (26)
- unraid-ui/src/components/modals/index.ts
- unraid-ui/src/forms/Switch.vue
- unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
- unraid-ui/src/forms/NumberField.vue
- unraid-ui/src/vite-env.d.ts
- unraid-ui/src/forms/UnraidSettingsLayout.vue
- unraid-ui/src/components.ts
- unraid-ui/src/forms/PreconditionsLabel.vue
- unraid-ui/src/forms/ControlWrapper.vue
- unraid-ui/src/forms/InputField.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- web/components/RClone/graphql/settings.query.ts
- unraid-ui/src/forms/VerticalLayout.vue
- unraid-ui/src/forms/HorizontalLayout.vue
- unraid-ui/src/forms/ComboBoxField.vue
- unraid-ui/src/register.ts
- unraid-ui/src/forms/StringArrayField.vue
- unraid-ui/src/components/common/dropdown-menu/index.ts
- unraid-ui/eslint.config.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- unraid-ui/src/forms/LabelRenderer.vue
- unraid-ui/src/components/modals/ModalTarget.vue
- web/components/RClone/RCloneConfig.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- web/composables/gql/gql.ts
- api/generated-schema.graphql
🧰 Additional context used
🧠 Learnings (1)
unraid-ui/src/lib/utils.ts (2)
Learnt from: pujitm
PR: unraid/api#963
File: web/helpers/markdown.ts:1-41
Timestamp: 2024-11-19T16:34:44.804Z
Learning: In `web/store/updateOsChangelog.ts`, when custom logic is required, `Markdown.create` is used to obtain a custom `marked` instance. This is the correct and intended usage of the `Markdown` helper in such cases.
Learnt from: elibosley
PR: unraid/api#963
File: web/helpers/markdown.ts:1-13
Timestamp: 2024-11-18T20:52:47.800Z
Learning: All configurations and direct usage of `marked` should be centralized in the `safeParseMarkdown` function in `web/helpers/markdown.ts` to maintain security and consistency.
🧬 Code Graph Analysis (1)
web/composables/gql/graphql.ts (2)
api/src/unraid-api/graph/directives/use-permissions.directive.ts (2)
AuthActionVerb
(14-14)AuthPossession
(14-14)web/types/server.ts (1)
Server
(70-121)
🪛 Biome (1.9.4)
unraid-ui/src/lib/utils.ts
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 378-378: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 435-435: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 436-436: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 437-437: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 479-479: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Build Web App
- GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
unraid-ui/src/lib/utils.ts (1)
10-20
: Well-implemented sanitization in the default Marked extensionThe DOMPurify configuration correctly forbids style tags and attributes, which provides good protection against XSS attacks via styles.
unraid-ui/src/forms/renderers.ts (1)
44-48
: Good validation in theisStringArray
predicateThe function correctly handles edge cases like null/undefined schemas, non-objects, and arrays by checking the schema type before accessing properties.
unraid-ui/src/index.ts (1)
4-18
: Clean export structure improves maintainabilityThe refactored exports simplify the file and make it more maintainable by centralizing component exports and using wildcard exports instead of individual named exports.
unraid-ui/src/forms/SteppedLayout.vue (3)
49-60
: Robust step handling with fallback mechanismsThis implementation handles both object and numeric step formats while ensuring steps remain within valid bounds. The fallback logic and bounds checking provide resilience against invalid inputs.
74-85
: Well-implemented filtering for current step elementsThe current step elements filtering properly checks for the existence of options and step properties, avoiding potential runtime errors on malformed schema elements.
147-159
: Good approach to content re-rendering on step changeUsing a key tied to the current step on line 148 ensures the component tree is properly re-rendered when steps change, avoiding stale UI state.
web/composables/gql/graphql.ts (3)
23-24
: Good use of specialized Port scalar type.Adding a dedicated scalar for TCP port values improves type safety and documentation.
407-408
: Improved type safety by using Port scalar for port fields.Using the specialized Port scalar type instead of generic number improves type safety and clarity.
1157-1195
: Well-structured GraphQL types for RClone integration.The RClone types are comprehensive and properly structured, with clear separation between config forms, settings, drives, and remotes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (7)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (7)
91-91
: Use consistent quotation styleChange double quotes to single quotes to maintain consistency with the codebase style.
-it("should return only standard options when isAdvancedStep is false", () => { +it('should return only standard options when isAdvancedStep is false', () => {🧰 Tools
🪛 GitHub Check: Test API
[failure] 91-91:
Replace"should·return·only·standard·options·when·isAdvancedStep·is·false"
with'should·return·only·standard·options·when·isAdvancedStep·is·false'
🪛 GitHub Check: Build API
[failure] 91-91:
Replace"should·return·only·standard·options·when·isAdvancedStep·is·false"
with'should·return·only·standard·options·when·isAdvancedStep·is·false'
145-145
: Use consistent quotation styleChange double quotes to single quotes to maintain consistency with the codebase style.
-it("should return only advanced options when isAdvancedStep is true", () => { +it('should return only advanced options when isAdvancedStep is true', () => {🧰 Tools
🪛 GitHub Check: Test API
[failure] 145-145:
Replace"should·return·only·advanced·options·when·isAdvancedStep·is·true"
with'should·return·only·advanced·options·when·isAdvancedStep·is·true'
🪛 GitHub Check: Build API
[failure] 145-145:
Replace"should·return·only·advanced·options·when·isAdvancedStep·is·true"
with'should·return·only·advanced·options·when·isAdvancedStep·is·true'
133-134
: Consider using more specific typingUsing
as any[]
loses type safety. Consider creating a more specific type for the elements array.- const elementsArray = verticalLayoutStd.elements as any[]; + // Define a more specific type + type ElementType = UIElement & {scope: string}; + const elementsArray = verticalLayoutStd.elements as ElementType[];
186-187
: Consider using more specific typingUsing
as any[]
loses type safety. Consider creating a more specific type for the elements array.- const elementsArray = verticalLayoutAdv.elements as any[]; + // Define a more specific type + type ElementType = UIElement & {scope: string}; + const elementsArray = verticalLayoutAdv.elements as ElementType[];
247-249
: Fix line break formattingImprove code formatting by fixing the line break position.
- const foundDuplicateElement = verticalLayoutDup.elements.find( - (el: any) => el.scope?.includes('duplicate_opt') - ); + const foundDuplicateElement = verticalLayoutDup.elements.find((el: any) => + el.scope?.includes('duplicate_opt') + );🧰 Tools
🪛 GitHub Check: Test API
[failure] 247-247:
Replace⏎············(el:·any)·=>
with(el:·any)·=>⏎···········
🪛 GitHub Check: Build API
[failure] 247-247:
Replace⏎············(el:·any)·=>
with(el:·any)·=>⏎···········
287-289
: Fix line break formattingImprove code formatting by improving line breaks for readability.
- const alwaysShowEl = verticalLayoutPos.elements.find((el: any) => el.scope.includes('always_show')); - const s3OnlyEl = verticalLayoutPos.elements.find((el: any) => el.scope.includes('s3_only')); - const gdriveOnlyEl = verticalLayoutPos.elements.find((el: any) => el.scope.includes('gdrive_only')); + const alwaysShowEl = verticalLayoutPos.elements.find((el: any) => + el.scope.includes('always_show') + ); + const s3OnlyEl = verticalLayoutPos.elements.find((el: any) => + el.scope.includes('s3_only') + ); + const gdriveOnlyEl = verticalLayoutPos.elements.find((el: any) => + el.scope.includes('gdrive_only') + );🧰 Tools
🪛 GitHub Check: Test API
[failure] 287-287:
Replace·el.scope.includes('always_show')
with⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace·el.scope.includes('gdrive_only')
with⏎············el.scope.includes('gdrive_only')⏎········
🪛 GitHub Check: Build API
[failure] 287-287:
Replace·el.scope.includes('always_show')
with⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace·el.scope.includes('gdrive_only')
with⏎············el.scope.includes('gdrive_only')⏎········
258-264
: Consider extracting a helper function for checking label descriptionsThis logic is complex and could be extracted into a helper function for better readability.
+ // Helper function to check if any label has a specific description + const hasLabelWithDescription = (elements: any[], description: string): boolean => { + return elements.some((el) => + el.elements?.some( + (innerEl: any) => + innerEl.type === 'Label' && innerEl.options?.description === description + ) + ); + }; + - const containsSkipped = verticalLayoutDup.elements.some((el: any) => - el.elements?.some( - (innerEl: any) => - innerEl.type === 'Label' && innerEl.options?.description === 'Skip this one' - ) - ); + const containsSkipped = hasLabelWithDescription(verticalLayoutDup.elements, 'Skip this one');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (2)
api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- api/src/unraid-api/auth/auth.service.ts
🧰 Additional context used
🪛 GitHub Check: Test API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
[failure] 91-91:
Replace "should·return·only·standard·options·when·isAdvancedStep·is·false"
with 'should·return·only·standard·options·when·isAdvancedStep·is·false'
[failure] 145-145:
Replace "should·return·only·advanced·options·when·isAdvancedStep·is·true"
with 'should·return·only·advanced·options·when·isAdvancedStep·is·true'
[failure] 247-247:
Replace ⏎············(el:·any)·=>
with (el:·any)·=>⏎···········
[failure] 287-287:
Replace ·el.scope.includes('always_show')
with ⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace ·el.scope.includes('gdrive_only')
with ⏎············el.scope.includes('gdrive_only')⏎········
🪛 GitHub Check: Build API
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
[failure] 91-91:
Replace "should·return·only·standard·options·when·isAdvancedStep·is·false"
with 'should·return·only·standard·options·when·isAdvancedStep·is·false'
[failure] 145-145:
Replace "should·return·only·advanced·options·when·isAdvancedStep·is·true"
with 'should·return·only·advanced·options·when·isAdvancedStep·is·true'
[failure] 247-247:
Replace ⏎············(el:·any)·=>
with (el:·any)·=>⏎···········
[failure] 287-287:
Replace ·el.scope.includes('always_show')
with ⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace ·el.scope.includes('gdrive_only')
with ⏎············el.scope.includes('gdrive_only')⏎········
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Build Web App
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
1-359
: Overall: Well-structured and comprehensive test suiteThe test suite is well-organized and thoroughly tests the
getProviderConfigSlice
function across various scenarios including:
- Invalid inputs
- Standard vs advanced options
- Duplicate option handling
- Provider-specific filters
- Conditional visibility rules
Good job implementing comprehensive test coverage for this functionality.
🧰 Tools
🪛 GitHub Check: Test API
[failure] 91-91:
Replace"should·return·only·standard·options·when·isAdvancedStep·is·false"
with'should·return·only·standard·options·when·isAdvancedStep·is·false'
[failure] 145-145:
Replace"should·return·only·advanced·options·when·isAdvancedStep·is·true"
with'should·return·only·advanced·options·when·isAdvancedStep·is·true'
[failure] 247-247:
Replace⏎············(el:·any)·=>
with(el:·any)·=>⏎···········
[failure] 287-287:
Replace·el.scope.includes('always_show')
with⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace·el.scope.includes('gdrive_only')
with⏎············el.scope.includes('gdrive_only')⏎········
🪛 GitHub Check: Build API
[failure] 91-91:
Replace"should·return·only·standard·options·when·isAdvancedStep·is·false"
with'should·return·only·standard·options·when·isAdvancedStep·is·false'
[failure] 145-145:
Replace"should·return·only·advanced·options·when·isAdvancedStep·is·true"
with'should·return·only·advanced·options·when·isAdvancedStep·is·true'
[failure] 247-247:
Replace⏎············(el:·any)·=>
with(el:·any)·=>⏎···········
[failure] 287-287:
Replace·el.scope.includes('always_show')
with⏎············el.scope.includes('always_show')⏎········
[failure] 289-289:
Replace·el.scope.includes('gdrive_only')
with⏎············el.scope.includes('gdrive_only')⏎········
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
Show resolved
Hide resolved
There was a problem hiding this 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
♻️ Duplicate comments (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
362-362
:⚠️ Potential issueTODOs are still in place for additional tests
The file contains a comment indicating "More tests will be added here..." but implementation is missing. This was highlighted in a previous review comment.
Please implement the additional test cases that were previously requested:
- Tests for combining multiple provider filters (e.g., apply
filter: ['aws', 'gcp']
together)- Edge cases in provider option structures (missing or extra fields, unexpected data types)
- Error handling scenarios (invalid provider IDs, malformed configs)
🧹 Nitpick comments (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
32-36
: Consider enhancing the interface definitionThe RawProviderConfigEntry interface could benefit from being more complete to better match the actual structure of the imported config.
interface RawProviderConfigEntry { Name: string; Options: RCloneProviderOptionResponse[]; - // Add other properties from config.ts if needed, though Name and Options are primary + Description?: string; + Prefix?: string; + DefaultParent?: string; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (3)
api/src/unraid-api/types/json-forms.ts (1)
UIElement
(22-22)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(35-51)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
getProviderConfigSlice
(220-417)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Build Web App
- GitHub Check: Test API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (7)
218-265
: Good test for duplicate options handlingThis test effectively verifies that duplicate option names are properly handled by keeping the first occurrence and discarding subsequent ones, which is critical for proper form generation.
267-314
: Well-constructed test for provider filteringThe test properly verifies that SHOW rules are correctly generated for provider-specific options, including space handling in provider names like 'google drive'.
316-360
: Thorough test for negated provider filtersThis test effectively verifies the handling of negative provider filters, including the proper construction of schema conditions with the
not
operator.
198-216
: Effective validation for empty advanced optionsGood edge case testing to ensure proper behavior when no advanced options exist for a provider.
11-26
: Well-structured placeholder UIElement typeThe placeholder UIElement type is properly defined with all necessary properties to support the testing requirements, including type assertions for schema-based conditions.
91-143
: Comprehensive test for standard options filteringThis test thoroughly validates that standard options are correctly filtered and formatted in the output schema and UI elements.
145-196
: Thorough validation of advanced options filteringThe test effectively mirrors the standard options test but for advanced options, providing good test coverage for this filtering logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (4)
211-214
: Consider using a logger instead of console.warnDirect console methods are generally avoided in production server code. Consider using a structured logger instead.
- console.warn( - `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` - ); + // Use a logger instance instead + this.logger.warn( + `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` + );
347-350
: Avoid usingany
typeUsing
as any
bypasses TypeScript's type checking. Consider defining a proper interface for the configuration properties structure.- configProperties.parameters = { type: 'object', properties: {} } as any; + // Define a proper interface for the configuration properties + configProperties.parameters = { + type: 'object', + properties: {} + } as JsonSchema7;
374-394
: Simplify switch statement by combining casesThe static analysis tool correctly identified useless case clauses. These cases can be combined since they result in the same outcome.
function getJsonSchemaType(rcloneType: string): string { switch (rcloneType?.toLowerCase()) { case 'int': return 'integer'; // Use 'integer' for whole numbers case 'size': // Assuming 'size' might imply large numbers, but 'number' is safer if decimals possible case 'number': // If rclone explicitly uses 'number' return 'number'; case 'sizesuffix': case 'duration': // Represent these as strings, validation handled by pattern/format return 'string'; case 'bool': return 'boolean'; - case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string'; } }🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
396-439
: Similarly simplify the getJsonFormElementForType functionThis function also has useless case clauses that can be combined or removed.
function getJsonFormElementForType({ rcloneType = '', examples = null, isPassword = false, }: { rcloneType?: string; examples?: string[] | null; isPassword?: boolean; }): string | undefined { if (isPassword) { return 'password'; } switch (rcloneType?.toLowerCase()) { case 'int': case 'size': // Schema type 'integer'/'number' is sufficient. // UI framework should infer NumberField from schema type. return undefined; case 'sizesuffix': return undefined; // Use default InputField (via isStringControl) case 'duration': return undefined; // Use default InputField (via isStringControl) case 'bool': // ALWAYS use checkbox/toggle for boolean fields, regardless of examples. // RClone examples ("true"/"false") don't map well to UI boolean controls. return 'toggle'; case 'text': // Consider 'textarea' format later if needed return undefined; // Use default InputField (via isStringControl) case 'password': return 'password'; // Explicit format for password managers etc. - case 'string': default: // Use combobox for string fields with examples if (examples && examples.length > 0) { return 'combobox'; } return undefined; // Use default InputField (via isStringControl) } }
🧰 Tools
🪛 Biome (1.9.4)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (4)
.cursor/rules/no-comments.mdc
(1 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
(7 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/utils/form-utils.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- .cursor/rules/no-comments.mdc
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts (1)
api/src/unraid-api/graph/utils/form-utils.ts (1)
createLabeledControl
(6-46)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
api/src/unraid-api/graph/utils/form-utils.ts (1)
1-46
: Well-structured utility function for form controlsThis utility function creates a standardized layout object containing a label and control element, both scoped to the same path. It significantly reduces boilerplate in form definitions while ensuring consistency.
The TypeScript types are comprehensive, and the function handles optional parameters appropriately. The conditional rule addition is clean and maintainable.
api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts (7)
36-36
: Import of new utility functionGood addition of the
createLabeledControl
import, which will be used to refactor form controls throughout this file.
349-354
: Refactored remote access control using utility functionThe remote access type control has been successfully refactored to use the new utility function, maintaining the same functionality while reducing boilerplate.
354-368
: Refactored forward type control with rule preservationThe forward type control refactoring correctly preserves the conditional rule logic that disables the control when access type is set to disabled.
368-393
: Refactored port control with complex visibility ruleThe port control refactoring maintains the complex visibility rule logic while simplifying the component structure. The number formatting options are properly preserved.
440-448
: Refactored sandbox toggle with conditional descriptionThe sandbox toggle refactoring correctly includes the conditional description that only appears when sandbox is enabled. The toggle option is preserved in the control options.
490-501
: Refactored extra origins input with improved optionsThe extra origins input has been refactored to use the utility function while maintaining the array format and placeholder behavior.
521-531
: Refactored SSO users input with security enhancementThe SSO users input refactoring correctly implements the utility function while adding
rel="noopener noreferrer"
to the external link, which is an important security improvement for target="_blank" links.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
89-167
: Well-structured basic configuration sliceThe basic configuration slice is effectively implemented using the new utility function. The form includes essential fields with appropriate validation and descriptions.
169-369
: Comprehensive provider configuration with dynamic rulesThe provider configuration slice handles complex scenarios including:
- Filtering based on advanced/standard options
- Ensuring option name uniqueness
- Dynamic visibility rules based on provider types
- Appropriate mapping of RClone types to form controls
The function appropriately uses the new
createLabeledControl
utility for consistency.
441-564
: Well-structured multi-step form generatorThe
buildRcloneConfigSchema
function effectively assembles the complete form schema, handling conditional steps based on the selected provider and advanced option flag. The resulting structure provides a clean user experience with a title, description, and appropriate step navigation.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
Outdated
Show resolved
Hide resolved
c4afb6c
to
64252c9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (9)
unraid-ui/src/forms/renderers.ts (2)
108-110
: Fallback tester rank collides with other rank 3 testers.With identical ranks, the first matching entry wins, meaning
MissingRenderer
might pre-emptLabelRenderer
or custom low-rank controls.Lower the fallback rank to
0
(or even-1
) to guarantee it's chosen only when nothing else applies.- tester: rankWith(3, isControl), // Adjusted rank to ensure it's tested after more specific controls + tester: rankWith(0, isControl), // Lower rank ensures it's only used as a last resort
36-42
: 🛠️ Refactor suggestionPreserve component name & props in
withErrorWrapper
.This implementation returns an anonymous functional component that strips component names and loses prop validation. This can make debugging harder in DevTools.
Consider using
defineComponent
to preserve component options:-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
70-80
:⚠️ Potential issueFix regex patterns in validation rules.
The regular expression patterns for SizeSuffix and Duration validation use incorrect syntax. The
d+
should be\\d+
to match digits.- schema.pattern = '^(off|(d+([KMGTPE]i?B?)?)+)$'; + schema.pattern = '^(off|(\\d+([KMGTPE]i?B?)?)+)$'; - schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (4)
272-287
: 🛠️ Refactor suggestionAdd input validation for createRemote method.
The method doesn't validate its inputs before making the API call, which could lead to errors or security issues.
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key };
314-327
: 🛠️ Refactor suggestionAdd input validation for startBackup method.
Similar to the
createRemote
method, this method doesn't validate its inputs before making the API call.async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, };
349-350
: 🛠️ Refactor suggestionEnhance parameter security in callRcloneApi logging.
The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
422-432
: 🛠️ Refactor suggestionEnhance security for web GUI credentials.
The method returns username and password in plain text, which could pose a security risk.
Consider adding logging for credential access attempts and potentially using temporary credentials with limited lifetime:
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
362-362
: Add more test coverage as indicated by TODOThis TODO comment indicates incomplete test coverage. Based on the previous review, you should implement additional test cases covering:
- Combining multiple provider filters
- Edge cases in provider option structures (missing fields, unexpected data types)
- Error handling scenarios (invalid provider IDs, malformed configs)
web/composables/gql/graphql.ts (1)
1989-2006
: Implement error handling for duplicate remote namesWhen implementing the client code that calls
CreateRCloneRemoteMutation
, remember to add error handling for the case where a user attempts to create a remote with a name that already exists.What happens when you try to create an RClone remote with a name that already exists?
🧹 Nitpick comments (5)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
212-214
: Consider using a structured logging approach for duplicate options.The current console.warn approach works but isn't consistent with the logger-based approach used elsewhere.
- console.warn( - `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` - ); + // Use a logger instance for consistent logging + // this.logger.warn(`Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}`); + // Or silently skip duplicates without logging in production
284-285
: Remove commented-out console.log statement.Debug statements should be removed from production code.
- // console.log('Result was: ', result); // Result is usually empty on success, potentially remove
387-388
: Simplify conditional expression with optional chaining.The current condition can be simplified using optional chaining for better readability.
- if (errorBody && errorBody.error) { + if (errorBody?.error) {🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
unraid-ui/src/lib/utils.ts (1)
29-54
: Consider using plain functions instead of a static-only class.The
Markdown
class only contains static methods, which could be simpler as standalone functions.-export class Markdown { - private static instance = Markdown.create(); - - /** - * Creates a `Marked` instance with default MarkedExtension's already added. - * - * - * Default behaviors: - * - Sanitizes html after parsing - * - * - * @param args any number of Marked Extensions - * @returns Marked parser instance - */ - static create(...args: Parameters<Marked['use']>) { - return new Marked(defaultMarkedExtension, ...args); - } - - /** - * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. - * - * - * @param markdownContent string of markdown content - * @returns safe, sanitized html content - */ - static async parse(markdownContent: string): Promise<string> { - return Markdown.instance.parse(markdownContent); - } -} +const createMarkedInstance = (...args: Parameters<Marked['use']>) => { + return new Marked(defaultMarkedExtension, ...args); +}; + +// Singleton instance for general use +const defaultMarkedInstance = createMarkedInstance(); + +/** + * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. + * + * @param markdownContent string of markdown content + * @returns safe, sanitized html content + */ +export async function parseMarkdown(markdownContent: string): Promise<string> { + return defaultMarkedInstance.parse(markdownContent); +} + +// Export the creation function for cases needing custom instances +export { createMarkedInstance };🧰 Tools
🪛 Biome (1.9.4)
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
44-44
: Remove unnecessary constructor.This empty constructor can be removed as it provides no functionality beyond the default.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (2)
api/src/__test__/store/modules/__snapshots__/paths.test.ts.snap
is excluded by!**/*.snap
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (96)
.cursor/rules/no-comments.mdc
(1 hunks)api/.env.development
(1 hunks)api/dev/states/myservers.cfg
(1 hunks)api/generated-schema.graphql
(6 hunks)api/package.json
(0 hunks)api/src/store/modules/paths.ts
(2 hunks)api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
(7 hunks)api/src/unraid-api/graph/resolvers/docker/docker.model.ts
(5 hunks)api/src/unraid-api/graph/resolvers/docker/docker.service.ts
(0 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/resolvers.module.ts
(3 hunks)api/src/unraid-api/graph/utils/form-utils.ts
(1 hunks)api/src/unraid-api/rest/rest.controller.ts
(2 hunks)api/src/unraid-api/rest/rest.module.ts
(1 hunks)api/src/unraid-api/types/json-forms.test.ts
(1 hunks)api/src/unraid-api/types/json-forms.ts
(2 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/.login.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DefaultPageLayout.php
(3 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/DefaultPageLayout.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/Notifications.page.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/__fixtures__/downloaded/auth-request.php.last-download-time
(1 hunks)api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php
(3 hunks)api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch
(2 hunks)plugin/plugins/dynamix.unraid.net.plg
(3 hunks)unraid-ui/.gitignore
(1 hunks)unraid-ui/.storybook/main.ts
(2 hunks)unraid-ui/.storybook/preview.ts
(2 hunks)unraid-ui/.storybook/tsconfig.json
(0 hunks)unraid-ui/eslint.config.ts
(1 hunks)unraid-ui/package.json
(5 hunks)unraid-ui/scripts/build-style.mjs
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/dropdown-menu/index.ts
(1 hunks)unraid-ui/src/components/common/sheet/SheetFooter.vue
(1 hunks)unraid-ui/src/components/common/tooltip/TooltipContent.vue
(1 hunks)unraid-ui/src/components/form/combobox/Combobox.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxGroup.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxItem.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
(1 hunks)unraid-ui/src/components/form/combobox/index.ts
(1 hunks)unraid-ui/src/components/index.ts
(0 hunks)unraid-ui/src/components/modals/ModalTarget.vue
(1 hunks)unraid-ui/src/components/modals/index.ts
(1 hunks)unraid-ui/src/composables/useTeleport.ts
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/ControlLayout.vue
(0 hunks)unraid-ui/src/forms/ControlWrapper.vue
(1 hunks)unraid-ui/src/forms/FormErrors.vue
(1 hunks)unraid-ui/src/forms/HorizontalLayout.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/MissingRenderer.vue
(1 hunks)unraid-ui/src/forms/NumberField.vue
(1 hunks)unraid-ui/src/forms/PreconditionsLabel.vue
(1 hunks)unraid-ui/src/forms/Select.vue
(3 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/StringArrayField.vue
(1 hunks)unraid-ui/src/forms/Switch.vue
(1 hunks)unraid-ui/src/forms/UnraidSettingsLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(0 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/forms/vertical-layout.renderer.ts
(0 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/lib/utils.ts
(1 hunks)unraid-ui/src/register.ts
(1 hunks)unraid-ui/src/vite-env.d.ts
(1 hunks)unraid-ui/stories/components/common/DropdownMenu.stories.ts
(1 hunks)unraid-ui/stories/components/form/Combobox.stories.ts
(1 hunks)unraid-ui/stories/components/form/Select.stories.ts
(1 hunks)unraid-ui/tsconfig.json
(2 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/graphql/settings.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(12 hunks)web/composables/useTeleport.ts
(0 hunks)web/helpers/create-apollo-client.ts
(2 hunks)web/pages/flashbackup.vue
(1 hunks)
💤 Files with no reviewable changes (8)
- web/composables/useTeleport.ts
- api/package.json
- unraid-ui/src/forms/vertical-layout.renderer.ts
- api/src/unraid-api/graph/resolvers/docker/docker.service.ts
- unraid-ui/.storybook/tsconfig.json
- unraid-ui/src/forms/ControlLayout.vue
- unraid-ui/src/components/index.ts
- unraid-ui/src/forms/renderer-entries.ts
✅ Files skipped from review due to trivial changes (3)
- api/dev/states/myservers.cfg
- unraid-ui/src/components/form/combobox/Combobox.vue
- unraid-ui/scripts/build-style.mjs
🚧 Files skipped from review as they are similar to previous changes (77)
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/Notifications.page.last-download-time
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/auth-request.php.last-download-time
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/DefaultPageLayout.php.last-download-time
- unraid-ui/src/components/modals/index.ts
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/.login.php.last-download-time
- unraid-ui/.gitignore
- api/src/unraid-api/graph/resolvers/resolvers.module.ts
- unraid-ui/src/components/common/tooltip/TooltipContent.vue
- .cursor/rules/no-comments.mdc
- unraid-ui/src/forms/NumberField.vue
- unraid-ui/src/components.ts
- api/.env.development
- unraid-ui/stories/components/form/Select.stories.ts
- unraid-ui/src/components/modals/ModalTarget.vue
- unraid-ui/src/composables/useTeleport.ts
- unraid-ui/src/components/common/sheet/SheetFooter.vue
- api/src/unraid-api/types/json-forms.ts
- unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
- unraid-ui/src/forms/InputField.vue
- unraid-ui/src/forms/Switch.vue
- unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
- api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
- unraid-ui/src/forms/PreconditionsLabel.vue
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
- unraid-ui/stories/components/common/DropdownMenu.stories.ts
- unraid-ui/tsconfig.json
- unraid-ui/.storybook/main.ts
- unraid-ui/src/components/common/dropdown-menu/index.ts
- api/src/unraid-api/rest/rest.module.ts
- web/pages/flashbackup.vue
- unraid-ui/src/forms/VerticalLayout.vue
- unraid-ui/.storybook/preview.ts
- unraid-ui/src/forms/StringArrayField.vue
- unraid-ui/src/forms/UnraidSettingsLayout.vue
- unraid-ui/src/index.ts
- unraid-ui/src/forms/MissingRenderer.vue
- unraid-ui/src/vite-env.d.ts
- unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
- unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- api/src/unraid-api/auth/auth.service.ts
- unraid-ui/stories/components/form/Combobox.stories.ts
- unraid-ui/src/forms/ControlWrapper.vue
- unraid-ui/src/forms/FormErrors.vue
- api/src/store/modules/paths.ts
- api/src/unraid-api/graph/utils/form-utils.ts
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- web/components/RClone/graphql/settings.query.ts
- api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
- api/src/unraid-api/types/json-forms.test.ts
- unraid-ui/src/forms/LabelRenderer.vue
- unraid-ui/src/forms/ComboBoxField.vue
- api/src/unraid-api/graph/resolvers/docker/docker.model.ts
- unraid-ui/src/forms/SteppedLayout.vue
- unraid-ui/src/forms/HorizontalLayout.vue
- unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
- unraid-ui/eslint.config.ts
- web/components/RClone/RCloneConfig.vue
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
- unraid-ui/src/forms/Select.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- unraid-ui/src/components/form/combobox/ComboboxGroup.vue
- unraid-ui/src/components/form/combobox/index.ts
- unraid-ui/package.json
- api/src/unraid-api/unraid-file-modifier/modifications/test/fixtures/downloaded/DefaultPageLayout.php
- unraid-ui/src/components/form/combobox/ComboboxList.vue
- unraid-ui/src/components/form/combobox/ComboboxItem.vue
- unraid-ui/src/register.ts
- api/src/unraid-api/rest/rest.controller.ts
- plugin/plugins/dynamix.unraid.net.plg
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- api/src/unraid-api/unraid-file-modifier/modifications/patches/default-page-layout.patch
- api/generated-schema.graphql
- web/composables/gql/gql.ts
🧰 Additional context used
🧠 Learnings (1)
unraid-ui/src/lib/utils.ts (2)
Learnt from: pujitm
PR: unraid/api#963
File: web/helpers/markdown.ts:1-41
Timestamp: 2024-11-19T16:34:44.804Z
Learning: In `web/store/updateOsChangelog.ts`, when custom logic is required, `Markdown.create` is used to obtain a custom `marked` instance. This is the correct and intended usage of the `Markdown` helper in such cases.
Learnt from: elibosley
PR: unraid/api#963
File: web/helpers/markdown.ts:1-13
Timestamp: 2024-11-18T20:52:47.800Z
Learning: All configurations and direct usage of `marked` should be centralized in the `safeParseMarkdown` function in `web/helpers/markdown.ts` to maintain security and consistency.
🧬 Code Graph Analysis (1)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (5)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (1)
RCloneProviderOptionResponse
(35-51)api/src/unraid-api/types/json-forms.ts (4)
SettingSlice
(36-44)UIElement
(22-22)DataSlice
(17-17)mergeSettingSlices
(67-67)api/src/unraid-api/graph/utils/form-utils.ts (1)
createLabeledControl
(6-46)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (2)
providerTypes
(34-36)providerOptions
(41-43)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts (2)
dataSchema
(41-47)uiSchema
(50-56)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 44-44: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 378-378: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
unraid-ui/src/lib/utils.ts
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
🪛 GitHub Check: Build Web App
web/helpers/create-apollo-client.ts
[failure] 13-13:
'DEV_MODE' is assigned a value but never used. Allowed unused vars must match /^_/u
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Test API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (8)
api/src/unraid-api/unraid-file-modifier/modifications/__test__/snapshots/DefaultPageLayout.php.modified.snapshot.php (3)
781-804
: Improved page reload logic with dialog detectionThe changes improve user experience by preventing page reloads when modal dialogs are open and resetting the reload timer on user interaction.
The new implementation:
- Only reloads the page when no dialog is open
- Resets the timer when users interact with the page
- Uses a clean function to detect visible dialogs
This is a good improvement over the previous simple interval reload.
797-799
: Well-implemented dialog detection functionThe
dialogOpen()
function checks for both modern and legacy SweetAlert modals, ensuring compatibility across different versions.function dialogOpen() { return ($('.sweet-alert').is(':visible') || $('.swal-overlay--show-modal').is(':visible') ); }
1292-1323
: Smart conditional handling of window focus eventsThe code now intelligently includes focus/blur handlers only when live updates are disabled, while properly handling dialog detection during visibility changes.
This approach:
- Prevents redundant page reloads when live updates are already active
- Maintains backward compatibility by defining global variables
- Intelligently restarts timers rather than reloading when dialogs are open
The conditional PHP implementation is clean and well-commented.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
1-363
: Comprehensive and well-structured test suiteThe test suite is thorough and covers basic functionality well with clear, organized test cases. Each test focuses on a specific behavior of the
getProviderConfigSlice
function. The approach of processing raw provider configuration into a map is clean and makes the tests more readable.web/composables/gql/graphql.ts (4)
491-495
: New RClone remote input type looks goodThe
CreateRCloneRemoteInput
type properly defines the structure needed for creating RClone remotes with a name, type, and JSON parameters.
718-724
: Well-designed FlashBackupStatus response typeThe
FlashBackupStatus
type provides appropriate fields for tracking backup operations: a jobId for subsequent status checks and a status message for operation outcome information.
802-811
: Comprehensive flash backup input parametersThe
InitiateFlashBackupInput
type includes all necessary parameters for a backup operation (source, destination, remote name) with optional configuration fields. This flexibility allows for both simple and advanced backup scenarios.
639-640
: Good transition from JSONObject to standard JSON scalarConsistently replacing
JSONObject
with the standardJSON
scalar type across Docker-related fields improves schema compliance and consistency.Also applies to: 642-642, 671-671, 673-673, 680-683
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (7)
api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (1)
65-66
: 🛠️ Refactor suggestion
providerOptions
structure is inconsistent
_providerOptions
is typed asRecord<string, any>
(implying a map), yet the assignment stores the raw array response. This makesproviderOptions['s3']
undefined and forces consumers to re-scan the array.Either change the type to the array interface or build a lookup map:
- this._providerOptions = providersResponse; + this._providerOptions = providersResponse.reduce((acc, p) => { + acc[p.Name] = p; + return acc; + }, {} as Record<string, ProviderInfo>);api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (4)
281-296
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call.async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key };
323-336
: Add input validation for startBackup method.Similar to the
createRemote
method, thestartBackup
method doesn't validate its inputs before making the API call.async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, };
358-359
: Enhance parameter security in callRcloneApi.The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
431-441
: Enhance security for web GUI credentials.The
serveWebGui
method returns username and password in plain text, which could pose a security risk.Consider adding logging for credential access attempts and potentially using temporary credentials with limited lifetime:
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }web/composables/gql/graphql.ts (1)
1271-1299
: Implement error handling for remote creation.The
RCloneMutations
type is well-structured, but the frontend implementation should handle the case when a user tries to create a remote with a name that already exists.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
70-78
:⚠️ Potential issueFix regex patterns in validation rules.
The regular expression patterns for SizeSuffix and Duration validation use incorrect syntax. The
d+
should be\\d+
to match digits.Apply this diff to fix the patterns:
- schema.pattern = '^(off|(d+([KMGTPE]i?B?)?)+)$'; + schema.pattern = '^(off|(\\d+([KMGTPE]i?B?)?)+)$'; - schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';
🧹 Nitpick comments (13)
api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts (1)
41-44
: Use typed Apollo errors for better client handlingWrapping the original error in
new Error()
loses context and makes it harder for clients to distinguish between expected vs. unexpected failures. UseApolloError
(or nestjs-graphql’sGraphQLError
) with a customcode
.- this.logger.error(`Error creating remote: ${error}`); - throw new Error(`Failed to create remote: ${error}`); + this.logger.error(`Error creating remote`, error); + throw new ApolloError('REMOTE_CREATE_FAILED', 'REMOTE_CREATE_FAILED');web/components/RClone/RCloneConfig.vue (2)
54-61
: Browser build: avoidNodeJS.Timeout
NodeJS.Timeout
comes from the Node type declarations.
In a browser bundle (Vite / Vue CLI) this resolves toany
, which can break when@types/node
is removed or mismatched. Prefer the portable type:-let refetchTimeout: NodeJS.Timeout | null = null; +let refetchTimeout: ReturnType<typeof setTimeout> | null = null;Same applies to
changeTimeout
below.
124-126
: Surface backend errors to the userA rejected mutation only prints to the console. Users receive no feedback and the form remains enabled, which is confusing.
- } catch (error) { - console.error('Error creating remote:', error); + } catch (error) { + console.error('Error creating remote:', error); + if (window.toast) { + window.toast.error('Failed to create remote', { + description: error instanceof Error ? error.message : String(error), + }); + } }web/components/RClone/RemoteItem.vue (1)
50-55
: Template type-safety: narrow unknown parameters
remote.parameters
is typed asRecord<string, unknown>
but accessed with dot notation (remote.parameters.provider
).
This bypasses TS checks and will error ifparameters
isnull
.Consider introducing a helper type guard or casting:
const params = remote.parameters as Record<string, string | undefined>;Then use
params.provider
,params.region
, …api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts (1)
95-115
: Fetch remote details in parallel for O(1) latencyCurrently each
getRemoteDetails
call waits for the previous one (for…of + await
). With N remotes the latency is O(N). UsePromise.all
to parallelize:- for (const name of remoteNames) { - try { - const config = await this.rcloneApiService.getRemoteDetails(name); - ... - } catch (error) { ... } - } + const remoteDetails = await Promise.all( + remoteNames.map(async (name) => { + try { + const config = await this.rcloneApiService.getRemoteDetails(name); + const { type, ...parameters } = config; + return { name, type: type ?? '', parameters, config }; + } catch (error) { + this.logger.error(`Error getting details for remote ${name}: ${error}`); + return null; + } + }), + ); + return remoteDetails.filter(Boolean) as RCloneRemote[];Cuts total wait time and simplifies flow control.
web/components/RClone/RCloneOverview.vue (3)
55-59
: Use a custom confirmation dialog for better UX.The native browser
confirm()
dialog doesn't match the UI style of the rest of the application and lacks customization options.Consider implementing a custom confirmation dialog component that matches your UI design system.
88-95
: Extend toast type definition with error method.The global toast type definition only includes a success method, but should also include methods for error, warning, etc.
declare global { interface Window { toast?: { success: (title: string, options: { description?: string }) => void; + error: (title: string, options: { description?: string }) => void; + warning: (title: string, options: { description?: string }) => void; + info: (title: string, options: { description?: string }) => void; }; } }
134-134
: Add null check for selectedRemote.The component accesses
selectedRemote?.name
in the template, but doesn't properly handle the case whenselectedRemote
is null.-<h2 class="text-xl font-semibold">{{ initialFormState ? 'Add Crypt to ' + selectedRemote?.name : 'Add New Remote' }}</h2> +<h2 class="text-xl font-semibold">{{ initialFormState ? `Add Crypt to ${selectedRemote?.name || ''}` : 'Add New Remote' }}</h2>api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
45-45
: Remove unnecessary empty constructor.The empty constructor can be removed as it doesn't serve any purpose.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 45-45: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
387-387
: Change to an optional chain.The conditional check could be simplified with an optional chain.
- if (errorBody && errorBody.error) { + if (errorBody?.error) {🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/generated-schema.graphql (1)
1715-1729
: Consider adding description for options parameter.The
options
parameter inInitiateFlashBackupInput
has a description, but it could be more specific about the expected format and common options.While this is a generated file and changes should be made in the source, consider enhancing the input field description in the source schema to be more specific about expected options format and values.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
387-393
: Simplify switch statement with redundant cases.The
string
,text
, andpassword
cases are redundant as they result in the same behavior as the default case.- case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
426-431
: Remove redundant case in switch statement.The
password
case is redundant as it was already handled by the earlier check forisPassword
.case 'text': // Consider 'textarea' format later if needed return undefined; // Use default InputField (via isStringControl) - case 'password': - return 'password'; // Explicit format for password managers etc. case 'string': default:🧰 Tools
🪛 Biome (1.9.4)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (21)
api/dev/Unraid.net/myservers.cfg
(1 hunks)api/generated-schema.graphql
(7 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
(3 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/RCloneOverview.vue
(1 hunks)web/components/RClone/RemoteItem.vue
(1 hunks)web/components/RClone/graphql/rclone.mutations.ts
(1 hunks)web/components/RClone/graphql/rclone.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(11 hunks)web/helpers/create-apollo-client.ts
(4 hunks)web/pages/flashbackup.vue
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- web/components/RClone/graphql/rclone.query.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- api/dev/Unraid.net/myservers.cfg
- web/pages/flashbackup.vue
- web/helpers/create-apollo-client.ts
- unraid-ui/src/forms/InputField.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- web/composables/gql/gql.ts
🧰 Additional context used
🧬 Code Graph Analysis (5)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)
web/composables/gql/graphql.ts (6)
ArrayMutations
(254-268)Mutation
(878-912)DockerMutations
(654-660)VmMutations
(1797-1813)ParityCheckMutations
(1125-1135)RCloneMutations
(1272-1278)
api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts (4)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (1)
Resolver
(12-38)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts (1)
Resolver
(21-64)web/composables/gql/graphql.ts (4)
RCloneMutations
(1272-1278)RCloneRemote
(1292-1299)CreateRCloneRemoteInput
(491-495)DeleteRCloneRemoteInput
(504-506)api/src/unraid-api/graph/directives/use-permissions.directive.ts (3)
UsePermissions
(69-85)AuthActionVerb
(14-14)AuthPossession
(14-14)
api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts (3)
api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts (1)
Module
(6-11)api/src/unraid-api/rest/rest.module.ts (1)
Module
(7-12)api/src/unraid-api/graph/resolvers/resolvers.module.ts (1)
Module
(35-73)
api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (2)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (4)
ObjectType
(9-16)ObjectType
(79-89)ObjectType
(91-101)ObjectType
(103-116)web/composables/gql/graphql.ts (1)
RCloneMutations
(1272-1278)
web/components/RClone/graphql/rclone.mutations.ts (1)
web/composables/gql/gql.ts (1)
graphql
(222-224)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 45-45: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 387-387: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Build Web App
- GitHub Check: Test API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (24)
api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts (2)
7-7
: Added import for RCloneMutations to support new mutation resolver.The import statement correctly adds the RCloneMutations type needed for the new mutation resolver.
34-37
: New resolver method added for RClone functionality.This implementation correctly follows the established pattern for mutation resolvers in this file, exposing the RClone mutations through a field named "rclone". The implementation is consistent with other mutation resolvers like array(), docker(), etc.
api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts (1)
1-20
: Well-structured NestJS module for RClone functionality.The module follows NestJS best practices with clear providers and exports. The module correctly exports RCloneService and RCloneApiService for use in other modules, which is consistent with the imports shown in FlashBackupModule, RestModule, and ResolversModule.
api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (3)
3-9
: Helpful file-level comment for maintainability.This comment provides valuable guidance to developers about the need to register new mutations in the RootMutations resolver, improving code maintainability.
25-28
: New RCloneMutations class added with appropriate description.The class definition follows the same pattern as other mutation classes in the file with a clear, consistent description.
44-45
: Added RCloneMutations field to RootMutations.The field is correctly added to the RootMutations class with a consistent description pattern, following the established structure of the file.
web/components/RClone/graphql/rclone.mutations.ts (1)
1-24
: Well-structured GraphQL mutations for RClone operations.The file defines two clear GraphQL mutations:
- CREATE_REMOTE - Creates a new RClone remote with appropriate fields returned
- DELETE_REMOTE - Deletes an existing RClone remote
Both mutations correctly use the rclone field that corresponds to the backend mutation structure. The mutations are properly wrapped with the graphql helper for integration with the GraphQL client.
api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts (1)
53-60
: Mirror success/failure semantics
deleteRCloneRemote
always returnstrue
on success but throws on error.
Consider returningfalse
(instead of throwing) for “remote not found” to let callers treat it as a soft failure, or at minimum document the thrown error codes so the UI can distinguish them.web/components/RClone/RemoteItem.vue (1)
62-65
: Event payload does not match declared type
open-crypt-modal
is declared to emit{ name; type }
but the handler passes the wholeremote
(which includesparameters
). Align the types or adjust the emit call to prevent runtime/TS mismatch.-@click="emit('open-crypt-modal', remote)" +@click="emit('open-crypt-modal', { name: remote.name, type: remote.type })"api/generated-schema.graphql (3)
910-918
: Well-structured input types for RClone operations.The input types are properly defined with clear field names and appropriate types.
1319-1353
: Comprehensive type definitions for RClone functionality.The type definitions for RClone drives, config forms, backup settings, and remotes provide a robust foundation for the GraphQL API.
1273-1303
: Consistent use of JSON scalar for Docker-related types.Replacing
JSONObject
withJSON
scalar across Docker-related types improves schema consistency.web/composables/gql/graphql.ts (7)
491-495
: Nicely structured input type for remote creation.The
CreateRCloneRemoteInput
type provides a clean interface with all necessary fields for remote creation: name, type, and parameters. The use of JSON for parameters allows for flexible configuration.
504-506
: Simple and clear deletion input type.The
DeleteRCloneRemoteInput
is appropriately minimal, requiring only the name of the remote to delete.
643-647
: Standardized JSON scalar usage improves consistency.Good replacement of
JSONObject
withJSON
scalar across Docker-related fields. This standardization simplifies type handling and ensures consistency throughout the codebase.Also applies to: 675-687
722-728
: Well-structured backup status response.The
FlashBackupStatus
type provides a clear interface with appropriate fields for tracking backup operations. The inclusion of an optionaljobId
allows for future status checking.
806-815
: Comprehensive input type for flash backups.The
InitiateFlashBackupInput
type provides all necessary parameters for a backup operation while making additional options optional for flexibility.
1238-1250
: Well-structured schema types for dynamic form generation.The
RCloneBackupConfigForm
andRCloneBackupSettings
types provide a solid foundation for schema-driven UI generation. The inclusion of both data schema and UI schema follows best practices for JSON Schema form generation.
2107-2110
: GraphQL operations match schema definitions.The defined GraphQL operations for RClone remote management correctly match the schema types. The operations include all necessary fields for the frontend implementation.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (5)
201-205
: Clear and correct filtering logic for standard options.The filtering logic correctly separates standard from advanced options. This ensures users see the appropriate configuration options at each step of the wizard.
206-216
: Good handling of duplicate option names.The uniqueness check prevents overwrites when multiple options have the same name, and the warning helps identify potential issues during development.
228-232
: Smart filtering of always-hidden elements.The code correctly filters out options that are configured to be always hidden, improving performance by avoiding unnecessary elements in the UI.
445-564
: Well-implemented dynamic schema generation.The
buildRcloneConfigSchema
function effectively handles the complexity of creating multi-step form schemas with conditional elements. The approach of merging slices provides good modularity and maintainability.I particularly appreciate how the code:
- Conditionally includes advanced options based on user preference
- Dynamically adjusts step descriptions based on selected provider
- Properly handles empty steps to avoid showing unnecessary UI
516-528
: Dynamic step generation improves user experience.The dynamic generation of steps based on context (selected provider and advanced options toggle) creates a streamlined user experience, showing only relevant steps.
api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
initiateFlashBackup: FlashBackupStatus; | ||
parityCheck: ParityCheckMutations; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for flash backup operations.
The initiateFlashBackup
mutation is well-structured, but should include error handling in the frontend implementation for cases where the backup might fail to initiate.
When implementing this mutation in the frontend, add try/catch blocks to handle potential errors:
try {
const result = await initiateFlashBackup({
variables: { input: backupParameters }
});
// Handle success
} catch (error) {
// Show error notification
console.error('Backup initiation failed:', error);
}
Also applies to: 961-963
46fce12
to
35d463e
Compare
There was a problem hiding this 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
♻️ Duplicate comments (16)
web/components/RClone/RCloneConfig.vue (1)
114-126
: 🛠️ Refactor suggestionError handling could be improved in submitForm.
While error logging is implemented, there's no user feedback when an error occurs during form submission.
const submitForm = async () => { try { await createRemote({ input: { name: formState.value.name, type: formState.value.type, parameters: formState.value.parameters, }, }); } catch (error) { console.error('Error creating remote:', error); + // Provide user feedback for errors + if (window.toast) { + window.toast.error('Remote Creation Failed', { + description: error instanceof Error ? error.message : 'An unknown error occurred', + }); + } } };unraid-ui/src/forms/renderers.ts (1)
108-110
: 🛠️ Refactor suggestionFallback renderer rank should be lower.
With a rank of 3, the fallback renderer has the same priority as other specific renderers, which could lead to unexpected behavior.
{ renderer: markRaw(withErrorWrapper(MissingRenderer)), - tester: rankWith(3, isControl), // Adjusted rank to ensure it's tested after more specific controls + tester: rankWith(1, isControl), // Lower rank ensures it's only used when no specific renderer matches },web/components/RClone/RCloneOverview.vue (3)
42-52
: Add error handling to deletion response.The success branch of the deletion flow is handled correctly, but there's no handling for the error case if the deletion fails.
Apply this diff to handle deletion failures:
onDeleteDone((result) => { const data = result?.data; if (data?.rclone?.deleteRCloneRemote) { if (window.toast) { window.toast.success('Remote Deleted', { description: 'Remote deleted successfully', }); } + } else { + if (window.toast) { + window.toast.error('Deletion Failed', { + description: 'Failed to delete remote. Please try again.', + }); + } } });
27-31
: Add error handling for query failures.The template handles loading and empty states but doesn't handle query errors.
Modify the query setup to track errors:
const { result: remotes, loading: loadingRemotes, refetch: refetchRemotes, + error, } = useQuery(GET_RCLONE_REMOTES);
Then add error state handling in the template section.
127-141
: Enhance modal accessibility.The modal implementation lacks essential accessibility attributes.
Apply this diff to improve accessibility:
<div v-if="showConfigModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" + role="dialog" + aria-modal="true" + aria-labelledby="modal-title" > <div class="bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] overflow-auto"> <div class="p-4 border-b border-gray-200 flex justify-between items-center"> - <h2 class="text-xl font-semibold">{{ initialFormState ? 'Add Crypt to ' + selectedRemote?.name : 'Add New Remote' }}</h2> + <h2 id="modal-title" class="text-xl font-semibold">{{ initialFormState ? 'Add Crypt to ' + selectedRemote?.name : 'Add New Remote' }}</h2> - <Button variant="ghost" size="sm" @click="showConfigModal = false">×</Button> + <Button variant="ghost" size="sm" @click="showConfigModal = false" aria-label="Close dialog">×</Button> </div> <div class="p-6"> <RCloneConfig :initial-state="initialFormState || undefined" @complete="onConfigComplete" /> </div> </div> </div>api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
362-362
: Complete test coverage by implementing additional test cases.The placeholder comment "More tests will be added here..." indicates incomplete test coverage.
Please implement additional test cases covering:
- Combining multiple provider filters (e.g., apply
filter: ['aws', 'gcp']
together)- Edge cases in provider option structures (missing or extra fields, unexpected data types)
- Error scenarios (invalid provider IDs, malformed configs)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (6)
281-296
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call.async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key }; this.logger.log( `Creating new remote: ${name} of type: ${type} with params: ${JSON.stringify(params)}` ); // Added params logging const result = await this.callRcloneApi('config/create', params); // console.log('Result was: ', result); // Result is usually empty on success, potentially remove this.logger.log(`Successfully created remote: ${name}`); // Improved success log return result; // Rclone 'config/create' usually returns an empty object on success }
289-294
: Avoid logging sensitive configuration parameters.The creation of a remote might involve sensitive configuration parameters, which are currently being logged.
- this.logger.log( - `Creating new remote: ${name} of type: ${type} with params: ${JSON.stringify(params)}` - ); // Added params logging + this.logger.log(`Creating new remote: ${name} of type: ${type}`); const result = await this.callRcloneApi('config/create', params); - // console.log('Result was: ', result); // Result is usually empty on success, potentially remove this.logger.log(`Successfully created remote: ${name}`); // Improved success log
323-336
: Add input validation for startBackup method.Similar to the
createRemote
method, thestartBackup
method doesn't validate its inputs before making the API call.async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, }; return this.callRcloneApi('sync/copy', params); }
358-360
: Enhance parameter security in callRcloneApi.The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
140-144
: Replace arbitrary delay with actual socket readiness check.The code currently uses a fixed 1-second delay before checking if the socket is running, which is unreliable.
Implement a more robust approach with exponential backoff and a timeout:
- // Consider the service initialized shortly after starting the process - // A better approach might involve checking the socket or API readiness - await new Promise((resolve) => setTimeout(resolve, 1000)); // Small delay + // Wait for socket to be ready with exponential backoff + let backoff = 100; // Start with 100ms + const maxBackoff = 5000; // Max 5 seconds + const timeout = 30000; // 30 second timeout + const startTime = Date.now(); + + while (Date.now() - startTime < timeout) { + // Check if socket is running + const isRunning = await this.checkRcloneSocketRunning(); + if (isRunning) { + break; + } + + // Wait with exponential backoff + await new Promise((resolve) => setTimeout(resolve, backoff)); + backoff = Math.min(backoff * 2, maxBackoff); + } + // Re-check if socket is running after attempting start const isRunning = await this.checkRcloneSocketRunning(); if (!isRunning) { throw new Error('Rclone socket failed to start or become responsive.'); }
431-441
: Enhance security for web GUI credentials.The
serveWebGui
method returns username and password in plain text, which could pose a security risk.Consider adding logging for credential access attempts and potentially using temporary credentials with limited lifetime:
async serveWebGui(): Promise<{ url: string; username: string; password: string }> { if (!this.isInitialized) { throw new Error('RClone service is not initialized'); } + // Log access attempt for audit purposes + this.logger.log('Web GUI credentials accessed'); + + // Consider generating a temporary password with limited lifetime + // const tempPassword = crypto.randomBytes(16).toString('hex'); + // const expiryTime = Date.now() + 3600000; // 1 hour expiry + return { url: this.rcloneBaseUrl, username: this.rcloneUsername, password: this.rclonePassword, + // expiry: expiryTime }; }web/composables/gql/graphql.ts (2)
896-898
: Ensure error handling for flash backup operationsThe
initiateFlashBackup
mutation returns aFlashBackupStatus
which is good for tracking the operation. When implementing the frontend logic that calls this mutation, make sure to add proper error handling for cases where the backup might fail to initiate.
2011-2017
: Add error handling for duplicate remotesThe
CreateRCloneRemoteMutation
is well-defined, but you should implement error handling in the frontend for cases where a user tries to create a remote with a name that already exists.api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
70-72
:⚠️ Potential issueFix regex patterns in validation rules
The regular expression pattern for SizeSuffix validation has an error. The
d+
should be\\d+
to match digits.- schema.pattern = '^(off|(d+([KMGTPE]i?B?)?)+)$'; + schema.pattern = '^(off|(\\d+([KMGTPE]i?B?)?)+)$';
76-79
:⚠️ Potential issueFix regex pattern for Duration validation
Similarly, the Duration validation pattern has the same issue with
d+
instead of\\d+
, and.d+
instead of.\\d+
.- schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';
🧹 Nitpick comments (12)
unraid-ui/src/lib/utils.ts (1)
29-54
: Consider using utility functions instead of a static-only class.The static analysis tool correctly identifies that this class only contains static methods. Consider refactoring to simple exported functions for better maintainability.
-export class Markdown { - private static instance = Markdown.create(); - - /** - * Creates a `Marked` instance with default MarkedExtension's already added. - * - * - * Default behaviors: - * - Sanitizes html after parsing - * - * - * @param args any number of Marked Extensions - * @returns Marked parser instance - */ - static create(...args: Parameters<Marked['use']>) { - return new Marked(defaultMarkedExtension, ...args); - } - - /** - * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. - * - * - * @param markdownContent string of markdown content - * @returns safe, sanitized html content - */ - static async parse(markdownContent: string): Promise<string> { - return Markdown.instance.parse(markdownContent); - } -} +/** + * Creates a `Marked` instance with default MarkedExtension's already added. + * + * Default behaviors: + * - Sanitizes html after parsing + * + * @param args any number of Marked Extensions + * @returns Marked parser instance + */ +export function createMarkdownParser(...args: Parameters<Marked['use']>) { + return new Marked(defaultMarkedExtension, ...args); +} + +// Create a singleton instance for common use +const defaultParser = createMarkdownParser(); + +/** + * Parses arbitrary markdown content as sanitized html. May throw if parsing fails. + * + * @param markdownContent string of markdown content + * @returns safe, sanitized html content + */ +export async function parseMarkdown(markdownContent: string): Promise<string> { + return defaultParser.parse(markdownContent); +}🧰 Tools
🪛 Biome (1.9.4)
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
unraid-ui/src/forms/renderers.ts (1)
35-42
: Consider preserving component names in withErrorWrapper.The current implementation doesn't preserve the original component's name or props validation, which can make debugging harder.
-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });web/components/RClone/RCloneOverview.vue (2)
89-95
: Strengthen type safety for toast notification.The global declaration for
window.toast
is incomplete, as it only includes thesuccess
method but not other potential methods likeerror
.Enhance the type declaration to include all methods you might use:
declare global { interface Window { toast?: { success: (title: string, options: { description?: string }) => void; + error: (title: string, options: { description?: string }) => void; + warning: (title: string, options: { description?: string }) => void; + info: (title: string, options: { description?: string }) => void; }; } }
54-59
: Improve deletion confirmation dialog.The current confirmation dialog uses the built-in browser
confirm()
which lacks styling and consistency with the rest of the UI.Consider using a custom confirmation dialog component that matches your UI design:
const confirmDelete = (remote: string) => { - if (confirm(`Are you sure you want to delete "${remote}"?`)) { - deleteRemote({ input: { name: remote } }); - } + if (window.confirm) { + window.confirm.show({ + title: 'Confirm Deletion', + message: `Are you sure you want to delete "${remote}"?`, + confirmText: 'Delete', + cancelText: 'Cancel', + variant: 'danger', + onConfirm: () => { + deleteRemote({ input: { name: remote } }); + } + }); + } else { + // Fallback to browser confirm + if (confirm(`Are you sure you want to delete "${remote}"?`)) { + deleteRemote({ input: { name: remote } }); + } + } };This would require adding the confirm service to your global Window interface declaration.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (3)
27-28
: Replace placeholder type with proper import.The code uses a placeholder type definition with a commented-out import, suggesting the proper type should be imported from the codebase.
-// Type assertion for the imported config -interface RawProviderConfigEntry { - Name: string; - Options: RCloneProviderOptionResponse[]; - // Add other properties from config.ts if needed, though Name and Options are primary -} -// import type { UIElement } from '@app/unraid-api/types/json-forms'; // Original import commented out +import type { UIElement } from '@app/unraid-api/types/json-forms'; +import type { RawProviderConfigEntry } from '@app/unraid-api/graph/resolvers/rclone/rclone.model.js';Ensure the proper imports are available and update references throughout the file.
132-134
: Avoid usingany
type in TypeScript test code.Using
as any
in the test reduces type safety and may hide issues that would be caught by the TypeScript compiler.Replace the
any
type assertion with a proper type:-const elementsArray = verticalLayoutStd.elements as any[]; +const elementsArray = verticalLayoutStd.elements as UIElement[];This change should be applied to all similar occurrences in the file (lines 186, 247, 248, 258, 259).
57-68
: Add test preparation check for missing configuration.The test suite assumes that the
s3
provider exists in the configuration but doesn't handle the case where it might be missing.Add a check to skip or fail the test if the required test data is not available:
describe('getProviderConfigSlice', () => { // Example provider to use in tests - choose one with both standard and advanced options const testProvider = 's3'; // S3 usually has a good mix let s3Options: RCloneProviderOptionResponse[]; beforeEach(() => { // Ensure we have the options for the test provider s3Options = providerOptionsMap[testProvider]; + if (!s3Options || s3Options.length === 0) { + console.warn(`Test provider '${testProvider}' not found or has no options. Some tests may be skipped.`); + } expect(s3Options).toBeDefined(); expect(s3Options.length).toBeGreaterThan(0); });api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
45-45
: Remove unnecessary empty constructor.The empty constructor doesn't add any value to the class.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 45-45: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
387-396
: Use optional chaining to simplify conditional logic.The code can be simplified using optional chaining.
- if (errorBody && errorBody.error) { + if (errorBody?.error) { rcloneError = `Rclone Error: ${errorBody.error}`; // Add input details if available, check for different structures - if (errorBody.input) { + if (errorBody?.input) { rcloneError += ` | Input: ${JSON.stringify(errorBody.input)}`; } else if (params) { // Fallback to original params if errorBody.input is missing rcloneError += ` | Original Params: ${JSON.stringify(params)}`; }🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
387-393
: Remove useless case clauses in switch statementThe static analysis tool correctly identifies that case clauses 'string', 'text', and 'password' are redundant since there's a default clause that handles all other cases.
- case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
431-438
: Remove redundant case clause for 'password'The 'password' case is redundant as the default clause would handle it. Consider restructuring this section.
- case 'password': - return 'password'; // Explicit format for password managers etc. case 'string': default: // Use combobox for string fields with examples if (examples && examples.length > 0) { return 'combobox'; } return undefined; // Use default InputField (via isStringControl)🧰 Tools
🪛 Biome (1.9.4)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
211-214
: Console warning in production codeWhile console warnings are useful during development, consider using a proper logging service for production code that can be configured based on environment.
- console.warn( - `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` - ); + // Use a logger service instead of console.warn + logger.warn(`Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
api/src/__test__/store/modules/__snapshots__/paths.test.ts.snap
is excluded by!**/*.snap
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (96)
.cursor/rules/no-comments.mdc
(1 hunks)api/.env.development
(1 hunks)api/dev/Unraid.net/myservers.cfg
(1 hunks)api/dev/states/myservers.cfg
(1 hunks)api/generated-schema.graphql
(7 hunks)api/package.json
(0 hunks)api/src/store/modules/paths.ts
(2 hunks)api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
(7 hunks)api/src/unraid-api/graph/resolvers/docker/docker.model.ts
(5 hunks)api/src/unraid-api/graph/resolvers/docker/docker.service.ts
(0 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
(3 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/resolvers.module.ts
(3 hunks)api/src/unraid-api/graph/utils/form-utils.ts
(1 hunks)api/src/unraid-api/rest/rest.controller.ts
(2 hunks)api/src/unraid-api/rest/rest.module.ts
(1 hunks)api/src/unraid-api/types/json-forms.test.ts
(1 hunks)api/src/unraid-api/types/json-forms.ts
(2 hunks)plugin/plugins/dynamix.unraid.net.plg
(1 hunks)unraid-ui/.gitignore
(1 hunks)unraid-ui/.storybook/main.ts
(2 hunks)unraid-ui/.storybook/preview.ts
(2 hunks)unraid-ui/.storybook/tsconfig.json
(0 hunks)unraid-ui/eslint.config.ts
(1 hunks)unraid-ui/package.json
(5 hunks)unraid-ui/scripts/build-style.mjs
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/dropdown-menu/index.ts
(1 hunks)unraid-ui/src/components/common/sheet/SheetFooter.vue
(1 hunks)unraid-ui/src/components/common/tooltip/TooltipContent.vue
(1 hunks)unraid-ui/src/components/form/combobox/Combobox.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxGroup.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxItem.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
(1 hunks)unraid-ui/src/components/form/combobox/index.ts
(1 hunks)unraid-ui/src/components/index.ts
(0 hunks)unraid-ui/src/components/modals/ModalTarget.vue
(1 hunks)unraid-ui/src/components/modals/index.ts
(1 hunks)unraid-ui/src/composables/useTeleport.ts
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/ControlLayout.vue
(0 hunks)unraid-ui/src/forms/ControlWrapper.vue
(1 hunks)unraid-ui/src/forms/FormErrors.vue
(1 hunks)unraid-ui/src/forms/HorizontalLayout.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/MissingRenderer.vue
(1 hunks)unraid-ui/src/forms/NumberField.vue
(1 hunks)unraid-ui/src/forms/PreconditionsLabel.vue
(1 hunks)unraid-ui/src/forms/Select.vue
(3 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/StringArrayField.vue
(1 hunks)unraid-ui/src/forms/Switch.vue
(1 hunks)unraid-ui/src/forms/UnraidSettingsLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(0 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/forms/vertical-layout.renderer.ts
(0 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/lib/utils.ts
(1 hunks)unraid-ui/src/register.ts
(1 hunks)unraid-ui/src/vite-env.d.ts
(1 hunks)unraid-ui/stories/components/common/DropdownMenu.stories.ts
(1 hunks)unraid-ui/stories/components/form/Combobox.stories.ts
(1 hunks)unraid-ui/stories/components/form/Select.stories.ts
(1 hunks)unraid-ui/tsconfig.json
(2 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/RCloneOverview.vue
(1 hunks)web/components/RClone/RemoteItem.vue
(1 hunks)web/components/RClone/graphql/rclone.mutations.ts
(1 hunks)web/components/RClone/graphql/rclone.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(11 hunks)web/composables/useTeleport.ts
(0 hunks)web/helpers/create-apollo-client.ts
(4 hunks)web/pages/flashbackup.vue
(1 hunks)
💤 Files with no reviewable changes (8)
- api/package.json
- web/composables/useTeleport.ts
- api/src/unraid-api/graph/resolvers/docker/docker.service.ts
- unraid-ui/.storybook/tsconfig.json
- unraid-ui/src/forms/vertical-layout.renderer.ts
- unraid-ui/src/forms/ControlLayout.vue
- unraid-ui/src/forms/renderer-entries.ts
- unraid-ui/src/components/index.ts
✅ Files skipped from review due to trivial changes (4)
- unraid-ui/src/components/common/sheet/SheetFooter.vue
- unraid-ui/stories/components/common/DropdownMenu.stories.ts
- unraid-ui/src/components/form/combobox/index.ts
- web/components/RClone/graphql/rclone.query.ts
🚧 Files skipped from review as they are similar to previous changes (75)
- unraid-ui/stories/components/form/Select.stories.ts
- unraid-ui/src/components/form/combobox/Combobox.vue
- api/dev/states/myservers.cfg
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- api/dev/Unraid.net/myservers.cfg
- unraid-ui/.gitignore
- .cursor/rules/no-comments.mdc
- api/.env.development
- unraid-ui/src/composables/useTeleport.ts
- api/src/unraid-api/rest/rest.module.ts
- unraid-ui/src/forms/Switch.vue
- unraid-ui/tsconfig.json
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
- unraid-ui/src/forms/PreconditionsLabel.vue
- api/src/unraid-api/graph/resolvers/resolvers.module.ts
- api/src/unraid-api/graph/resolvers/docker/docker.model.ts
- api/src/store/modules/paths.ts
- unraid-ui/src/components/modals/ModalTarget.vue
- api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
- unraid-ui/src/vite-env.d.ts
- unraid-ui/src/forms/StringArrayField.vue
- api/src/unraid-api/auth/auth.service.ts
- web/pages/flashbackup.vue
- unraid-ui/src/forms/MissingRenderer.vue
- unraid-ui/src/components/modals/index.ts
- unraid-ui/src/forms/ControlWrapper.vue
- unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
- web/components/RClone/graphql/rclone.mutations.ts
- api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
- unraid-ui/src/forms/UnraidSettingsLayout.vue
- unraid-ui/src/components.ts
- unraid-ui/scripts/build-style.mjs
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
- api/src/unraid-api/types/json-forms.test.ts
- unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
- unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
- unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
- unraid-ui/src/forms/Select.vue
- web/helpers/create-apollo-client.ts
- api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
- unraid-ui/src/forms/HorizontalLayout.vue
- unraid-ui/src/forms/InputField.vue
- unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
- plugin/plugins/dynamix.unraid.net.plg
- unraid-ui/.storybook/preview.ts
- unraid-ui/src/forms/ComboBoxField.vue
- unraid-ui/src/components/form/combobox/ComboboxItem.vue
- unraid-ui/src/forms/NumberField.vue
- unraid-ui/stories/components/form/Combobox.stories.ts
- unraid-ui/src/components/form/combobox/ComboboxGroup.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- unraid-ui/.storybook/main.ts
- unraid-ui/src/components/common/dropdown-menu/index.ts
- api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
- unraid-ui/package.json
- unraid-ui/src/forms/LabelRenderer.vue
- unraid-ui/src/register.ts
- api/src/unraid-api/graph/utils/form-utils.ts
- unraid-ui/src/index.ts
- unraid-ui/src/components/form/combobox/ComboboxList.vue
- unraid-ui/src/forms/SteppedLayout.vue
- unraid-ui/eslint.config.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- api/src/unraid-api/rest/rest.controller.ts
- web/components/RClone/RemoteItem.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- unraid-ui/src/forms/FormErrors.vue
- api/src/unraid-api/types/json-forms.ts
- api/generated-schema.graphql
- unraid-ui/src/components/common/tooltip/TooltipContent.vue
- web/composables/gql/gql.ts
🧰 Additional context used
🧠 Learnings (1)
unraid-ui/src/lib/utils.ts (2)
Learnt from: pujitm
PR: unraid/api#963
File: web/helpers/markdown.ts:1-41
Timestamp: 2024-11-19T16:34:44.804Z
Learning: In `web/store/updateOsChangelog.ts`, when custom logic is required, `Markdown.create` is used to obtain a custom `marked` instance. This is the correct and intended usage of the `Markdown` helper in such cases.
Learnt from: elibosley
PR: unraid/api#963
File: web/helpers/markdown.ts:1-13
Timestamp: 2024-11-18T20:52:47.800Z
Learning: All configurations and direct usage of `marked` should be centralized in the `safeParseMarkdown` function in `web/helpers/markdown.ts` to maintain security and consistency.
🪛 Biome (1.9.4)
unraid-ui/src/lib/utils.ts
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 45-45: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 387-387: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Build Web App
- GitHub Check: Test API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (17)
unraid-ui/src/lib/utils.ts (2)
2-3
: Good choice of libraries for markdown processing.The combination of Marked for parsing markdown and DOMPurify for sanitization is a robust approach for safely rendering markdown content.
10-20
: Well-implemented sanitization defaults.The default configuration correctly forbids style tags and attributes, which helps prevent CSS-based attacks. This aligns with security best practices for user-generated content.
unraid-ui/src/forms/VerticalLayout.vue (5)
17-17
: Good use of composable for visibility management.Using
useJsonFormsVisibility
composable improves code organization and reusability across different layout components.
24-28
: Well-structured reactive properties.The composable pattern correctly extracts visibility logic, and the computed property for dividers properly accesses nested properties with null checks.
30-32
: Improved element access.The change to access elements through the layout value improves consistency with the reactive pattern established in the component.
36-42
: Improved layout structure with conditional styling.The switch from grid to flex column layout with conditional dividers provides better flexibility and visual separation when needed.
43-51
: Consistent prop passing pattern.Sourcing all props from the layout.layout.value ensures consistency and reactivity throughout the component.
web/components/RClone/RCloneConfig.vue (5)
12-12
: Good handling of unused variables.Using the underscore prefix for unused variables is a clean way to suppress linting warnings while maintaining clarity about what's imported.
54-101
: Well-implemented debouncing for form schema updates.The consolidated watcher with debouncing prevents excessive API calls when multiple form fields change. The conditional logging helps with debugging without cluttering the console.
159-175
: Good use of debouncing for form data changes.The debounced handling of form data changes prevents excessive logging and improves performance by reducing unnecessary updates.
180-199
: Well-structured step management.The computed properties for handling steps provide a clean abstraction over the potentially complex step structure, making the logic for determining the current and last step clear and maintainable.
231-237
: Good conditional display of the submit button.Showing the submit button only on the last step provides a clear user flow through the multi-step form process.
unraid-ui/src/forms/renderers.ts (2)
44-48
: Well-implemented string array detection.The
isStringArray
predicate function correctly identifies schema objects representing arrays of strings, with proper type checking.
50-111
: Well-organized renderer registry.The explicit organization of renderers into categories (layouts, controls, labels, fallback) makes the code more maintainable and easier to understand.
web/composables/gql/graphql.ts (2)
491-495
: RCloneRemote creation input is well structuredThe
CreateRCloneRemoteInput
type provides a clean interface for creating new RClone remotes with name, type, and JSON parameters. This matches the backend implementation pattern and provides good type safety.
1238-1250
: Well-structured configuration form typesThe RClone backup configuration types follow a clean pattern with separate concerns:
RCloneBackupConfigForm
for the form schemaRCloneBackupSettings
to aggregate forms, drives, and remotesThis separation of concerns will make maintenance and extensions easier.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
445-564
: Well-structured multi-step form builderThe
buildRcloneConfigSchema
function is well-structured with clear separation of concerns:
- Generates basic configuration
- Conditionally adds standard provider config
- Conditionally adds advanced config based on user preference
- Assembles the final schema with appropriate steps
This approach provides a good user experience by progressively revealing complexity and maintaining a clean, maintainable codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
unraid-ui/src/forms/renderers.ts (2)
110-110
: Correct implementation of the fallback rank changeThe change to lower the fallback renderer's rank to 0 is correct and addresses the previous feedback about collision with other rank 3 testers.
🧰 Tools
🪛 Biome (1.9.4)
[error] 110-110: expected
,
but instead found:
Remove :
(parse)
[error] 110-110: expected
,
but instead found(
Remove (
(parse)
36-42
: Consider improving thewithErrorWrapper
implementationThe current implementation strips component names and omits prop validation, which can impact DevTools debugging and type safety.
Consider implementing the previous suggestion to preserve component name and props:
-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });
🧹 Nitpick comments (3)
plugin/builder/utils/rclone-helper.ts (3)
8-20
: Good version management approach, but error message could be improved.The approach to finding the rclone version from multiple possible paths is well-structured. However, when the .rclone-version file isn't found, the error message could be more helpful by suggesting where to create the file.
Consider improving the error message:
- throw new Error(".rclone-version file not found"); + throw new Error(".rclone-version file not found. Please create one at the project root with the desired rclone version.");
29-42
: Consider adding download progress indication for better user experience.The fetchFile function works correctly but doesn't provide any feedback during downloads, which could be confusing for large files.
You could add a simple progress indicator:
async function fetchFile(url: string, dest: string) { return new Promise((resolve, reject) => { const file = createWriteStream(dest); get(url, (response) => { if (response.statusCode !== 200) { reject(new Error(`Failed to get '${url}' (${response.statusCode})`)); return; } + const totalSize = parseInt(response.headers['content-length'] || '0', 10); + let downloadedSize = 0; + console.log(`Downloading ${url}...`); + + response.on('data', (chunk) => { + downloadedSize += chunk.length; + if (totalSize > 0) { + const percent = Math.round((downloadedSize / totalSize) * 100); + process.stdout.write(`\rProgress: ${percent}% (${downloadedSize}/${totalSize} bytes)`); + } + }); response.pipe(file); file.on("finish", () => file.close(resolve)); file.on("error", reject); }).on("error", reject); }); }
44-64
: Well-implemented ensureRclone function, but could benefit from file integrity check.The ensureRclone function correctly manages the rclone binary, following a similar pattern to ensureNodeJs. However, it doesn't verify the integrity of the downloaded file, which could be important for security.
Consider adding checksum verification:
export async function ensureRclone() { let currentVersion: string | null = null; if (existsSync(RCLONE_VERSION_FILE)) { currentVersion = (await readFile(RCLONE_VERSION_FILE, "utf8")).trim(); } if (currentVersion !== RCLONE_VERSION) { mkdirSync(RCLONE_DEST, { recursive: true }); if (!existsSync(RCLONE_FILENAME)) { await fetchFile(RCLONE_URL, RCLONE_FILENAME); + // Verify file integrity using SHA-256 checksum + // This would require defining RCLONE_CHECKSUM constant and downloading the checksum file + // Example: + // await verifyFileChecksum(RCLONE_FILENAME, RCLONE_CHECKSUM); } await $`unzip -oj ${RCLONE_FILENAME} rclone-v${RCLONE_VERSION}-linux-amd64/rclone -d ${RCLONE_DEST}`; await $`chmod +x ${RCLONE_BIN}`; await writeFile(RCLONE_VERSION_FILE, RCLONE_VERSION, "utf8"); // Clean up old rclone archives const glob = await import("glob"); const files = glob.sync("rclone-v*-linux-amd64.zip", { cwd: startingDir }); for (const file of files) { if (file !== RCLONE_FILENAME) await unlink(join(startingDir, file)); } } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.rclone-version
(1 hunks)plugin/builder/build-txz.ts
(2 hunks)plugin/builder/utils/rclone-helper.ts
(1 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- .rclone-version
🧰 Additional context used
🧬 Code Graph Analysis (2)
plugin/builder/build-txz.ts (2)
plugin/builder/utils/nodejs-helper.ts (1)
ensureNodeJs
(45-59)plugin/builder/utils/rclone-helper.ts (1)
ensureRclone
(44-64)
plugin/builder/utils/rclone-helper.ts (1)
plugin/builder/utils/consts.ts (1)
startingDir
(11-11)
🪛 Biome (1.9.4)
unraid-ui/src/forms/renderers.ts
[error] 108-109: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '-'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 109-109: expected ,
but instead found :
Remove :
(parse)
[error] 109-109: expected ,
but instead found (
Remove (
(parse)
[error] 110-110: expected ,
but instead found :
Remove :
(parse)
[error] 110-110: expected ,
but instead found (
Remove (
(parse)
[error] 111-111: Expected a property, an expression, or a method but instead found '},'.
Expected a property, an expression, or a method here.
(parse)
🪛 GitHub Actions: CI - Main (API)
unraid-ui/src/forms/renderers.ts
[error] 107-112: TypeScript errors: TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type; TS2322 - Type 'number' is not assignable to type 'JsonFormsRendererRegistryEntry'; TS2304 - Cannot find name 'tester'; TS1136 - Property assignment expected; TS1005 - ',' expected; TS1137 - Expression or comma expected; TS1128 - Declaration or statement expected.
[error] 109-109: Build failed during vite esbuild transform: Syntax error - Expected identifier but found '-' at line 109, column 0.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
plugin/builder/build-txz.ts (2)
13-13
: Import added correctly for new rclone helper.The import is correctly placed with other utility imports, maintaining good code organization.
177-180
: Good use of Promise.all for concurrent execution.Running both
ensureNodeJs()
andensureRclone()
concurrently withPromise.all
is an excellent optimization. This allows both tools to be prepared in parallel rather than sequentially, potentially reducing build time.plugin/builder/utils/rclone-helper.ts (1)
1-64
: Overall implementation is well-structured and follows best practices.The rclone helper module is implemented in a clean, efficient manner that:
- Properly handles version checking
- Downloads files only when necessary
- Sets appropriate permissions
- Cleans up old archives
- Follows the same pattern as the existing nodejs helper
The code is well-organized, with clear separation of concerns between finding versions, downloading files, and ensuring the binary is ready to use.
unraid-ui/src/forms/renderers.ts (2)
44-48
: Good implementation of theisStringArray
predicateThe function correctly checks if a schema represents an array of strings by verifying both the parent type and the items type.
50-112
: Well-structured renderer registry with explicit entriesThe explicit definition of renderers with appropriate ranks improves maintainability and control over the rendering process. The organization into layouts, controls, labels, and fallbacks provides clear separation of concerns.
🧰 Tools
🪛 Biome (1.9.4)
[error] 108-109: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '-'.
Expected a property, a shorthand property, a getter, a setter, or a method here.
(parse)
[error] 109-109: expected
,
but instead found:
Remove :
(parse)
[error] 109-109: expected
,
but instead found(
Remove (
(parse)
[error] 110-110: expected
,
but instead found:
Remove :
(parse)
[error] 110-110: expected
,
but instead found(
Remove (
(parse)
[error] 111-111: Expected a property, an expression, or a method but instead found '},'.
Expected a property, an expression, or a method here.
(parse)
🪛 GitHub Actions: CI - Main (API)
[error] 107-112: TypeScript errors: TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type; TS2322 - Type 'number' is not assignable to type 'JsonFormsRendererRegistryEntry'; TS2304 - Cannot find name 'tester'; TS1136 - Property assignment expected; TS1005 - ',' expected; TS1137 - Expression or comma expected; TS1128 - Declaration or statement expected.
[error] 109-109: Build failed during vite esbuild transform: Syntax error - Expected identifier but found '-' at line 109, column 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
76-78
:⚠️ Potential issueFix regular expression pattern for duration validation.
The current pattern uses
d+
which should be\\d+
to correctly match digits in JavaScript regular expressions.- schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (3)
350-360
: 🛠️ Refactor suggestionEnhance parameter security in callRcloneApi.
The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
273-288
: 🛠️ Refactor suggestionAdd input validation for createRemote method.
The method doesn't validate inputs before making the API call, which could lead to errors.
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key }; - this.logger.log( - `Creating new remote: ${name} of type: ${type} with params: ${JSON.stringify(params)}` - ); // Added params logging + this.logger.log(`Creating new remote: ${name} of type: ${type}`); const result = await this.callRcloneApi('config/create', params); - // console.log('Result was: ', result); // Result is usually empty on success, potentially remove this.logger.log(`Successfully created remote: ${name}`); // Improved success log return result; // Rclone 'config/create' usually returns an empty object on success }
315-328
: 🛠️ Refactor suggestionAdd input validation for startBackup method.
Similar to createRemote, this method should validate its inputs before making the API call.
async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, }; return this.callRcloneApi('sync/copy', params); }
🧹 Nitpick comments (5)
unraid-ui/src/forms/renderers.ts (1)
36-42
: Enhance component name & props with defineComponent.The current functional component approach in
withErrorWrapper
loses the component name and prop validation, which affects DevTools debugging and type safety.-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
211-216
: Consider using a Set for more efficient duplicate detection.The current approach uses
find()
on an array, which has O(n) complexity for each check. Using a Set would be more efficient for larger option lists.- const uniqueOptionsByName = filteredOptions.reduce((acc, current) => { - if (!acc.find((item) => item.Name === current.Name)) { - acc.push(current); - } else { - console.warn( - `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` - ); - } - return acc; - }, [] as RCloneProviderOptionResponse[]); + const uniqueOptionsByName: RCloneProviderOptionResponse[] = []; + const seenNames = new Set<string>(); + + for (const option of filteredOptions) { + if (!seenNames.has(option.Name)) { + seenNames.add(option.Name); + uniqueOptionsByName.push(option); + } else { + console.warn( + `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${option.Name}` + ); + } + }
374-394
: Simplify switch statement by combining case statements.Several case statements in the switch lead to the same return value, which can be combined.
function getJsonSchemaType(rcloneType: string): string { switch (rcloneType?.toLowerCase()) { case 'int': return 'integer'; // Use 'integer' for whole numbers - case 'size': // Assuming 'size' might imply large numbers, but 'number' is safer if decimals possible - case 'number': // If rclone explicitly uses 'number' + case 'size': + case 'number': return 'number'; - case 'sizesuffix': - case 'duration': + case 'sizesuffix': + case 'duration': // Represent these as strings, validation handled by pattern/format return 'string'; case 'bool': return 'boolean'; - case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings + case 'string': + case 'text': + case 'password': default: // Default to string if type is unknown or not provided return 'string'; } }🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
43-43
: Remove unnecessary empty constructor.The empty constructor adds no value and can be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 43-43: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
379-387
: Use optional chaining for cleaner error handling.Use the optional chaining operator for a more concise and safer property access.
- if (errorBody && errorBody.error) { + if (errorBody?.error) { rcloneError = `Rclone Error: ${errorBody.error}`; // Add input details if available, check for different structures - if (errorBody.input) { + if (errorBody?.input) { rcloneError += ` | Input: ${JSON.stringify(errorBody.input)}`; } else if (params) { // Fallback to original params if errorBody.input is missing rcloneError += ` | Original Params: ${JSON.stringify(params)}`; }🧰 Tools
🪛 Biome (1.9.4)
[error] 379-379: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/rest/rest.controller.ts
(1 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- api/src/unraid-api/rest/rest.controller.ts
🧰 Additional context used
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 43-43: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 379-379: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Test API
- GitHub Check: Build Web App
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
unraid-ui/src/forms/renderers.ts (1)
109-110
: LGTM! Proper rank for fallback renderer.The fallback renderer's rank has been correctly set to 0, ensuring it's only chosen when nothing else applies.
There was a problem hiding this 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
♻️ Duplicate comments (5)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
338-338
: Remove or replace the TODO commentThe TODO comment at line 338 was previously flagged and marked as addressed in commits 72860e7 to 64252c9, but it's still present in the code. Either remove it completely if the additional tests have been implemented, or replace it with specific test cases that still need to be added.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (4)
273-285
: Add input validation for createRemote method.The
createRemote
method doesn't validate its inputs before making the API call.Add validation to ensure the required parameters are provided:
async createRemote(name: string, type: string, parameters: Record<string, any> = {}): Promise<any> { + // Validate required parameters + if (!name || !type) { + throw new Error('Name and type are required to create a remote'); + } + + // Validate name format (e.g., no spaces or special characters) + if (!/^[a-zA-Z0-9_-]+$/.test(name)) { + throw new Error('Remote name should only contain alphanumeric characters, underscores, and hyphens'); + } + // Structure the payload as expected by Rclone API const params = { name, type, parameters: parameters, // Nest the parameters object under the 'parameters' key }; this.logger.log(`Creating new remote: ${name} of type: ${type}`); const result = await this.callRcloneApi('config/create', params); this.logger.log(`Successfully created remote: ${name}`); return result; // Rclone 'config/create' usually returns an empty object on success }
312-325
: Add input validation for startBackup method.Similar to the
createRemote
method, thestartBackup
method doesn't validate its inputs before making the API call.async startBackup( srcPath: string, dstPath: string, options: Record<string, any> = {} ): Promise<any> { + // Validate required parameters + if (!srcPath || !dstPath) { + throw new Error('Source and destination paths are required to start a backup'); + } + this.logger.log(`Starting backup from ${srcPath} to ${dstPath}`); const params = { srcFs: srcPath, dstFs: dstPath, ...options, }; return this.callRcloneApi('sync/copy', params); }
347-348
: Enhance parameter security in callRcloneApi.The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
354-354
: Initialize rcloneUsername and rclonePassword properties.These credentials are used for authentication but aren't defined or initialized anywhere in the class.
+ private rcloneUsername: string = ''; + private rclonePassword: string = '';Add these properties near the other class properties (around line 42) and initialize them in the onModuleInit method if needed.
🧹 Nitpick comments (8)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (1)
112-113
: Use more specific types instead ofas any
Consider using more specific type assertions rather than
as any
. Define an interface that represents the elements structure or use the Layout type from JSONForms.- const elementsArray = verticalLayoutStd.elements as any[]; + const elementsArray = verticalLayoutStd.elements as Layout[];api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (4)
43-43
: Remove unnecessary constructor.The empty constructor doesn't add any functionality and can be safely removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 43-43: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
376-376
: Use optional chaining for better code clarity.The static analysis tool suggests using optional chaining when checking properties, which makes the code more concise and safer.
- if (errorBody && errorBody.error) { + if (errorBody?.error) {🧰 Tools
🪛 Biome (1.9.4)
[error] 376-376: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
60-79
: Simplify socket existence and running checks.The current code structure has some redundancy with duplicate logic for starting the socket. Consider simplifying this logic.
- if (socketExists) { - const isRunning = await this.checkRcloneSocketRunning(); - if (isRunning) { - this.isInitialized = true; - return; - } else { - this.logger.warn( - 'RClone socket is not running but socket exists, removing socket before starting...' - ); - await rm(this.rcloneSocketPath, { force: true }); - } - - this.logger.warn('RClone socket is not running, starting it...'); - this.isInitialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); - return; - } else { - this.logger.warn('RClone socket does not exist, creating it...'); - this.isInitialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); - return; - } + // If socket exists, check if it's running + if (socketExists) { + const isRunning = await this.checkRcloneSocketRunning(); + if (isRunning) { + this.isInitialized = true; + return; + } + // Socket exists but not running, remove it + this.logger.warn('RClone socket is not running but socket exists, removing socket before starting...'); + await rm(this.rcloneSocketPath, { force: true }); + } + + // Start socket (either because it didn't exist or wasn't running) + this.logger.warn(socketExists ? 'RClone socket is not running, starting it...' : 'RClone socket does not exist, creating it...'); + this.isInitialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath);
355-357
: Consider adding timeout and retry configuration for API calls.The comment suggests adding timeout and retry logic, but it's not implemented. It would be beneficial to add these configurations for better error handling and reliability.
headers: { Authorization: `Basic ${Buffer.from(`${this.rcloneUsername}:${this.rclonePassword}`).toString('base64')}`, }, - // Add timeout? retry logic? Consider these based on need. + timeout: { request: 30000 }, // 30 seconds timeout + retry: { + limit: 3, + methods: ['POST'], + statusCodes: [408, 429, 500, 502, 503, 504], + },web/components/RClone/RCloneOverview.vue (3)
96-103
: Fix inconsistent toast method type definition.The success method is defined as required while the error method is defined as optional, which is inconsistent.
toast?: { success: (title: string, options: { description?: string }) => void; - error?: (title: string, options: { description?: string }) => void; + error: (title: string, options: { description?: string }) => void; };
62-66
: Replace browser confirm with a styled confirmation dialog.Using the native browser confirm dialog is inconsistent with the application's design system.
Consider replacing the native browser confirm with a custom styled confirmation dialog that matches the rest of your UI components. This would provide a more consistent user experience.
142-158
: Enhance modal UX with keyboard support and click-outside handling.The modal is accessible but lacks common UX patterns like closing on ESC key or clicking outside.
Add event listeners to close the modal when:
- The user presses the ESC key
- The user clicks outside the modal content
<div v-if="showConfigModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" role="dialog" aria-modal="true" aria-labelledby="modal-title" + @keydown.esc="showConfigModal = false" + @click.self="showConfigModal = false" > <div class="bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] overflow-auto">Also consider adding focus trapping within the modal for better keyboard navigation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
(1 hunks)api/src/unraid-api/types/json-forms.ts
(3 hunks)web/components/RClone/RCloneOverview.vue
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- api/src/unraid-api/types/json-forms.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
🧰 Additional context used
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 43-43: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 376-376: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Build Web App
- GitHub Check: Test API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts (2)
1-340
: Well-structured and comprehensive test suiteThe test suite is well-organized with clear test cases covering invalid inputs, standard/advanced options filtering, duplicate handling, and provider-specific filtering rules. The test setup is clean, and assertions are thorough.
5-7
:✅ Verification successful
Ensure consistent import path style
The import paths use both absolute imports with
@app
prefix and explicitly add.js
extensions. Ensure this pattern is consistent with the project's import conventions and ESM compatibility requirements.
🏁 Script executed:
#!/bin/bash # Check the import pattern consistency across the codebase rg -g "*.ts" "from '@app.*\.js'" | head -n 10Length of output: 947
Consistent
.js
extensions confirmedVerified that
.js
extensions are already used consistently across your TypeScript files (e.g.,api/src/run.ts
,api/src/mothership/graphql-client.ts
). No further changes needed.web/components/RClone/RCloneOverview.vue (5)
129-133
: Error handling implementation looks good.The error handling for query failures has been properly implemented as suggested in previous reviews.
52-58
: Error handling for deletion looks good.The error handling for deletion response has been properly implemented as suggested in previous reviews.
145-147
: Accessibility attributes look good.The modal accessibility attributes have been properly implemented as suggested in previous reviews.
1-11
: Component imports and setup look good.The imports are well-organized and the component is properly set up using Vue 3's Composition API with TypeScript.
118-127
: Remote list implementation looks good.The remote list uses a responsive grid layout and properly delegates to the RemoteItem component.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
Outdated
Show resolved
Hide resolved
configStep: 0, | ||
showAdvanced: false, | ||
name: `${remote.name}-crypt`, | ||
type: 'crypt', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Add validation for the crypt remote name.
There's no check to ensure the auto-generated remote name (with "-crypt" suffix) doesn't already exist.
Verify that the same remote name doesn't already exist when creating a crypt remote. Consider adding a check against existing remote names or implementing a unique name generator.
🏁 Script executed:
#!/bin/bash
# Search for existing remote name validation logic in the codebase
rg -A 2 -B 2 "validateRemoteName|uniqueRemoteName|duplicate.+remote" --type ts --type vue
Length of output: 119
🏁 Script executed:
#!/bin/bash
# 1. Locate the RCloneOverview.vue file and inspect the crypt remote section
file=$(fd -t f 'RCloneOverview.vue')
echo "=== Inspecting $file ==="
sed -n '1,150p' "$file"
echo "=== Lines 68-90 ==="
sed -n '68,90p' "$file"
# 2. Search for any existing remote-name validation or uniqueness checks
rg -A2 -B2 "validateRemoteName|uniqueRemoteName|duplicate.+remote"
# 3. Find where existing remotes are listed/fetched in the codebase
rg -n "listRemotes"
Length of output: 5783
Ensure unique crypt remote names before opening the modal
There’s no client-side check in openCryptModal
to prevent the auto-generated name ("${remote.name}-crypt"
) from colliding with an existing remote. Without this, users may see a server error only after submission.
Please update web/components/RClone/RCloneOverview.vue
in the openCryptModal
function to:
• Compute the intended crypt name:
const cryptName = `${remote.name}-crypt`
• Check against fetched remotes (remotes.value?.rclone?.remotes
):
if (remotes.value?.rclone?.remotes.some(r => r.name === cryptName)) {
window.toast?.error(
'Name Already Exists',
{ description: `A crypt remote named "${cryptName}" already exists.` }
)
return
}
• Only then proceed to set initialFormState.name = cryptName
and open the modal.
This client-side validation will improve UX by preventing duplicate-name errors up front.
🤖 Prompt for AI Agents
In web/components/RClone/RCloneOverview.vue around lines 73 to 76, add a
client-side check in the openCryptModal function to ensure the auto-generated
crypt remote name (constructed as `${remote.name}-crypt`) does not already exist
in remotes.value?.rclone?.remotes. If a duplicate is found, show an error toast
with a descriptive message and return early to prevent opening the modal. Only
set initialFormState.name to the unique crypt name and proceed if no duplicates
exist. This prevents server errors caused by duplicate remote names.
There was a problem hiding this 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
♻️ Duplicate comments (2)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
366-382
: 🛠️ Refactor suggestionImprove security by redacting sensitive parameters in logs.
The method logs all parameters which might include sensitive information like passwords or tokens.
- this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(params)}`); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.debug(`Calling RClone API: ${url} with params: ${JSON.stringify(sanitizedParams)}`);
421-424
: 🛠️ Refactor suggestionAvoid logging sensitive parameters in error messages.
Similar to the previous issue, error logs may also expose sensitive information.
- this.logger.error( - `Original ${detailedErrorMessage} | Params: ${JSON.stringify(params)}`, - error.stack // Log the original HTTPError stack - ); + // Sanitize sensitive data before logging + const sanitizedParams = { ...params }; + Object.keys(sanitizedParams).forEach(key => { + if (key.toLowerCase().includes('pass') || + key.toLowerCase().includes('secret') || + key.toLowerCase().includes('token') || + key.toLowerCase().includes('key')) { + sanitizedParams[key] = '***REDACTED***'; + } + }); + this.logger.error( + `Original ${detailedErrorMessage} | Params: ${JSON.stringify(sanitizedParams)}`, + error.stack // Log the original HTTPError stack + );
🧹 Nitpick comments (3)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
56-56
: Remove unnecessary empty constructor.The empty constructor doesn't add any value and should be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 56-56: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (2)
153-153
: Replaceany
with stricter type.Using
any
reduces type safety. Consider using a more specific type for the options.- options?: Record<string, any>; + options?: Record<string, unknown>;
168-168
: Replaceany
with stricter type for consistency.For consistency with other similar fields in the codebase, consider using
unknown
instead ofany
.- parameters!: Record<string, any>; + parameters!: Record<string, unknown>;Similar change should be applied to UpdateRCloneRemoteDto parameters field on line 179.
Also applies to: 179-179
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
api/src/core/log.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (4)
api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts (6)
ObjectType
(11-12)ObjectType
(14-15)ObjectType
(17-18)ObjectType
(20-23)ObjectType
(25-28)ObjectType
(30-46)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts (4)
ObjectType
(23-35)ObjectType
(37-47)ObjectType
(49-53)InputType
(5-21)web/composables/gql/graphql.ts (7)
RCloneDrive
(1263-1269)RCloneConfigFormInput
(1257-1261)RCloneBackupConfigForm
(1238-1243)RCloneBackupSettings
(1245-1250)RCloneRemote
(1292-1299)CreateRCloneRemoteInput
(491-495)DeleteRCloneRemoteInput
(504-506)api/src/unraid-api/types/json-forms.ts (1)
DataSlice
(17-17)
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 56-56: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 398-398: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: Test API
- GitHub Check: Build Web App
- GitHub Check: Build API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
api/src/core/log.ts (2)
31-31
: Minor formatting change in the timestamp string.A trailing comma was added to the timestamp string format, which doesn't affect the functionality but ensures proper JSON formatting.
35-49
: Great addition of log redaction for sensitive data.This security enhancement will automatically redact sensitive information (passwords, tokens, secrets, keys) from logs, preventing accidental exposure of credentials. The implementation properly handles both lowercase and capitalized variations of sensitive field names.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (5)
52-55
: Good approach for handling credentials.Using environment variables with secure fallback to randomly generated credentials enhances security. The random generation is sufficiently strong using crypto.randomBytes.
150-162
: Good implementation of retry with exponential backoff.Using pRetry with exponential backoff for socket readiness checks is a robust approach that properly handles temporary failures during initialization.
218-231
: Improved socket check implementation.The implementation correctly uses a direct API call to check socket responsiveness, which is more reliable than checking file existence. The commented-out error log also helps reduce log verbosity during routine checks.
390-397
: Clean and safe response body handling.The implementation properly handles different response body formats (string or object) without assuming a specific structure, preventing potential runtime errors.
428-438
: Comprehensive error handling for different error types.The error handling differentiates between HTTP errors, standard errors, and unknown error types, providing detailed logging and appropriate error propagation.
api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts (4)
93-101
: Well-structured GraphQL schema for RClone backup settings.The RCloneBackupSettings class effectively combines configuration forms, available drives, and configured remotes in a single GraphQL type, providing a comprehensive data structure for the frontend.
79-89
: Good use of JSON Forms integration.The RCloneBackupConfigForm properly integrates with JSON Forms by exposing both data schema and UI schema, allowing for dynamic form generation based on provider options.
21-51
: Comprehensive interface modeling of RClone API responses.The interfaces accurately reflect the structure of RClone API responses, with proper typing for all fields and handling of optional properties.
118-139
: Proper validation for GraphQL input types.The CreateRCloneRemoteInput and DeleteRCloneRemoteInput classes correctly use class-validator decorators to ensure input validation before processing.
@@ -346,15 +347,15 @@ export class ConnectSettingsService { | |||
|
|||
/** shown when preconditions are met */ | |||
const formControls: UIElement[] = [ | |||
{ | |||
type: 'Control', | |||
createLabeledControl({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we should store these chunks in separate folders as we start using more of them. They add a lot of clutter to the service classes.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
ec0a535
to
d83d36c
Compare
There was a problem hiding this 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
♻️ Duplicate comments (4)
web/composables/gql/graphql.ts (1)
2063-2088
: Generated RClone operations are comprehensive.The four operations provide complete functionality for RClone remote management. Field selections are appropriate and follow consistent patterns with existing operations.
Note: As mentioned in previous reviews, ensure proper error handling is implemented when these operations are used in frontend components, particularly for duplicate remote names and backup failures.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
268-268
: Reduce excessive inline comments.As noted in previous reviews, many comments explain obvious code operations. Consider reducing commentary that doesn't add meaningful context.
76-76
:⚠️ Potential issueFix invalid regex pattern for duration validation.
The regex pattern uses
d+
which matches literal 'd' characters, not digits. This should be\\d+
to match numeric digits.- schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
383-441
: Extract complex error handling into separate method.This error handling block is quite complex and could benefit from being extracted into a dedicated error processing method for better maintainability.
🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🧹 Nitpick comments (7)
unraid-ui/src/lib/utils.ts (1)
10-20
: Good security implementation with minor suggestions.The HTML sanitization configuration properly forbids
<style>
tags and attributes, which prevents XSS attacks. The function declaration approach to preserve bindings is well-documented.Consider adding additional security restrictions:
const defaultMarkedExtension: MarkedExtension = { hooks: { postprocess(html) { return DOMPurify.sanitize(html, { FORBID_TAGS: ['style'], FORBID_ATTR: ['style'], + ALLOW_DATA_ATTR: false, + USE_PROFILES: { html: true } }); }, }, };api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (2)
387-390
: Remove redundant switch cases.The cases for 'string', 'text', and 'password' all fall through to the default case, making them redundant since the default returns 'string' anyway.
- case 'string': - case 'text': // Treat 'text' (multi-line) as 'string' in schema type - case 'password': // Passwords are strings default: // Default to string if type is unknown or not provided return 'string';🧰 Tools
🪛 Biome (1.9.4)
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
431-431
: Remove redundant switch case.The 'password' case is redundant since it falls through to the default case anyway.
- case 'password': - return 'password'; // Explicit format for password managers etc. case 'string': default:🧰 Tools
🪛 Biome (1.9.4)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (2)
57-57
: Remove unnecessary empty constructor.The empty constructor serves no purpose and can be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 57-57: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
399-399
: Use optional chaining for cleaner code.The conditional check can be simplified using optional chaining.
- if (errorBody && errorBody.error) { + if (errorBody?.error) {🧰 Tools
🪛 Biome (1.9.4)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
unraid-ui/src/forms/ComboBoxField.vue (1)
53-53
: Fix arrow function parameter formatting.Missing parentheses around the arrow function parameter.
- suggestion => suggestion.value === inputValue.value || suggestion.label === inputValue.value + (suggestion) => suggestion.value === inputValue.value || suggestion.label === inputValue.value🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 53-53:
Replacesuggestion
with(suggestion)
unraid-ui/src/forms/renderers.ts (1)
36-42
: Consider using defineComponent for better DevTools support.While the current functional component approach works, using
defineComponent
would preserve component names in Vue DevTools and provide better debugging experience.-const withErrorWrapper = (RendererComponent: Component) => { - return (props: RendererProps<ControlElement>) => { - return h(ControlWrapper, props, { - default: () => h(RendererComponent, props), - }); - }; -}; +import { defineComponent } from 'vue'; +const withErrorWrapper = (RendererComponent: Component) => + defineComponent({ + name: `WithError(${RendererComponent.name ?? 'Renderer'})`, + setup(_, { attrs }) { + return () => + h(ControlWrapper, attrs, { + default: () => h(RendererComponent, attrs), + }); + }, + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
api/src/__test__/store/modules/__snapshots__/paths.test.ts.snap
is excluded by!**/*.snap
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (97)
.cursor/rules/no-comments.mdc
(1 hunks).rclone-version
(1 hunks)api/.env.development
(1 hunks)api/generated-schema.graphql
(7 hunks)api/package.json
(0 hunks)api/src/core/log.ts
(4 hunks)api/src/store/modules/paths.ts
(2 hunks)api/src/unraid-api/auth/auth.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
(7 hunks)api/src/unraid-api/graph/resolvers/docker/docker.model.ts
(5 hunks)api/src/unraid-api/graph/resolvers/docker/docker.service.ts
(0 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
(3 hunks)api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
(2 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
(1 hunks)api/src/unraid-api/graph/resolvers/resolvers.module.ts
(3 hunks)api/src/unraid-api/graph/utils/form-utils.ts
(1 hunks)api/src/unraid-api/rest/rest.controller.ts
(1 hunks)api/src/unraid-api/rest/rest.module.ts
(1 hunks)api/src/unraid-api/types/json-forms.test.ts
(1 hunks)api/src/unraid-api/types/json-forms.ts
(3 hunks)plugin/builder/build-txz.ts
(2 hunks)plugin/builder/utils/rclone-helper.ts
(1 hunks)unraid-ui/.gitignore
(1 hunks)unraid-ui/.storybook/main.ts
(2 hunks)unraid-ui/.storybook/preview.ts
(2 hunks)unraid-ui/.storybook/tsconfig.json
(0 hunks)unraid-ui/eslint.config.ts
(1 hunks)unraid-ui/package.json
(5 hunks)unraid-ui/scripts/build-style.mjs
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/components/common/dropdown-menu/index.ts
(1 hunks)unraid-ui/src/components/common/sheet/SheetFooter.vue
(1 hunks)unraid-ui/src/components/common/tooltip/TooltipContent.vue
(1 hunks)unraid-ui/src/components/form/combobox/Combobox.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxGroup.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxInput.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxItem.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxList.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
(1 hunks)unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
(1 hunks)unraid-ui/src/components/form/combobox/index.ts
(1 hunks)unraid-ui/src/components/index.ts
(0 hunks)unraid-ui/src/components/modals/ModalTarget.vue
(1 hunks)unraid-ui/src/components/modals/index.ts
(1 hunks)unraid-ui/src/composables/useTeleport.ts
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)unraid-ui/src/forms/ControlLayout.vue
(0 hunks)unraid-ui/src/forms/ControlWrapper.vue
(1 hunks)unraid-ui/src/forms/FormErrors.vue
(1 hunks)unraid-ui/src/forms/HorizontalLayout.vue
(1 hunks)unraid-ui/src/forms/InputField.vue
(1 hunks)unraid-ui/src/forms/LabelRenderer.vue
(1 hunks)unraid-ui/src/forms/MissingRenderer.vue
(1 hunks)unraid-ui/src/forms/NumberField.vue
(1 hunks)unraid-ui/src/forms/PreconditionsLabel.vue
(1 hunks)unraid-ui/src/forms/Select.vue
(3 hunks)unraid-ui/src/forms/SteppedLayout.vue
(1 hunks)unraid-ui/src/forms/StringArrayField.vue
(1 hunks)unraid-ui/src/forms/Switch.vue
(1 hunks)unraid-ui/src/forms/UnraidSettingsLayout.vue
(1 hunks)unraid-ui/src/forms/VerticalLayout.vue
(1 hunks)unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
(1 hunks)unraid-ui/src/forms/renderer-entries.ts
(0 hunks)unraid-ui/src/forms/renderers.ts
(1 hunks)unraid-ui/src/forms/vertical-layout.renderer.ts
(0 hunks)unraid-ui/src/index.ts
(1 hunks)unraid-ui/src/lib/utils.ts
(1 hunks)unraid-ui/src/register.ts
(1 hunks)unraid-ui/src/vite-env.d.ts
(1 hunks)unraid-ui/stories/components/common/DropdownMenu.stories.ts
(1 hunks)unraid-ui/stories/components/form/Combobox.stories.ts
(1 hunks)unraid-ui/stories/components/form/Select.stories.ts
(1 hunks)unraid-ui/tsconfig.json
(2 hunks)web/components/RClone/RCloneConfig.vue
(1 hunks)web/components/RClone/RCloneOverview.vue
(1 hunks)web/components/RClone/RemoteItem.vue
(1 hunks)web/components/RClone/graphql/rclone.mutations.ts
(1 hunks)web/components/RClone/graphql/rclone.query.ts
(1 hunks)web/composables/gql/gql.ts
(3 hunks)web/composables/gql/graphql.ts
(12 hunks)web/composables/useTeleport.ts
(0 hunks)web/helpers/create-apollo-client.ts
(4 hunks)web/pages/flashbackup.vue
(1 hunks)
💤 Files with no reviewable changes (8)
- api/package.json
- unraid-ui/src/forms/vertical-layout.renderer.ts
- unraid-ui/.storybook/tsconfig.json
- api/src/unraid-api/graph/resolvers/docker/docker.service.ts
- unraid-ui/src/forms/ControlLayout.vue
- web/composables/useTeleport.ts
- unraid-ui/src/forms/renderer-entries.ts
- unraid-ui/src/components/index.ts
✅ Files skipped from review due to trivial changes (2)
- api/src/unraid-api/graph/resolvers/connect/connect-settings.service.ts
- web/components/RClone/graphql/rclone.query.ts
🚧 Files skipped from review as they are similar to previous changes (81)
- .cursor/rules/no-comments.mdc
- .rclone-version
- unraid-ui/.gitignore
- unraid-ui/src/components/common/sheet/SheetFooter.vue
- unraid-ui/src/components/common/tooltip/TooltipContent.vue
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.module.ts
- api/src/unraid-api/rest/rest.controller.ts
- unraid-ui/src/composables/useTeleport.ts
- api/.env.development
- unraid-ui/tsconfig.json
- unraid-ui/src/vite-env.d.ts
- unraid-ui/src/components/modals/index.ts
- unraid-ui/src/components/modals/ModalTarget.vue
- unraid-ui/stories/components/common/DropdownMenu.stories.ts
- unraid-ui/src/forms/PreconditionsLabel.vue
- api/src/unraid-api/graph/resolvers/connect/connect-settings.resolver.ts
- unraid-ui/src/components/common/dropdown-menu/index.ts
- plugin/builder/build-txz.ts
- unraid-ui/src/forms/StringArrayField.vue
- unraid-ui/src/forms/Switch.vue
- api/src/unraid-api/graph/resolvers/mutation/mutation.resolver.ts
- web/pages/flashbackup.vue
- unraid-ui/src/forms/composables/useJsonFormsVisibility.ts
- api/src/unraid-api/auth/auth.service.ts
- unraid-ui/src/forms/FormErrors.vue
- api/src/unraid-api/graph/utils/form-utils.ts
- unraid-ui/src/components/form/combobox/ComboboxAnchor.vue
- unraid-ui/src/forms/InputField.vue
- unraid-ui/src/components/form/combobox/ComboboxGroup.vue
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.resolver.ts
- unraid-ui/src/components.ts
- unraid-ui/src/components/form/combobox/index.ts
- unraid-ui/scripts/build-style.mjs
- api/src/unraid-api/graph/resolvers/rclone/rclone-form.service.ts
- unraid-ui/src/forms/MissingRenderer.vue
- unraid-ui/src/components/form/combobox/ComboboxItem.vue
- unraid-ui/src/forms/Select.vue
- unraid-ui/stories/components/form/Select.stories.ts
- unraid-ui/src/index.ts
- api/src/unraid-api/types/json-forms.test.ts
- unraid-ui/.storybook/main.ts
- api/src/core/log.ts
- api/src/unraid-api/graph/resolvers/flash-backup/flash-backup.model.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.mutation.resolver.ts
- unraid-ui/src/components/form/combobox/ComboboxTrigger.vue
- api/src/unraid-api/rest/rest.module.ts
- api/src/unraid-api/graph/resolvers/mutation/mutation.model.ts
- unraid-ui/src/components/form/combobox/ComboboxSeparator.vue
- api/src/unraid-api/graph/resolvers/docker/docker.model.ts
- unraid-ui/src/components/form/combobox/ComboboxList.vue
- unraid-ui/src/components/form/combobox/Combobox.vue
- api/src/store/modules/paths.ts
- unraid-ui/stories/components/form/Combobox.stories.ts
- unraid-ui/.storybook/preview.ts
- unraid-ui/eslint.config.ts
- web/components/RClone/RCloneOverview.vue
- unraid-ui/src/forms/SteppedLayout.vue
- web/components/RClone/RemoteItem.vue
- unraid-ui/src/forms/LabelRenderer.vue
- web/helpers/create-apollo-client.ts
- api/src/unraid-api/types/json-forms.ts
- web/components/RClone/graphql/rclone.mutations.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts
- api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.spec.ts
- unraid-ui/package.json
- unraid-ui/src/forms/UnraidSettingsLayout.vue
- web/components/RClone/RCloneConfig.vue
- unraid-ui/src/forms/ControlWrapper.vue
- api/src/unraid-api/graph/resolvers/resolvers.module.ts
- api/src/unraid-api/graph/resolvers/rclone/rclone.module.ts
- unraid-ui/src/components/form/combobox/ComboboxEmpty.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.resolver.ts
- unraid-ui/src/components/form/combobox/ComboboxInput.vue
- unraid-ui/src/forms/NumberField.vue
- unraid-ui/src/register.ts
- unraid-ui/src/forms/VerticalLayout.vue
- plugin/builder/utils/rclone-helper.ts
- api/generated-schema.graphql
- unraid-ui/src/forms/HorizontalLayout.vue
- api/src/unraid-api/graph/resolvers/rclone/rclone.model.ts
- web/composables/gql/gql.ts
🧰 Additional context used
🧠 Learnings (2)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
Learnt from: mdatelle
PR: unraid/api#1362
File: api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts:421-424
Timestamp: 2025-05-23T20:11:08.824Z
Learning: When logging parameters or request data in error messages, always sanitize sensitive information by checking for common sensitive parameter names (pass, secret, token, key) and replacing their values with '***REDACTED***' or similar placeholders to prevent credential exposure in logs.
unraid-ui/src/lib/utils.ts (2)
Learnt from: pujitm
PR: unraid/api#963
File: web/helpers/markdown.ts:1-41
Timestamp: 2024-11-19T16:34:44.804Z
Learning: In `web/store/updateOsChangelog.ts`, when custom logic is required, `Markdown.create` is used to obtain a custom `marked` instance. This is the correct and intended usage of the `Markdown` helper in such cases.
Learnt from: elibosley
PR: unraid/api#963
File: web/helpers/markdown.ts:1-13
Timestamp: 2024-11-18T20:52:47.800Z
Learning: All configurations and direct usage of `marked` should be centralized in the `safeParseMarkdown` function in `web/helpers/markdown.ts` to maintain security and consistency.
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 387-387: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 388-388: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 389-389: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 431-431: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 57-57: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
[error] 399-399: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
unraid-ui/src/lib/utils.ts
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
unraid-ui/src/forms/ComboBoxField.vue
[failure] 53-53:
Replace suggestion
with (suggestion)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Test API
- GitHub Check: Build API
- GitHub Check: Cloudflare Pages
🔇 Additional comments (10)
unraid-ui/src/lib/utils.ts (2)
2-3
: LGTM: Appropriate dependencies for markdown processing.The imports for DOMPurify and Marked are well-suited for secure markdown parsing functionality.
29-54
:❓ Verification inconclusive
Address class design and potential duplication concerns.
Two issues need attention:
Static-only class: The static analysis correctly identifies that this class contains only static members and could be refactored to standalone functions.
Potential duplication: Based on previous learnings, there's an existing
safeParseMarkdown
function inweb/helpers/markdown.ts
that centralizes markdown configurations. This could create inconsistency.Verify if this duplicates existing markdown functionality and consider refactoring to functions:
Refactor suggestion:
-export class Markdown { - private static instance = Markdown.create(); - - static create(...args: Parameters<Marked['use']>) { - return new Marked(defaultMarkedExtension, ...args); - } - - static async parse(markdownContent: string): Promise<string> { - return Markdown.instance.parse(markdownContent); - } -} +const defaultInstance = new Marked(defaultMarkedExtension); + +export function createMarkdownParser(...args: Parameters<Marked['use']>) { + return new Marked(defaultMarkedExtension, ...args); +} + +export async function parseMarkdown(markdownContent: string): Promise<string> { + return defaultInstance.parse(markdownContent); +}
🏁 Script executed:
#!/bin/bash # Check for existing markdown utilities that might conflict fd -t f -e ts -e js | xargs rg -l "markdown|marked" | grep -E "(helper|util)"Length of output: 168
🏁 Script executed:
#!/bin/bash # Show the implementation of the centralized markdown helper sed -n '1,200p' web/helpers/markdown.tsLength of output: 1497
🏁 Script executed:
#!/bin/bash # Search for safeParseMarkdown usage rg -n "safeParseMarkdown"Length of output: 25
Verify reuse of centralized Markdown helper instead of duplicating logic
- No
safeParseMarkdown
function exists;web/helpers/markdown.ts
exports aclass Markdown
withstatic parse()
.unraid-ui/src/lib/utils.ts
reimplements the same parser, causing duplication.- Please confirm whether
web/helpers/markdown.ts
(or its exports) can be imported intounraid-ui
.
- If yes, replace the local class with:
import { Markdown as SharedMarkdown } from 'web/helpers/markdown'; export const parseMarkdown = SharedMarkdown.parse;- If no, consider extracting the shared logic into a common package or helper that both modules can consume.
🧰 Tools
🪛 Biome (1.9.4)
[error] 29-54: Avoid classes that contain only static members.
Prefer using simple functions instead of classes with only static members.
(lint/complexity/noStaticOnlyClass)
web/composables/gql/graphql.ts (5)
684-687
: Scalar type migration looks consistent.The change from
JSONObject
toJSON
scalar across Docker-related fields appears well-coordinated with the backend schema changes. This maintains type consistency throughout the GraphQL API.Also applies to: 716-730
528-532
: RClone input types are well-designed.The input types follow GraphQL best practices with appropriate field types. Using
JSON
for parameters provides the flexibility needed for various RClone provider configurations.Also applies to: 545-547
763-769
: Flash backup types are properly structured.The types provide comprehensive coverage for flash backup operations.
FlashBackupStatus
with optionaljobId
enables proper job tracking, whileInitiateFlashBackupInput
covers all necessary backup parameters.Also applies to: 847-856
936-937
: RClone schema integration is well-organized.The nested structure under
rclone
fields in both Mutation and Query types follows GraphQL best practices for feature organization. The comprehensive type definitions support the full RClone workflow from configuration to remote management.Also applies to: 939-939, 990-992, 1237-1237, 1266-1327
1-2174
: Schema integration is well-executed.The RClone functionality additions are comprehensive and maintain consistency with existing GraphQL patterns. The scalar type migration from
JSONObject
toJSON
is applied uniformly across all affected fields.Since this appears to be a generated file, avoid manual modifications and ensure any schema changes are made at the backend level.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
422-422
: Parameter sanitization properly implemented.Good to see the
sanitizeParams
function is being used here to prevent sensitive data exposure in logs, addressing the security concerns from previous reviews.unraid-ui/src/forms/ComboBoxField.vue (1)
51-59
: Well-implemented strict validation.The strict suggestions validation logic properly checks input validity and reverts to the original value when invalid input is provided. This addresses the validation concerns from previous reviews.
🧰 Tools
🪛 GitHub Check: Build Unraid UI Library (Webcomponent Version)
[failure] 53-53:
Replacesuggestion
with(suggestion)
unraid-ui/src/forms/renderers.ts (1)
50-111
: Excellent renderer organization.The explicit renderer registry with proper ranking and error wrapper integration provides much better control over the rendering pipeline compared to the previous approach. The fallback renderer rank of 0 ensures proper precedence.
There was a problem hiding this 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
♻️ Duplicate comments (2)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
349-353
: Security implementation matches best practices.The error logging properly sanitizes parameters before logging, following the established pattern for preventing credential exposure.
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (1)
57-57
:⚠️ Potential issueFix regex pattern for Duration validation.
The Duration pattern contains invalid regex syntax. The
d+
should be\\d+
to properly match digits.- schema.pattern = '^(off|(d+(.d+)?(ns|us|\u00b5s|ms|s|m|h))+)$'; + schema.pattern = '^(off|(\\d+(.\\d+)?(ns|us|\u00b5s|ms|s|m|h))+)$';
🧹 Nitpick comments (4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
38-38
: Remove unnecessary constructor.The empty constructor serves no purpose and can be removed.
- constructor() {}
🧰 Tools
🪛 Biome (1.9.4)
[error] 38-38: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts (3)
173-175
: Replace console.warn with proper logging.Console logging should be replaced with the proper logger for consistency.
- console.warn( - `Duplicate RClone option name skipped in ${isAdvancedStep ? 'advanced' : 'standard'} slice: ${current.Name}` - ); + // Consider using a proper logger here if available + // this.logger.warn(`Duplicate RClone option name skipped...`);
272-272
: Improve type safety for parameters object.The repeated use of
any
type assertions reduces type safety. Consider defining a proper interface for the parameters structure.+interface ParametersSchema extends JsonSchema7 { + type: 'object'; + properties: Record<string, JsonSchema7>; +} + if (Object.keys(paramProperties).length > 0) { - if (!configProperties.parameters) { - configProperties.parameters = { type: 'object', properties: {} } as any; - } else if (!(configProperties.parameters as any).properties) { - (configProperties.parameters as any).properties = {}; - } - (configProperties.parameters as any).properties = { - ...(configProperties.parameters as any).properties, - ...paramProperties, - }; + if (!configProperties.parameters) { + configProperties.parameters = { type: 'object', properties: {} } as ParametersSchema; + } else if (!(configProperties.parameters as ParametersSchema).properties) { + (configProperties.parameters as ParametersSchema).properties = {}; + } + (configProperties.parameters as ParametersSchema).properties = { + ...(configProperties.parameters as ParametersSchema).properties, + ...paramProperties, + }; }Also applies to: 276-279
306-310
: Remove redundant switch cases.These cases fall through to the default clause and serve no purpose.
function getJsonSchemaType(rcloneType: string): string { switch (rcloneType?.toLowerCase()) { case 'int': return 'integer'; case 'size': case 'number': return 'number'; case 'sizesuffix': case 'duration': return 'string'; case 'bool': return 'boolean'; - case 'string': - case 'text': - case 'password': default: return 'string'; } }case 'password': return 'password'; - case 'string': default: if (examples && examples.length > 0) { return 'combobox'; } return undefined;
Also applies to: 341-341
🧰 Tools
🪛 Biome (1.9.4)
[error] 306-306: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 307-307: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 308-308: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
api/src/__test__/graphql/resolvers/rclone-api.service.test.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
(1 hunks)api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
(1 hunks)unraid-ui/src/components.ts
(1 hunks)unraid-ui/src/forms/ComboBoxField.vue
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- unraid-ui/src/components.ts
- unraid-ui/src/forms/ComboBoxField.vue
🧰 Additional context used
🧠 Learnings (1)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
Learnt from: mdatelle
PR: unraid/api#1362
File: api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts:421-424
Timestamp: 2025-05-23T20:11:08.824Z
Learning: When logging parameters or request data in error messages, always sanitize sensitive information by checking for common sensitive parameter names (pass, secret, token, key) and replacing their values with '***REDACTED***' or similar placeholders to prevent credential exposure in logs.
🪛 Biome (1.9.4)
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts
[error] 38-38: This constructor is unnecessary.
Unsafe fix: Remove the unnecessary constructor.
(lint/complexity/noUselessConstructor)
api/src/unraid-api/graph/resolvers/rclone/jsonforms/rclone-jsonforms-config.ts
[error] 306-306: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 307-307: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 308-308: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 341-341: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Build Web App
- GitHub Check: Build API
- GitHub Check: Test API
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (3)
api/src/__test__/graphql/resolvers/rclone-api.service.test.ts (2)
1-84
: LGTM! Comprehensive test setup and initialization.The test file demonstrates excellent practices with proper dependency mocking, setup/teardown, and service initialization. The beforeEach hook properly clears mocks and initializes the service state.
86-373
: Excellent test coverage for all service methods.The test suite comprehensively covers all RClone API service methods including:
- Provider and remote management operations
- Backup operations and job status tracking
- Various error scenarios (HTTP errors, network errors, unknown errors)
The error handling tests are particularly thorough, covering edge cases like empty response bodies and malformed JSON.
api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts (1)
324-326
: Good security practice with parameter sanitization.The use of
sanitizeParams
for logging parameters aligns with the retrieved learning about preventing sensitive data exposure in logs.
This plugin has been deployed to Cloudflare R2 and is available for testing.
|
Summary by CodeRabbit
New Features
Bug Fixes
JSONObject
withJSON
.Chores
Documentation
Style