diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts
index a0e23193d..702a8dc6a 100644
--- a/src-ui/src/app/app.module.ts
+++ b/src-ui/src/app/app.module.ts
@@ -131,6 +131,7 @@ import { GlobalSearchComponent } from './components/app-frame/global-search/glob
import { HotkeyDialogComponent } from './components/common/hotkey-dialog/hotkey-dialog.component'
import { DeletePagesConfirmDialogComponent } from './components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component'
import { TrashComponent } from './components/admin/trash/trash.component'
+import { EntriesComponent } from './components/common/input/entries/entries.component'
import {
airplane,
archive,
@@ -522,6 +523,7 @@ function initializeApp(settings: SettingsService) {
HotkeyDialogComponent,
DeletePagesConfirmDialogComponent,
TrashComponent,
+ EntriesComponent,
],
bootstrap: [AppComponent],
imports: [
diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
index 5bbc2776c..c5c486fac 100644
--- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
@@ -336,8 +336,8 @@
diff --git a/src-ui/src/app/components/common/input/entries/entries.component.html b/src-ui/src/app/components/common/input/entries/entries.component.html
new file mode 100644
index 000000000..c75007c25
--- /dev/null
+++ b/src-ui/src/app/components/common/input/entries/entries.component.html
@@ -0,0 +1,29 @@
+
+
+
+ @if (title) {
+
+ }
+
+
+
+ @for (entry of entries; let i = $index; track entry[0]) {
+
+
+
+
+
+ }
+ @if (hint) {
+
+ }
+
+ {{error}}
+
+
+
+
diff --git a/src-ui/src/app/components/common/input/entries/entries.component.scss b/src-ui/src/app/components/common/input/entries/entries.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src-ui/src/app/components/common/input/entries/entries.component.spec.ts b/src-ui/src/app/components/common/input/entries/entries.component.spec.ts
new file mode 100644
index 000000000..0629393ce
--- /dev/null
+++ b/src-ui/src/app/components/common/input/entries/entries.component.spec.ts
@@ -0,0 +1,63 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import {
+ FormsModule,
+ NG_VALUE_ACCESSOR,
+ ReactiveFormsModule,
+} from '@angular/forms'
+import { EntriesComponent } from './entries.component'
+import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+
+describe('EntriesComponent', () => {
+ let component: EntriesComponent
+ let fixture: ComponentFixture
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [EntriesComponent],
+ imports: [
+ FormsModule,
+ ReactiveFormsModule,
+ NgxBootstrapIconsModule.pick(allIcons),
+ ],
+ }).compileComponents()
+ })
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(EntriesComponent)
+ component = fixture.componentInstance
+ fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
+ fixture.detectChanges()
+ })
+
+ it('should add an entry', () => {
+ component.addEntry()
+ expect(component.entries.length).toBe(1)
+ expect(component.entries[0]).toEqual(['', ''])
+ })
+
+ it('should remove an entry', () => {
+ component.addEntry()
+ component.addEntry()
+ expect(component.entries.length).toBe(2)
+ component.removeEntry(0)
+ expect(component.entries.length).toBe(1)
+ })
+
+ it('should write value correctly', () => {
+ const newValue = { key1: 'value1', key2: 'value2' }
+ component.writeValue(newValue)
+ expect(component.entries).toEqual(Object.entries(newValue))
+ })
+
+ it('should correctly generate the value on input change', () => {
+ const onChangeSpy = jest.spyOn(component, 'onChange')
+ component.entries = [
+ ['key1', 'value1'],
+ ['key2', ''],
+ ['', ''],
+ ]
+ component.inputChange()
+ // Only the first two entries should be included
+ expect(onChangeSpy).toHaveBeenCalledWith({ key1: 'value1', key2: '' })
+ })
+})
diff --git a/src-ui/src/app/components/common/input/entries/entries.component.ts b/src-ui/src/app/components/common/input/entries/entries.component.ts
new file mode 100644
index 000000000..9017477d4
--- /dev/null
+++ b/src-ui/src/app/components/common/input/entries/entries.component.ts
@@ -0,0 +1,45 @@
+import { Component, forwardRef } from '@angular/core'
+import { AbstractInputComponent } from '../abstract-input'
+import { NG_VALUE_ACCESSOR } from '@angular/forms'
+
+@Component({
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => EntriesComponent),
+ multi: true,
+ },
+ ],
+ selector: 'pngx-input-entries',
+ templateUrl: './entries.component.html',
+ styleUrl: './entries.component.scss',
+})
+export class EntriesComponent extends AbstractInputComponent