Skip to content

Commit 85b1d43

Browse files
committed
feat(ui): copy to clipboard for non-ssl hosts
1 parent aa1a14e commit 85b1d43

File tree

5 files changed

+108
-88
lines changed

5 files changed

+108
-88
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<template>
2+
<div>
3+
<textarea
4+
style="position: absolute; left: -9999px; top: -9999px;"
5+
ref="copy_to_clipboard_textarea"
6+
></textarea>
7+
<v-btn
8+
icon
9+
color="white"
10+
@click="copy()"
11+
>
12+
<v-icon>mdi-content-copy</v-icon>
13+
</v-btn>
14+
</div>
15+
</template>
16+
17+
<script>
18+
import EventBus from '@/event-bus';
19+
20+
export default {
21+
props: {
22+
text: String,
23+
successMessage: {
24+
type: String,
25+
default: 'Text copied to clipboard!'
26+
},
27+
},
28+
methods: {
29+
copy() {
30+
try {
31+
const el = this.$refs.copy_to_clipboard_textarea;
32+
el.value = this.text;
33+
el.focus();
34+
el.select();
35+
const successful = document.execCommand('copy');
36+
37+
if (!successful) {
38+
throw new Error('Fallback copy failed');
39+
}
40+
41+
EventBus.$emit('i-snackbar', {
42+
color: 'success',
43+
text: this.successMessage
44+
});
45+
} catch (e) {
46+
EventBus.$emit('i-snackbar', {
47+
color: 'error',
48+
text: `Can't copy to clipboard: ${e.message}`
49+
});
50+
}
51+
}
52+
}
53+
};
54+
</script>

