diff --git a/web/app/src/assets/base.css b/web/app/src/assets/base.css index b2ca0a42..04be3ee6 100644 --- a/web/app/src/assets/base.css +++ b/web/app/src/assets/base.css @@ -644,3 +644,7 @@ span.state-transition-arrow.lazy { max-height: 100%; max-width: 100%; } + +.click-to-copy { + cursor: pointer; +} diff --git a/web/app/src/clipboard.js b/web/app/src/clipboard.js new file mode 100644 index 00000000..7b524656 --- /dev/null +++ b/web/app/src/clipboard.js @@ -0,0 +1,21 @@ +/** + * Copy the inner text of an element to the clipboard. + * + * @param {Event } clickEvent the click event that triggered this function call. + */ +export function copyElementText(clickEvent) { + const sourceElement = clickEvent.target; + const inputElement = document.createElement("input"); + document.body.appendChild(inputElement); + inputElement.setAttribute("value", sourceElement.innerText); + inputElement.select(); + + // Note that the `navigator.clipboard` interface is only available when using + // a secure (HTTPS) connection, which Flamenco Manager will likely not have. + // This is why this code falls back to the deprecated `document.execCommand()` + // call. + // Source: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard + document.execCommand("copy"); + + document.body.removeChild(inputElement); +} diff --git a/web/app/src/components/GetTheAddon.vue b/web/app/src/components/GetTheAddon.vue new file mode 100644 index 00000000..7c4ae6e6 --- /dev/null +++ b/web/app/src/components/GetTheAddon.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/web/app/src/components/jobs/JobsTable.vue b/web/app/src/components/jobs/JobsTable.vue index e9b1713f..2c2299e9 100644 --- a/web/app/src/components/jobs/JobsTable.vue +++ b/web/app/src/components/jobs/JobsTable.vue @@ -18,6 +18,7 @@ import * as datetime from "@/datetime"; import * as API from '@/manager-api' import { indicator } from '@/statusindicator'; import { apiClient } from '@/stores/api-query-count'; +import { useJobs } from '@/stores/jobs'; import JobActionsBar from '@/components/jobs/JobActionsBar.vue' import StatusFilterBar from '@/components/StatusFilterBar.vue' @@ -33,6 +34,8 @@ export default { return { shownStatuses: [], availableStatuses: [], // Will be filled after data is loaded from the backend. + + jobs: useJobs(), }; }, mounted() { @@ -127,6 +130,7 @@ export default { fetchAllJobs() { const jobsApi = new API.JobsApi(apiClient); const jobsQuery = {}; + this.jobs.isJobless = false; jobsApi.queryJobs(jobsQuery).then(this.onJobsFetched, function (error) { // TODO: error handling. console.error(error); @@ -135,6 +139,8 @@ export default { onJobsFetched(data) { // "Down-cast" to JobUpdate to only get those fields, just for debugging things: // data.jobs = data.jobs.map((j) => API.JobUpdate.constructFromObject(j)); + const hasJobs = data && data.jobs && data.jobs.length > 0; + this.jobs.isJobless = !hasJobs; this.tabulator.setData(data.jobs); this._refreshAvailableStatuses(); diff --git a/web/app/src/stores/jobs.js b/web/app/src/stores/jobs.js index 6dc650dc..bd3c4ba4 100644 --- a/web/app/src/stores/jobs.js +++ b/web/app/src/stores/jobs.js @@ -17,6 +17,12 @@ export const useJobs = defineStore('jobs', { * @type {string} */ activeJobID: "", + + /** + * Set to true when it is known that there are no jobs at all in the system. + * This is written by the JobsTable.vue component. + */ + isJobless: false, }), getters: { canDelete() { diff --git a/web/app/src/views/JobsView.vue b/web/app/src/views/JobsView.vue index dde2e913..3e76ecb7 100644 --- a/web/app/src/views/JobsView.vue +++ b/web/app/src/views/JobsView.vue @@ -3,8 +3,12 @@
- - + +
@@ -37,6 +41,7 @@ import { useTaskLog } from '@/stores/tasklog' import { apiClient } from '@/stores/api-query-count'; import FooterPopup from '@/components/footer/FooterPopup.vue' +import GetTheAddon from '@/components/GetTheAddon.vue' import JobDetails from '@/components/jobs/JobDetails.vue' import JobsTable from '@/components/jobs/JobsTable.vue' import NotificationBar from '@/components/footer/NotificationBar.vue' @@ -49,6 +54,7 @@ export default { props: ["jobID", "taskID"], // provided by Vue Router. components: { FooterPopup, + GetTheAddon, JobDetails, JobsTable, NotificationBar,