1/** 2 * Controller for date-time 3 * 4 * @module app/configuration 5 * @exports dateTimeController 6 * @name dateTimeController 7 */ 8 9window.angular && (function(angular) { 10 'use strict'; 11 12 angular.module('app.configuration').controller('dateTimeController', [ 13 '$scope', 'APIUtils', '$route', '$q', 'toastService', '$timeout', 14 function($scope, APIUtils, $route, $q, toastService, $timeout) { 15 $scope.bmc = {}; 16 // Only used when the owner is "Split" 17 $scope.host = {}; 18 $scope.ntp = {servers: []}; 19 $scope.time = {mode: '', owner: ''}; 20 // Possible time owners 21 // https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Time/Owner.interface.yaml#L13 22 $scope.timeOwners = ['BMC', 'Host', 'Both', 'Split']; 23 $scope.loading = true; 24 var timePath = '/xyz/openbmc_project/time/'; 25 26 var getTimePromise = APIUtils.getTime().then( 27 function(data) { 28 // The time is returned as Epoch microseconds convert to 29 // milliseconds. 30 if (data.data[timePath + 'bmc'] && 31 data.data[timePath + 'bmc'].hasOwnProperty('Elapsed')) { 32 $scope.bmc.date = 33 new Date(data.data[timePath + 'bmc'].Elapsed / 1000); 34 // Don't care about milliseconds and don't want them displayed 35 $scope.bmc.date.setMilliseconds(0); 36 37 // Examples: 38 // Central Standard Time (UTC-06:00) 39 // Moscow Standard Time (UTC+03:00) 40 $scope.bmc.timezone = getUserTimezone($scope.bmc.date) + ' ' + 41 createOffset($scope.bmc.date); 42 } 43 if (data.data[timePath + 'host'] && 44 data.data[timePath + 'host'].hasOwnProperty('Elapsed')) { 45 $scope.host.date = 46 new Date(data.data[timePath + 'host'].Elapsed / 1000); 47 $scope.host.date.setMilliseconds(0); 48 $scope.host.timezone = getUserTimezone($scope.bmc.date) + ' ' + 49 createOffset($scope.bmc.date); 50 } 51 if (data.data[timePath + 'owner'] && 52 data.data[timePath + 'owner'].hasOwnProperty('TimeOwner')) { 53 $scope.time.owner = 54 data.data[timePath + 'owner'].TimeOwner.split('.').pop(); 55 } 56 if (data.data[timePath + 'sync_method'] && 57 data.data[timePath + 'sync_method'].hasOwnProperty( 58 'TimeSyncMethod')) { 59 $scope.time.mode = data.data[timePath + 'sync_method'] 60 .TimeSyncMethod.split('.') 61 .pop(); 62 } 63 }, 64 function(error) { 65 console.log(JSON.stringify(error)); 66 }); 67 68 var getNTPPromise = APIUtils.getNTPServers().then( 69 function(data) { 70 $scope.ntp.servers = data.data; 71 }, 72 function(error) { 73 console.log(JSON.stringify(error)); 74 }); 75 76 var promises = [getTimePromise, getNTPPromise]; 77 78 $q.all(promises).finally(function() { 79 $scope.loading = false; 80 }); 81 82 /** 83 * https://github.com/openbmc/phosphor-time-manager/blob/master/README.md#special-note-on-changing-ntp-setting 84 * When time mode is initially set to Manual from NTP, 85 * NTP service is disabled and the NTP service is 86 * stopping but not stopped, setting time will return an error. 87 * There are no responses from backend to notify when NTP is stopped. 88 * To work around, a timeout is set to allow NTP to fully stop 89 * TODO: remove timeout if backend solves 90 * https://github.com/openbmc/openbmc/issues/3459 91 */ 92 $scope.saveDateTimeSettings = function() { 93 $scope.loading = true; 94 if ($scope.time.mode == 'Manual' || $scope.time.owner == 'Split') { 95 setTimeMode() 96 .then(setTimeOwner) 97 .then(setNTPServers) 98 .then($timeout(setDateTime, 20000)); 99 } else { 100 setTimeMode() 101 .then(setTimeOwner) 102 .then(setNTPServers) 103 .then( 104 function() { 105 toastService.success('Date and time settings saved'); 106 }, 107 function(errors) { 108 console.log(JSON.stringify(errors)); 109 toastService.error( 110 'Date and time settings could not be saved'); 111 }) 112 .finally(function() { 113 $scope.loading = false; 114 }); 115 } 116 }; 117 118 const setDateTime = function() { 119 var manualPromises = []; 120 if ($scope.time.mode == 'Manual') { 121 // If owner is 'Split' set both. 122 // If owner is 'Host' set only it. 123 if ($scope.time.owner != 'Host') { 124 manualPromises.push(setBMCTime($scope.bmc.date.getTime() * 1000)); 125 } 126 // Even though we are setting Host time, we are setting from 127 // the BMC date and time fields labeled "BMC and Host Time" 128 // currently. 129 if ($scope.time.owner == 'Host') { 130 manualPromises.push(setHostTime($scope.bmc.date.getTime() * 1000)); 131 } 132 } 133 // Set the Host if Split even if NTP. In split mode, the host has 134 // its own date and time field set from it. 135 if ($scope.time.owner == 'Split') { 136 manualPromises.push(setHostTime($scope.host.date.getTime() * 1000)); 137 } 138 139 $q.all(manualPromises) 140 .then( 141 function() { 142 toastService.success('Date and time settings saved'); 143 }, 144 function(errors) { 145 console.log(JSON.stringify(errors)); 146 toastService.error( 147 'Date and time settings could not be saved'); 148 }) 149 .finally(function() { 150 $scope.loading = false; 151 }); 152 }; 153 154 $scope.refresh = function() { 155 $route.reload(); 156 }; 157 158 $scope.addNTPField = function() { 159 $scope.ntp.servers.push(''); 160 }; 161 162 $scope.removeNTPField = function(index) { 163 $scope.ntp.servers.splice(index, 1); 164 }; 165 166 function setNTPServers() { 167 // Remove any empty strings from the array. Important because we add an 168 // empty string to the end so the user can add a new NTP server, if the 169 // user doesn't fill out the field, we don't want to add. 170 $scope.ntp.servers = $scope.ntp.servers.filter(Boolean); 171 // NTP servers does not allow an empty array, since we remove all empty 172 // strings above, could have an empty array. TODO: openbmc/openbmc#3240 173 if ($scope.ntp.servers.length == 0) { 174 $scope.ntp.servers.push(''); 175 } 176 return APIUtils.setNTPServers($scope.ntp.servers); 177 } 178 179 function setTimeMode() { 180 return APIUtils.setTimeMode( 181 'xyz.openbmc_project.Time.Synchronization.Method.' + 182 $scope.time.mode); 183 } 184 185 function setTimeOwner() { 186 return APIUtils.setTimeOwner( 187 'xyz.openbmc_project.Time.Owner.Owners.' + $scope.time.owner); 188 } 189 190 function setBMCTime(time) { 191 // Add the separate date and time objects and convert to Epoch time in 192 // microseconds. 193 return APIUtils.setBMCTime(time); 194 } 195 196 function setHostTime(time) { 197 // Add the separate date and time objects and convert to Epoch time 198 // microseconds. 199 return APIUtils.setHostTime(time); 200 } 201 function createOffset(date) { 202 // https://stackoverflow.com/questions/9149556/how-to-get-utc-offset-in-javascript-analog-of-timezoneinfo-getutcoffset-in-c 203 var sign = (date.getTimezoneOffset() > 0) ? '-' : '+'; 204 var offset = Math.abs(date.getTimezoneOffset()); 205 var hours = pad(Math.floor(offset / 60)); 206 var minutes = pad(offset % 60); 207 return '(UTC' + sign + hours + ':' + minutes + ')'; 208 } 209 function getUserTimezone(date) { 210 const ro = Intl.DateTimeFormat().resolvedOptions(); 211 // A safe, easy way to get the timezone (e.g. Central Standard Time) is 212 // to subtract the time string without a timezone from the time string 213 // with a timezone. 214 // Hardcoded to 'en-US' so all timezones are displayed in English 215 // (e.g. Moscow Standard Time). 216 var ret = date.toLocaleTimeString('en-US', {timeZoneName: 'long'}) 217 .replace(date.toLocaleTimeString('en-US'), '') 218 .trim(); 219 // Do not return GMT+/-offset. 220 if (ret.indexOf('GMT') >= 0) { 221 return ''; 222 } 223 return ret; 224 } 225 function pad(value) { 226 return value < 10 ? '0' + value : value; 227 } 228 } 229 ]); 230})(angular); 231