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 @@
+ + + + + + + + - + - - - -
+ +