]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Use password and select config fields
authorshamoon <4887959+shamoon@users.noreply.github.com>
Thu, 24 Apr 2025 20:54:42 +0000 (13:54 -0700)
committershamoon <4887959+shamoon@users.noreply.github.com>
Thu, 24 Apr 2025 21:14:25 +0000 (14:14 -0700)
src-ui/src/app/components/admin/config/config.component.html
src-ui/src/app/components/admin/config/config.component.ts
src-ui/src/app/components/common/input/password/password.component.html
src-ui/src/app/data/paperless-config.ts
src/documents/tests/test_api_app_config.py
src/paperless/serialisers.py

index 0f74339fb513d273b66b7137a7f6a2286298f21e..ca1cc9e44770f779c8be14ec70faf737c9dc5bda 100644 (file)
@@ -35,6 +35,7 @@
                                                     @case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
                                                     @case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
                                                     @case (ConfigOptionType.File) { <pngx-input-file [formControlName]="option.key" (upload)="uploadFile($event, option.key)" [error]="errors[option.key]"></pngx-input-file> }
+                                                    @case (ConfigOptionType.Password) { <pngx-input-password [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-password> }
                                                 }
                                             </div>
                                         </div>
index 76f6b87955a6eab119a561643d68813d4c02ee63..767c084b101cb6aa02530c2cfac1fbb97a741fc5 100644 (file)
@@ -29,6 +29,7 @@ import { SettingsService } from 'src/app/services/settings.service'
 import { ToastService } from 'src/app/services/toast.service'
 import { FileComponent } from '../../common/input/file/file.component'
 import { NumberComponent } from '../../common/input/number/number.component'
+import { PasswordComponent } from '../../common/input/password/password.component'
 import { SelectComponent } from '../../common/input/select/select.component'
 import { SwitchComponent } from '../../common/input/switch/switch.component'
 import { TextComponent } from '../../common/input/text/text.component'
@@ -46,6 +47,7 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
     TextComponent,
     NumberComponent,
     FileComponent,
+    PasswordComponent,
     AsyncPipe,
     NgbNavModule,
     FormsModule,
