diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 60b12c0d2827..530f4e438291 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -8555,6 +8555,11 @@ export interface components { * @default false */ use_cached_job?: boolean | null; + /** + * Version + * @description The version of the workflow to invoke. + */ + version?: number | null; }; /** * ItemTagsCreatePayload diff --git a/client/src/components/Form/FormElement.vue b/client/src/components/Form/FormElement.vue index 3ecde5128092..1a7296f401ef 100644 --- a/client/src/components/Form/FormElement.vue +++ b/client/src/components/Form/FormElement.vue @@ -3,9 +3,12 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faCaretSquareDown, faCaretSquareUp } from "@fortawesome/free-regular-svg-icons"; import { faArrowsAltH, faExclamation, faTimes } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import { sanitize } from "dompurify"; import type { ComputedRef } from "vue"; import { computed, ref, useAttrs } from "vue"; +import { linkify } from "@/utils/utils"; + import type { FormParameterAttributes, FormParameterTypes, FormParameterValue } from "./parameterTypes"; import FormBoolean from "./Elements/FormBoolean.vue"; @@ -181,7 +184,9 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] ! :class="{ alert: hasAlert, 'alert-info': hasAlert }">
- +
diff --git a/client/src/components/Sharing/Embeds/WorkflowEmbed.vue b/client/src/components/Sharing/Embeds/WorkflowEmbed.vue index 538ca956c8ea..649794bc7d9b 100644 --- a/client/src/components/Sharing/Embeds/WorkflowEmbed.vue +++ b/client/src/components/Sharing/Embeds/WorkflowEmbed.vue @@ -6,8 +6,8 @@ import { useDebounce } from "@vueuse/core"; import { BButton, BFormCheckbox, BFormInput, BInputGroup, BInputGroupAppend } from "bootstrap-vue"; import { computed, reactive, ref } from "vue"; -import { getAppRoot } from "@/onload/loadConfig"; import { copy } from "@/utils/clipboard"; +import { getFullAppUrl } from "@/utils/utils"; import ZoomControl from "@/components/Workflow/Editor/ZoomControl.vue"; import WorkflowPublished from "@/components/Workflow/Published/WorkflowPublished.vue"; @@ -39,13 +39,8 @@ function onChangePosition(event: Event, xy: "x" | "y") { } } -const root = computed(() => { - const port = window.location.port ? `:${window.location.port}` : ""; - return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`; -}); - const embedUrl = computed(() => { - let url = `${root.value}published/workflow?id=${props.id}&embed=true`; + let url = getFullAppUrl(`published/workflow?id=${props.id}&embed=true`); url += `&buttons=${settings.buttons}`; url += `&about=${settings.about}`; url += `&heading=${settings.heading}`; diff --git a/client/src/components/Sharing/SharingPage.vue b/client/src/components/Sharing/SharingPage.vue index e05fcb2d3480..1bbfb73a981c 100644 --- a/client/src/components/Sharing/SharingPage.vue +++ b/client/src/components/Sharing/SharingPage.vue @@ -9,6 +9,7 @@ import { getGalaxyInstance } from "@/app"; import { useToast } from "@/composables/toast"; import { getAppRoot } from "@/onload/loadConfig"; import { errorMessageAsString } from "@/utils/simple-error"; +import { getFullAppUrl } from "@/utils/utils"; import type { Item, ShareOption } from "./item"; @@ -52,11 +53,6 @@ const item = ref({ extra: defaultExtra(), }); -const itemRoot = computed(() => { - const port = window.location.port ? `:${window.location.port}` : ""; - return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`; -}); - const itemUrl = reactive({ prefix: "", slug: "", @@ -68,7 +64,7 @@ watch( if (value) { const index = value.lastIndexOf("/"); - itemUrl.prefix = itemRoot.value + value.substring(0, index + 1); + itemUrl.prefix = getFullAppUrl(value.substring(0, index + 1)); itemUrl.slug = value.substring(index + 1); } }, diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index a3ffdf9873a0..ffb07080dc1f 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -774,7 +774,9 @@ export default { this.report.markdown = markdown; }, onRun() { - const runUrl = `/workflows/run?id=${this.id}`; + const runUrl = `/workflows/run?id=${this.id}${ + this.version !== undefined ? `&version=${this.version}` : "" + }`; this.onNavigate(runUrl); }, async onNavigate(url, forceSave = false, ignoreChanges = false) { diff --git a/client/src/components/Workflow/Published/WorkflowInformation.vue b/client/src/components/Workflow/Published/WorkflowInformation.vue index 2b5e986ff9c2..b9d9afb1acb5 100644 --- a/client/src/components/Workflow/Published/WorkflowInformation.vue +++ b/client/src/components/Workflow/Published/WorkflowInformation.vue @@ -5,8 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { computed } from "vue"; import { RouterLink } from "vue-router"; -import { getAppRoot } from "@/onload/loadConfig"; import { useUserStore } from "@/stores/userStore"; +import { getFullAppUrl } from "@/utils/utils"; import Heading from "@/components/Common/Heading.vue"; import CopyToClipboard from "@/components/CopyToClipboard.vue"; @@ -42,17 +42,12 @@ const gravatarSource = computed( const publishedByUser = computed(() => `/workflows/list_published?owner=${props.workflowInfo?.owner}`); -const root = computed(() => { - const port = window.location.port ? `:${window.location.port}` : ""; - return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`; -}); - const relativeLink = computed(() => { return `/published/workflow?id=${props.workflowInfo.id}`; }); const fullLink = computed(() => { - return `${root.value}${relativeLink.value.substring(1)}`; + return getFullAppUrl(relativeLink.value.substring(1)); }); const userOwned = computed(() => { diff --git a/client/src/components/Workflow/Run/WorkflowRun.vue b/client/src/components/Workflow/Run/WorkflowRun.vue index 91b338323ad9..fbf47ec93a9d 100644 --- a/client/src/components/Workflow/Run/WorkflowRun.vue +++ b/client/src/components/Workflow/Run/WorkflowRun.vue @@ -27,12 +27,14 @@ const router = useRouter(); interface Props { workflowId: string; + version?: string; preferSimpleForm?: boolean; simpleFormTargetHistory?: string; simpleFormUseJobCache?: boolean; } const props = withDefaults(defineProps(), { + version: undefined, preferSimpleForm: false, simpleFormTargetHistory: "current", simpleFormUseJobCache: false, @@ -49,7 +51,9 @@ const workflowName = ref(""); const workflowModel: any = ref(null); const currentHistoryId = computed(() => historyStore.currentHistoryId); -const editorLink = computed(() => `/workflows/edit?id=${props.workflowId}`); +const editorLink = computed( + () => `/workflows/edit?id=${props.workflowId}${props.version ? `&version=${props.version}` : ""}` +); const historyStatusKey = computed(() => `${currentHistoryId.value}_${lastUpdateTime.value}`); const isOwner = computed(() => currentUser.value?.username === workflowModel.value.runData.owner); const lastUpdateTime = computed(() => historyItemsStore.lastUpdateTime); @@ -74,7 +78,7 @@ function handleSubmissionError(error: string) { } function loadRun() { - getRunData(props.workflowId) + getRunData(props.workflowId, props.version || undefined) .then((runData) => { const incomingModel = new WorkflowRunModel(runData); simpleForm.value = props.preferSimpleForm; @@ -116,7 +120,7 @@ function loadRun() { } async function onImport() { - const response = await copyWorkflow(props.workflowId, workflowModel.value.runData.owner); + const response = await copyWorkflow(props.workflowId, workflowModel.value.runData.owner, props.version); router.push(`/workflows/edit?id=${response.id}`); } diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index 2627554e526f..6b23e8895ea0 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -7,7 +7,7 @@
- Workflow: {{ model.name }} + Workflow: {{ model.name }} (version: {{ model.runData.version + 1 }}) - Workflow: {{ model.name }} + Workflow: {{ model.name }} (version: {{ model.runData.version + 1 }}) import { library } from "@fortawesome/fontawesome-svg-core"; -import { faStar as farStar } from "@fortawesome/free-regular-svg-icons"; -import { - faCaretDown, - faCopy, - faDownload, - faFileExport, - faShareAlt, - faStar, - faTrashRestore, -} from "@fortawesome/free-solid-svg-icons"; +import { faCopy, faDownload, faLink, faShareAlt, faTrashRestore } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BButton } from "bootstrap-vue"; import { storeToRefs } from "pinia"; @@ -19,9 +10,11 @@ import { copyWorkflow, undeleteWorkflow } from "@/components/Workflow/workflows. import { useConfirmDialog } from "@/composables/confirmDialog"; import { Toast } from "@/composables/toast"; import { useUserStore } from "@/stores/userStore"; +import { copy } from "@/utils/clipboard"; import { withPrefix } from "@/utils/redirect"; +import { getFullAppUrl } from "@/utils/utils"; -library.add(faCaretDown, faCopy, faDownload, faFileExport, faShareAlt, farStar, faStar, faTrashRestore); +library.add(faCopy, faDownload, faLink, faShareAlt, faTrashRestore); interface Props { workflow: any; @@ -72,11 +65,36 @@ async function onRestore() { Toast.info("Workflow restored"); } } + +const relativeLink = computed(() => { + return `/published/workflow?id=${props.workflow.id}`; +}); + +const fullLink = computed(() => { + return getFullAppUrl(relativeLink.value.substring(1)); +}); + +function onCopyPublicLink() { + copy(fullLink.value); + Toast.success("Link to workflow copied"); +}