Skip to content

Commit

Permalink
core: frontend: wizard: allow downloading scripts in wizard
Browse files Browse the repository at this point in the history
  • Loading branch information
Williangalvani committed Mar 25, 2024
1 parent 0c4c256 commit 31d054c
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 0 deletions.
150 changes: 150 additions & 0 deletions core/frontend/src/components/wizard/ScriptLoader.vue
@@ -0,0 +1,150 @@
<template>
<div class="d-flex flex-column align-center ma-5" style="width: 100%; height: 100%;">
<v-form>
<v-select
v-model="selected_scripts"
clearable
multiple
chips
persistent-hint
:items="[...filtered_scripts]"
item-text="sanitized"
item-value="full"
:label="`Scripts for (${board} - ${vehicle} - ${version})`"
:loading="is_loading"
:disabled="is_loading_scripts"
style="min-width: 330px;"
:rules="[isNotEmpty]"
@change="setScriptsList(selected_scripts)"
/>
</v-form>
<p v-if="is_loading_scripts">
Loading scripts...
</p>
<p v-else-if="has_error">
Unable to load scripts.
</p>
<p v-else-if="(!loading_timeout_reached && invalid_board_or_version)">
Determining current board and firmware version...
</p>
<p v-else-if="(loading_timeout_reached && invalid_board_or_version)">
Unable to determine current board or firmware version.
</p>
<p v-else-if="filtered_scripts?.length === 0">
No scripts available for this setup
</p>
</div>
</template>
<script lang="ts">
import { SemVer } from 'semver'
import Vue from 'vue'
import autopilot from '@/store/autopilot_manager'
import { Firmware, Vehicle } from '@/types/autopilot'
import { callPeriodically, stopCallingPeriodically } from '@/utils/helper_functions'
import { availableFirmwares, fetchCurrentBoard } from '../autopilot/AutopilotManagerUpdater'
const REPOSITORY_ROOT = 'https://williangalvani.github.io/Blueos-Parameter-Repository_/'
const REPOSITORY_URL = `${REPOSITORY_ROOT}scripts_v1.json`
const MAX_LOADING_TIME_MS = 25000
export default Vue.extend({
name: 'ScriptLoader',
props: {
vehicle: {
type: String,
required: true,
},
},
data: () => ({
all_scripts: [] as string[],
version: undefined as (undefined | SemVer),
selected_scripts: [] as string[],
is_loading_scripts: true,
loading_timeout_reached: false,
has_error: false,
}),
computed: {
filtered_scripts(): string[] | undefined {
// for scripts, we only check major version for now
// TODO: support other vehicles
let match_string: string
if (this.vehicle === 'Rover') {
match_string = `ArduRover/${this.version?.major}.`
} else {
return []
}
console.log(match_string)
return this.all_scripts.filter((name) => name.includes(match_string)).map(
(name) => name.replace('scripts/ardupilot/', ''),
)
},
board(): string | undefined {
return autopilot.current_board?.name
},
invalid_board_or_version(): boolean {
return !this.board || !this.version
},
is_loading(): boolean {
return (this.is_loading_scripts || this.invalid_board_or_version) && !this.loading_timeout_reached
},
},
watch: {
vehicle() {
this.updateLatestFirmwareVersion().then((version: string) => {
this.version = new SemVer(version.split('-')[1])
})
},
},
mounted() {
callPeriodically(fetchCurrentBoard, 10000)
this.updateLatestFirmwareVersion().then((version: string) => {
this.version = new SemVer(version.split('-')[1])
})
this.fetchScripts()
setTimeout(() => { this.onLoadingTimeout() }, MAX_LOADING_TIME_MS)
},
beforeDestroy() {
stopCallingPeriodically(fetchCurrentBoard)
},
methods: {
isNotEmpty(value: string): boolean {
return value !== ''
},
async fetchScripts() {
const response = await fetch(REPOSITORY_URL)
const scripts = await response.json()
this.all_scripts = scripts
this.is_loading_scripts = false
},
updateLatestFirmwareVersion() {
return availableFirmwares(this.vehicle as Vehicle)
.then((firmwares: Firmware[]) => {
const found: Firmware | undefined = firmwares.find((firmware) => firmware.name.includes('STABLE'))
if (found === undefined) {
return `Failed to find a stable version for vehicle (${this.vehicle})`
}
return found.name
})
},
setScriptsList(list: string[]) {
this.selected_scripts = list
this.$emit('input', list)
},
onLoadingTimeout() {
if (this.is_loading) {
this.loading_timeout_reached = true
if (!this.selected_scripts) {
this.selected_scripts = []
}
}
},
},
})
</script>
47 changes: 47 additions & 0 deletions core/frontend/src/components/wizard/Wizard.vue
Expand Up @@ -129,6 +129,10 @@
<v-text-field v-model="vehicle_name" label="Vehicle Name" />
<v-text-field v-model="mdns_name" label="MDNS Name" />
</div>
<ScriptLoader
v-model="scripts"
:vehicle="vehicle_type"
/>
<DefaultParamLoader
ref="param_loader"
v-model="params"
Expand Down Expand Up @@ -269,6 +273,7 @@ import {
fetchFirmwareInfo,
installFirmwareFromUrl,
} from '@/components/autopilot/AutopilotManagerUpdater'
import filebrowser from '@/libs/filebrowser'
import mavlink2rest from '@/libs/MAVLink2Rest'
import { MavCmd } from '@/libs/MAVLink2Rest/mavlink2rest-ts/messages/mavlink2rest-enum'
import ardupilot_data from '@/store/autopilot'
Expand All @@ -284,9 +289,12 @@ import { sleep } from '@/utils/helper_functions'
import ActionStepper, { Configuration, ConfigurationStatus } from './ActionStepper.vue'
import DefaultParamLoader from './DefaultParamLoader.vue'
import RequireInternet from './RequireInternet.vue'
import ScriptLoader from './ScriptLoader.vue'
const WIZARD_VERSION = 4
const REPOSITORY_ROOT = 'https://williangalvani.github.io/Blueos-Parameter-Repository_/'
const models: Record<string, string> = import.meta.glob('/src/assets/vehicles/models/**', { eager: true })
function get_model(vehicle_name: string, frame_name: string): undefined | string {
Expand Down Expand Up @@ -314,10 +322,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,
Expand Down Expand Up @@ -487,6 +497,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',
Expand Down Expand Up @@ -648,6 +667,34 @@ export default Vue.extend({
})
.catch((error) => `Failed to fetch available firmware: ${error.message ?? error.response?.data}.`)
},
async installScripts(): Promise<ConfigurationStatus> {
const scripts_folder = 'ardupilot_logs/firmware/scripts/'
await filebrowser.createFolder('')
try {
// Use allSettled to allow promises to fail in parallel
await Promise.allSettled(
this.scripts.map(
async (script) => filebrowser.createFile(scripts_folder + script.split('/').slice(-1)[0], true),
),
)
await Promise.allSettled(
this.scripts.map(async (script) => {
await filebrowser.writeToFile(
scripts_folder + script.split('/').slice(-1)[0],
await this.fetchScript(script),
)
}),
)
return undefined
} catch (e) {
console.log(e)
return `Failed to install scripts ${e}`
}
},
async fetchScript(script: string): Promise<string> {
const response = await fetch(`${REPOSITORY_ROOT}scripts/ardupilot/${script}`)
return response.text()
},
validateParams(): boolean {
return this.$refs.param_loader?.validateParams()
},
Expand Down
36 changes: 36 additions & 0 deletions core/frontend/src/libs/filebrowser.ts
Expand Up @@ -66,6 +66,42 @@ class Filebrowser {
})
}

async createFolder(folder_path: string): Promise<void> {
if (!folder_path.endsWith('/')) {
folder_path += '/'
}
this.createFile(folder_path)
}

async createFile(folder_path: string, override: Boolean = false): Promise<void> {
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<void> {
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. */
/**
Expand Down

0 comments on commit 31d054c

Please sign in to comment.