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