|
@@ -1,5 +1,5 @@
|
|
|
import { Node } from 'reactflow'
|
|
import { Node } from 'reactflow'
|
|
|
-import { useEffect, useState } from 'react'
|
|
|
|
|
|
|
+import { useEffect, useRef, useState } from 'react'
|
|
|
import { X, Database, Maximize2, Minimize2, ExternalLink, Download, Loader2 } from 'lucide-react'
|
|
import { X, Database, Maximize2, Minimize2, ExternalLink, Download, Loader2 } from 'lucide-react'
|
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
import Editor from '@monaco-editor/react'
|
|
import Editor from '@monaco-editor/react'
|
|
@@ -106,6 +106,44 @@ export function NodeConfigModal({
|
|
|
// the port to 8090 by hand.
|
|
// the port to 8090 by hand.
|
|
|
const formUrl = `${window.location.origin}/webhook/${workflowId}${editingConfig.path || ''}`
|
|
const formUrl = `${window.location.origin}/webhook/${workflowId}${editingConfig.path || ''}`
|
|
|
|
|
|
|
|
|
|
+ // navigator.clipboard is only available in a secure context - localhost
|
|
|
|
|
+ // counts, plain HTTP over a LAN by hostname or IP does not - so the button
|
|
|
|
|
+ // has to work either way and say which happened rather than fail quietly.
|
|
|
|
|
+ const [formUrlCopyState, setFormUrlCopyState] = useState<'idle' | 'copied' | 'manual'>('idle')
|
|
|
|
|
+ const formUrlRef = useRef<HTMLElement>(null)
|
|
|
|
|
+
|
|
|
|
|
+ const selectFormUrlText = () => {
|
|
|
|
|
+ const el = formUrlRef.current
|
|
|
|
|
+ const selection = window.getSelection()
|
|
|
|
|
+ if (!el || !selection) return
|
|
|
|
|
+ const range = document.createRange()
|
|
|
|
|
+ range.selectNodeContents(el)
|
|
|
|
|
+ selection.removeAllRanges()
|
|
|
|
|
+ selection.addRange(range)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const copyFormUrl = () => {
|
|
|
|
|
+ if (!navigator.clipboard?.writeText) {
|
|
|
|
|
+ selectFormUrlText()
|
|
|
|
|
+ setFormUrlCopyState('manual')
|
|
|
|
|
+ setTimeout(() => setFormUrlCopyState('idle'), 3000)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ navigator.clipboard.writeText(formUrl).then(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ setFormUrlCopyState('copied')
|
|
|
|
|
+ setTimeout(() => setFormUrlCopyState('idle'), 2000)
|
|
|
|
|
+ },
|
|
|
|
|
+ () => {
|
|
|
|
|
+ // The API exists but the write was refused or interrupted - the
|
|
|
|
|
+ // address is still selected so it can be copied by hand.
|
|
|
|
|
+ selectFormUrlText()
|
|
|
|
|
+ setFormUrlCopyState('manual')
|
|
|
|
|
+ setTimeout(() => setFormUrlCopyState('idle'), 3000)
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 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> = {}
|
|
@@ -354,15 +392,29 @@ export function NodeConfigModal({
|
|
|
<div className="rounded border border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 p-3">
|
|
<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="text-xs font-medium text-gray-600 dark:text-gray-400">Form address</div>
|
|
|
<div className="mt-1 flex items-center gap-2">
|
|
<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>
|
|
|
|
|
|
|
+ <code
|
|
|
|
|
+ ref={formUrlRef}
|
|
|
|
|
+ className="flex-1 truncate text-sm text-gray-900 dark:text-gray-100"
|
|
|
|
|
+ >
|
|
|
|
|
+ {formUrl}
|
|
|
|
|
+ </code>
|
|
|
<button
|
|
<button
|
|
|
type="button"
|
|
type="button"
|
|
|
- onClick={() => navigator.clipboard.writeText(formUrl)}
|
|
|
|
|
|
|
+ onClick={copyFormUrl}
|
|
|
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"
|
|
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
|
|
|
|
|
|
|
+ {formUrlCopyState === 'copied'
|
|
|
|
|
+ ? 'Copied'
|
|
|
|
|
+ : formUrlCopyState === 'manual'
|
|
|
|
|
+ ? 'Selected'
|
|
|
|
|
+ : 'Copy'}
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ {formUrlCopyState === 'manual' && (
|
|
|
|
|
+ <p className="mt-1 text-xs text-amber-600 dark:text-amber-400">
|
|
|
|
|
+ Could not copy automatically - the address is selected, so copy it by hand (Ctrl+C).
|
|
|
|
|
+ </p>
|
|
|
|
|
+ )}
|
|
|
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<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.
|
|
Anyone with this address can open the form once the workflow is published and active.
|
|
|
</p>
|
|
</p>
|