Repository: kestra-io/kestra
Issue: Copy buttons don't work in blueprints
Labels: bug

### Expected Behavior

_No response_

### Actual Behaviour

When clicking on the button to copy blueprint code content, the clipboard doesn't get the data (pasting doesn't work).


This seems to be only the case on localhost installation (when deployed on proper https endpoints the copy button works)

https://stackoverflow.com/questions/51805395/navigator-clipboard-is-undefined)




### Steps To Reproduce

_No response_

### Environment Information

- Kestra Version: 0.10.2
- Operating System (OS / Docker / Kubernetes): Docker, MacOS
- Java Version (If not docker):


### Example flow

_No response_

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/ui/src/components/flows/blueprints/BlueprintDetail.vue b/ui/src/components/flows/blueprints/BlueprintDetail.vue
index 56871259ba..b88af2c6c0 100644
--- a/ui/src/components/flows/blueprints/BlueprintDetail.vue
+++ b/ui/src/components/flows/blueprints/BlueprintDetail.vue
@@ -55,7 +55,7 @@
                                 <template #nav>
                                     <div class="position-absolute copy-wrapper">
                                         <el-tooltip trigger="click" content="Copied" placement="left" :auto-close="2000">
-                                            <el-button text round :icon="icon.ContentCopy" @click="copy(blueprint.flow)" />
+                                            <el-button text round :icon="icon.ContentCopy" @click="Utils.copy(blueprint.flow)" />
                                         </el-tooltip>
                                     </div>
                                 </template>
@@ -87,6 +87,7 @@
     import LowCodeEditor from "../../inputs/LowCodeEditor.vue";
     import TaskIcon from "../../plugins/TaskIcon.vue";
     import HomeOutline from "vue-material-design-icons/HomeOutline.vue";
+    import Utils from "../../../utils/utils";
 </script>
 <script>
     import YamlUtils from "../../../utils/yamlUtils";
@@ -97,7 +98,6 @@
     import permission from "../../../models/permission";
     import action from "../../../models/action";
 
-
     export default {
         components: {Markdown},
         data() {
@@ -130,9 +130,6 @@
                     this.$router.push({name: "blueprints"})
                 }
             },
-            copy(text) {
-                navigator.clipboard.writeText(text);
-            },
             asAutoRestoreDraft() {
                 localStorage.setItem("autoRestore-creation_draft", this.blueprint.flow);
             }
diff --git a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
index e41dfb91d9..cfdfc486f3 100644
--- a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
+++ b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
@@ -79,6 +79,7 @@
     import permission from "../../../../models/permission";
     import action from "../../../../models/action";
     import {mapState} from "vuex";
+    import Utils from "../../../../utils/utils";
 
     export default {
         mixins: [RestoreUrl, DataTableActions],
@@ -115,7 +116,7 @@
                 return this.$route?.query?.selectedTag ?? 0
             },
             async copy(blueprintId) {
-                await navigator.clipboard.writeText(
+                await Utils.copy(
                     (await this.$http.get(`${this.blueprintBaseUri}/${blueprintId}/flow`)).data
                 );
             },
diff --git a/ui/src/utils/utils.js b/ui/src/utils/utils.js
index 2eb04fca11..bcae98933a 100644
--- a/ui/src/utils/utils.js
+++ b/ui/src/utils/utils.js
@@ -192,4 +192,22 @@ export default class Utils {
 
         return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
     }
+
+    static async copy(text) {
+        if(navigator.clipboard) {
+            await navigator.clipboard.writeText(text);
+            return;
+        }
+
+        const node = document.createElement("textarea");
+        node.style.position = "absolute";
+        node.style.left = "-9999px";
+        node.textContent = text;
+        document.body.appendChild(node).value = text;
+        node.select()
+
+        document.execCommand('copy');
+
+        document.body.removeChild(node);
+    }
 }


Test patch: