mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-15 20:35:23 +03:00
Setup for the cloud build implementation
This commit is contained in:
parent
a49a6b98ba
commit
6aae795781
10 changed files with 570 additions and 1142 deletions
|
@ -3068,7 +3068,9 @@
|
||||||
"sdcardStatusUnknown": {
|
"sdcardStatusUnknown": {
|
||||||
"message": "Unknown state $1"
|
"message": "Unknown state $1"
|
||||||
},
|
},
|
||||||
|
"firmwareFlasherBranch": {
|
||||||
|
"message": "Select commit"
|
||||||
|
},
|
||||||
"firmwareFlasherReleaseSummaryHead": {
|
"firmwareFlasherReleaseSummaryHead": {
|
||||||
"message": "Release info"
|
"message": "Release info"
|
||||||
},
|
},
|
||||||
|
@ -3090,17 +3092,17 @@
|
||||||
"firmwareFlasherReleaseTarget": {
|
"firmwareFlasherReleaseTarget": {
|
||||||
"message": "Target:"
|
"message": "Target:"
|
||||||
},
|
},
|
||||||
"firmwareFlasherReleaseFile": {
|
"firmwareFlasherReleaseMCU": {
|
||||||
"message": "Binary:"
|
"message": "MCU:"
|
||||||
},
|
},
|
||||||
"firmwareFlasherUnifiedTargetName": {
|
"firmwareFlasherCloudBuildDetails": {
|
||||||
"message": "Unified Target:"
|
"message": "Cloud Build Details:"
|
||||||
},
|
},
|
||||||
"firmwareFlasherUnifiedTargetFileUrl": {
|
"firmwareFlasherCloudBuildLogUrl": {
|
||||||
"message": "Show config."
|
"message": "Show Log."
|
||||||
},
|
},
|
||||||
"firmwareFlasherUnifiedTargetDate": {
|
"firmwareFlasherCloudBuildStatus": {
|
||||||
"message": "Date:"
|
"message": "Status:"
|
||||||
},
|
},
|
||||||
"firmwareFlasherReleaseFileUrl": {
|
"firmwareFlasherReleaseFileUrl": {
|
||||||
"message": "Download manually."
|
"message": "Download manually."
|
||||||
|
@ -6674,5 +6676,20 @@
|
||||||
"presetsReviewOptionsWarning": {
|
"presetsReviewOptionsWarning": {
|
||||||
"message": "Please, review the list of options before picking this preset.",
|
"message": "Please, review the list of options before picking this preset.",
|
||||||
"description": "Dialog text to prompt user to review options for the preset"
|
"description": "Dialog text to prompt user to review options for the preset"
|
||||||
|
},
|
||||||
|
"firmwareFlasherBuildConfigurationHead": {
|
||||||
|
"message": "Build Configuration"
|
||||||
|
},
|
||||||
|
"firmwareFlasherBuildOptions": {
|
||||||
|
"message": "Other Options"
|
||||||
|
},
|
||||||
|
"firmwareFlasherBuildRadioProtocols": {
|
||||||
|
"message": "Radio Protocols"
|
||||||
|
},
|
||||||
|
"firmwareFlasherBuildTelemetryProtocols": {
|
||||||
|
"message": "Telemetry Protocols"
|
||||||
|
},
|
||||||
|
"firmwareFlasherBuildMotorProtocols": {
|
||||||
|
"message": "Motor Protocols"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.release_info {
|
.build_configuration {
|
||||||
|
.select2-selection__choice {
|
||||||
|
margin: auto;
|
||||||
|
color: #3f4241;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.release_info, .build_configuration {
|
||||||
display: none;
|
display: none;
|
||||||
.title {
|
.title {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const ConfigInserter = function () {
|
|
||||||
};
|
|
||||||
|
|
||||||
const CUSTOM_DEFAULTS_POINTER_ADDRESS = 0x08002800;
|
|
||||||
const BLOCK_SIZE = 16384;
|
|
||||||
|
|
||||||
function seek(firmware, address) {
|
|
||||||
let index = 0;
|
|
||||||
for (; index < firmware.data.length && address >= firmware.data[index].address + firmware.data[index].bytes; index++);
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
lineIndex: index,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (firmware.data[index] && address >= firmware.data[index].address) {
|
|
||||||
result.byteIndex = address - firmware.data[index].address;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readUint32(firmware, index) {
|
|
||||||
let result = 0;
|
|
||||||
for (let position = 0; position < 4; position++) {
|
|
||||||
result += firmware.data[index.lineIndex].data[index.byteIndex++] << (8 * position);
|
|
||||||
if (index.byteIndex >= firmware.data[index.lineIndex].bytes) {
|
|
||||||
index.lineIndex++;
|
|
||||||
index.byteIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCustomDefaultsArea(firmware) {
|
|
||||||
const result = {};
|
|
||||||
|
|
||||||
const index = seek(firmware, CUSTOM_DEFAULTS_POINTER_ADDRESS);
|
|
||||||
|
|
||||||
if (index.byteIndex === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.startAddress = readUint32(firmware, index);
|
|
||||||
result.endAddress = readUint32(firmware, index);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateData(firmware, input, startAddress) {
|
|
||||||
let address = startAddress;
|
|
||||||
|
|
||||||
const index = seek(firmware, address);
|
|
||||||
|
|
||||||
if (index.byteIndex !== undefined) {
|
|
||||||
throw new Error('Configuration area in firmware not free.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add 0 terminator
|
|
||||||
input = `${input}\0`;
|
|
||||||
|
|
||||||
let inputIndex = 0;
|
|
||||||
while (inputIndex < input.length) {
|
|
||||||
const remaining = input.length - inputIndex;
|
|
||||||
const line = {
|
|
||||||
address: address,
|
|
||||||
bytes: BLOCK_SIZE > remaining ? remaining : BLOCK_SIZE,
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (firmware.data[index.lineIndex] && (line.address + line.bytes) > firmware.data[index.lineIndex].address) {
|
|
||||||
throw new Error("Aborting data generation, free area too small.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < line.bytes; i++) {
|
|
||||||
line.data.push(input.charCodeAt(inputIndex++));
|
|
||||||
}
|
|
||||||
|
|
||||||
address = address + line.bytes;
|
|
||||||
|
|
||||||
firmware.data.splice(index.lineIndex++, 0, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
firmware.bytes_total += input.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONFIG_LABEL = `Custom defaults inserted in`;
|
|
||||||
|
|
||||||
ConfigInserter.prototype.insertConfig = function (firmware, input) {
|
|
||||||
console.time(CONFIG_LABEL);
|
|
||||||
|
|
||||||
const customDefaultsArea = getCustomDefaultsArea(firmware);
|
|
||||||
|
|
||||||
if (!customDefaultsArea || customDefaultsArea.endAddress - customDefaultsArea.startAddress === 0) {
|
|
||||||
return false;
|
|
||||||
} else if (input.length >= customDefaultsArea.endAddress - customDefaultsArea.startAddress) {
|
|
||||||
throw new Error(`Custom defaults area too small (${customDefaultsArea.endAddress - customDefaultsArea.startAddress} bytes), ${input.length + 1} bytes needed.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
generateData(firmware, input, customDefaultsArea.startAddress);
|
|
||||||
|
|
||||||
console.timeEnd(CONFIG_LABEL);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
|
@ -1,242 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Caching of previously downloaded firmwares and release descriptions
|
|
||||||
*
|
|
||||||
* Depends on LRUMap for which the docs can be found here:
|
|
||||||
* https://github.com/rsms/js-lru
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} Descriptor Release descriptor object
|
|
||||||
* @property {string} releaseUrl
|
|
||||||
* @property {string} name
|
|
||||||
* @property {string} version
|
|
||||||
* @property {string} url
|
|
||||||
* @property {string} file
|
|
||||||
* @property {string} target
|
|
||||||
* @property {string} date
|
|
||||||
* @property {string} notes
|
|
||||||
* @property {string} status
|
|
||||||
* @see buildBoardOptions() in {@link release_checker.js}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} CacheItem
|
|
||||||
* @property {Descriptor} release
|
|
||||||
* @property {string} hexdata
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages caching of downloaded firmware files
|
|
||||||
*/
|
|
||||||
let FirmwareCache = (function () {
|
|
||||||
|
|
||||||
let onPutToCacheCallback,
|
|
||||||
onRemoveFromCacheCallback;
|
|
||||||
|
|
||||||
let JournalStorage = (function () {
|
|
||||||
let CACHEKEY = "firmware-cache-journal";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array} data LRU key-value pairs
|
|
||||||
*/
|
|
||||||
function persist(data) {
|
|
||||||
let obj = {};
|
|
||||||
obj[CACHEKEY] = data;
|
|
||||||
SessionStorage.set(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
function load(callback) {
|
|
||||||
const obj = SessionStorage.get(CACHEKEY);
|
|
||||||
let entries = typeof obj === "object" && obj.hasOwnProperty(CACHEKEY)
|
|
||||||
? obj[CACHEKEY]
|
|
||||||
: [];
|
|
||||||
callback(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
persist: persist,
|
|
||||||
load: load,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
let journal = new LRUMap(100),
|
|
||||||
journalLoaded = false;
|
|
||||||
|
|
||||||
journal.shift = function () {
|
|
||||||
// remove cached data for oldest release
|
|
||||||
let oldest = LRUMap.prototype.shift.call(this);
|
|
||||||
if (oldest === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
let key = oldest[0];
|
|
||||||
let cacheKey = withCachePrefix(key);
|
|
||||||
const obj = SessionStorage.get(cacheKey);
|
|
||||||
/** @type {CacheItem} */
|
|
||||||
const cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey) ? obj[cacheKey] : null;
|
|
||||||
if (cached === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
SessionStorage.remove(cacheKey);
|
|
||||||
onRemoveFromCache(cached.release);
|
|
||||||
return oldest;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
* @returns {string} A key used to store a release in the journal
|
|
||||||
*/
|
|
||||||
function keyOf(release) {
|
|
||||||
return release.file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {string} A key for storing cached data for a release
|
|
||||||
*/
|
|
||||||
function withCachePrefix(key) {
|
|
||||||
return `cache:${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function has(release) {
|
|
||||||
if (!release) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!journalLoaded) {
|
|
||||||
console.warn("Cache not yet loaded");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return journal.has(keyOf(release));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
* @param {string} hexdata
|
|
||||||
*/
|
|
||||||
function put(release, hexdata) {
|
|
||||||
if (!journalLoaded) {
|
|
||||||
console.warn("Cache journal not yet loaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let key = keyOf(release);
|
|
||||||
if (has(release)) {
|
|
||||||
console.debug(`Firmware is already cached: ${key}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
journal.set(key, true);
|
|
||||||
JournalStorage.persist(journal.toJSON());
|
|
||||||
let obj = {};
|
|
||||||
obj[withCachePrefix(key)] = {
|
|
||||||
release: release,
|
|
||||||
hexdata: hexdata,
|
|
||||||
};
|
|
||||||
SessionStorage.set(obj);
|
|
||||||
onPutToCache(release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
function get(release, callback) {
|
|
||||||
if (!journalLoaded) {
|
|
||||||
console.warn("Cache journal not yet loaded");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
let key = keyOf(release);
|
|
||||||
if (!has(release)) {
|
|
||||||
console.debug(`Firmware is not cached: ${key}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let cacheKey = withCachePrefix(key);
|
|
||||||
const obj = SessionStorage.get(cacheKey);
|
|
||||||
const cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey) ? obj[cacheKey] : null;
|
|
||||||
callback(cached);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all cached data
|
|
||||||
*/
|
|
||||||
function invalidate() {
|
|
||||||
if (!journalLoaded) {
|
|
||||||
console.warn("Cache journal not yet loaded");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
let cacheKeys = [];
|
|
||||||
for (let key of journal.keys()) {
|
|
||||||
cacheKeys.push(withCachePrefix(key));
|
|
||||||
}
|
|
||||||
const obj = SessionStorage.get(cacheKeys);
|
|
||||||
if (typeof obj !== "object") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(obj.entries());
|
|
||||||
for (let cacheKey of cacheKeys) {
|
|
||||||
if (obj.hasOwnProperty(cacheKey)) {
|
|
||||||
/** @type {CacheItem} */
|
|
||||||
let item = obj[cacheKey];
|
|
||||||
onRemoveFromCache(item.release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SessionStorage.remove(cacheKeys);
|
|
||||||
journal.clear();
|
|
||||||
JournalStorage.persist(journal.toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
*/
|
|
||||||
function onPutToCache(release) {
|
|
||||||
if (typeof onPutToCacheCallback === "function") {
|
|
||||||
onPutToCacheCallback(release);
|
|
||||||
}
|
|
||||||
console.info(`Release put to cache: ${keyOf(release)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Descriptor} release
|
|
||||||
*/
|
|
||||||
function onRemoveFromCache(release) {
|
|
||||||
if (typeof onRemoveFromCacheCallback === "function") {
|
|
||||||
onRemoveFromCacheCallback(release);
|
|
||||||
}
|
|
||||||
console.debug(`Cache data removed: ${keyOf(release)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array} entries
|
|
||||||
*/
|
|
||||||
function onEntriesLoaded(entries) {
|
|
||||||
let pairs = [];
|
|
||||||
for (let entry of entries) {
|
|
||||||
pairs.push([entry.key, entry.value]);
|
|
||||||
}
|
|
||||||
journal.assign(pairs);
|
|
||||||
journalLoaded = true;
|
|
||||||
console.info(`Firmware cache journal loaded; number of entries: ${entries.length}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
has: has,
|
|
||||||
put: put,
|
|
||||||
get: get,
|
|
||||||
onPutToCache: callback => onPutToCacheCallback = callback,
|
|
||||||
onRemoveFromCache: callback => onRemoveFromCacheCallback = callback,
|
|
||||||
load: () => {
|
|
||||||
JournalStorage.load(onEntriesLoaded);
|
|
||||||
},
|
|
||||||
unload: () => {
|
|
||||||
JournalStorage.persist(journal.toJSON());
|
|
||||||
journal.clear();
|
|
||||||
},
|
|
||||||
invalidate: invalidate,
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,160 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const JenkinsLoader = function (url) {
|
|
||||||
this._url = url;
|
|
||||||
this._jobs = [];
|
|
||||||
this._cacheExpirationPeriod = 3600 * 1000;
|
|
||||||
|
|
||||||
this._jobsRequest = '/api/json?tree=jobs[name]';
|
|
||||||
this._buildsRequest = '/api/json?tree=builds[number,result,timestamp,artifacts[relativePath],changeSet[items[commitId,msg]]]';
|
|
||||||
};
|
|
||||||
|
|
||||||
JenkinsLoader.prototype.loadJobs = function (viewName, callback) {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
const viewUrl = `${self._url}/view/${viewName}`;
|
|
||||||
const jobsDataTag = `${viewUrl}_JobsData`;
|
|
||||||
const cacheLastUpdateTag = `${viewUrl}_JobsLastUpdate`;
|
|
||||||
|
|
||||||
const wrappedCallback = jobs => {
|
|
||||||
self._jobs = jobs;
|
|
||||||
callback(jobs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = SessionStorage.get([cacheLastUpdateTag, jobsDataTag]);
|
|
||||||
const jobsDataTimestamp = $.now();
|
|
||||||
const cachedJobsData = result[jobsDataTag];
|
|
||||||
const cachedJobsLastUpdate = result[cacheLastUpdateTag];
|
|
||||||
|
|
||||||
const cachedCallback = () => {
|
|
||||||
if (cachedJobsData) {
|
|
||||||
GUI.log(i18n.getMessage('buildServerUsingCached', ['jobs']));
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedCallback(cachedJobsData ? cachedJobsData : []);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!cachedJobsData || !cachedJobsLastUpdate || jobsDataTimestamp - cachedJobsLastUpdate > self._cacheExpirationPeriod) {
|
|
||||||
const url = `${viewUrl}${self._jobsRequest}`;
|
|
||||||
|
|
||||||
$.get(url, jobsInfo => {
|
|
||||||
GUI.log(i18n.getMessage('buildServerLoaded', ['jobs']));
|
|
||||||
|
|
||||||
// remove Betaflight prefix, rename Betaflight job to Development
|
|
||||||
const jobs = jobsInfo.jobs.map(job => {
|
|
||||||
return { title: job.name.replace('Betaflight ', '').replace('Betaflight', 'Development'), name: job.name };
|
|
||||||
});
|
|
||||||
|
|
||||||
// cache loaded info
|
|
||||||
const object = {};
|
|
||||||
object[jobsDataTag] = jobs;
|
|
||||||
object[cacheLastUpdateTag] = $.now();
|
|
||||||
SessionStorage.set(object);
|
|
||||||
|
|
||||||
wrappedCallback(jobs);
|
|
||||||
}).fail(xhr => {
|
|
||||||
GUI.log(i18n.getMessage('buildServerLoadFailed', ['jobs', `HTTP ${xhr.status}`]));
|
|
||||||
cachedCallback();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cachedCallback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JenkinsLoader.prototype.loadBuilds = function (jobName, callback) {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
const jobUrl = `${self._url}/job/${jobName}`;
|
|
||||||
const buildsDataTag = `${jobUrl}BuildsData`;
|
|
||||||
const cacheLastUpdateTag = `${jobUrl}BuildsLastUpdate`;
|
|
||||||
|
|
||||||
const result = SessionStorage.get([cacheLastUpdateTag, buildsDataTag]);
|
|
||||||
const buildsDataTimestamp = $.now();
|
|
||||||
const cachedBuildsData = result[buildsDataTag];
|
|
||||||
const cachedBuildsLastUpdate = result[cacheLastUpdateTag];
|
|
||||||
|
|
||||||
const cachedCallback = () => {
|
|
||||||
if (cachedBuildsData) {
|
|
||||||
GUI.log(i18n.getMessage('buildServerUsingCached', [jobName]));
|
|
||||||
}
|
|
||||||
|
|
||||||
self._parseBuilds(jobUrl, jobName, cachedBuildsData ? cachedBuildsData : [], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!cachedBuildsData || !cachedBuildsLastUpdate || buildsDataTimestamp - cachedBuildsLastUpdate > self._cacheExpirationPeriod) {
|
|
||||||
const url = `${jobUrl}${self._buildsRequest}`;
|
|
||||||
|
|
||||||
$.get(url, function (buildsInfo) {
|
|
||||||
GUI.log(i18n.getMessage('buildServerLoaded', [jobName]));
|
|
||||||
|
|
||||||
// filter successful builds
|
|
||||||
const builds = buildsInfo.builds.filter(build => build.result == 'SUCCESS')
|
|
||||||
.map(build => ({
|
|
||||||
number: build.number,
|
|
||||||
artifacts: build.artifacts.map(artifact => artifact.relativePath),
|
|
||||||
changes: build.changeSet.items.map(item => `* ${item.msg}`).join('<br>\n'),
|
|
||||||
timestamp: build.timestamp,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// cache loaded info
|
|
||||||
const object = {};
|
|
||||||
object[buildsDataTag] = builds;
|
|
||||||
object[cacheLastUpdateTag] = $.now();
|
|
||||||
SessionStorage.set(object);
|
|
||||||
self._parseBuilds(jobUrl, jobName, builds, callback);
|
|
||||||
}).fail(xhr => {
|
|
||||||
GUI.log(i18n.getMessage('buildServerLoadFailed', [jobName, `HTTP ${xhr.status}`]));
|
|
||||||
cachedCallback();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cachedCallback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JenkinsLoader.prototype._parseBuilds = function (jobUrl, jobName, builds, callback) {
|
|
||||||
// convert from `build -> targets` to `target -> builds` mapping
|
|
||||||
const targetBuilds = {};
|
|
||||||
|
|
||||||
const targetFromFilenameExpression = /betaflight_([\d.]+)?_?(\w+)(\-.*)?\.(.*)/;
|
|
||||||
|
|
||||||
builds.forEach(build => {
|
|
||||||
build.artifacts.forEach(relativePath => {
|
|
||||||
const match = targetFromFilenameExpression.exec(relativePath);
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = match[1];
|
|
||||||
const target = match[2];
|
|
||||||
const date = new Date(build.timestamp);
|
|
||||||
|
|
||||||
const day = (`0${date.getDate()}`).slice(-2);
|
|
||||||
const month = (`0${(date.getMonth() + 1)}`).slice(-2);
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const hours = (`0${date.getHours()}`).slice(-2);
|
|
||||||
const minutes = (`0${date.getMinutes()}`).slice(-2);
|
|
||||||
|
|
||||||
const formattedDate = `${day}-${month}-${year} ${hours}:${minutes}`;
|
|
||||||
|
|
||||||
const descriptor = {
|
|
||||||
'releaseUrl': `${jobUrl}/${build.number}`,
|
|
||||||
'name' : `${jobName} #${build.number}`,
|
|
||||||
'version' : `${version} #${build.number}`,
|
|
||||||
'url' : `${jobUrl}/${build.number}/artifact/${relativePath}`,
|
|
||||||
'file' : relativePath.split('/').slice(-1)[0],
|
|
||||||
'target' : target,
|
|
||||||
'date' : formattedDate,
|
|
||||||
'notes' : build.changes,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (targetBuilds[target]) {
|
|
||||||
targetBuilds[target].push(descriptor);
|
|
||||||
} else {
|
|
||||||
targetBuilds[target] = [ descriptor ];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(targetBuilds);
|
|
||||||
};
|
|
|
@ -638,6 +638,12 @@ function notifyOutdatedVersion(releaseData) {
|
||||||
if (result.checkForConfiguratorUnstableVersions) {
|
if (result.checkForConfiguratorUnstableVersions) {
|
||||||
showUnstableReleases = true;
|
showUnstableReleases = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (releaseData === undefined) {
|
||||||
|
console.log('No releaseData');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const versions = releaseData.filter(function (version) {
|
const versions = releaseData.filter(function (version) {
|
||||||
const semVerVersion = semver.parse(version.tag_name);
|
const semVerVersion = semver.parse(version.tag_name);
|
||||||
if (semVerVersion && (showUnstableReleases || semVerVersion.prerelease.length === 0)) {
|
if (semVerVersion && (showUnstableReleases || semVerVersion.prerelease.length === 0)) {
|
||||||
|
|
129
src/js/release_loader.js
Normal file
129
src/js/release_loader.js
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class ReleaseLoader {
|
||||||
|
|
||||||
|
constructor (url) {
|
||||||
|
this._url = url;
|
||||||
|
this._cacheExpirationPeriod = 3600 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(url, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const dataTag = `${url}_Data`;
|
||||||
|
const cacheLastUpdateTag = `${url}_LastUpdate`;
|
||||||
|
|
||||||
|
const result = SessionStorage.get([cacheLastUpdateTag, dataTag]);
|
||||||
|
const dataTimestamp = $.now();
|
||||||
|
const cachedData = result[dataTag];
|
||||||
|
const cachedLastUpdate = result[cacheLastUpdateTag];
|
||||||
|
|
||||||
|
const cachedCallback = () => {
|
||||||
|
if (cachedData) {
|
||||||
|
GUI.log(i18n.getMessage('buildServerUsingCached', [url]));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(cachedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!cachedData || !cachedLastUpdate || dataTimestamp - cachedLastUpdate > this._cacheExpirationPeriod) {
|
||||||
|
$.get(url, function (info) {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoaded', [url]));
|
||||||
|
|
||||||
|
// cache loaded info
|
||||||
|
const object = {};
|
||||||
|
object[dataTag] = info;
|
||||||
|
object[cacheLastUpdateTag] = $.now();
|
||||||
|
SessionStorage.set(object);
|
||||||
|
onSuccess(info);
|
||||||
|
}).fail(xhr => {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoadFailed', [url, `HTTP ${xhr.status}`]));
|
||||||
|
if (onFailure !== undefined) {
|
||||||
|
onFailure();
|
||||||
|
} else {
|
||||||
|
cachedCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cachedCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTargets(callback) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/targets`;
|
||||||
|
this.load(url, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTargetReleases(target, callback) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/targets/${target}`;
|
||||||
|
this.load(url, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTarget(target, release, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/builds/${release}/${target}`;
|
||||||
|
this.load(url, onSuccess, onFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTargetHex(path, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}${path}`;
|
||||||
|
$.get(url, function (data) {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoaded', [path]));
|
||||||
|
onSuccess(data);
|
||||||
|
}).fail(xhr => {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoadFailed', [path, `HTTP ${xhr.status}`]));
|
||||||
|
if (onFailure !== undefined) {
|
||||||
|
onFailure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBuild(request, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/builds`;
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
success: function(data) {
|
||||||
|
data.url = `/api/builds/${data.key}/hex`;
|
||||||
|
onSuccess(data);
|
||||||
|
},
|
||||||
|
}).fail(xhr => {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoadFailed', [url, `HTTP ${xhr.status}`]));
|
||||||
|
if (onFailure !== undefined) {
|
||||||
|
onFailure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBuildStatus(key, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/builds/${key}/status`;
|
||||||
|
$.get(url, function (data) {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoaded', [url]));
|
||||||
|
onSuccess(data);
|
||||||
|
}).fail(xhr => {
|
||||||
|
GUI.log(i18n.getMessage('buildServerLoadFailed', [url, `HTTP ${xhr.status}`]));
|
||||||
|
if (onFailure !== undefined) {
|
||||||
|
onFailure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOptions(onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/options`;
|
||||||
|
this.load(url, onSuccess, onFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCommits(release, onSuccess, onFailure) {
|
||||||
|
|
||||||
|
const url = `${this._url}/api/releases/${release}/commits`;
|
||||||
|
this.load(url, onSuccess, onFailure);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -110,7 +110,7 @@
|
||||||
<script type="text/javascript" src="./js/Features.js"></script>
|
<script type="text/javascript" src="./js/Features.js"></script>
|
||||||
<script type="text/javascript" src="./js/Beepers.js"></script>
|
<script type="text/javascript" src="./js/Beepers.js"></script>
|
||||||
<script type="text/javascript" src="./js/release_checker.js"></script>
|
<script type="text/javascript" src="./js/release_checker.js"></script>
|
||||||
<script type="text/javascript" src="./js/jenkins_loader.js"></script>
|
<script type="text/javascript" src="./js/release_loader.js"></script>
|
||||||
<script type="text/javascript" src="./js/Analytics.js"></script>
|
<script type="text/javascript" src="./js/Analytics.js"></script>
|
||||||
<script type="text/javascript" src="./js/GitHubApi.js"></script>
|
<script type="text/javascript" src="./js/GitHubApi.js"></script>
|
||||||
<script type="module" src="./js/main.js"></script>
|
<script type="module" src="./js/main.js"></script>
|
||||||
|
@ -127,12 +127,10 @@
|
||||||
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcesDialog.js"></script>
|
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcesDialog.js"></script>
|
||||||
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcePanel.js"></script>
|
<script type="text/javascript" src="./tabs/presets/SourcesDialog/SourcePanel.js"></script>
|
||||||
<script type="text/javascript" src="./tabs/presets/SourcesDialog/PresetSource.js"></script>
|
<script type="text/javascript" src="./tabs/presets/SourcesDialog/PresetSource.js"></script>
|
||||||
<script type="text/javascript" src="./js/FirmwareCache.js"></script>
|
|
||||||
<script type="text/javascript" src="./js/LogoManager.js"></script>
|
<script type="text/javascript" src="./js/LogoManager.js"></script>
|
||||||
<script type="text/javascript" src="./node_modules/jquery-textcomplete/dist/jquery.textcomplete.min.js"></script>
|
<script type="text/javascript" src="./node_modules/jquery-textcomplete/dist/jquery.textcomplete.min.js"></script>
|
||||||
<script type="text/javascript" src="./js/CliAutoComplete.js"></script>
|
<script type="text/javascript" src="./js/CliAutoComplete.js"></script>
|
||||||
<script type="text/javascript" src="./js/DarkTheme.js"></script>
|
<script type="text/javascript" src="./js/DarkTheme.js"></script>
|
||||||
<script type="text/javascript" src="./js/ConfigInserter.js"></script>
|
|
||||||
<script type="text/javascript" src="./js/TuningSliders.js"></script>
|
<script type="text/javascript" src="./js/TuningSliders.js"></script>
|
||||||
<script type="text/javascript" src="./js/phones_ui.js"></script>
|
<script type="text/javascript" src="./js/phones_ui.js"></script>
|
||||||
<script type="text/javascript" src="./node_modules/jquery-touchswipe/jquery.touchSwipe.min.js"></script>
|
<script type="text/javascript" src="./node_modules/jquery-touchswipe/jquery.touchSwipe.min.js"></script>
|
||||||
|
|
|
@ -1,68 +1,104 @@
|
||||||
<div class="tab-firmware_flasher toolbar_fixed_bottom">
|
<div class="tab-firmware_flasher toolbar_fixed_bottom">
|
||||||
<div class="content_wrapper">
|
<div class="content_wrapper">
|
||||||
<div class="options gui_box">
|
<div class="options gui_box" style="float: left; width: 460px; ">
|
||||||
<div class="spacer">
|
<div class="spacer" style="margin-bottom: 10px;">
|
||||||
|
<div class="margin-bottom">
|
||||||
<table class="cf_table" style="margin-top: 10px;">
|
<table class="cf_table" style="margin-top: 10px;">
|
||||||
<tr class="option">
|
<tr class="option">
|
||||||
<td><label> <input class="show_development_releases toggle" type="checkbox" /> <span
|
<td>
|
||||||
i18n="firmwareFlasherShowDevelopmentReleases"></span>
|
<label>
|
||||||
</label></td>
|
<input class="show_development_releases toggle" type="checkbox" />
|
||||||
<td><span class="description" i18n="firmwareFlasherShowDevelopmentReleasesDescription"></span></td>
|
<span i18n="firmwareFlasherShowDevelopmentReleases"></span>
|
||||||
|
</label>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherShowDevelopmentReleasesDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="expert_mode option">
|
<tr class="expert_mode option">
|
||||||
<td><label><input class="expert_mode toggle" type="checkbox" /><span i18n="expertMode"></span>
|
<td>
|
||||||
</label></td>
|
<label>
|
||||||
<td><span class="description" i18n="expertModeDescription"></span></td>
|
<input class="expert_mode toggle" type="checkbox" />
|
||||||
|
<span i18n="expertMode"></span>
|
||||||
|
</label>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="expertModeDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="build_type">
|
<tr class="build_type">
|
||||||
<td>
|
<td>
|
||||||
<select name="build_type">
|
<select name="build_type">
|
||||||
<!-- options generated at runtime -->
|
<!-- options generated at runtime -->
|
||||||
</select>
|
</select>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherOnlineSelectBuildType"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="description" i18n="firmwareFlasherOnlineSelectBuildType"></span></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="board-select"><select name="board">
|
<td class="board-select">
|
||||||
|
<select name="board">
|
||||||
<option value="0" i18n="firmwareFlasherOptionLoading">Loading ...</option>
|
<option value="0" i18n="firmwareFlasherOptionLoading">Loading ...</option>
|
||||||
</select></td>
|
</select>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherOnlineSelectBoardDescription"></div>
|
||||||
|
</td>
|
||||||
<td class="board-description">
|
<td class="board-description">
|
||||||
<div class="btn default_btn">
|
<div class="btn default_btn">
|
||||||
<a class="detect-board disabled" href="#" i18n="firmwareFlasherDetectBoardButton"></a>
|
<a class="detect-board disabled" href="#" i18n="firmwareFlasherDetectBoardButton"></a>
|
||||||
</div>
|
</div>
|
||||||
<span class="description" i18n="firmwareFlasherOnlineSelectBoardDescription"></span>
|
|
||||||
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherOnlineSelectBoardHint"></div>
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherOnlineSelectBoardHint"></div>
|
||||||
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherDetectBoardDescriptionHint"></div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><select name="firmware_version">
|
<td>
|
||||||
|
<select name="firmware_version">
|
||||||
<option value="0" i18n="firmwareFlasherOptionLoading">Loading ...</option>
|
<option value="0" i18n="firmwareFlasherOptionLoading">Loading ...</option>
|
||||||
</select></td>
|
</select>
|
||||||
<td><span class="description" i18n="firmwareFlasherOnlineSelectFirmwareVersionDescription"></span></td>
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherOnlineSelectFirmwareVersionDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><label> <input class="updating toggle" type="checkbox" /> <span
|
<td>
|
||||||
i18n="firmwareFlasherNoReboot"></span>
|
<label>
|
||||||
</label></td>
|
<input class="updating toggle" type="checkbox" />
|
||||||
<td><span class="description" i18n="firmwareFlasherNoRebootDescription"></span></td>
|
<span i18n="firmwareFlasherNoReboot"></span>
|
||||||
|
</label>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherNoRebootDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="option flash_on_connect_wrapper">
|
<tr class="option flash_on_connect_wrapper">
|
||||||
<td><label> <input class="flash_on_connect toggle" type="checkbox" /> <span
|
<td>
|
||||||
i18n="firmwareFlasherFlashOnConnect"></span></label></td>
|
<label>
|
||||||
|
<input class="flash_on_connect toggle" type="checkbox" />
|
||||||
<td><span class="description" i18n="firmwareFlasherFlashOnConnectDescription"></span></td>
|
<span i18n="firmwareFlasherFlashOnConnect"></span>
|
||||||
|
</label>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherFlashOnConnectDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="option">
|
<tr class="option">
|
||||||
<td><label> <input class="erase_chip toggle" type="checkbox" /> <span
|
<td>
|
||||||
i18n="firmwareFlasherFullChipErase"></span>
|
<label>
|
||||||
</label></td>
|
<input class="erase_chip toggle" type="checkbox" />
|
||||||
<td><span class="description" i18n="firmwareFlasherFullChipEraseDescription"></span></td>
|
<span i18n="firmwareFlasherFullChipErase"></span>
|
||||||
|
</label>
|
||||||
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherFullChipEraseDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="option manual_baud_rate noboarder">
|
<tr class="option manual_baud_rate noboarder">
|
||||||
<td><label> <input class="flash_manual_baud toggle" type="checkbox" /> <span
|
<td>
|
||||||
i18n="firmwareFlasherManualBaud"></span> <select id="flash_manual_baud_rate"
|
<label>
|
||||||
i18n_title="firmwareFlasherBaudRate">
|
<input class="flash_manual_baud toggle" type="checkbox" />
|
||||||
|
<span i18n="firmwareFlasherManualBaud"></span>
|
||||||
|
<select id="flash_manual_baud_rate" i18n_title="firmwareFlasherBaudRate">
|
||||||
<option value="921600">921600</option>
|
<option value="921600">921600</option>
|
||||||
<option value="460800">460800</option>
|
<option value="460800">460800</option>
|
||||||
<option value="256000" selected="selected">256000</option>
|
<option value="256000" selected="selected">256000</option>
|
||||||
|
@ -72,27 +108,101 @@
|
||||||
<option value="38400">38400</option>
|
<option value="38400">38400</option>
|
||||||
<option value="28800">28800</option>
|
<option value="28800">28800</option>
|
||||||
<option value="19200">19200</option>
|
<option value="19200">19200</option>
|
||||||
</select>
|
</select>
|
||||||
</label></td>
|
</label>
|
||||||
<td><span class="description" i18n="firmwareFlasherManualBaudDescription"></span></td>
|
<div class="helpicon cf_tip_wide" i18n_title="firmwareFlasherManualBaudDescription"></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gui_box gui_warning" style="max-width: calc(100% - 470px); float: right;">
|
||||||
|
<div class="gui_box_titlebar">
|
||||||
|
<div class="spacer_box_title" style="text-align: center;"
|
||||||
|
i18n="warningTitle">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="spacer" style="margin-bottom: 10px;">
|
||||||
|
<p i18n="firmwareFlasherWarningText"></p>
|
||||||
|
<br />
|
||||||
|
<p i18n="firmwareFlasherTargetWarning"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="clear-both"></div>
|
<div class="clear-both"></div>
|
||||||
<div class="git_info">
|
<div class="git_info">
|
||||||
<div class="title" i18n="firmwareFlasherGithubInfoHead"></div>
|
<div class="title" i18n="firmwareFlasherGithubInfoHead"></div>
|
||||||
<p>
|
<p>
|
||||||
<strong i18n="firmwareFlasherHash"></strong> <a i18n_title="firmwareFlasherUrl" class="hash" href="#"
|
<strong i18n="firmwareFlasherHash"></strong>
|
||||||
target="_blank"></a><br /> <strong i18n="firmwareFlasherCommiter"></strong> <span class="committer"></span><br />
|
<a i18n_title="firmwareFlasherUrl" class="hash" href="#" target="_blank"></a><br />
|
||||||
<strong i18n="firmwareFlasherDate"></strong> <span class="date"></span><br /> <strong
|
<strong i18n="firmwareFlasherCommiter"></strong> <span class="committer"></span><br />
|
||||||
i18n="firmwareFlasherMessage"></strong> <span class="message"></span>
|
<strong i18n="firmwareFlasherDate"></strong> <span class="date"></span><br />
|
||||||
|
<strong i18n="firmwareFlasherMessage"></strong> <span class="message"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="build_configuration gui_box">
|
||||||
|
<div class="darkgrey_box gui_box_titlebar">
|
||||||
|
<div class="spacer_box_title" style="text-align: center;" i18n="firmwareFlasherBuildConfigurationHead">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="spacer" style="margin-bottom: 10px;">
|
||||||
|
<div class="margin-bottom">
|
||||||
|
<div style="width: 49%; float: left;">
|
||||||
|
<strong i18n="firmwareFlasherBuildRadioProtocols"></strong>
|
||||||
|
<div id="radioProtocolInfo">
|
||||||
|
<select id="radioProtocols" name="radioProtocols" multiple="multiple" class="select2" style="width: 95%; color: #424242">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 49%; float: right;">
|
||||||
|
<strong i18n="firmwareFlasherBuildTelemetryProtocols"></strong>
|
||||||
|
<div id="telemetryProtocolInfo">
|
||||||
|
<select id="telemetryProtocols" name="telemetryProtocols" multiple="multiple" class="select2" style="width: 95%; color: #424242">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="spacer" style="margin-bottom: 10px;">
|
||||||
|
<div class="margin-bottom">
|
||||||
|
<div style="width: 49%; float: left;">
|
||||||
|
<strong i18n="firmwareFlasherBuildOptions"></strong>
|
||||||
|
<div id="optionsInfo">
|
||||||
|
<select id="options" name="options" multiple="multiple" class="select2" style="width: 95%; color: #424242">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 49%; float: right;">
|
||||||
|
<strong i18n="firmwareFlasherBuildMotorProtocols"></strong>
|
||||||
|
<div id="motorProtocolInfo">
|
||||||
|
<select id="motorProtocols" name="motorProtocols" multiple="multiple" class="select2" style="width: 95%; color: #424242">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="commitSelection spacer" style="margin-bottom: 10px;">
|
||||||
|
<div class="margin-bottom">
|
||||||
|
<div style="width: 49%; float: left;">
|
||||||
|
<strong i18n="firmwareFlasherBranch"></strong>
|
||||||
|
<div id="branchInfo">
|
||||||
|
<select id="commits" name="commits" class="select2" style="width: 95%; color: #424242">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="release_info gui_box">
|
<div class="release_info gui_box">
|
||||||
<div class="darkgrey_box gui_box_titlebar">
|
<div class="darkgrey_box gui_box_titlebar">
|
||||||
<div class="spacer_box_title" style="text-align: center;"
|
<div class="spacer_box_title" style="text-align: center;" i18n="firmwareFlasherReleaseSummaryHead">
|
||||||
i18n="firmwareFlasherReleaseSummaryHead"></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer" style="margin-bottom: 10px;">
|
<div class="spacer" style="margin-bottom: 10px;">
|
||||||
<div class="margin-bottom">
|
<div class="margin-bottom">
|
||||||
|
@ -107,37 +217,26 @@
|
||||||
<strong i18n="firmwareFlasherReleaseVersion"></strong>
|
<strong i18n="firmwareFlasherReleaseVersion"></strong>
|
||||||
<a i18n_title="firmwareFlasherReleaseVersionUrl" class="name" href="#" target="_blank"></a>
|
<a i18n_title="firmwareFlasherReleaseVersionUrl" class="name" href="#" target="_blank"></a>
|
||||||
<br />
|
<br />
|
||||||
<strong i18n="firmwareFlasherReleaseFile"></strong>
|
<strong i18n="firmwareFlasherReleaseMCU"></strong>
|
||||||
<a i18n_title="firmwareFlasherReleaseFileUrl" class="file" href="#" target="_blank"></a>
|
<span id="targetMCU"></span>
|
||||||
<br />
|
<br />
|
||||||
<strong i18n="firmwareFlasherReleaseDate"></strong>
|
<strong i18n="firmwareFlasherReleaseDate"></strong>
|
||||||
<span class="date"></span>
|
<span class="date"></span>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="margin-bottom" id="unifiedTargetInfo">
|
<div class="margin-bottom" id="cloudTargetInfo">
|
||||||
<strong i18n="firmwareFlasherUnifiedTargetName"></strong>
|
<strong i18n="firmwareFlasherCloudBuildDetails"></strong>
|
||||||
<a i18n_title="firmwareFlasherUnifiedTargetFileUrl" id="unifiedTargetFile" href="#" target="_blank"></a>
|
<a i18n_title="firmwareFlasherCloudBuildLogUrl" id="cloudTargetLog" href="#" target="_blank"></a>
|
||||||
<br />
|
<br />
|
||||||
<strong i18n="firmwareFlasherUnifiedTargetDate"></strong>
|
<strong i18n="firmwareFlasherCloudBuildStatus"></strong>
|
||||||
<span id="unifiedTargetDate"></span>
|
<span id="cloudTargetStatus"></span>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<strong i18n="firmwareFlasherReleaseNotes"></strong>
|
<strong i18n="firmwareFlasherReleaseNotes"></strong>
|
||||||
<div class=notes></div>
|
<div class=notes></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gui_box gui_warning">
|
|
||||||
<div class="gui_box_titlebar">
|
|
||||||
<div class="spacer_box_title" style="text-align: center;"
|
|
||||||
i18n="warningTitle">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="spacer" style="margin-bottom: 10px;">
|
|
||||||
<p i18n="firmwareFlasherWarningText"></p>
|
|
||||||
<br />
|
|
||||||
<p i18n="firmwareFlasherTargetWarning"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="gui_box gui_note">
|
<div class="gui_box gui_note">
|
||||||
<div class="gui_box_titlebar">
|
<div class="gui_box_titlebar">
|
||||||
<div class="spacer_box_title" style="text-align: center;"
|
<div class="spacer_box_title" style="text-align: center;"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue