Skip to content

Commit f639ac6

Browse files
committed
Feature: Show outdated badges in sidebar for conductors, servers, and marketplace plugins. Fixes #305.
1 parent cff6921 commit f639ac6

9 files changed

Lines changed: 214 additions & 31 deletions

File tree

docs/config.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ See [JobHookData](data.md#jobhookdata) and [AlertHookData](data.md#alerthookdata
400400

401401
This section configures the multi-server subsystem.
402402

403+
### multi.enable_version_checks
404+
<!-- Title: Enable Version Checks -->
405+
406+
When set to `true`, xyOps will check for version updates of the core app, which will make external web requests to the Release Metadata URL.
407+
403408
### multi.list_url
404409
<!-- Title: Release Metadata URL -->
405410

@@ -433,11 +438,6 @@ Default:
433438
{ "rejectUnauthorized": false }
434439
```
435440

436-
### multi.enable_version_checks
437-
<!-- Title: Enable Version Checks -->
438-
439-
When set to `true`, xyOps will automatically check for version updates of the core app, and display hints in the UI.
440-
441441

442442

443443
## satellite
@@ -446,6 +446,11 @@ When set to `true`, xyOps will automatically check for version updates of the co
446446

447447
This section configures xySat, our remote satellite agent.
448448

449+
### satellite.enable_version_checks
450+
<!-- Title: Enable Version Checks -->
451+
452+
When set to `true`, xyOps will check for version updates of the xySat app, which will make external web requests to the Release Metadata URL.
453+
449454
### satellite.list_url
450455
<!-- Title: Release Metadata URL -->
451456

@@ -476,11 +481,6 @@ This number (seconds) sets the cache TTL for satellite release metadata and tarb
476481

477482
This object contains web server and runtime settings for xySat; these options are passed along when managing or provisioning satellite nodes (defaults provided in the sample config).
478483

479-
### satellite.enable_version_checks
480-
<!-- Title: Enable Version Checks -->
481-
482-
When set to `true`, xyOps will automatically check for version updates of the xySat app, and display hints in the UI.
483-
484484

485485

486486
## marketplace
@@ -521,11 +521,6 @@ https://raw.githubusercontent.com/[id]/refs/tags/[version]/[filename]
521521

522522
This is the number of seconds to cache the marketplace metadata locally before re-fetching from origin. The default is `3600` (one hour).
523523

524-
### marketplace.enable_version_checks
525-
<!-- Title: Enable Version Checks -->
526-
527-
When set to `true`, xyOps will automatically check for version updates of all installed marketplace plugins, and display hints in the UI.
528-
529524

530525

531526
<!-- Group: Default User Settings -->
@@ -672,6 +667,11 @@ Maximum size of job output to display inline on the details page (default: `5 MB
672667

673668
Requires the user to hold the Opt/Alt key to toggle the `enabled` property of certain entities in the UI (prevents accidental clicks).
674669

670+
### client.outdated_badges
671+
<!-- Title: Show Outdated Version Badges -->
672+
673+
When set to `true`, will show a colored badge on sidebar sections for outdated software versions on the Conductors, Servers and Marketplace tabs. Admin only.
674+
675675
### client.new_event_template
676676
<!-- Title: New Event Template -->
677677

htdocs/css/style.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,15 @@ body.dark .button.delete {
254254
border: 1px solid var(--pink-highlight);
255255
}
256256

257+
button.link.outdated {
258+
color: var(--orange);
259+
font-weight: bold;
260+
}
261+
button.link.outdated:hover {
262+
color: var(--orange-highlight);
263+
font-weight: bold;
264+
}
265+
257266
#d_logo_overlay {
258267
position: absolute;
259268
top: 0;
@@ -1196,12 +1205,38 @@ div.td_big {
11961205
}
11971206

11981207
.section_item {
1208+
position: relative;
11991209
padding-left: 37px;
12001210
}
12011211
.section_item.filtered {
12021212
color: var(--icon-color);
12031213
}
12041214

1215+
.section_item.outdated {
1216+
padding-right: 30px;
1217+
}
1218+
.section_item.outdated:not(.active) {
1219+
color: var(--orange);
1220+
}
1221+
.section_item.outdated:not(.active):hover {
1222+
color: var(--orange-highlight);
1223+
}
1224+
1225+
.section_item > i.section_badge {
1226+
position: absolute;
1227+
left: 100%;
1228+
top: 0;
1229+
margin-left: -29px;
1230+
padding-right: 0 !important;
1231+
height: 25px;
1232+
line-height: 25px;
1233+
font-size: 14px;
1234+
display: none;
1235+
}
1236+
.section_item.outdated > i.section_badge {
1237+
display: block;
1238+
}
1239+
12051240
.multiselect > .item {
12061241
font-size: 13px;
12071242
}

htdocs/js/app.js

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,134 @@ app.extend({
680680
this.user.groups = [...new Set(cgrps)]; // remove dupes
681681
},
682682

683+
startLazyVersionChecks() {
684+
// lazily check versions for conductors, servers, and optionally marketplace plugins
685+
// highlight sidebar sections for outdated entities
686+
if (config.outdated_badges === false) return;
687+
688+
setTimeout( this.checkConductorVersions.bind(this), 500 );
689+
setTimeout( this.checkServerVersions.bind(this), 1000 );
690+
setTimeout( this.checkPluginVersions.bind(this), 1500 );
691+
},
692+
693+
checkConductorVersions() {
694+
// check all conductor versions to see if any are out of date
695+
var self = this;
696+
if (config.outdated_badges === false) return;
697+
if (!this.isAdmin()) return;
698+
699+
// fetch data if needed
700+
if (!this.latestMasterVersion) {
701+
this.api.get( 'app/get_master_releases', {}, function(resp) {
702+
// releases[0] is always `latest` (unless airgapped), releases[1] is the latest version
703+
if (resp && resp.releases && resp.releases[1]) {
704+
self.latestMasterVersion = resp.releases[1];
705+
self.checkConductorVersions();
706+
}
707+
});
708+
return;
709+
}
710+
711+
var latest_int = get_int_version( this.latestMasterVersion.replace(/^v/, '') );
712+
var masters = Object.values( app.masters );
713+
var is_outdated = false;
714+
715+
masters.forEach( function(item) {
716+
if (!item.version || !item.online) return;
717+
if (is_outdated) return;
718+
719+
var master_int = get_int_version( item.version );
720+
if (master_int < latest_int) {
721+
// outdated!
722+
is_outdated = true;
723+
}
724+
} );
725+
726+
$('#tab_Conductors').toggleClass( 'outdated', is_outdated ).attr( 'title', is_outdated ? `One or more conductors have outdated xyOps software.` : null );
727+
728+
if (is_outdated && !$('#tab_Conductors > i.section_badge').length) {
729+
$('#tab_Conductors').append( `<i class="mdi mdi-alert-rhombus-outline section_badge"></i>` );
730+
}
731+
},
732+
733+
checkServerVersions() {
734+
// check all server versions to see if any are out of date
735+
var self = this;
736+
if (config.outdated_badges === false) return;
737+
if (!this.isAdmin()) return;
738+
739+
// fetch data if needed
740+
if (!this.latestSatVersion) {
741+
this.api.get( 'app/get_satellite_releases', {}, function(resp) {
742+
// releases[0] is always `latest` (unless airgapped), releases[1] is the latest version
743+
if (resp && resp.releases && resp.releases[1]) {
744+
self.latestSatVersion = resp.releases[1];
745+
self.checkServerVersions();
746+
}
747+
});
748+
return;
749+
}
750+
751+
var latest_int = get_int_version( this.latestSatVersion.replace(/^v/, '') );
752+
var servers = Object.values(app.servers);
753+
var is_outdated = false;
754+
755+
servers.forEach( function(server) {
756+
if (!server.info || !server.info.satellite) return;
757+
if (is_outdated) return;
758+
759+
var server_int = get_int_version( server.info.satellite );
760+
if (server_int < latest_int) {
761+
// outdated!
762+
is_outdated = true;
763+
}
764+
} );
765+
766+
$('#tab_Servers').toggleClass( 'outdated', is_outdated ).attr( 'title', is_outdated ? `One or more servers have outdated xySat software.` : null );
767+
768+
if (is_outdated && !$('#tab_Servers > i.section_badge').length) {
769+
$('#tab_Servers').append( `<i class="mdi mdi-alert-rhombus-outline section_badge"></i>` );
770+
}
771+
},
772+
773+
checkPluginVersions() {
774+
// check all installed marketplace plugins to see if any are out of date
775+
var self = this;
776+
if (config.outdated_badges === false) return;
777+
if (!this.hasPrivilege('create_plugins') || !this.hasPrivilege('edit_plugins')) return;
778+
779+
// fetch data if needed
780+
if (!this.marketRows) {
781+
this.api.get( 'app/marketplace', {}, function(resp) {
782+
if (resp && resp.rows && resp.rows.length) {
783+
self.marketRows = resp.rows;
784+
self.checkPluginVersions();
785+
}
786+
});
787+
return;
788+
}
789+
790+
var is_outdated = false;
791+
792+
app.plugins.forEach( function(plugin) {
793+
if (is_outdated) return;
794+
if (!plugin.marketplace || !plugin.marketplace.id) return;
795+
796+
var meta = find_object( self.marketRows, { id: plugin.marketplace.id } );
797+
if (!meta) return;
798+
799+
var installed_int = get_int_version( plugin.marketplace.version );
800+
var market_latest_int = get_int_version( meta.versions[0] );
801+
if (installed_int < market_latest_int) is_outdated = true;
802+
} );
803+
804+
$('#tab_Marketplace').toggleClass( 'outdated', is_outdated ).attr( 'title', is_outdated ? `One or more installed marketplace plugins are outdated.` : null );
805+
806+
if (is_outdated && !$('#tab_Marketplace > i.section_badge').length) {
807+
$('#tab_Marketplace').append( `<i class="mdi mdi-alert-rhombus-outline section_badge"></i>` );
808+
}
809+
},
810+
683811
doUserLogin: function(resp) {
684812
// user login, called from login page, or session recover
685813
// overriding this from base.js
@@ -700,6 +828,7 @@ app.extend({
700828
this.setupDragDrop();
701829
this.pruneData();
702830
this.updateAccessibility();
831+
this.startLazyVersionChecks();
703832

704833
// login resp should have epoch, so track delta from it
705834
this.serverPerfStart = performance.now();
@@ -1023,7 +1152,7 @@ app.extend({
10231152
},
10241153

10251154
hasAnyPrivilege(...privs) {
1026-
// check if user has priv, show full page error if not
1155+
// check if user has any priv in list
10271156
if (!app.user || !app.user.privileges) return false;
10281157
if (app.user.privileges.admin) return true;
10291158
return !!privs.filter( (priv_id) => this.hasPrivilege(priv_id) ).length;

htdocs/js/comm.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,14 @@ app.comm = {
398398
var username = item.admin || item.username;
399399
if ((username != app.username) && !app.user.admin_hide_notify_user) app.showMessage(type, desc, 8);
400400
}
401+
402+
// recheck versions for specific actions (lazy)
403+
// these should all be cached at this point and not trigger any server requests
404+
setTimeout( function() {
405+
if (item.action.match(/^(peer_add|peer_disconnect)$/)) app.checkConductorVersions();
406+
if (item.action.match(/^(server_add|server_remove|server_delete)$/)) app.checkServerVersions();
407+
if (item.action.match(/^(plugin_create|plugin_update|plugin_delete)$/)) app.checkPluginVersions();
408+
}, 1000 );
401409
},
402410

403411
sendCommand: function(cmd, data) {

htdocs/js/pages/Servers.class.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,18 @@ Page.Servers = class Servers extends Page.ServerUtils {
227227
var self = this;
228228

229229
// fetch data if needed
230-
if (!this.latestSatVersion) {
230+
if (!app.latestSatVersion) {
231231
app.api.get( 'app/get_satellite_releases', {}, function(resp) {
232232
// releases[0] is always `latest` (unless airgapped), releases[1] is the latest version
233233
if (resp && resp.releases && resp.releases[1]) {
234-
self.latestSatVersion = resp.releases[1];
234+
app.latestSatVersion = resp.releases[1];
235235
self.checkAllSatelliteVersions();
236236
}
237237
});
238238
return;
239239
}
240240

241-
var latest_int = get_int_version( this.latestSatVersion.replace(/^v/, '') );
241+
var latest_int = get_int_version( app.latestSatVersion );
242242

243243
this.servers.forEach( function(server) {
244244
// decorate as needed
@@ -247,7 +247,7 @@ Page.Servers = class Servers extends Page.ServerUtils {
247247
if (server_int < latest_int) {
248248
// outdated!
249249
self.div.find('#d_es_sat_ver_' + server.id).html(
250-
'<button class="link danger" style="color:var(--red); font-weight:bold;" data-server="' + server.id + '" onClick="$P().go_nav_upgrade(this)" title="xySat version is outdated."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v' + server.info.satellite + '</span>'
250+
`<button class="link outdated" data-server="${server.id}" onClick="$P().go_nav_upgrade(this)" title="A new xySat version is available (${app.latestSatVersion})."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v${server.info.satellite}</span>`
251251
);
252252
}
253253
else {
@@ -1162,23 +1162,23 @@ Page.Servers = class Servers extends Page.ServerUtils {
11621162
if (!server || !server.info || !server.info.satellite) return;
11631163

11641164
// fetch data if needed
1165-
if (!this.latestSatVersion) {
1165+
if (!app.latestSatVersion) {
11661166
app.api.get( 'app/get_satellite_releases', {}, function(resp) {
11671167
// releases[0] is always `latest` (unless airgapped), releases[1] is the latest version
11681168
if (resp && resp.releases && resp.releases[1]) {
1169-
self.latestSatVersion = resp.releases[1];
1169+
app.latestSatVersion = resp.releases[1];
11701170
self.checkSatelliteVersion();
11711171
}
11721172
});
11731173
return;
11741174
}
11751175

1176-
var latest_int = get_int_version( this.latestSatVersion.replace(/^v/, '') );
1176+
var latest_int = get_int_version( app.latestSatVersion );
11771177
var server_int = get_int_version( server.info.satellite );
11781178
if (server_int < latest_int) {
11791179
// outdated!
11801180
this.div.find('#d_vs_sat_ver').html(
1181-
'<button class="link danger" style="color:var(--red); font-weight:bold;" data-server="' + server.id + '" onClick="$P().go_nav_upgrade(this)" title="xySat version is outdated."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v' + server.info.satellite + '</span>'
1181+
`<button class="link outdated" data-server="${server.id}" onClick="$P().go_nav_upgrade(this)" title="A new xySat version is available (${app.latestSatVersion})."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v${server.info.satellite}</span>`
11821182
);
11831183
}
11841184
else {

htdocs/js/pages/admin/Conductors.class.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,18 @@ Page.Conductors = class Conductors extends Page.PageUtils {
9898
var self = this;
9999

100100
// fetch data if needed
101-
if (!this.latestMasterVersion) {
101+
if (!app.latestMasterVersion) {
102102
app.api.get( 'app/get_master_releases', {}, function(resp) {
103103
// releases[0] is always `latest` (unless airgapped), releases[1] is the latest version
104104
if (resp && resp.releases && resp.releases[1]) {
105-
self.latestMasterVersion = resp.releases[1];
105+
app.latestMasterVersion = resp.releases[1];
106106
self.checkAllVersions();
107107
}
108108
});
109109
return;
110110
}
111111

112-
var latest_int = get_int_version( this.latestMasterVersion.replace(/^v/, '') );
112+
var latest_int = get_int_version( app.latestMasterVersion );
113113

114114
this.masters.forEach( function(item) {
115115
// decorate as needed
@@ -118,7 +118,7 @@ Page.Conductors = class Conductors extends Page.PageUtils {
118118
if (master_int < latest_int) {
119119
// outdated!
120120
self.div.find('#d_ec_ver_' + CSS.escape(item.id)).html(
121-
'<button class="link danger" style="color:var(--red); font-weight:bold;" data-host="' + item.id + '" onClick="$P().go_nav_upgrade(this)" title="xyOps version is outdated."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v' + item.version + '</span>'
121+
`<button class="link outdated" data-host="${item.id}" onClick="$P().go_nav_upgrade(this)" title="A new xyOps version is available (${app.latestMasterVersion})."><i class="mdi mdi-alert-rhombus">&nbsp;</i>v${item.version}</span>`
122122
);
123123
}
124124
else {

lib/api/config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ class Configuration {
5555
default_user_prefs: this.config.get('default_user_prefs'),
5656
job_universal_limits: this.config.get('job_universal_limits'),
5757
job_universal_actions: this.config.get('job_universal_actions'),
58-
log_columns: this.config.get('log_columns'),
59-
mkt_version_checks: !(this.config.getPath('marketplace.enable_version_checks') === false)
58+
log_columns: this.config.get('log_columns')
6059
} );
6160
resp.masters = this.getMasterPeerData();
6261
}

0 commit comments

Comments
 (0)