diff --git a/locales/en/messages.json b/locales/en/messages.json
index 0b6e81a2..5a67b8a8 100644
--- a/locales/en/messages.json
+++ b/locales/en/messages.json
@@ -2137,6 +2137,9 @@
"firmwareFlasherNoReboot": {
"message": "No reboot sequence"
},
+ "firmwareFlasherOnlineSelectBuildType": {
+ "message": "Select build type to see available boards."
+ },
"firmwareFlasherOnlineSelectBoardDescription": {
"message": "Select your board to see available online firmware releases - Select the correct firmware appropriate for your board."
},
@@ -2185,6 +2188,21 @@
"firmwareFlasherOptionLoading": {
"message": "Loading ..."
},
+ "firmwareFlasherOptionLabelBuildTypeRelease": {
+ "message": "Release"
+ },
+ "firmwareFlasherOptionLabelBuildTypeReleaseCandidate": {
+ "message": "Release And Release Candidate"
+ },
+ "firmwareFlasherOptionLabelBuildTypeDevelopment": {
+ "message": "Development"
+ },
+ "firmwareFlasherOptionLabelBuildTypeAKK3_3": {
+ "message": "3.3 AKK & RDQ VTX Patch"
+ },
+ "firmwareFlasherOptionLabelBuildTypeAKK3_4": {
+ "message": "3.4 AKK & RDQ VTX Patch"
+ },
"firmwareFlasherOptionLabelSelectFirmware": {
"message": "Choose a Firmware / Board"
},
diff --git a/src/css/main.css b/src/css/main.css
index 8d4f3805..85f5a671 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -198,7 +198,7 @@ input[type="number"]::-webkit-inner-spin-button {
width: 136px;
}
-#port-picker .auto_connect {
+#port-picker .auto_connect, .gray {
color: #ddd;
}
diff --git a/src/js/jenkins_loader.js b/src/js/jenkins_loader.js
new file mode 100644
index 00000000..8cdf040f
--- /dev/null
+++ b/src/js/jenkins_loader.js
@@ -0,0 +1,97 @@
+ 'use strict;'
+
+var JenkinsLoader = function (url, jobName) {
+ var self = this;
+
+ self._url = url;
+ self._jobName = jobName;
+ self._jobUrl = self._url + '/job/' + self._jobName;
+ self._buildsRequest = '/api/json?tree=builds[number,result,timestamp,artifacts[relativePath],changeSet[items[commitId,msg]]]';
+ self._builds = {};
+
+ self._buildsDataTag = `${self._jobUrl}BuildsData`;
+ self._cacheLastUpdateTag = `${self._jobUrl}BuildsLastUpdate`
+}
+
+JenkinsLoader.prototype.loadBuilds = function (callback) {
+ var self = this;
+
+ chrome.storage.local.get([self._cacheLastUpdateTag, self._buildsDataTag], function (result) {
+ var buildsDataTimestamp = $.now();
+ var cachedBuildsData = result[self._buildsDataTag];
+ var cachedBuildsLastUpdate = result[self._cacheLastUpdateTag];
+
+ if (!cachedBuildsData || !cachedBuildsLastUpdate || buildsDataTimestamp - cachedBuildsLastUpdate > 3600 * 1000) {
+ var request = self._jobUrl + self._buildsRequest;
+
+ $.get(request, function (buildsInfo) {
+ // filter successful builds
+ self._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('
\n'),
+ date: new Date(build.timestamp)
+ }));
+
+ self._parseBuilds(callback);
+ }).fail(function (data) {
+ GUI.log(i18n.getMessage('releaseCheckFailed', [self._jobName, 'failed to load builds']));
+
+ self._builds = cachedBuildsData;
+ self._parseBuilds(callback);
+ });
+ } else {
+ if (cachedBuildsData) {
+ GUI.log(i18n.getMessage('releaseCheckCached', [self._jobName]));
+ }
+
+ self._builds = cachedBuildsData;
+ self._parseBuilds(callback);
+ }
+ });
+}
+
+JenkinsLoader.prototype._parseBuilds = function (callback) {
+ var self = this;
+
+ // convert from `build -> targets` to `target -> builds` mapping
+ var targetBuilds = {};
+
+ var targetFromFilenameExpression = /betaflight_([\d.]+)?_?(\w+)(\-.*)?\.(.*)/;
+
+ self._builds.forEach(build => {
+ build.artifacts.forEach(relativePath => {
+ var match = targetFromFilenameExpression.exec(relativePath);
+
+ if (!match) {
+ return;
+ }
+
+ var version = match[1];
+ var target = match[2];
+
+ var formattedDate = ("0" + build.date.getDate()).slice(-2) + "-" + ("0" + (build.date.getMonth()+1)).slice(-2) + "-" +
+ build.date.getFullYear() + " " + ("0" + build.date.getHours()).slice(-2) + ":" + ("0" + build.date.getMinutes()).slice(-2);
+
+ var descriptor = {
+ 'releaseUrl': self._jobUrl + '/' + build.number,
+ 'name' : self._jobName + ' #' + build.number,
+ 'version' : version + ' #' + build.number,
+ 'url' : self._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);
+}
diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js
index 0928ecbe..72a3bc60 100755
--- a/src/js/tabs/firmware_flasher.js
+++ b/src/js/tabs/firmware_flasher.js
@@ -93,17 +93,51 @@ TABS.firmware_flasher.initialize = function (callback) {
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonLoadOnline'));
};
- function buildBoardOptions(releaseData) {
+ function buildJenkinsBoardOptions(builds) {
+ if (!builds) {
+ $('select[name="board"]').empty().append('');
+ $('select[name="firmware_version"]').empty().append('');
+
+ return;
+ }
+
+ var boards_e = $('select[name="board"]');
+ var versions_e = $('select[name="firmware_version"]');
+
+ var selectTargets = [];
+ Object.keys(builds)
+ .sort()
+ .forEach(function(target, i) {
+ var descriptors = builds[target];
+ descriptors.forEach(function(descriptor){
+ if($.inArray(target, selectTargets) == -1) {
+ selectTargets.push(target);
+ var select_e =
+ $("".format(
+ descriptor.target
+ )).data('summary', descriptor);
+ boards_e.append(select_e);
+ }
+ });
+ });
+
+ TABS.firmware_flasher.releases = builds;
+
+ chrome.storage.local.get('selected_board', function (result) {
+ if (result.selected_board) {
+ var boardBuilds = builds[result.selected_board]
+ $('select[name="board"]').val(boardBuilds ? result.selected_board : 0).trigger('change');
+ }
+ });
+ }
+
+ function buildBoardOptions(releaseData, showDevReleases) {
if (!releaseData) {
$('select[name="board"]').empty().append('');
$('select[name="firmware_version"]').empty().append('');
} else {
- var boards_e = $('select[name="board"]').empty();
- var showDevReleases = ($('input.show_development_releases').is(':checked'));
- boards_e.append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectBoard'))));
-
- var versions_e = $('select[name="firmware_version"]').empty();
- versions_e.append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectFirmwareVersion'))));
+ var boards_e = $('select[name="board"]');
+ var versions_e = $('select[name="firmware_version"]');
var releases = {};
var sortedTargets = [];
@@ -158,8 +192,7 @@ TABS.firmware_flasher.initialize = function (callback) {
"file" : asset.name,
"target" : target,
"date" : formattedDate,
- "notes" : release.body,
- "status" : release.prerelease ? "release-candidate" : "stable"
+ "notes" : release.body
};
releases[target].push(descriptor);
});
@@ -181,21 +214,81 @@ TABS.firmware_flasher.initialize = function (callback) {
});
});
TABS.firmware_flasher.releases = releases;
+
chrome.storage.local.get('selected_board', function (result) {
if (result.selected_board) {
- $('select[name="board"]').val(result.selected_board);
- $('select[name="board"]').trigger("change");
+ var boardReleases = releases[result.selected_board]
+ $('select[name="board"]').val(boardReleases ? result.selected_board : 0).trigger('change');
}
});
}
};
+ function showOrHideBuildTypeSelect() {
+ var showDevReleases = $(this).is(':checked');
+
+ if (showDevReleases) {
+ $('tr.build_type').show();
+ } else {
+ $('tr.build_type').hide();
+ buildType_e.val(0).trigger('change');
+ }
+ }
+
+ var buildTypes = [
+ {
+ tag: 'firmwareFlasherOptionLabelBuildTypeRelease',
+ loader: () => self.releaseChecker.loadReleaseData(releaseData => buildBoardOptions(releaseData, false))
+ },
+ {
+ tag: 'firmwareFlasherOptionLabelBuildTypeReleaseCandidate',
+ loader: () => self.releaseChecker.loadReleaseData(releaseData => buildBoardOptions(releaseData, true))
+ },
+ {
+ tag: 'firmwareFlasherOptionLabelBuildTypeDevelopment',
+ loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight').loadBuilds(buildJenkinsBoardOptions)
+ },
+ {
+ tag: 'firmwareFlasherOptionLabelBuildTypeAKK3_3',
+ loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight Maintenance 3.3 (AKK - RDQ VTX Patch)').loadBuilds(buildJenkinsBoardOptions)
+ },
+ {
+ tag: 'firmwareFlasherOptionLabelBuildTypeAKK3_4',
+ loader: () => new JenkinsLoader('https://ci.betaflight.tech', 'Betaflight Maintenance 3.4 (AKK - RDQ VTX Patch)').loadBuilds(buildJenkinsBoardOptions)
+ }
+ ];
+
+ var buildType_e = $('select[name="build_type"]');
+ buildTypes.forEach((build, index) => {
+ buildType_e.append($("".format(index, i18n.getMessage(build.tag))))
+ });
+
+ showOrHideBuildTypeSelect();
+ $('input.show_development_releases').change(showOrHideBuildTypeSelect);
+
// translate to user-selected language
i18n.localizePage();
- // bind events
- $('input.show_development_releases').click(function () {
- self.releaseChecker.loadReleaseData(buildBoardOptions);
+ chrome.storage.local.get('selected_build_type', function (result) {
+ // ensure default build type is selected
+ buildType_e.val(result.selected_build_type || 0).trigger('change');
+ });
+
+ buildType_e.change(function() {
+ $("a.load_remote_file").addClass('disabled');
+ var build_type = $(this).val();
+
+ $('select[name="board"]').empty()
+ .append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectBoard'))));
+
+ $('select[name="firmware_version"]').empty()
+ .append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectFirmwareVersion'))));
+
+ if (!GUI.connect_lock) {
+ buildTypes[build_type].loader();
+ }
+
+ chrome.storage.local.set({'selected_build_type': build_type});
});
$('select[name="board"]').change(function() {
@@ -214,24 +307,23 @@ TABS.firmware_flasher.initialize = function (callback) {
versions_e.append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectFirmwareVersion'))));
} else {
versions_e.append($("".format(i18n.getMessage('firmwareFlasherOptionLabelSelectFirmwareVersionFor'), target)));
+
+ TABS.firmware_flasher.releases[target].forEach(function(descriptor) {
+ var select_e =
+ $("".format(
+ descriptor.version,
+ descriptor.target,
+ descriptor.date,
+ ))
+ .css("font-weight", FirmwareCache.has(descriptor)
+ ? "bold"
+ : "normal"
+ )
+ .data('summary', descriptor);
+
+ versions_e.append(select_e);
+ });
}
-
- TABS.firmware_flasher.releases[target].forEach(function(descriptor) {
- var select_e =
- $("".format(
- descriptor.version,
- descriptor.target,
- descriptor.date,
- descriptor.status
- ))
- .css("font-weight", FirmwareCache.has(descriptor)
- ? "bold"
- : "normal"
- )
- .data('summary', descriptor);
-
- versions_e.append(select_e);
- });
}
chrome.storage.local.set({'selected_board': target});
});
diff --git a/src/main.html b/src/main.html
index 2507775f..cdd9775b 100755
--- a/src/main.html
+++ b/src/main.html
@@ -78,6 +78,7 @@
+
diff --git a/src/tabs/firmware_flasher.html b/src/tabs/firmware_flasher.html
index 4b99fe43..df4bc6d8 100755
--- a/src/tabs/firmware_flasher.html
+++ b/src/tabs/firmware_flasher.html
@@ -3,6 +3,20 @@