]> 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>
Wed, 2 Jul 2025 18:01:52 +0000 (11:01 -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/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 eee61731070d83eca4c10a66c6fa00d3745cc89d..b0dcba57b4b37159078d6ff764abc9395d260ed0 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 1d8f27b33f0c3757b1a568bc46f2651581a2dbae..eea134692d12d554f84fcc7bcaa4199e3461ed87 100644 (file)
@@ -44,6 +44,7 @@ export enum ConfigOptionType {
   Boolean = 'boolean',
   JSON = 'json',
   File = 'file',
+  Password = 'password',
 }
 
 export const ConfigCategory = {
@@ -53,6 +54,11 @@ export const ConfigCategory = {
   AI: $localize`AI Settings`,
 }
 
+export const LLMBackendConfig = {
+  OPENAI: 'openai',
+  OLLAMA: 'ollama',
+}
+
 export interface ConfigOption {
   key: string
   title: string
@@ -267,7 +273,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,
   },
@@ -281,7 +288,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 9943a76ee2bee1541ab095d2bd8b5c5989e59763..716b72a8ae87ff8bad4c9b726210d005fbb7d7f8 100644 (file)
@@ -190,6 +190,10 @@ class ProfileSerializer(serializers.ModelSerializer):
 class ApplicationConfigurationSerializer(serializers.ModelSerializer):
     user_args = serializers.JSONField(binary=True, allow_null=True)
     barcode_tag_mapping = 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