From 26dd9d361594614d7812ab2014d99bcf87e7b35b Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Mon, 25 Mar 2024 15:10:20 -0300 Subject: [PATCH 1/3] core: frontend: wizard: allow downloading scripts in wizard --- .../src/components/wizard/ScriptLoader.vue | 150 ++++++++++++++++++ .../frontend/src/components/wizard/Wizard.vue | 46 ++++++ core/frontend/src/libs/filebrowser.ts | 36 +++++ 3 files changed, 232 insertions(+) create mode 100644 core/frontend/src/components/wizard/ScriptLoader.vue diff --git a/core/frontend/src/components/wizard/ScriptLoader.vue b/core/frontend/src/components/wizard/ScriptLoader.vue new file mode 100644 index 000000000..0aa71edca --- /dev/null +++ b/core/frontend/src/components/wizard/ScriptLoader.vue @@ -0,0 +1,150 @@ + + + diff --git a/core/frontend/src/components/wizard/Wizard.vue b/core/frontend/src/components/wizard/Wizard.vue index defa75d2d..20c13242f 100644 --- a/core/frontend/src/components/wizard/Wizard.vue +++ b/core/frontend/src/components/wizard/Wizard.vue @@ -129,6 +129,10 @@ + = import.meta.glob('/public/assets/vehicles/models/**', { eager: true }) +const REPOSITORY_ROOT = 'https://docs.bluerobotics.com/Blueos-Parameter-Repository' function get_model(vehicle_name: string, frame_name: string): undefined | string { const release_path = `assets/vehicles/models/${vehicle_name}/${frame_name}.glb` @@ -317,10 +324,12 @@ export default Vue.extend({ components: { DefaultParamLoader, RequireInternet, + ScriptLoader, }, data() { return { boat_model: get_model('boat', 'UNDEFINED'), + scripts: [] as string[], configuration_failed: false, error_message: 'The operation failed!', apply_status: ApplyStatus.Waiting, @@ -490,6 +499,15 @@ export default Vue.extend({ skip: false, started: false, }, + { + title: 'Install scripts', + summary: 'Download and install selected scripts', + promise: () => this.installScripts(), + message: undefined, + done: false, + skip: false, + started: false, + }, { title: 'Disable Wi-Fi hotspot', summary: 'Wi-Fi hotspot need to be disable to not interfere with onboard radio', @@ -651,6 +669,34 @@ export default Vue.extend({ }) .catch((error) => `Failed to fetch available firmware: ${error.message ?? error.response?.data}.`) }, + async installScripts(): Promise { + const scripts_folder = 'configs/ardupilot-manager/firmware/scripts/' + try { + // Use allSettled to allow promises to fail in parallel + await Promise.allSettled( + this.scripts.map( + async (script) => filebrowser.createFile(scripts_folder + script.split('/').last(), true), + ), + ) + await Promise.allSettled( + this.scripts.map(async (script) => { + await filebrowser.writeToFile( + scripts_folder + script.split('/').last(), + await this.fetchScript(script), + ) + }), + ) + return undefined + } catch (e) { + const error = `Failed to install scripts ${e}` + console.error(error) + return error + } + }, + async fetchScript(script: string): Promise { + const response = await fetch(`${REPOSITORY_ROOT}/scripts/ardupilot/${script}`) + return response.text() + }, validateParams(): boolean { return this.$refs.param_loader?.validateParams() }, diff --git a/core/frontend/src/libs/filebrowser.ts b/core/frontend/src/libs/filebrowser.ts index 958421df7..23fdd4580 100644 --- a/core/frontend/src/libs/filebrowser.ts +++ b/core/frontend/src/libs/filebrowser.ts @@ -66,6 +66,42 @@ class Filebrowser { }) } + async createFolder(folder_path: string): Promise { + if (!folder_path.endsWith('/')) { + folder_path += '/' + } + this.createFile(folder_path) + } + + async createFile(folder_path: string, override: Boolean = false): Promise { + back_axios({ + method: 'post', + url: `${filebrowser_url}/resources${folder_path}?override=${override}`, + timeout: 10000, + headers: { 'X-Auth': await this.filebrowserToken() }, + }) + .catch((error) => { + const message = `Could not create folder ${folder_path}: ${error.message}` + notifier.pushError('FOLDER_CREATE_FAIL', message) + throw new Error(message) + }) + } + + async writeToFile(file: string, content: string): Promise { + back_axios({ + method: 'put', + url: `/file-browser/api/resources${file}`, + timeout: 10000, + headers: { 'X-Auth': await this.filebrowserToken() }, + data: content, + }) + .catch((error) => { + const message = `Could not write to file ${file}: ${error.message}` + notifier.pushError('FILE_WRITE_FAIL', message) + throw new Error(message) + }) + } + /* Delete a single file. */ /* Register a notification and throws if delete fails. */ /** From ae99f43e2fb038ee1e03e61dc77d486d8b750480 Mon Sep 17 00:00:00 2001 From: Willian Galvani Date: Tue, 26 Mar 2024 12:08:23 -0300 Subject: [PATCH 2/3] core: frontend: tweak wizard style --- core/frontend/src/components/wizard/DefaultParamLoader.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/frontend/src/components/wizard/DefaultParamLoader.vue b/core/frontend/src/components/wizard/DefaultParamLoader.vue index e2c906a44..400ffc497 100755 --- a/core/frontend/src/components/wizard/DefaultParamLoader.vue +++ b/core/frontend/src/components/wizard/DefaultParamLoader.vue @@ -9,7 +9,7 @@ :label="`Parameter Sets (${board} - ${vehicle} - ${version})`" :loading="is_loading" :disabled="is_loading_paramsets" - style="min-width: 60%;" + style="min-width: 330px;" :rules="[isNotEmpty]" @change="setParamSet(filtered_param_sets[selected_param_set_name])" /> From 77e8951ebd3cece866f9511faa0c20bd6abe282e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Jos=C3=A9=20Pereira?= Date: Tue, 26 Mar 2024 08:28:37 -0300 Subject: [PATCH 3/3] core: frontend: cosmos: Add Array.last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick José Pereira --- core/frontend/src/cosmos.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/frontend/src/cosmos.ts b/core/frontend/src/cosmos.ts index 09b62e24b..df614f78b 100644 --- a/core/frontend/src/cosmos.ts +++ b/core/frontend/src/cosmos.ts @@ -3,6 +3,7 @@ export {} declare global { interface Array { first(): T | undefined; + last(): T | undefined; isEmpty(): boolean; } @@ -18,6 +19,11 @@ Array.prototype.first = function (this: T[]): T | undefined { return this.isEmpty() ? undefined : this[0] } +// eslint-disable-next-line +Array.prototype.last = function (this: T[]): T | undefined { + return this.at(-1) +} + // eslint-disable-next-line Array.prototype.isEmpty = function (this: T[]): boolean { return this.length === 0