index 1a70ff4f68aae7398c02d15c952a209b97ff9bcc..6b8ae75cdd310505bd10cdf652f8c5c4c52ce91d 100644 (file)
@@ -1,17 +1,24 @@
-<div class="mb-3">
-  <label class="form-label" [for]="inputId">{{title}}</label>
-  <div class="input-group" [class.is-invalid]="error">
-    <input #inputField [type]="showReveal && textVisible ? 'text' : 'password'" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (focus)="onFocus()" (focusout)="onFocusOut()" (change)="onChange(value)" [disabled]="disabled" [autocomplete]="autocomplete">
-    @if (showReveal) {
-      <button type="button" class="btn btn-outline-secondary" (click)="toggleVisibility()" i18n-title title="Show password" [disabled]="disabled || disableRevealToggle">
-        <i-bs name="eye"></i-bs>
-      </button>
+<div class="mb-3" [class.pb-3]="error">
+  <div class="row">
+    <div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
+      @if (title) {
+        <label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
+      }
+    </div>
+  <div class="position-relative" [class.col-md-9]="horizontal">
+    <div class="input-group" [class.is-invalid]="error">
+      <input #inputField [type]="showReveal && textVisible ? 'text' : 'password'" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (focus)="onFocus()" (focusout)="onFocusOut()" (change)="onChange(value)" [disabled]="disabled" [autocomplete]="autocomplete">
+      @if (showReveal) {
+        <button type="button" class="btn btn-outline-secondary" (click)="toggleVisibility()" i18n-title title="Show password" [disabled]="disabled || disableRevealToggle">
+          <i-bs name="eye"></i-bs>
+        </button>
+      }
+    </div>
+    <div class="invalid-feedback">
+      {{error}}
+    </div>
+    @if (hint) {
+      <small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
     }
   </div>
-  <div class="invalid-feedback">
-    {{error}}
-  </div>
-  @if (hint) {
-    <small class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
-  }
 </div>
index 0c309c7d2bf5f57b3896822001f1670af2c10d59..96e4da924fae2df9bd1555f11793f6a65f12bc6b 100644 (file)
@@ -44,6 +44,7 @@ export enum ConfigOptionType {
   Boolean = 'boolean',
   JSON = 'json',
   File = 'file',
+  Password = 'password',
 }
 
 export const ConfigCategory = {
@@ -52,6 +53,11 @@ export const ConfigCategory = {
   AI: $localize`AI Settings`,
 }
 
+export const LLMBackendConfig = {
+  OPENAI: 'openai',
+  OLLAMA: 'ollama',
+}
+
 export interface ConfigOption {
   key: string
   title: string
@@ -191,7 +197,8 @@ export const PaperlessConfigOptions: ConfigOption[] = [
   {
     key: 'llm_backend',
     title: $localize`LLM Backend`,
-    type: ConfigOptionType.String,
+    type: ConfigOptionType.Select,
+    choices: mapToItems(LLMBackendConfig),
     config_key: 'PAPERLESS_LLM_BACKEND',
     category: ConfigCategory.AI,
   },
@@ -205,7 +212,7 @@ export const PaperlessConfigOptions: ConfigOption[] = [
   {
     key: 'llm_api_key',
     title: $localize`LLM API Key`,
-    type: ConfigOptionType.String,
+    type: ConfigOptionType.Password,
     config_key: 'PAPERLESS_LLM_API_KEY',
     category: ConfigCategory.AI,
   },
index 0e298545cba11e64e83a65f38f033f2d38ccb4f6..25d18e140a1c8d65c655ebd10963d76c63d38a99 100644 (file)
@@ -32,33 +32,31 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
 
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         self.maxDiff = None
-        self.assertEqual(
-            json.dumps(response.data[0]),
-            json.dumps(
-                {
-                    "id": 1,
-                    "user_args": None,
-                    "output_type": None,
-                    "pages": None,
-                    "language": None,
-                    "mode": None,
-                    "skip_archive_file": None,
-                    "image_dpi": None,
-                    "unpaper_clean": None,
-                    "deskew": None,
-                    "rotate_pages": None,
-                    "rotate_pages_threshold": None,
-                    "max_image_pixels": None,
-                    "color_conversion_strategy": None,
-                    "app_title": None,
-                    "app_logo": None,
-                    "ai_enabled": False,
-                    "llm_backend": None,
-                    "llm_model": None,
-                    "llm_api_key": None,
-                    "llm_url": None,
-                },
-            ),
+        self.assertDictEqual(
+            response.data[0],
+            {
+                "id": 1,
+                "user_args": None,
+                "output_type": None,
+                "pages": None,
+                "language": None,
+                "mode": None,
+                "skip_archive_file": None,
+                "image_dpi": None,
+                "unpaper_clean": None,
+                "deskew": None,
+                "rotate_pages": None,
+                "rotate_pages_threshold": None,
+                "max_image_pixels": None,
+                "color_conversion_strategy": None,
+                "app_title": None,
+                "app_logo": None,
+                "ai_enabled": False,
+                "llm_backend": None,
+                "llm_model": None,
+                "llm_api_key": None,
+                "llm_url": None,
+            },
         )
 
     def test_api_get_ui_settings_with_config(self):
index 461eef587230ae1ce6d0e329e76612d2631a29ef..4ff9ceb466ab1b6f6b83b80682eceb480405d0c4 100644 (file)
@@ -185,6 +185,10 @@ class ProfileSerializer(serializers.ModelSerializer):
 
 class ApplicationConfigurationSerializer(serializers.ModelSerializer):
     user_args = serializers.JSONField(binary=True, allow_null=True)
+    llm_api_key = ObfuscatedPasswordField(
+        required=False,
+        allow_null=True,
+    )
 
     def run_validation(self, data):
         # Empty strings treated as None to avoid unexpected behavior