Webapp: avoid browser JS errors about forbidden 'User-Agent' header
Brave (and maybe other browseres) refuse to set the 'User-Agent' header in XMLHTTPRequests, and are vocal about this in the debug log. Since the OpenAPI code generator always outputs a custom 'User-Agent' header, I've added some JS code to strip that off when constructing an API client.
This commit is contained in:
parent
a5cfa9959b
commit
1add6bfc8a
@ -26,7 +26,7 @@
|
||||
|
||||
<script>
|
||||
import * as API from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { backendURL } from '@/urls';
|
||||
|
||||
import ApiSpinner from '@/components/ApiSpinner.vue'
|
||||
@ -51,7 +51,7 @@ export default {
|
||||
methods: {
|
||||
// TODO: also call this when SocketIO reconnects.
|
||||
fetchManagerInfo() {
|
||||
const metaAPI = new API.MetaApi(apiClient);
|
||||
const metaAPI = new API.MetaApi(getAPIClient());
|
||||
metaAPI.getVersion().then((version) => {
|
||||
this.flamencoName = version.name;
|
||||
this.flamencoVersion = version.version;
|
||||
|
@ -17,7 +17,7 @@ const DEFAULT_FLAMENCO_NAME = "Flamenco";
|
||||
const DEFAULT_FLAMENCO_VERSION = "unknown";
|
||||
import ApiSpinner from '@/components/ApiSpinner.vue'
|
||||
import { MetaApi } from "@/manager-api";
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
export default {
|
||||
name: 'SetupAssistant',
|
||||
@ -35,7 +35,7 @@ export default {
|
||||
methods: {
|
||||
// TODO: also call this when SocketIO reconnects.
|
||||
fetchManagerInfo() {
|
||||
const metaAPI = new MetaApi(apiClient);
|
||||
const metaAPI = new MetaApi(getAPIClient());
|
||||
metaAPI.getVersion().then((version) => {
|
||||
this.flamencoName = version.name;
|
||||
this.flamencoVersion = version.version;
|
||||
|
39
web/app/src/api-client.js
Normal file
39
web/app/src/api-client.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { ApiClient } from "@/manager-api";
|
||||
import { CountingApiClient } from "@/stores/api-query-count";
|
||||
import { api as apiURL } from '@/urls'
|
||||
|
||||
/**
|
||||
* Scrub the custom User-Agent header from the API client, for those webbrowsers
|
||||
* who do not want to have it set.
|
||||
*
|
||||
* It's actually scrubbed for all webbrowsers, as those privacy-first
|
||||
* webbrowsers also make it hard to fingerprint which browser you're using (for
|
||||
* good reason).
|
||||
*
|
||||
* @param {ApiClient} apiClient
|
||||
*/
|
||||
export function scrubAPIClient(apiClient) {
|
||||
delete apiClient.defaultHeaders['User-Agent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ApiClient} Bare API client that is not connected to the UI in any way.
|
||||
*/
|
||||
export function newBareAPIClient() {
|
||||
const apiClient = new ApiClient(apiURL());
|
||||
scrubAPIClient(apiClient);
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
/**
|
||||
* @returns {ApiClient} API client that updates the UI to show long-running queries.
|
||||
*/
|
||||
export function getAPIClient() {
|
||||
if (apiClient == null) {
|
||||
apiClient = new CountingApiClient(apiURL());
|
||||
scrubAPIClient(apiClient);
|
||||
}
|
||||
return apiClient;
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useNotifs } from '@/stores/notifications';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { JobsApi, JobPriorityChange } from '@/manager-api';
|
||||
|
||||
const props = defineProps({
|
||||
@ -45,7 +45,7 @@ const errorMessage = ref('');
|
||||
// Methods
|
||||
function updateJobPriority() {
|
||||
const jobPriorityChange = new JobPriorityChange(priorityState.value);
|
||||
const jobsAPI = new JobsApi(apiClient);
|
||||
const jobsAPI = new JobsApi(getAPIClient());
|
||||
return jobsAPI.setJobPriority(props.jobId, jobPriorityChange)
|
||||
.then(() => {
|
||||
notifs.add(`Updated job priority to ${priorityState.value}`)
|
||||
|
@ -3,7 +3,7 @@ import { onMounted, onUnmounted } from 'vue'
|
||||
import { TabulatorFull as Tabulator } from 'tabulator-tables';
|
||||
import { useTaskLog } from '@/stores/tasklog'
|
||||
import { useTasks } from '@/stores/tasks'
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { JobsApi } from '@/manager-api';
|
||||
|
||||
const taskLog = useTaskLog();
|
||||
@ -59,7 +59,7 @@ function _fetchLogTail(taskID) {
|
||||
|
||||
if (!taskID) return;
|
||||
|
||||
const jobsAPI = new JobsApi(apiClient);
|
||||
const jobsAPI = new JobsApi(getAPIClient());
|
||||
return jobsAPI.fetchTaskLogTail(taskID)
|
||||
.then((logTail) => {
|
||||
taskLog.addChunk(logTail);
|
||||
|
@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { JobsApi } from '@/manager-api';
|
||||
import LinkWorker from '@/components/LinkWorker.vue';
|
||||
import { watch, onMounted, inject, ref, nextTick } from 'vue'
|
||||
@ -35,7 +35,7 @@ import { watch, onMounted, inject, ref, nextTick } from 'vue'
|
||||
const props = defineProps(['jobID']);
|
||||
const emit = defineEmits(['reshuffled'])
|
||||
|
||||
const jobsApi = new JobsApi(apiClient);
|
||||
const jobsApi = new JobsApi(getAPIClient());
|
||||
const isVisible = inject("isVisible");
|
||||
const isFetching = ref(false);
|
||||
const errorMsg = ref("");
|
||||
|
@ -18,7 +18,7 @@
|
||||
<script>
|
||||
import { useJobs } from '@/stores/jobs';
|
||||
import { useNotifs } from '@/stores/notifications';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { JobsApi } from '@/manager-api';
|
||||
import { JobDeletionInfo } from '@/manager-api';
|
||||
|
||||
@ -31,7 +31,7 @@ export default {
|
||||
data: () => ({
|
||||
jobs: useJobs(),
|
||||
notifs: useNotifs(),
|
||||
jobsAPI: new JobsApi(apiClient),
|
||||
jobsAPI: new JobsApi(getAPIClient()),
|
||||
|
||||
deleteInfo: null,
|
||||
}),
|
||||
|
@ -39,7 +39,7 @@
|
||||
<dd class="field-status-label" :class="'status-' + jobData.status">{{ jobData.status }}</dd>
|
||||
|
||||
<dt class="field-type" title="Type">Type</dt>
|
||||
<dd>{{ jobType? jobType.label : jobData.type }}</dd>
|
||||
<dd>{{ jobType ? jobType.label : jobData.type }}</dd>
|
||||
|
||||
<dt class="field-priority" title="Priority">Priority</dt>
|
||||
<dd>
|
||||
@ -76,7 +76,7 @@
|
||||
<script>
|
||||
import * as datetime from "@/datetime";
|
||||
import * as API from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import LastRenderedImage from '@/components/jobs/LastRenderedImage.vue'
|
||||
import Blocklist from './Blocklist.vue'
|
||||
import TabItem from '@/components/TabItem.vue'
|
||||
@ -103,7 +103,7 @@ export default {
|
||||
datetime: datetime, // So that the template can access it.
|
||||
copyElementText: copyElementText,
|
||||
simpleSettings: null, // Object with filtered job settings, or null if there is no job.
|
||||
jobsApi: new API.JobsApi(apiClient),
|
||||
jobsApi: new API.JobsApi(getAPIClient()),
|
||||
jobType: null, // API.AvailableJobType object for the current job type.
|
||||
jobTypeSettings: null, // Mapping from setting key to its definition in the job type.
|
||||
showAllSettings: false,
|
||||
|
@ -17,7 +17,7 @@ import { TabulatorFull as Tabulator } from 'tabulator-tables';
|
||||
import * as datetime from "@/datetime";
|
||||
import * as API from '@/manager-api'
|
||||
import { indicator } from '@/statusindicator';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { useJobs } from '@/stores/jobs';
|
||||
|
||||
import JobActionsBar from '@/components/jobs/JobActionsBar.vue'
|
||||
@ -130,7 +130,7 @@ export default {
|
||||
this.fetchAllJobs();
|
||||
},
|
||||
fetchAllJobs() {
|
||||
const jobsApi = new API.JobsApi(apiClient);
|
||||
const jobsApi = new API.JobsApi(getAPIClient());
|
||||
const jobsQuery = {};
|
||||
this.jobs.isJobless = false;
|
||||
jobsApi.queryJobs(jobsQuery).then(this.onJobsFetched, function (error) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { api } from '@/urls';
|
||||
import { JobsApi, JobLastRenderedImageInfo, SocketIOLastRenderedUpdate } from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
const props = defineProps([
|
||||
/* The job UUID to show renders for, or some false-y value if renders from all
|
||||
@ -27,7 +27,7 @@ const cssClasses = reactive({
|
||||
'nothing-rendered-yet': true,
|
||||
})
|
||||
|
||||
const jobsApi = new JobsApi(apiClient);
|
||||
const jobsApi = new JobsApi(getAPIClient());
|
||||
|
||||
/**
|
||||
* Fetches the last-rendered info for the given job, then updates the <img> tag for it.
|
||||
|
@ -73,7 +73,7 @@
|
||||
import * as datetime from "@/datetime";
|
||||
import { JobsApi } from '@/manager-api';
|
||||
import { backendURL } from '@/urls';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { useNotifs } from "@/stores/notifications";
|
||||
import LinkWorker from '@/components/LinkWorker.vue';
|
||||
import { copyElementText } from '@/clipboard';
|
||||
@ -90,7 +90,7 @@ export default {
|
||||
return {
|
||||
datetime: datetime, // So that the template can access it.
|
||||
copyElementText: copyElementText,
|
||||
jobsApi: new JobsApi(apiClient),
|
||||
jobsApi: new JobsApi(getAPIClient()),
|
||||
notifs: useNotifs(),
|
||||
};
|
||||
},
|
||||
|
@ -17,7 +17,7 @@ import { TabulatorFull as Tabulator } from 'tabulator-tables';
|
||||
import * as datetime from "@/datetime";
|
||||
import * as API from '@/manager-api'
|
||||
import { indicator } from '@/statusindicator';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { useTasks } from '@/stores/tasks';
|
||||
|
||||
import TaskActionsBar from '@/components/jobs/TaskActionsBar.vue'
|
||||
@ -132,7 +132,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const jobsApi = new API.JobsApi(apiClient);
|
||||
const jobsApi = new API.JobsApi(getAPIClient());
|
||||
jobsApi.fetchJobTasks(this.jobID)
|
||||
.then(this.onTasksFetched, function (error) {
|
||||
// TODO: error handling.
|
||||
|
@ -6,11 +6,7 @@
|
||||
</option>
|
||||
<option v-for="(action, key) in WORKER_ACTIONS" :value="key">{{ action.label }}</option>
|
||||
</select>
|
||||
<button
|
||||
:disabled="!canPerformAction"
|
||||
class="btn"
|
||||
@click.prevent="performWorkerAction"
|
||||
>Apply</button>
|
||||
<button :disabled="!canPerformAction" class="btn" @click.prevent="performWorkerAction">Apply</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -18,46 +14,46 @@ import { computed, ref } from 'vue'
|
||||
import { useWorkers } from '@/stores/workers';
|
||||
import { useNotifs } from '@/stores/notifications';
|
||||
import { WorkerMgtApi, WorkerStatusChangeRequest } from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
/* Freeze to prevent Vue.js from creating getters & setters all over this object.
|
||||
* We don't need it to be tracked, as it won't be changed anyway. */
|
||||
const WORKER_ACTIONS = Object.freeze({
|
||||
offline_lazy: {
|
||||
label: 'Shut Down (after task is finished)',
|
||||
icon: '✝',
|
||||
title: 'Shut down the worker after the current task finishes. The worker may automatically restart.',
|
||||
target_status: 'offline',
|
||||
lazy: true,
|
||||
},
|
||||
offline_immediate: {
|
||||
label: 'Shut Down (immediately)',
|
||||
icon: '✝!',
|
||||
title: 'Immediately shut down the worker. It may automatically restart.',
|
||||
target_status: 'offline',
|
||||
lazy: false,
|
||||
},
|
||||
asleep_lazy: {
|
||||
label: 'Send to Sleep (after task is finished)',
|
||||
icon: '😴',
|
||||
title: 'Let the worker sleep after finishing this task.',
|
||||
target_status: 'asleep',
|
||||
lazy: true,
|
||||
},
|
||||
asleep_immediate: {
|
||||
label: 'Send to Sleep (immediately)',
|
||||
icon: '😴!',
|
||||
title: 'Let the worker sleep immediately.',
|
||||
target_status: 'asleep',
|
||||
lazy: false,
|
||||
},
|
||||
wakeup: {
|
||||
label: 'Wake Up',
|
||||
icon: '😃',
|
||||
title: 'Wake the worker up. A sleeping worker can take a minute to respond.',
|
||||
target_status: 'awake',
|
||||
lazy: false,
|
||||
},
|
||||
offline_lazy: {
|
||||
label: 'Shut Down (after task is finished)',
|
||||
icon: '✝',
|
||||
title: 'Shut down the worker after the current task finishes. The worker may automatically restart.',
|
||||
target_status: 'offline',
|
||||
lazy: true,
|
||||
},
|
||||
offline_immediate: {
|
||||
label: 'Shut Down (immediately)',
|
||||
icon: '✝!',
|
||||
title: 'Immediately shut down the worker. It may automatically restart.',
|
||||
target_status: 'offline',
|
||||
lazy: false,
|
||||
},
|
||||
asleep_lazy: {
|
||||
label: 'Send to Sleep (after task is finished)',
|
||||
icon: '😴',
|
||||
title: 'Let the worker sleep after finishing this task.',
|
||||
target_status: 'asleep',
|
||||
lazy: true,
|
||||
},
|
||||
asleep_immediate: {
|
||||
label: 'Send to Sleep (immediately)',
|
||||
icon: '😴!',
|
||||
title: 'Let the worker sleep immediately.',
|
||||
target_status: 'asleep',
|
||||
lazy: false,
|
||||
},
|
||||
wakeup: {
|
||||
label: 'Wake Up',
|
||||
icon: '😃',
|
||||
title: 'Wake the worker up. A sleeping worker can take a minute to respond.',
|
||||
target_status: 'awake',
|
||||
lazy: false,
|
||||
},
|
||||
});
|
||||
|
||||
const selectedAction = ref('');
|
||||
@ -73,7 +69,7 @@ function performWorkerAction() {
|
||||
return;
|
||||
}
|
||||
|
||||
const api = new WorkerMgtApi(apiClient);
|
||||
const api = new WorkerMgtApi(getAPIClient());
|
||||
const action = WORKER_ACTIONS[selectedAction.value];
|
||||
const statuschange = new WorkerStatusChangeRequest(action.target_status, action.lazy);
|
||||
console.log("Requesting worker status change", statuschange);
|
||||
|
@ -123,7 +123,7 @@ import { useNotifs } from '@/stores/notifications'
|
||||
|
||||
import * as datetime from "@/datetime";
|
||||
import { WorkerMgtApi, WorkerSleepSchedule } from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { workerStatus } from "../../statusindicator";
|
||||
import LinkWorkerTask from '@/components/LinkWorkerTask.vue';
|
||||
import SwitchCheckbox from '@/components/SwitchCheckbox.vue';
|
||||
@ -140,7 +140,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
datetime: datetime, // So that the template can access it.
|
||||
api: new WorkerMgtApi(apiClient),
|
||||
api: new WorkerMgtApi(getAPIClient()),
|
||||
workerStatusHTML: "",
|
||||
workerSleepSchedule: this.defaultWorkerSleepSchedule(),
|
||||
isScheduleEditing: false,
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { TabulatorFull as Tabulator } from 'tabulator-tables';
|
||||
import { WorkerMgtApi } from '@/manager-api'
|
||||
import { indicator, workerStatus } from '@/statusindicator';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { useWorkers } from '@/stores/workers';
|
||||
|
||||
import StatusFilterBar from '@/components/StatusFilterBar.vue'
|
||||
@ -117,7 +117,7 @@ export default {
|
||||
this.fetchAllWorkers();
|
||||
},
|
||||
fetchAllWorkers() {
|
||||
const api = new WorkerMgtApi(apiClient);
|
||||
const api = new WorkerMgtApi(getAPIClient());
|
||||
api.fetchWorkers().then(this.onWorkersFetched, function (error) {
|
||||
// TODO: error handling.
|
||||
console.error(error);
|
||||
|
@ -7,7 +7,8 @@ import SetupAssistant from '@/SetupAssistant.vue'
|
||||
import autoreload from '@/autoreloader'
|
||||
import router from '@/router/index'
|
||||
import setupAssistantRouter from '@/router/setup-assistant'
|
||||
import { ApiClient, MetaApi } from "@/manager-api";
|
||||
import { MetaApi } from "@/manager-api";
|
||||
import { newBareAPIClient } from "@/api-client";
|
||||
import * as urls from '@/urls'
|
||||
|
||||
// Ensure Tabulator can find `luxon`, which it needs for sorting by
|
||||
@ -42,7 +43,7 @@ function setupAssistantMode() {
|
||||
/* This cannot use the client from '@/stores/api-query-count', as that would
|
||||
* require Pinia, which is unavailable until the app is actually started. And to
|
||||
* know which app to start, this API call needs to return data. */
|
||||
const apiClient = new ApiClient(urls.api());;
|
||||
const apiClient = newBareAPIClient();
|
||||
const metaAPI = new MetaApi(apiClient);
|
||||
metaAPI.getConfiguration()
|
||||
.then((config) => {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ApiClient } from "@/manager-api";
|
||||
import * as urls from '@/urls'
|
||||
|
||||
/**
|
||||
* Keep track of running API queries.
|
||||
@ -42,5 +41,3 @@ export class CountingApiClient extends ApiClient {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new CountingApiClient(urls.api());;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import * as API from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
|
||||
const jobsAPI = new API.JobsApi(apiClient);
|
||||
const jobsAPI = new API.JobsApi(getAPIClient());
|
||||
|
||||
// 'use' prefix is idiomatic for Pinia stores.
|
||||
// See https://pinia.vuejs.org/core-concepts/
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import * as API from '@/manager-api';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
|
||||
const jobsAPI = new API.JobsApi(apiClient);
|
||||
const jobsAPI = new API.JobsApi(getAPIClient());
|
||||
|
||||
// 'use' prefix is idiomatic for Pinia stores.
|
||||
// See https://pinia.vuejs.org/core-concepts/
|
||||
|
@ -39,7 +39,7 @@ import { useJobs } from '@/stores/jobs';
|
||||
import { useTasks } from '@/stores/tasks';
|
||||
import { useNotifs } from '@/stores/notifications'
|
||||
import { useTaskLog } from '@/stores/tasklog'
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
import FooterPopup from '@/components/footer/FooterPopup.vue'
|
||||
import GetTheAddon from '@/components/GetTheAddon.vue'
|
||||
@ -127,7 +127,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const jobsAPI = new API.JobsApi(apiClient);
|
||||
const jobsAPI = new API.JobsApi(getAPIClient());
|
||||
jobsAPI.fetchTask(taskSummary.id)
|
||||
.then((task) => {
|
||||
this.tasks.setActiveTask(task);
|
||||
@ -225,7 +225,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const jobsAPI = new API.JobsApi(apiClient);
|
||||
const jobsAPI = new API.JobsApi(getAPIClient());
|
||||
return jobsAPI.fetchJob(jobID)
|
||||
.then((job) => {
|
||||
this.jobs.setActiveJob(job);
|
||||
@ -254,7 +254,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const jobsAPI = new API.JobsApi(apiClient);
|
||||
const jobsAPI = new API.JobsApi(getAPIClient());
|
||||
return jobsAPI.fetchTask(taskID)
|
||||
.then((task) => {
|
||||
this.tasks.setActiveTask(task);
|
||||
|
@ -2,38 +2,28 @@
|
||||
<div class="setup-container">
|
||||
<h1>Flamenco Setup Assistant</h1>
|
||||
<ul class="progress">
|
||||
<li
|
||||
v-for="step in totalSetupSteps" :key="step"
|
||||
@click="jumpToStep(step)"
|
||||
:class="{
|
||||
current: step == currentSetupStep,
|
||||
done: step < overallSetupStep,
|
||||
done_previously: (step < overallSetupStep && currentSetupStep > step),
|
||||
done_and_current: step == currentSetupStep && (step < overallSetupStep || step == 1),
|
||||
disabled: step > overallSetupStep,
|
||||
}"
|
||||
>
|
||||
<li v-for="step in totalSetupSteps" :key="step" @click="jumpToStep(step)" :class="{
|
||||
current: step == currentSetupStep,
|
||||
done: step < overallSetupStep,
|
||||
done_previously: (step < overallSetupStep && currentSetupStep > step),
|
||||
done_and_current: step == currentSetupStep && (step < overallSetupStep || step == 1),
|
||||
disabled: step > overallSetupStep,
|
||||
}">
|
||||
<span></span>
|
||||
</li>
|
||||
<div class="progress-bar"></div>
|
||||
</ul>
|
||||
<div class="setup-step step-welcome">
|
||||
|
||||
<step-item
|
||||
v-show="currentSetupStep == 1"
|
||||
@next-clicked="nextStep"
|
||||
:is-next-clickable="true"
|
||||
:is-back-visible="false"
|
||||
title="Welcome!"
|
||||
next-label="Let's go"
|
||||
>
|
||||
<step-item v-show="currentSetupStep == 1" @next-clicked="nextStep" :is-next-clickable="true"
|
||||
:is-back-visible="false" title="Welcome!" next-label="Let's go">
|
||||
<p>
|
||||
This setup assistant will guide you through the initial configuration of Flamenco. You will be up
|
||||
and running in a few minutes!
|
||||
</p>
|
||||
<p>Before we start, here is a quick overview of the Flamenco architecture.</p>
|
||||
|
||||
<img src="@/assets/architecture.png" alt="Flamenco architecture"/>
|
||||
<img src="@/assets/architecture.png" alt="Flamenco architecture" />
|
||||
|
||||
<p>The illustration shows the key components and how they interact together:</p>
|
||||
<ul>
|
||||
@ -41,26 +31,24 @@
|
||||
<strong>Manager</strong>: This application. It coordinates all the activity.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Worker</strong>: A workstation or dedicated rendering machine. It executes the tasks assigned by the Manager.
|
||||
<strong>Worker</strong>: A workstation or dedicated rendering machine. It executes the tasks assigned by the
|
||||
Manager.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Shared Storage</strong>: A location accessible by the Manager and the Workers, where the files to be rendered should be saved.
|
||||
<strong>Shared Storage</strong>: A location accessible by the Manager and the Workers, where the files to be
|
||||
rendered should be saved.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Blender Add-on</strong>: This is needed to connect to the Manager and submit a job from Blender.
|
||||
</li>
|
||||
</ul>
|
||||
<p>More information is available on the online documentation at
|
||||
<a href="https://flamenco.blender.org">flamenco.blender.org</a>.</p>
|
||||
<a href="https://flamenco.blender.org">flamenco.blender.org</a>.
|
||||
</p>
|
||||
</step-item>
|
||||
|
||||
<step-item
|
||||
v-show="currentSetupStep == 2"
|
||||
@next-clicked="nextStepAfterCheckSharedStoragePath"
|
||||
@back-clicked="prevStep"
|
||||
:is-next-clickable="sharedStoragePath.length > 0"
|
||||
title="Shared Storage"
|
||||
>
|
||||
<step-item v-show="currentSetupStep == 2" @next-clicked="nextStepAfterCheckSharedStoragePath"
|
||||
@back-clicked="prevStep" :is-next-clickable="sharedStoragePath.length > 0" title="Shared Storage">
|
||||
<p>Specify a path (or drive) where you want to store your Flamenco data.</p>
|
||||
<p>
|
||||
The location of the shared storage should be accessible by Flamenco Manager and by the Workers.
|
||||
@ -79,37 +67,26 @@
|
||||
<a href="https://flamenco.blender.org/usage/shared-storage/">Learn more</a>.
|
||||
</p>
|
||||
|
||||
<input
|
||||
v-model="sharedStoragePath"
|
||||
@keyup.enter="nextStepAfterCheckSharedStoragePath"
|
||||
type="text"
|
||||
placeholder="Path to shared storage"
|
||||
:class="{
|
||||
<input v-model="sharedStoragePath" @keyup.enter="nextStepAfterCheckSharedStoragePath" type="text"
|
||||
placeholder="Path to shared storage" :class="{
|
||||
'is-invalid': (sharedStorageCheckResult != null) && !sharedStorageCheckResult.is_usable
|
||||
}"
|
||||
>
|
||||
<p v-if="sharedStorageCheckResult != null"
|
||||
:class="{
|
||||
'check-ok': sharedStorageCheckResult.is_usable,
|
||||
'check-failed': !sharedStorageCheckResult.is_usable
|
||||
}">
|
||||
<p v-if="sharedStorageCheckResult != null" :class="{
|
||||
'check-ok': sharedStorageCheckResult.is_usable,
|
||||
'check-failed': !sharedStorageCheckResult.is_usable
|
||||
}">
|
||||
{{ sharedStorageCheckResult.cause }}
|
||||
</p>
|
||||
</step-item>
|
||||
|
||||
<step-item
|
||||
v-show="currentSetupStep == 3"
|
||||
@next-clicked="nextStepAfterCheckBlenderExePath"
|
||||
@back-clicked="prevStep"
|
||||
:is-next-clickable="selectedBlender != null || customBlenderExe != (null || '')"
|
||||
title="Blender"
|
||||
>
|
||||
<step-item v-show="currentSetupStep == 3" @next-clicked="nextStepAfterCheckBlenderExePath" @back-clicked="prevStep"
|
||||
:is-next-clickable="selectedBlender != null || customBlenderExe != (null || '')" title="Blender">
|
||||
|
||||
<div v-if="isBlenderExeFinding" class="is-in-progress">Looking for Blender installs...</div>
|
||||
|
||||
<p v-if="autoFoundBlenders.length === 0">
|
||||
Provide a path to a Blender executable accessible by all Workers.
|
||||
<br/><br/>
|
||||
<br /><br />
|
||||
If your rendering setup features operating systems different from the one you are currently using,
|
||||
you can manually set up the other paths later.
|
||||
</p>
|
||||
@ -119,23 +96,16 @@
|
||||
<fieldset v-if="autoFoundBlenders.length >= 1">
|
||||
<label v-if="autoFoundBlenderPathEnvvar" for="blender-path_envvar">
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedBlender"
|
||||
:value="autoFoundBlenderPathEnvvar"
|
||||
id="blender-path_envvar"
|
||||
name="blender"
|
||||
<input v-model="selectedBlender" :value="autoFoundBlenderPathEnvvar" id="blender-path_envvar" name="blender"
|
||||
type="radio">
|
||||
{{ sourceLabels[autoFoundBlenderPathEnvvar.source] }}
|
||||
{{ sourceLabels[autoFoundBlenderPathEnvvar.source] }}
|
||||
</div>
|
||||
<div class="setup-path-command">
|
||||
<span class="path">
|
||||
{{autoFoundBlenderPathEnvvar.path}}
|
||||
{{ autoFoundBlenderPathEnvvar.path }}
|
||||
</span>
|
||||
<span
|
||||
aria-label="Console output when running with --version"
|
||||
class="command-preview"
|
||||
data-microtip-position="top"
|
||||
role="tooltip">
|
||||
<span aria-label="Console output when running with --version" class="command-preview"
|
||||
data-microtip-position="top" role="tooltip">
|
||||
{{ autoFoundBlenderPathEnvvar.cause }}
|
||||
</span>
|
||||
</div>
|
||||
@ -143,23 +113,16 @@
|
||||
|
||||
<label v-if="autoFoundBlenderFileAssociation" for="blender-file_association">
|
||||
<div>
|
||||
<input
|
||||
v-model="selectedBlender"
|
||||
:value="autoFoundBlenderFileAssociation"
|
||||
id="blender-file_association"
|
||||
name="blender"
|
||||
type="radio">
|
||||
{{ sourceLabels[autoFoundBlenderFileAssociation.source] }}
|
||||
<input v-model="selectedBlender" :value="autoFoundBlenderFileAssociation" id="blender-file_association"
|
||||
name="blender" type="radio">
|
||||
{{ sourceLabels[autoFoundBlenderFileAssociation.source] }}
|
||||
</div>
|
||||
<div class="setup-path-command">
|
||||
<span class="path">
|
||||
{{autoFoundBlenderFileAssociation.path}}
|
||||
{{ autoFoundBlenderFileAssociation.path }}
|
||||
</span>
|
||||
<span
|
||||
aria-label="Console output when running with --version"
|
||||
class="command-preview"
|
||||
data-microtip-position="top"
|
||||
role="tooltip">
|
||||
<span aria-label="Console output when running with --version" class="command-preview"
|
||||
data-microtip-position="top" role="tooltip">
|
||||
{{ autoFoundBlenderFileAssociation.cause }}
|
||||
</span>
|
||||
</div>
|
||||
@ -167,24 +130,15 @@
|
||||
|
||||
<label for="blender-input_path">
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
v-model="selectedBlender"
|
||||
name="blender"
|
||||
:value="blenderFromInputPath"
|
||||
id="blender-input_path"
|
||||
>
|
||||
<input type="radio" v-model="selectedBlender" name="blender" :value="blenderFromInputPath"
|
||||
id="blender-input_path">
|
||||
{{ sourceLabels['input_path'] }}
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
v-model="customBlenderExe"
|
||||
@keyup.enter="nextStepAfterCheckBlenderExePath"
|
||||
<input v-model="customBlenderExe" @keyup.enter="nextStepAfterCheckBlenderExePath"
|
||||
@focus="selectedBlender = null"
|
||||
:class="{'is-invalid': blenderExeCheckResult != null && !blenderExeCheckResult.is_usable}"
|
||||
type="text"
|
||||
placeholder="Path to Blender"
|
||||
>
|
||||
:class="{ 'is-invalid': blenderExeCheckResult != null && !blenderExeCheckResult.is_usable }" type="text"
|
||||
placeholder="Path to Blender">
|
||||
<p v-if="isBlenderExeChecking" class="is-in-progress">Checking...</p>
|
||||
<p v-if="blenderExeCheckResult != null && !blenderExeCheckResult.is_usable" class="check-failed">
|
||||
{{ blenderExeCheckResult.cause }}</p>
|
||||
@ -193,13 +147,9 @@
|
||||
</fieldset>
|
||||
|
||||
<div v-if="autoFoundBlenders.length === 0">
|
||||
<input
|
||||
v-model="customBlenderExe"
|
||||
@keyup.enter="nextStepAfterCheckBlenderExePath"
|
||||
:class="{'is-invalid': blenderExeCheckResult != null && !blenderExeCheckResult.is_usable}"
|
||||
type="text"
|
||||
placeholder="Path to Blender executable"
|
||||
>
|
||||
<input v-model="customBlenderExe" @keyup.enter="nextStepAfterCheckBlenderExePath"
|
||||
:class="{ 'is-invalid': blenderExeCheckResult != null && !blenderExeCheckResult.is_usable }" type="text"
|
||||
placeholder="Path to Blender executable">
|
||||
|
||||
<p v-if="isBlenderExeChecking" class="is-in-progress">Checking...</p>
|
||||
<p v-if="blenderExeCheckResult != null && !blenderExeCheckResult.is_usable" class="check-failed">
|
||||
@ -207,14 +157,8 @@
|
||||
</div>
|
||||
</step-item>
|
||||
|
||||
<step-item
|
||||
v-show="currentSetupStep == 4"
|
||||
@next-clicked="confirmSetupAssistant"
|
||||
@back-clicked="prevStep"
|
||||
next-label="Confirm"
|
||||
title="Review"
|
||||
:is-next-clickable="setupConfirmIsClickable"
|
||||
>
|
||||
<step-item v-show="currentSetupStep == 4" @next-clicked="confirmSetupAssistant" @back-clicked="prevStep"
|
||||
next-label="Confirm" title="Review" :is-next-clickable="setupConfirmIsClickable">
|
||||
<div v-if="isConfigComplete">
|
||||
<p>This is the configuration that will be used by Flamenco:</p>
|
||||
<dl>
|
||||
@ -253,7 +197,7 @@ import NotificationBar from '@/components/footer/NotificationBar.vue'
|
||||
import UpdateListener from '@/components/UpdateListener.vue'
|
||||
import StepItem from '@/components/steps/StepItem.vue';
|
||||
import { MetaApi, PathCheckInput, SetupAssistantConfig } from "@/manager-api";
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
export default {
|
||||
name: 'SetupAssistantView',
|
||||
@ -265,7 +209,7 @@ export default {
|
||||
data: () => ({
|
||||
sharedStoragePath: "",
|
||||
sharedStorageCheckResult: null, // api.PathCheckResult
|
||||
metaAPI: new MetaApi(apiClient),
|
||||
metaAPI: new MetaApi(getAPIClient()),
|
||||
|
||||
allBlenders: [], // combination of autoFoundBlenders and blenderExeCheckResult.
|
||||
|
||||
@ -458,7 +402,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
.step-welcome ul {
|
||||
padding-left: var(--spacer-xl);
|
||||
margin-bottom: var(--spacer-xl);
|
||||
@ -517,7 +460,8 @@ export default {
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
--width-each-segment: calc(100% / calc(v-bind('totalSetupSteps') - 1)); /* Substract 1 because the first step has no progress. */
|
||||
--width-each-segment: calc(100% / calc(v-bind('totalSetupSteps') - 1));
|
||||
/* Substract 1 because the first step has no progress. */
|
||||
--progress-bar-width-at-current-step: calc(var(--width-each-segment) * calc(v-bind('currentSetupStep') - 1));
|
||||
|
||||
position: absolute;
|
||||
|
@ -7,8 +7,7 @@
|
||||
</div>
|
||||
<footer class="app-footer">
|
||||
<notification-bar />
|
||||
<update-listener ref="updateListener" mainSubscription="allWorkers"
|
||||
@workerUpdate="onSIOWorkerUpdate"
|
||||
<update-listener ref="updateListener" mainSubscription="allWorkers" @workerUpdate="onSIOWorkerUpdate"
|
||||
@sioReconnected="onSIOReconnected" @sioDisconnected="onSIODisconnected" />
|
||||
</footer>
|
||||
</template>
|
||||
@ -17,6 +16,7 @@
|
||||
.col-workers-list {
|
||||
grid-area: col-1;
|
||||
}
|
||||
|
||||
.col-workers-2 {
|
||||
grid-area: col-2;
|
||||
}
|
||||
@ -26,7 +26,7 @@
|
||||
import { WorkerMgtApi } from '@/manager-api';
|
||||
import { useNotifs } from '@/stores/notifications'
|
||||
import { useWorkers } from '@/stores/workers';
|
||||
import { apiClient } from '@/stores/api-query-count';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
import NotificationBar from '@/components/footer/NotificationBar.vue'
|
||||
import UpdateListener from '@/components/UpdateListener.vue'
|
||||
@ -45,7 +45,7 @@ export default {
|
||||
data: () => ({
|
||||
workers: useWorkers(),
|
||||
notifs: useNotifs(),
|
||||
api: new WorkerMgtApi(apiClient),
|
||||
api: new WorkerMgtApi(getAPIClient()),
|
||||
}),
|
||||
mounted() {
|
||||
window.workersView = this;
|
||||
|
Loading…
x
Reference in New Issue
Block a user