web/src/components/UserForm.vue

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ import axios from 'axios';
191191
import EventBus from '@/event-bus';
192192
import EditDialog from '@/components/EditDialog.vue';
193193
import ChangePasswordForm from '@/components/ChangePasswordForm.vue';
194+
import copyToClipboard from '@/lib/copyToClipboard';
194195
195196
export default {
196197
components: { ChangePasswordForm, EditDialog },
@@ -249,6 +250,8 @@ export default {
249250
},
250251
251252
methods: {
253+
copyToClipboard,
254+
252255
afterLoadData() {
253256
if (this.item.totp == null) {
254257
this.totpEnabled = false;
@@ -259,21 +262,6 @@ export default {
259262
}
260263
},
261264
262-
async copyToClipboard(text) {
263-
try {
264-
await window.navigator.clipboard.writeText(text);
265-
EventBus.$emit('i-snackbar', {
266-
color: 'success',
267-
text: 'The recovery code has been copied to your clipboard.',
268-
});
269-
} catch (e) {
270-
EventBus.$emit('i-snackbar', {
271-
color: 'error',
272-
text: `Can't copy the recovery code: ${e.message}`,
273-
});
274-
}
275-
},
276-
277265
getItemsUrl() {
278266
return '/api/users';
279267
},

web/src/lib/copyToClipboard.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,29 @@ import EventBus from '@/event-bus';
22

33
export default async function copyToClipboard(content, message) {
44
try {
5-
await window.navigator.clipboard.writeText(content);
5+
const el = document.createElement('textarea');
6+
el.value = this.link_url;
7+
el.setAttribute('readonly', '');
8+
el.style.position = 'absolute';
9+
el.style.left = '-9999px';
10+
document.body.appendChild(el);
11+
const selected = document.getSelection().rangeCount > 0
12+
? document.getSelection().getRangeAt(0) : false;
13+
el.select();
14+
document.execCommand('copy');
15+
document.body.removeChild(el);
16+
if (selected) {
17+
document.getSelection().removeAllRanges();
18+
document.getSelection().addRange(selected);
19+
}
20+
21+
const successful = document.execCommand('copy');
22+
// document.body.removeChild(textArea);
23+
24+
if (!successful) {
25+
throw new Error('Fallback copy failed');
26+
}
27+
628
EventBus.$emit('i-snackbar', {
729
color: 'success',
830
text: message,

web/src/views/Runners.vue

Lines changed: 25 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,11 @@
5151
style="background: gray; color: white; display: block; font-size: 14px;"
5252
>{{ (newRunner || {}).token }}</code>
5353

54-
<v-btn
54+
<CopyClipboardButton
5555
style="position: absolute; right: 10px; top: 2px;"
56-
icon
57-
color="white"
58-
@click="copyToClipboard((newRunner || {}).token)"
59-
>
60-
<v-icon>mdi-content-copy</v-icon>
61-
</v-btn>
56+
:text="(newRunner || {}).token"
57+
/>
58+
6259
</div>
6360
</div>
6461

@@ -82,14 +79,10 @@
8279
</v-icon>
8380
</v-btn>
8481

85-
<v-btn
82+
<CopyClipboardButton
8683
style="position: absolute; right: 50px; top: 2px;"
87-
icon
88-
color="white"
89-
@click="copyToClipboard((newRunner || {}).private_key)"
90-
>
91-
<v-icon>mdi-content-copy</v-icon>
92-
</v-btn>
84+
:text="(newRunner || {}).private_key"
85+
/>
9386
</div>
9487
</div>
9588

@@ -116,14 +109,10 @@
116109
class="pa-2"
117110
>{{ runnerConfigCommand }}</pre>
118111

119-
<v-btn
112+
<CopyClipboardButton
120113
style="position: absolute; right: 10px; top: 10px;"
121-
icon
122-
color="white"
123-
@click="copyToClipboard(runnerConfigCommand)"
124-
>
125-
<v-icon>mdi-content-copy</v-icon>
126-
</v-btn>
114+
:text="runnerConfigCommand"
115+
/>
127116
</div>
128117

129118
<div class="mt-3">Launching the runner:</div>
@@ -148,14 +137,10 @@
148137
class="pa-2"
149138
>{{ runnerSetupCommand }}</pre>
150139

151-
<v-btn
140+
<CopyClipboardButton
152141
style="position: absolute; right: 10px; top: 10px;"
153-
icon
154-
color="white"
155-
@click="copyToClipboard(runnerSetupCommand)"
156-
>
157-
<v-icon>mdi-content-copy</v-icon>
158-
</v-btn>
142+
:text="runnerSetupCommand"
143+
/>
159144
</div>
160145

161146
<div class="mt-3">
@@ -180,14 +165,10 @@
180165
class="pa-2"
181166
>{{ runnerEnvCommand }}</pre>
182167

183-
<v-btn
168+
<CopyClipboardButton
184169
style="position: absolute; right: 10px; top: 10px;"
185-
icon
186-
color="white"
187-
@click="copyToClipboard(runnerEnvCommand)"
188-
>
189-
<v-icon>mdi-content-copy</v-icon>
190-
</v-btn>
170+
:text="runnerEnvCommand"
171+
/>
191172
</div>
192173
</v-tab-item>
193174

@@ -202,14 +183,10 @@
202183
class="pa-2"
203184
>{{ runnerDockerCommand }}</pre>
204185

205-
<v-btn
186+
<CopyClipboardButton
206187
style="position: absolute; right: 10px; top: 10px;"
207-
icon
208-
color="white"
209-
@click="copyToClipboard(runnerDockerCommand)"
210-
>
211-
<v-icon>mdi-content-copy</v-icon>
212-
</v-btn>
188+
:text="runnerDockerCommand"
189+
/>
213190
</div>
214191
</v-tab-item>
215192
</v-tabs-items>
@@ -385,11 +362,14 @@ import RunnerForm from '@/components/RunnerForm.vue';
385362
import axios from 'axios';
386363
import DashboardMenu from '@/components/DashboardMenu.vue';
387364
import delay from '@/lib/delay';
365+
import copyToClipboard from '@/lib/copyToClipboard';
366+
import CopyClipboardButton from '@/components/CopyClipboardButton.vue';
388367
389368
export default {
390369
mixins: [ItemListPageBase],
391370
392371
components: {
372+
CopyClipboardButton,
393373
DashboardMenu,
394374
RunnerForm,
395375
YesNoDialog,
@@ -468,6 +448,9 @@ semaphore runner start --no-config`;
468448
},
469449
470450
methods: {
451+
452+
copyToClipboard,
453+
471454
async clearCache(runner) {
472455
const projectId = this.projectId || this.getProjectIdOfItem(runner.id);
473456
@@ -539,21 +522,6 @@ semaphore runner start --no-config`;
539522
return this.loadItems();
540523
},
541524
542-
async copyToClipboard(text) {
543-
try {
544-
await window.navigator.clipboard.writeText(text);
545-
EventBus.$emit('i-snackbar', {
546-
color: 'success',
547-
text: 'The command has been copied to the clipboard.',
548-
});
549-
} catch (e) {
550-
EventBus.$emit('i-snackbar', {
551-
color: 'error',
552-
text: `Can't copy the command: ${e.message}`,
553-
});
554-
}
555-
},
556-
557525
async setActive(runnerId, active) {
558526
const projectId = this.projectId || this.getProjectIdOfItem(runnerId);
559527

web/src/views/project/template/TemplateTerraformState.vue

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ import TerraformAliasForm from '@/components/TerraformAliasForm.vue';
300300
import TaskStatus from '@/components/TaskStatus.vue';
301301
import TaskLink from '@/components/TaskLink.vue';
302302
import TerraformStateView from '@/components/TerraformStateView.vue';
303+
import copyToClipboard from '@/lib/copyToClipboard';
303304
304305
export default {
305306
mixins: [AppsMixin],
@@ -380,6 +381,8 @@ export default {
380381
},
381382
382383
methods: {
384+
copyToClipboard,
385+
383386
deleteState(id) {
384387
console.log(id);
385388
},
@@ -467,21 +470,6 @@ export default {
467470
await this.loadAliases();
468471
},
469472
470-
async copyToClipboard(text) {
471-
try {
472-
await window.navigator.clipboard.writeText(text);
473-
EventBus.$emit('i-snackbar', {
474-
color: 'success',
475-
text: 'The command has been copied to the clipboard.',
476-
});
477-
} catch (e) {
478-
EventBus.$emit('i-snackbar', {
479-
color: 'error',
480-
text: `Can't copy the command: ${e.message}`,
481-
});
482-
}
483-
},
484-
485473
editAlias(alias) {
486474
this.aliasId = alias;
487475
this.editDialog = true;

0 commit comments

Comments
 (0)