1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 cr.define('policy', function() {
7 * A hack to check if we are displaying the mobile version of this page by
8 * checking if the first column is hidden.
9 * @return {boolean} True if this is the mobile version.
11 var isMobilePage = function() {
12 return document.defaultView.getComputedStyle(document.querySelector(
13 '.scope-column')).display == 'none';
17 * A box that shows the status of cloud policy for a device or user.
19 * @extends {HTMLFieldSetElement}
21 var StatusBox = cr.ui.define(function() {
22 var node = $('status-box-template').cloneNode(true);
23 node.removeAttribute('id');
27 StatusBox.prototype = {
28 // Set up the prototype chain.
29 __proto__: HTMLFieldSetElement.prototype,
32 * Initialization function for the cr.ui framework.
34 decorate: function() {
38 * Populate the box with the given cloud policy status.
39 * @param {string} scope The policy scope, either "device" or "user".
40 * @param {Object} status Dictionary with information about the status.
42 initialize: function(scope, status) {
43 if (scope == 'device') {
44 // For device policy, set the appropriate title and populate the topmost
45 // status item with the domain the device is enrolled into.
46 this.querySelector('.legend').textContent =
47 loadTimeData.getString('statusDevice');
48 var domain = this.querySelector('.domain');
49 domain.textContent = status.domain;
50 domain.parentElement.hidden = false;
52 // Populate the device naming information.
53 // Populate the asset identifier.
54 var assetId = this.querySelector('.asset-id');
55 assetId.textContent = status.assetId ||
56 loadTimeData.getString('notSpecified');
57 assetId.parentElement.hidden = false;
59 // Populate the device location.
60 var location = this.querySelector('.location');
61 location.textContent = status.location ||
62 loadTimeData.getString('notSpecified');
63 location.parentElement.hidden = false;
65 // Populate the directory API ID.
66 var directoryApiId = this.querySelector('.directory-api-id');
67 directoryApiId.textContent = status.directoryApiId ||
68 loadTimeData.getString('notSpecified');
69 directoryApiId.parentElement.hidden = false;
71 // For user policy, set the appropriate title and populate the topmost
72 // status item with the username that policies apply to.
73 this.querySelector('.legend').textContent =
74 loadTimeData.getString('statusUser');
75 // Populate the topmost item with the username.
76 var username = this.querySelector('.username');
77 username.textContent = status.username;
78 username.parentElement.hidden = false;
80 // Populate all remaining items.
81 this.querySelector('.client-id').textContent = status.clientId || '';
82 this.querySelector('.time-since-last-refresh').textContent =
83 status.timeSinceLastRefresh || '';
84 this.querySelector('.refresh-interval').textContent =
85 status.refreshInterval || '';
86 this.querySelector('.status').textContent = status.status || '';
91 * A single policy's entry in the policy table.
93 * @extends {HTMLTableSectionElement}
95 var Policy = cr.ui.define(function() {
96 var node = $('policy-template').cloneNode(true);
97 node.removeAttribute('id');
102 // Set up the prototype chain.
103 __proto__: HTMLTableSectionElement.prototype,
106 * Initialization function for the cr.ui framework.
108 decorate: function() {
109 this.updateToggleExpandedValueText_();
110 this.querySelector('.toggle-expanded-value').addEventListener(
111 'click', this.toggleExpandedValue_.bind(this));
115 * Populate the table columns with information about the policy name, value
117 * @param {string} name The policy name.
118 * @param {Object} value Dictionary with information about the policy value.
119 * @param {boolean} unknown Whether the policy name is not recognized.
121 initialize: function(name, value, unknown) {
125 // Populate the name column.
126 this.querySelector('.name').textContent = name;
128 // Populate the remaining columns with policy scope, level and value if a
129 // value has been set. Otherwise, leave them blank.
131 this.querySelector('.scope').textContent =
132 loadTimeData.getString(value.scope == 'user' ?
133 'scopeUser' : 'scopeDevice');
134 this.querySelector('.level').textContent =
135 loadTimeData.getString(value.level == 'recommended' ?
136 'levelRecommended' : 'levelMandatory');
137 this.querySelector('.value').textContent = value.value;
138 this.querySelector('.expanded-value').textContent = value.value;
141 // Populate the status column.
144 // If the policy value has not been set, show an error message.
145 status = loadTimeData.getString('unset');
146 } else if (unknown) {
147 // If the policy name is not recognized, show an error message.
148 status = loadTimeData.getString('unknown');
149 } else if (value.error) {
150 // If an error occurred while parsing the policy value, show the error
152 status = value.error;
154 // Otherwise, indicate that the policy value was parsed correctly.
155 status = loadTimeData.getString('ok');
157 this.querySelector('.status').textContent = status;
159 if (isMobilePage()) {
160 // The number of columns which are hidden by the css file for the mobile
161 // (Android) version of this page.
162 /** @const */ var HIDDEN_COLUMNS_IN_MOBILE_VERSION = 2;
164 var expandedValue = this.querySelector('.expanded-value');
165 expandedValue.setAttribute('colspan',
166 expandedValue.colSpan - HIDDEN_COLUMNS_IN_MOBILE_VERSION);
171 * Check the table columns for overflow. Most columns are automatically
172 * elided when overflow occurs. The only action required is to add a tooltip
173 * that shows the complete content. The value column is an exception. If
174 * overflow occurs here, the contents is replaced with a link that toggles
175 * the visibility of an additional row containing the complete value.
177 checkOverflow: function() {
178 // Set a tooltip on all overflowed columns except the value column.
179 var divs = this.querySelectorAll('div.elide');
180 for (var i = 0; i < divs.length; i++) {
182 div.title = div.offsetWidth < div.scrollWidth ? div.textContent : '';
185 // Cache the width of the value column's contents when it is first shown.
186 // This is required to be able to check whether the contents would still
187 // overflow the column once it has been hidden and replaced by a link.
188 var valueContainer = this.querySelector('.value-container');
189 if (valueContainer.valueWidth == undefined) {
190 valueContainer.valueWidth =
191 valueContainer.querySelector('.value').offsetWidth;
194 // Determine whether the contents of the value column overflows. The
195 // visibility of the contents, replacement link and additional row
196 // containing the complete value that depend on this are handled by CSS.
197 if (valueContainer.offsetWidth < valueContainer.valueWidth)
198 this.classList.add('has-overflowed-value');
200 this.classList.remove('has-overflowed-value');
204 * Update the text of the link that toggles the visibility of an additional
205 * row containing the complete policy value, depending on the toggle state.
208 updateToggleExpandedValueText_: function(event) {
209 this.querySelector('.toggle-expanded-value').textContent =
210 loadTimeData.getString(
211 this.classList.contains('show-overflowed-value') ?
212 'hideExpandedValue' : 'showExpandedValue');
216 * Toggle the visibility of an additional row containing the complete policy
220 toggleExpandedValue_: function() {
221 this.classList.toggle('show-overflowed-value');
222 this.updateToggleExpandedValueText_();
227 * A table of policies and their values.
229 * @extends {HTMLTableElement}
231 var PolicyTable = cr.ui.define('tbody');
233 PolicyTable.prototype = {
234 // Set up the prototype chain.
235 __proto__: HTMLTableElement.prototype,
238 * Initialization function for the cr.ui framework.
240 decorate: function() {
242 this.filterPattern_ = '';
243 window.addEventListener('resize', this.checkOverflow_.bind(this));
247 * Initialize the list of all known policies.
248 * @param {Object} names Dictionary containing all known policy names.
250 setPolicyNames: function(names) {
251 this.policies_ = names;
252 this.setPolicyValues({});
256 * Populate the table with the currently set policy values and any errors
257 * detected while parsing these.
258 * @param {Object} values Dictionary containing the current policy values.
260 setPolicyValues: function(values) {
261 // Remove all policies from the table.
262 var policies = this.getElementsByTagName('tbody');
263 while (policies.length > 0)
264 this.removeChild(policies.item(0));
266 // First, add known policies whose value is currently set.
268 for (var name in this.policies_) {
270 this.setPolicyValue_(name, values[name], false);
275 // Second, add policies whose value is currently set but whose name is not
277 for (var name in values) {
278 if (!(name in this.policies_))
279 this.setPolicyValue_(name, values[name], true);
282 // Finally, add known policies whose value is not currently set.
283 for (var i = 0; i < unset.length; i++)
284 this.setPolicyValue_(unset[i], undefined, false);
286 // Filter the policies.
291 * Set the filter pattern. Only policies whose name contains |pattern| are
292 * shown in the policy table. The filter is case insensitive. It can be
293 * disabled by setting |pattern| to an empty string.
294 * @param {string} pattern The filter pattern.
296 setFilterPattern: function(pattern) {
297 this.filterPattern_ = pattern.toLowerCase();
302 * Filter policies. Only policies whose name contains the filter pattern are
303 * shown in the table. Furthermore, policies whose value is not currently
304 * set are only shown if the corresponding checkbox is checked.
307 var showUnset = $('show-unset').checked;
308 var policies = this.getElementsByTagName('tbody');
309 for (var i = 0; i < policies.length; i++) {
310 var policy = policies[i];
312 policy.unset && !showUnset ||
313 policy.name.toLowerCase().indexOf(this.filterPattern_) == -1;
315 if (this.querySelector('tbody:not([hidden])'))
316 this.parentElement.classList.remove('empty');
318 this.parentElement.classList.add('empty');
319 setTimeout(this.checkOverflow_.bind(this), 0);
323 * Check the table columns for overflow.
326 checkOverflow_: function() {
327 var policies = this.getElementsByTagName('tbody');
328 for (var i = 0; i < policies.length; i++) {
329 if (!policies[i].hidden)
330 policies[i].checkOverflow();
335 * Add a policy with the given |name| and |value| to the table.
336 * @param {string} name The policy name.
337 * @param {Object} value Dictionary with information about the policy value.
338 * @param {boolean} unknown Whether the policy name is not recoginzed.
341 setPolicyValue_: function(name, value, unknown) {
342 var policy = new Policy;
343 policy.initialize(name, value, unknown);
344 this.appendChild(policy);
349 * A singelton object that handles communication between browser and WebUI.
355 // Make Page a singleton.
356 cr.addSingletonGetter(Page);
359 * Provide a list of all known policies to the UI. Called by the browser on
361 * @param {Object} names Dictionary containing all known policy names.
363 Page.setPolicyNames = function(names) {
364 var page = this.getInstance();
366 // Clear all policy tables.
367 page.mainSection.innerHTML = '';
368 page.policyTables = {};
370 // Create tables and set known policy names for Chrome and extensions.
371 if (names.hasOwnProperty('chromePolicyNames')) {
372 var table = page.appendNewTable('chrome', 'Chrome policies', '');
373 table.setPolicyNames(names.chromePolicyNames);
376 if (names.hasOwnProperty('extensionPolicyNames')) {
377 for (var ext in names.extensionPolicyNames) {
378 var table = page.appendNewTable('extension-' + ext,
379 names.extensionPolicyNames[ext].name, 'ID: ' + ext);
380 table.setPolicyNames(names.extensionPolicyNames[ext].policyNames);
386 * Provide a list of the currently set policy values and any errors detected
387 * while parsing these to the UI. Called by the browser on page load and
388 * whenever policy values change.
389 * @param {Object} values Dictionary containing the current policy values.
391 Page.setPolicyValues = function(values) {
392 var page = this.getInstance();
393 if (values.hasOwnProperty('chromePolicies')) {
394 var table = page.policyTables['chrome'];
395 table.setPolicyValues(values.chromePolicies);
398 if (values.hasOwnProperty('extensionPolicies')) {
399 for (var extensionId in values.extensionPolicies) {
400 var table = page.policyTables['extension-' + extensionId];
402 table.setPolicyValues(values.extensionPolicies[extensionId]);
408 * Provide the current cloud policy status to the UI. Called by the browser on
409 * page load if cloud policy is present and whenever the status changes.
410 * @param {Object} status Dictionary containing the current policy status.
412 Page.setStatus = function(status) {
413 this.getInstance().setStatus(status);
417 * Notify the UI that a request to reload policy values has completed. Called
418 * by the browser after a request to reload policy has been sent by the UI.
420 Page.reloadPoliciesDone = function() {
421 this.getInstance().reloadPoliciesDone();
426 * Main initialization function. Called by the browser on page load.
428 initialize: function() {
429 uber.onContentFrameLoaded();
430 cr.ui.FocusOutlineManager.forDocument(document);
432 this.mainSection = $('main-section');
433 this.policyTables = {};
435 // Place the initial focus on the filter input field.
439 $('filter').onsearch = function(event) {
440 for (policyTable in self.policyTables) {
441 self.policyTables[policyTable].setFilterPattern(this.value);
444 $('reload-policies').onclick = function(event) {
445 this.disabled = true;
446 chrome.send('reloadPolicies');
449 $('show-unset').onchange = function() {
450 for (policyTable in self.policyTables) {
451 self.policyTables[policyTable].filter();
455 // Notify the browser that the page has loaded, causing it to send the
456 // list of all known policies, the current policy values and the cloud
458 chrome.send('initialized');
462 * Creates a new policy table section, adds the section to the page,
463 * and returns the new table from that section.
464 * @param {string} id The key for storing the new table in policyTables.
465 * @param {string} label_title Title for this policy table.
466 * @param {string} label_content Description for the policy table.
467 * @return {Element} The newly created table.
469 appendNewTable: function(id, label_title, label_content) {
470 var newSection = this.createPolicyTableSection(id, label_title,
472 this.mainSection.appendChild(newSection);
473 return this.policyTables[id];
477 * Creates a new section containing a title, description and table of
479 * @param {id} id The key for storing the new table in policyTables.
480 * @param {string} label_title Title for this policy table.
481 * @param {string} label_content Description for the policy table.
482 * @return {Element} The newly created section.
484 createPolicyTableSection: function(id, label_title, label_content) {
485 var section = document.createElement('section');
486 section.setAttribute('class', 'policy-table-section');
488 // Add title and description.
489 var title = window.document.createElement('h3');
490 title.textContent = label_title;
491 section.appendChild(title);
494 var description = window.document.createElement('div');
495 description.classList.add('table-description');
496 description.textContent = label_content;
497 section.appendChild(description);
500 // Add 'No Policies Set' element.
501 var noPolicies = window.document.createElement('div');
502 noPolicies.classList.add('no-policies-set');
503 noPolicies.textContent = loadTimeData.getString('noPoliciesSet');
504 section.appendChild(noPolicies);
506 // Add table of policies.
507 var newTable = this.createPolicyTable();
508 this.policyTables[id] = newTable;
509 section.appendChild(newTable);
515 * Creates a new table for displaying policies.
516 * @return {Element} The newly created table.
518 createPolicyTable: function() {
519 var newTable = window.document.createElement('table');
520 var tableHead = window.document.createElement('thead');
521 var tableRow = window.document.createElement('tr');
522 var tableHeadings = ['Scope', 'Level', 'Name', 'Value', 'Status'];
523 for (var i = 0; i < tableHeadings.length; i++) {
524 var tableHeader = window.document.createElement('th');
525 tableHeader.classList.add(tableHeadings[i].toLowerCase() + '-column');
526 tableHeader.textContent = loadTimeData.getString('header' +
528 tableRow.appendChild(tableHeader);
530 tableHead.appendChild(tableRow);
531 newTable.appendChild(tableHead);
532 cr.ui.decorate(newTable, PolicyTable);
537 * Update the status section of the page to show the current cloud policy
539 * @param {Object} status Dictionary containing the current policy status.
541 setStatus: function(status) {
542 // Remove any existing status boxes.
543 var container = $('status-box-container');
544 while (container.firstChild)
545 container.removeChild(container.firstChild);
546 // Hide the status section.
547 var section = $('status-section');
548 section.hidden = true;
550 // Add a status box for each scope that has a cloud policy status.
551 for (var scope in status) {
552 var box = new StatusBox;
553 box.initialize(scope, status[scope]);
554 container.appendChild(box);
555 // Show the status section.
556 section.hidden = false;
561 * Re-enable the reload policies button when the previous request to reload
562 * policies values has completed.
564 reloadPoliciesDone: function() {
565 $('reload-policies').disabled = false;
574 // Have the main initialization function be called when the page finishes
576 document.addEventListener(
578 policy.Page.getInstance().initialize.bind(policy.Page.getInstance()));