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', '$window', 'APIUtils', '$route', '$q', 'ngToast',
14    function($scope, $window, APIUtils, $route, $q, ngToast) {
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 = [
77        getTimePromise,
78        getNTPPromise,
79      ];
80
81      $q.all(promises).finally(function() {
82        $scope.loading = false;
83      });
84
85      $scope.setTime = function() {
86        $scope.loading = true;
87        var promises = [setTimeMode(), setTimeOwner(), setNTPServers()];
88
89        $q.all(promises).then(
90            function() {
91              // Have to set the time mode and time owner first to avoid a
92              // insufficient permissions if the time mode or time owner had
93              // changed.
94              var manual_promises = [];
95              if ($scope.time.mode == 'Manual') {
96                // If owner is 'Split' set both.
97                // If owner is 'Host' set only it.
98                // Else set BMC only. See:
99                // https://github.com/openbmc/phosphor-time-manager/blob/master/README.md
100                if ($scope.time.owner != 'Host') {
101                  manual_promises.push(
102                      setBMCTime($scope.bmc.date.getTime() * 1000));
103                }
104                // Even though we are setting Host time, we are setting from
105                // the BMC date and time fields labeled "BMC and Host Time"
106                // currently.
107                if ($scope.time.owner == 'Host') {
108                  manual_promises.push(
109                      setHostTime($scope.bmc.date.getTime() * 1000));
110                }
111              }
112              // Set the Host if Split even if NTP. In split mode, the host has
113              // its own date and time field. Set from it.
114              if ($scope.time.owner == 'Split') {
115                manual_promises.push(
116                    setHostTime($scope.host.date.getTime() * 1000));
117              }
118
119              $q.all(manual_promises)
120                  .then(
121                      function() {
122                        ngToast.success('Date and time settings saved');
123                      },
124                      function(errors) {
125                        console.log(JSON.stringify(errors));
126                        ngToast.danger(
127                            'Date and time settings could not be saved');
128                      })
129                  .finally(function() {
130                    $scope.loading = false;
131                  });
132            },
133            function(errors) {
134              console.log(JSON.stringify(errors));
135              ngToast.danger('Date and time settings could not be saved');
136              $scope.loading = false;
137            });
138      };
139      $scope.refresh = function() {
140        $route.reload();
141      };
142
143      $scope.addNTPField = function() {
144        $scope.ntp.servers.push('');
145      };
146
147      $scope.removeNTPField = function(index) {
148        $scope.ntp.servers.splice(index, 1);
149      };
150
151      function setNTPServers() {
152        // Remove any empty strings from the array. Important because we add an
153        // empty string to the end so the user can add a new NTP server, if the
154        // user doesn't fill out the field, we don't want to add.
155        $scope.ntp.servers = $scope.ntp.servers.filter(Boolean);
156        // NTP servers does not allow an empty array, since we remove all empty
157        // strings above, could have an empty array. TODO: openbmc/openbmc#3240
158        if ($scope.ntp.servers.length == 0) {
159          $scope.ntp.servers.push('');
160        }
161        return APIUtils.setNTPServers($scope.ntp.servers);
162      }
163
164      function setTimeMode() {
165        return APIUtils.setTimeMode(
166            'xyz.openbmc_project.Time.Synchronization.Method.' +
167            $scope.time.mode);
168      }
169
170      function setTimeOwner() {
171        return APIUtils.setTimeOwner(
172            'xyz.openbmc_project.Time.Owner.Owners.' + $scope.time.owner);
173      }
174
175      function setBMCTime(time) {
176        // Add the separate date and time objects and convert to Epoch time in
177        // microseconds.
178        return APIUtils.setBMCTime(time);
179      }
180
181      function setHostTime(time) {
182        // Add the separate date and time objects and convert to Epoch time
183        // microseconds.
184        return APIUtils.setHostTime(time);
185      }
186      function createOffset(date) {
187        // https://stackoverflow.com/questions/9149556/how-to-get-utc-offset-in-javascript-analog-of-timezoneinfo-getutcoffset-in-c
188        var sign = (date.getTimezoneOffset() > 0) ? '-' : '+';
189        var offset = Math.abs(date.getTimezoneOffset());
190        var hours = pad(Math.floor(offset / 60));
191        var minutes = pad(offset % 60);
192        return '(UTC' + sign + hours + ':' + minutes + ')';
193      }
194      function getUserTimezone(date) {
195        const ro = Intl.DateTimeFormat().resolvedOptions();
196        // A safe, easy way to get the timezone (e.g. Central Standard Time) is
197        // to subtract the time string without a timezone from the time string
198        // with a timezone.
199        // Hardcoded to 'en-US' so all timezones are displayed in English
200        // (e.g. Moscow Standard Time).
201        var ret = date.toLocaleTimeString('en-US', {timeZoneName: 'long'})
202                      .replace(date.toLocaleTimeString('en-US'), '')
203                      .trim();
204        // Do not return GMT+/-offset.
205        if (ret.indexOf('GMT') >= 0) {
206          return '';
207        }
208        return ret;
209      }
210      function pad(value) {
211        return value < 10 ? '0' + value : value;
212      }
213    }
214  ]);
215})(angular);
216