Merge "Refactor the OTA configuration page using several components." am: c532190082 am: 5798a15596 am: b7d6e8d966
Original change: https://android-review.googlesource.com/c/platform/development/+/1770745 Change-Id: I9b07e22aaf1e2c8f340f870fed0e37ae8bcf6800
This commit is contained in:
120
tools/otagui/src/components/BuildLibrary.vue
Normal file
120
tools/otagui/src/components/BuildLibrary.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<ul>
|
||||
<h3>Build Library</h3>
|
||||
<UploadFile @file-uploaded="fetchTargetList" />
|
||||
<BuildTable
|
||||
v-if="targetDetails.length>0"
|
||||
:builds="targetDetails"
|
||||
/>
|
||||
<li
|
||||
v-for="targetDetail in targetDetails"
|
||||
:key="targetDetail.file_name"
|
||||
>
|
||||
<div>
|
||||
<h3> Build File Name: {{ targetDetail.file_name }} </h3>
|
||||
<strong> Uploaded time: </strong> {{ formDate(targetDetail.time) }}
|
||||
<br>
|
||||
<strong> Build ID: </strong> {{ targetDetail.build_id }}
|
||||
<br>
|
||||
<strong> Build Version: </strong> {{ targetDetail.build_version }}
|
||||
<br>
|
||||
<strong> Build Flavor: </strong> {{ targetDetail.build_flavor }}
|
||||
<br>
|
||||
<v-btn
|
||||
:disabled="!isIncremental"
|
||||
@click="selectIncremental(targetDetail.path)"
|
||||
>
|
||||
Select as Incremental File
|
||||
</v-btn>
|
||||
 
