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. If the
168        // user doesn't fill out the field, we don't want to add.
169        $scope.ntp.servers = $scope.ntp.servers.filter(Boolean);
170
171        return APIUtils.setNTPServers($scope.ntp.servers);
172      }
173
174      function setTimeMode() {
175        return APIUtils.setTimeMode(
176            'xyz.openbmc_project.Time.Synchronization.Method.' +
177            $scope.time.mode);
178      }
179
180      function setTimeOwner() {
181        return APIUtils.setTimeOwner(
182            'xyz.openbmc_project.Time.Owner.Owners.' + $scope.time.owner);
183      }
184
185      function setBMCTime(time) {
186        // Add the separate date and time objects and convert to Epoch time in
187        // microseconds.
188        return APIUtils.setBMCTime(time);
189      }
190
191      function setHostTime(time) {
192        // Add the separate date and time objects and convert to Epoch time
193        // microseconds.
194        return APIUtils.setHostTime(time);
195      }
196      function createOffset(date) {
197        // https://stackoverflow.com/questions/9149556/how-to-get-utc-offset-in-javascript-analog-of-timezoneinfo-getutcoffset-in-c
198        var sign = (date.getTimezoneOffset() > 0) ? '-' : '+';
199        var offset = Math.abs(date.getTimezoneOffset());
200        var hours = pad(Math.floor(offset / 60));
201        var minutes = pad(offset % 60);
202        return '(UTC' + sign + hours + ':' + minutes + ')';
203      }
204      function getUserTimezone(date) {
205        const ro = Intl.DateTimeFormat().resolvedOptions();
206        // A safe, easy way to get the timezone (e.g. Central Standard Time) is
207        // to subtract the time string without a timezone from the time string
208        // with a timezone.
209        // Hardcoded to 'en-US' so all timezones are displayed in English
210        // (e.g. Moscow Standard Time).
211        var ret = date.toLocaleTimeString('en-US', {timeZoneName: 'long'})
212                      .replace(date.toLocaleTimeString('en-US'), '')
213                      .trim();
214        // Do not return GMT+/-offset.
215        if (ret.indexOf('GMT') >= 0) {
216          return '';
217        }
218        return ret;
219      }
220      function pad(value) {
221        return value < 10 ? '0' + value : value;
222      }
223    }
224  ]);
225})(angular);
226