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