|
||||
<v-btn @click="selectTarget(targetDetail.path)">
|
||||
Select as Target File
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-divider class="my-5" />
|
||||
</li>
|
||||
<v-btn @click="updateBuildLib">
|
||||
Refresh the build Library (use with cautions)
|
||||
</v-btn>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UploadFile from '@/components/UploadFile.vue'
|
||||
import BuildTable from '@/components/BuildTable.vue'
|
||||
import ApiService from '@/services/ApiService.js'
|
||||
import FormDate from '@/services/FormDate.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UploadFile,
|
||||
BuildTable
|
||||
},
|
||||
props: {
|
||||
isIncremental: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
incrementalSource: '',
|
||||
targetBuild: '',
|
||||
targetDetails: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchTargetList()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Fetch the build list from backend.
|
||||
*/
|
||||
async fetchTargetList() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('')
|
||||
this.targetDetails = response.data
|
||||
this.$emit('update:targetDetails', response.data)
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Cannot fetch Android Builds list from the backend, for the following reasons:"
|
||||
+ err
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Let the backend reload the builds from disk. This will overwrite the
|
||||
* original upload time.
|
||||
*/
|
||||
async updateBuildLib() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('/target')
|
||||
this.targetDetails = response.data
|
||||
this.$emit('update:targetDetails', response.data)
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Cannot fetch Android Builds list from the backend, for the following reasons: "
|
||||
+ err
|
||||
)
|
||||
}
|
||||
},
|
||||
selectTarget(path) {
|
||||
this.targetBuild = path
|
||||
this.$emit('update:targetBuild', path)
|
||||
},
|
||||
selectIncremental(path) {
|
||||
this.incrementalSource = path
|
||||
this.$emit('update:incrementalSource', path)
|
||||
},
|
||||
formDate(unixTime) {
|
||||
return FormDate.formDate(unixTime)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ul > li {
|
||||
list-style: none
|
||||
}
|
||||
</style>
|
||||
150
tools/otagui/src/components/OTAOptions.vue
Normal file
150
tools/otagui/src/components/OTAOptions.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<form @submit.prevent="sendForm">
|
||||
<FileSelect
|
||||
v-if="input.isIncremental"
|
||||
v-model="input.incremental"
|
||||
label="Select the source file"
|
||||
:options="targetDetails"
|
||||
/>
|
||||
<FileSelect
|
||||
v-model="input.target"
|
||||
label="Select a target file"
|
||||
:options="targetDetails"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.verbose"
|
||||
:label="'Verbose'"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.isIncremental"
|
||||
:label="'Incremental'"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.isPartial"
|
||||
:label="'Partial'"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div>
|
||||
<PartialCheckbox
|
||||
v-if="input.isPartial"
|
||||
v-model="input.partial"
|
||||
:labels="updatablePartitions"
|
||||
/>
|
||||
</div>
|
||||
<v-divider class="my-5" />
|
||||
<BaseInput
|
||||
v-model="input.extra"
|
||||
:label="'Extra Configurations'"
|
||||
/>
|
||||
<v-divider class="my-5" />
|
||||
<v-btn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseInput from '@/components/BaseInput.vue'
|
||||
import BaseCheckbox from '@/components/BaseCheckbox.vue'
|
||||
import FileSelect from '@/components/FileSelect.vue'
|
||||
import PartialCheckbox from '@/components/PartialCheckbox.vue'
|
||||
import { OTAConfiguration } from '@/services/JobSubmission.js'
|
||||
|
||||
export default {
|
||||
components:{
|
||||
BaseInput,
|
||||
BaseCheckbox,
|
||||
FileSelect,
|
||||
PartialCheckbox,
|
||||
},
|
||||
props: {
|
||||
targetDetails: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
incrementalSource: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
targetBuild: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
input: new OTAConfiguration()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Return the partition list of the selected target build.
|
||||
* @return Array<String>
|
||||
*/
|
||||
updatablePartitions() {
|
||||
if (!this.input.target) return []
|
||||
let target = this.targetDetails.filter(
|
||||
(d) => d.path === this.input.target
|
||||
)
|
||||
return target[0].partitions
|
||||
},
|
||||
checkIncremental() {
|
||||
return this.input.isIncremental
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
incrementalSource: {
|
||||
handler: function () {
|
||||
this.input.isIncremental = true
|
||||
this.input.incremental = this.incrementalSource
|
||||
}
|
||||
},
|
||||
targetBuild: {
|
||||
handler: function () {
|
||||
this.input.target = this.targetBuild
|
||||
}
|
||||
},
|
||||
checkIncremental: {
|
||||
handler: function () {
|
||||
this.$emit('update:isIncremental', this.checkIncremental)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Send the configuration to the backend.
|
||||
*/
|
||||
async sendForm() {
|
||||
try {
|
||||
let response_message = await this.input.sendForm()
|
||||
alert(response_message)
|
||||
} catch (err) {
|
||||
alert('Job cannot be started properly for the following reasons: ' + err)
|
||||
}
|
||||
this.input = new OTAInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="label"
|
||||
:checked="modelValue.get(label)"
|
||||
:checked="partitionSelected.get(label)"
|
||||
@change="updateSelected($event.target.value)"
|
||||
>
|
||||
{{ label }}
|
||||
@@ -38,34 +38,48 @@ export default {
|
||||
default: new Array(),
|
||||
},
|
||||
modelValue: {
|
||||
type: Map,
|
||||
default: new Map(),
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectAll: 1,
|
||||
selectAllText: ['Select All', 'Unselect All'],
|
||||
partitionSelected: new Map()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
partitionSelected: {
|
||||
handler: function() {
|
||||
let list = ''
|
||||
for (let [key, value] of this.partitionSelected) {
|
||||
if (value) {
|
||||
list += key + ' '
|
||||
}
|
||||
}
|
||||
this.$emit('update:modelValue', list)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Set the default value to be true once mounted
|
||||
for (let key of this.labels) {
|
||||
this.modelValue.set(key, true)
|
||||
this.partitionSelected.set(key, true)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateSelected(newSelect) {
|
||||
this.modelValue.set(newSelect, !this.modelValue.get(newSelect))
|
||||
this.$emit('update:modelValue', this.modelValue)
|
||||
this.partitionSelected.set(newSelect, !this.partitionSelected.get(newSelect))
|
||||
},
|
||||
revertAllSelection() {
|
||||
this.selectAll = 1 - this.selectAll
|
||||
for (let key of this.modelValue.keys()) {
|
||||
this.modelValue.set(key, Boolean(this.selectAll))
|
||||
for (let key of this.partitionSelected.keys()) {
|
||||
this.partitionSelected.set(key, Boolean(this.selectAll))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import JobList from '@/views/JobList.vue'
|
||||
import JobDetails from '@/views/JobDetails.vue'
|
||||
import About from '@/views/About.vue'
|
||||
import SimpleForm from '@/views/SimpleForm.vue'
|
||||
import JobConfigure from '@/views/JobConfigure.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -24,7 +24,7 @@ const routes = [
|
||||
{
|
||||
path: '/create',
|
||||
name: 'Create',
|
||||
component: SimpleForm
|
||||
component: JobConfigure
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
37
tools/otagui/src/services/JobSubmission.js
Normal file
37
tools/otagui/src/services/JobSubmission.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @fileoverview Class OTAInput is used to configure and create a process in
|
||||
* the backend to to start OTA package generation.
|
||||
* @package vue-uuid
|
||||
* @package ApiServices
|
||||
*/
|
||||
import { uuid } from 'vue-uuid'
|
||||
import ApiServices from './ApiService.js'
|
||||
|
||||
export class OTAConfiguration {
|
||||
/**
|
||||
* Initialize the input for the api /run/<id>
|
||||
*/
|
||||
constructor() {
|
||||
this.verbose = false,
|
||||
this.target = '',
|
||||
this.output = 'output/' + String(this.id) + '.zip',
|
||||
this.incremental = '',
|
||||
this.isIncremental = false,
|
||||
this.partial = '',
|
||||
this.isPartial = false,
|
||||
this.extra = '',
|
||||
this.id = uuid.v1()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the generation process, will throw an error if not succeed
|
||||
*/
|
||||
async sendForm() {
|
||||
try {
|
||||
let response = await ApiServices.postInput(JSON.stringify(this), this.id)
|
||||
return response.data
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
55
tools/otagui/src/views/JobConfigure.vue
Normal file
55
tools/otagui/src/views/JobConfigure.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<OTAOptions
|
||||
:targetDetails="targetDetails"
|
||||
:incrementalSource="incrementalSource"
|
||||
:targetBuild="targetBuild"
|
||||
@update:isIncremental="isIncremental = $event"
|
||||
/>
|
||||
</v-col>
|
||||
<v-divider vertical />
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
class="library"
|
||||
>
|
||||
<BuildLibrary
|
||||
:isIncremental="isIncremental"
|
||||
@update:incrementalSource="incrementalSource = $event"
|
||||
@update:targetBuild="targetBuild = $event"
|
||||
@update:targetDetails="targetDetails = $event"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OTAOptions from '@/components/OTAOptions.vue'
|
||||
import BuildLibrary from '@/components/BuildLibrary.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OTAOptions,
|
||||
BuildLibrary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
incrementalSource: '',
|
||||
targetBuild: '',
|
||||
targetDetails: [],
|
||||
isIncremental: false
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.library {
|
||||
overflow: scroll;
|
||||
height:calc(100vh - 80px);
|
||||
}
|
||||
</style>>
|
||||
@@ -1,242 +0,0 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<form @submit.prevent="sendForm">
|
||||
<FileSelect
|
||||
v-if="input.isIncremental"
|
||||
v-model="input.incremental"
|
||||
label="Select the source file"
|
||||
:options="targetDetails"
|
||||
/>
|
||||
<FileSelect
|
||||
v-model="input.target"
|
||||
label="Select a target file"
|
||||
:options="targetDetails"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.verbose"
|
||||
:label="'Verbose'"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.isIncremental"
|
||||
:label="'Incremental'"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
align="center"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="input.isPartial"
|
||||
:label="'Partial'"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div>
|
||||
<PartialCheckbox
|
||||
v-if="input.isPartial"
|
||||
v-model="partitionInclude"
|
||||
:labels="updatePartitions"
|
||||
/>
|
||||
</div>
|
||||
<v-divider class="my-5" />
|
||||
<BaseInput
|
||||
v-model="input.extra"
|
||||
:label="'Extra Configurations'"
|
||||
/>
|
||||
<v-divider class="my-5" />
|
||||
<v-btn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</form>
|
||||
</v-col>
|
||||
<v-divider vertical />
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<ul>
|
||||
<h3>Build Library</h3>
|
||||
<UploadFile @file-uploaded="fetchTargetList" />
|
||||
<BuildTable
|
||||
v-if="targetDetails.length>0"
|
||||
:builds="targetDetails"
|
||||
/>
|
||||
<li
|
||||
v-for="targetDetail in targetDetails"
|
||||
:key="targetDetail.file_name"
|
||||
>
|
||||
<div>
|
||||
<h3> Build File Name: {{ targetDetail.file_name }} </h3>
|
||||
<strong> Uploaded time: </strong> {{ formDate(targetDetail.time) }}
|
||||
<br>
|
||||
<strong> Build ID: </strong> {{ targetDetail.build_id }}
|
||||
<br>
|
||||
<strong> Build Version: </strong> {{ targetDetail.build_version }}
|
||||
<br>
|
||||
<strong> Build Flavor: </strong> {{ targetDetail.build_flavor }}
|
||||
<br>
|
||||
<v-btn
|
||||
:disabled="!input.isIncremental"
|
||||
@click="selectIncremental(targetDetail.path)"
|
||||
>
|
||||
Select as Incremental File
|
||||
</v-btn>
|
||||
 
|
||||
<v-btn @click="selectTarget(targetDetail.path)">
|
||||
Select as Target File
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-divider class="my-5" />
|
||||
</li>
|
||||
<v-btn @click="updateBuildLib">
|
||||
Refresh the build Library (use with cautions)
|
||||
</v-btn>
|
||||
</ul>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseInput from '@/components/BaseInput.vue'
|
||||
import BaseCheckbox from '@/components/BaseCheckbox.vue'
|
||||
import FileSelect from '@/components/FileSelect.vue'
|
||||
import ApiService from '../services/ApiService.js'
|
||||
import UploadFile from '@/components/UploadFile.vue'
|
||||
import PartialCheckbox from '@/components/PartialCheckbox.vue'
|
||||
import FormDate from '../services/FormDate.js'
|
||||
import BuildTable from '@/components/BuildTable.vue'
|
||||
import { uuid } from 'vue-uuid'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseInput,
|
||||
BaseCheckbox,
|
||||
UploadFile,
|
||||
FileSelect,
|
||||
PartialCheckbox,
|
||||
BuildTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: 0,
|
||||
input: {},
|
||||
inputs: [],
|
||||
response_message: '',
|
||||
targetDetails: [],
|
||||
partitionInclude: new Map(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
updatePartitions() {
|
||||
if (!this.input.target) return []
|
||||
let target = this.targetDetails.filter(
|
||||
(d) => d.path === this.input.target
|
||||
)
|
||||
return target[0].partitions
|
||||
},
|
||||
partitionList() {
|
||||
let list = ''
|
||||
for (let [key, value] of this.partitionInclude) {
|
||||
if (value) {
|
||||
list += key + ' '
|
||||
}
|
||||
}
|
||||
return list
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
partitionList: {
|
||||
handler: function () {
|
||||
this.input.partial = this.partitionList
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.resetInput()
|
||||
this.fetchTargetList()
|
||||
this.updateUUID()
|
||||
},
|
||||
methods: {
|
||||
resetInput() {
|
||||
this.input = {
|
||||
verbose: false,
|
||||
target: '',
|
||||
output: 'output/',
|
||||
incremental: '',
|
||||
isIncremental: false,
|
||||
partial: '',
|
||||
isPartial: false,
|
||||
extra: '',
|
||||
},
|
||||
this.partitionInclude = new Map()
|
||||
},
|
||||
async sendForm(e) {
|
||||
try {
|
||||
let response = await ApiService.postInput(this.input, this.id)
|
||||
this.response_message = response.data
|
||||
alert(this.response_message)
|
||||
} catch (err) {
|
||||
alert('Job cannot be started properly, please check.')
|
||||
console.log(err)
|
||||
}
|
||||
this.resetInput()
|
||||
this.updateUUID()
|
||||
},
|
||||
async fetchTargetList() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('')
|
||||
this.targetDetails = response.data
|
||||
} catch (err) {
|
||||
console.log('Fetch Error', err)
|
||||
}
|
||||
},
|
||||
updateUUID() {
|
||||
this.id = uuid.v1()
|
||||
this.input.output += String(this.id) + '.zip'
|
||||
},
|
||||
formDate(unixTime) {
|
||||
return FormDate.formDate(unixTime)
|
||||
},
|
||||
selectTarget(path) {
|
||||
this.input.target = path
|
||||
},
|
||||
selectIncremental(path) {
|
||||
this.input.incremental = path
|
||||
},
|
||||
async updateBuildLib() {
|
||||
try {
|
||||
let response = await ApiService.getFileList('/target')
|
||||
this.targetDetails = response.data
|
||||
} catch (err) {
|
||||
console.log('Fetch Error', err)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
ul > li {
|
||||
list-style: none
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user