Просмотр исходного кода

feat: show a form's address in the node editor

The Form node's Fields setting already rendered a row per entry with
Name, Label, Type, Required, Placeholder, Options, Accept and Max Size -
ArrayFieldEditor builds those from the item schema, so no form-specific
component was needed.

One gap surfaced while checking it: a nested array property inside an
object-list item (Options, for a Select field) fell through to a plain
text box and wrote the comma-joined string itself into a setting the
node reads as an array - a Select field ending up with one garbled
option instead of several. Fixed generically in ArrayFieldEditor so
every array-valued setting with a nested array property benefits.

NodeConfigModal now shows the form's address above the settings for a
form-trigger node, with a copy button and a note that the workflow must
be published and active for the address to answer - handleWebhook
refuses anything else.
fszontagh 1 месяц назад
Родитель
Сommit
a8b0c54319

+ 24 - 0
webui/src/components/workflow/ArrayFieldEditor.tsx

@@ -102,6 +102,30 @@ export function ArrayFieldEditor({ fieldKey, prop, value, schema, onChange }: Ar
       )
       )
     }
     }
 
 
+    // A nested list - Options on a Select field, say. Without this it fell
+    // through to the plain text box below, which wrote the comma-joined
+    // string itself into a setting the node reads as an array: a Select
+    // field with one giant option instead of several.
+    if (p?.type === 'array') {
+      const currentArr: string[] = Array.isArray(current) ? current : []
+      return (
+        <input
+          type="text"
+          className={inputClass}
+          value={currentArr.join(', ')}
+          placeholder={p?.description ? String(p.description).slice(0, 40) : 'Comma separated'}
+          onChange={(e) =>
+            write(
+              e.target.value
+                .split(',')
+                .map((s) => s.trim())
+                .filter((s) => s !== '')
+            )
+          }
+        />
+      )
+    }
+
     return (
     return (
       <input
       <input
         type="text"
         type="text"

+ 31 - 0
webui/src/components/workflow/NodeConfigModal.tsx

@@ -98,6 +98,14 @@ export function NodeConfigModal({
     selectedNodeData.id
     selectedNodeData.id
   )
   )
 
 
+  // The address a Form node answers at. window.location.origin is the
+  // WebServer's own address in production, where the WebUI is served from
+  // 8090 too. In dev the WebUI runs on its own port (3000) and vite.config.ts
+  // only proxies /api and /ws, not /webhook - so this URL points at the dev
+  // server and 404s there. A developer testing this locally needs to swap
+  // the port to 8090 by hand.
+  const formUrl = `${window.location.origin}/webhook/${workflowId}${editingConfig.path || ''}`
+
   // Names for the credential types nodes registered, so a credential shows as
   // Names for the credential types nodes registered, so a credential shows as
   // "SD.cpp Server" rather than the "basic" it is stored as.
   // "SD.cpp Server" rather than the "basic" it is stored as.
   const credentialTypeLabels: Record<string, string> = {}
   const credentialTypeLabels: Record<string, string> = {}
@@ -338,6 +346,29 @@ export function NodeConfigModal({
               </div>
               </div>
             </div>
             </div>
 
 
+            {/* The form's address. Shown here rather than left to be worked out
+                from the workflow id and the node's path, because handleWebhook
+                refuses anything that is not published and active - a form that
+                silently 404s for that reason is the first thing someone hits. */}
+            {selectedNodeData.type === 'form-trigger' && (
+              <div className="rounded border border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 p-3">
+                <div className="text-xs font-medium text-gray-600 dark:text-gray-400">Form address</div>
+                <div className="mt-1 flex items-center gap-2">
+                  <code className="flex-1 truncate text-sm text-gray-900 dark:text-gray-100">{formUrl}</code>
+                  <button
+                    type="button"
+                    onClick={() => navigator.clipboard.writeText(formUrl)}
+                    className="rounded border border-gray-300 dark:border-slate-600 px-2 py-1 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate-800"
+                  >
+                    Copy
+                  </button>
+                </div>
+                <p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
+                  Anyone with this address can open the form once the workflow is published and active.
+                </p>
+              </div>
+            )}
+
             {/* Special handling for IF Condition node */}
             {/* Special handling for IF Condition node */}
             {selectedNodeData.type === 'if-condition' ? (
             {selectedNodeData.type === 'if-condition' ? (
               <div className="space-y-4">
               <div className="space-y-4">