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('.source').textContent
=
138 loadTimeData
.getString(value
.source
);
139 this.querySelector('.value').textContent
= value
.value
;
140 this.querySelector('.expanded-value').textContent
= value
.value
;
143 // Populate the status column.
146 // If the policy value has not been set, show an error message.
147 status
= loadTimeData
.getString('unset');
148 } else if (unknown
) {
149 // If the policy name is not recognized, show an error message.
150 status
= loadTimeData
.getString('unknown');
151 } else if (value
.error
) {
152 // If an error occurred while parsing the policy value, show the error
154 status
= value
.error
;
156 // Otherwise, indicate that the policy value was parsed correctly.
157 status
= loadTimeData
.getString('ok');
159 this.querySelector('.status').textContent
= status
;
161 if (isMobilePage()) {
162 // The number of columns which are hidden by the css file for the mobile
163 // (Android) version of this page.
164 /** @const */ var HIDDEN_COLUMNS_IN_MOBILE_VERSION
= 2;
166 var expandedValue
= this.querySelector('.expanded-value');
167 expandedValue
.setAttribute('colspan',
168 expandedValue
.colSpan
- HIDDEN_COLUMNS_IN_MOBILE_VERSION
);
173 * Check the table columns for overflow. Most columns are automatically
174 * elided when overflow occurs. The only action required is to add a tooltip
175 * that shows the complete content. The value column is an exception. If
176 * overflow occurs here, the contents is replaced with a link that toggles
177 * the visibility of an additional row containing the complete value.
179 checkOverflow: function() {
180 // Set a tooltip on all overflowed columns except the value column.
181 var divs
= this.querySelectorAll('div.elide');
182 for (var i
= 0; i
< divs
.length
; i
++) {
184 div
.title
= div
.offsetWidth
< div
.scrollWidth
? div
.textContent
: '';
187 // Cache the width of the value column's contents when it is first shown.
188 // This is required to be able to check whether the contents would still
189 // overflow the column once it has been hidden and replaced by a link.
190 var valueContainer
= this.querySelector('.value-container');
191 if (valueContainer
.valueWidth
== undefined) {
192 valueContainer
.valueWidth
=
193 valueContainer
.querySelector('.value').offsetWidth
;
196 // Determine whether the contents of the value column overflows. The
197 // visibility of the contents, replacement link and additional row
198 // containing the complete value that depend on this are handled by CSS.
199 if (valueContainer
.offsetWidth
< valueContainer
.valueWidth
)
200 this.classList
.add('has-overflowed-value');
202 this.classList
.remove('has-overflowed-value');
206 * Update the text of the link that toggles the visibility of an additional
207 * row containing the complete policy value, depending on the toggle state.
210 updateToggleExpandedValueText_: function(event
) {
211 this.querySelector('.toggle-expanded-value').textContent
=
212 loadTimeData
.getString(
213 this.classList
.contains('show-overflowed-value') ?
214 'hideExpandedValue' : 'showExpandedValue');
218 * Toggle the visibility of an additional row containing the complete policy
222 toggleExpandedValue_: function() {
223 this.classList
.toggle('show-overflowed-value');
224 this.updateToggleExpandedValueText_();
229 * A table of policies and their values.
231 * @extends {HTMLTableElement}
233 var PolicyTable
= cr
.ui
.define('tbody');
235 PolicyTable
.prototype = {
236 // Set up the prototype chain.
237 __proto__
: HTMLTableElement
.prototype,
240 * Initialization function for the cr.ui framework.
242 decorate: function() {
244 this.filterPattern_
= '';
245 window
.addEventListener('resize', this.checkOverflow_
.bind(this));
249 * Initialize the list of all known policies.
250 * @param {Object} names Dictionary containing all known policy names.
252 setPolicyNames: function(names
) {
253 this.policies_
= names
;
254 this.setPolicyValues({});
258 * Populate the table with the currently set policy values and any errors
259 * detected while parsing these.
260 * @param {Object} values Dictionary containing the current policy values.
262 setPolicyValues: function(values
) {
263 // Remove all policies from the table.
264 var policies
= this.getElementsByTagName('tbody');
265 while (policies
.length
> 0)
266 this.removeChild(policies
.item(0));
268 // First, add known policies whose value is currently set.
270 for (var name
in this.policies_
) {
272 this.setPolicyValue_(name
, values
[name
], false);
277 // Second, add policies whose value is currently set but whose name is not
279 for (var name
in values
) {
280 if (!(name
in this.policies_
))
281 this.setPolicyValue_(name
, values
[name
], true);
284 // Finally, add known policies whose value is not currently set.
285 for (var i
= 0; i
< unset
.length
; i
++)
286 this.setPolicyValue_(unset
[i
], undefined, false);
288 // Filter the policies.
293 * Set the filter pattern. Only policies whose name contains |pattern| are
294 * shown in the policy table. The filter is case insensitive. It can be
295 * disabled by setting |pattern| to an empty string.
296 * @param {string} pattern The filter pattern.
298 setFilterPattern: function(pattern
) {
299 this.filterPattern_
= pattern
.toLowerCase();
304 * Filter policies. Only policies whose name contains the filter pattern are
305 * shown in the table. Furthermore, policies whose value is not currently
306 * set are only shown if the corresponding checkbox is checked.
309 var showUnset
= $('show-unset').checked
;
310 var policies
= this.getElementsByTagName('tbody');
311 for (var i
= 0; i
< policies
.length
; i
++) {
312 var policy
= policies
[i
];
314 policy
.unset
&& !showUnset
||
315 policy
.name
.toLowerCase().indexOf(this.filterPattern_
) == -1;
317 if (this.querySelector('tbody:not([hidden])'))
318 this.parentElement
.classList
.remove('empty');
320 this.parentElement
.classList
.add('empty');
321 setTimeout(this.checkOverflow_
.bind(this), 0);
325 * Check the table columns for overflow.
328 checkOverflow_: function() {
329 var policies
= this.getElementsByTagName('tbody');
330 for (var i
= 0; i
< policies
.length
; i
++) {
331 if (!policies
[i
].hidden
)
332 policies
[i
].checkOverflow();
337 * Add a policy with the given |name| and |value| to the table.
338 * @param {string} name The policy name.
339 * @param {Object} value Dictionary with information about the policy value.
340 * @param {boolean} unknown Whether the policy name is not recoginzed.
343 setPolicyValue_: function(name
, value
, unknown
) {
344 var policy
= new Policy
;
345 policy
.initialize(name
, value
, unknown
);
346 this.appendChild(policy
);
351 * A singelton object that handles communication between browser and WebUI.
357 // Make Page a singleton.
358 cr
.addSingletonGetter(Page
);
361 * Provide a list of all known policies to the UI. Called by the browser on
363 * @param {Object} names Dictionary containing all known policy names.
365 Page
.setPolicyNames = function(names
) {
366 var page
= this.getInstance();
368 // Clear all policy tables.
369 page
.mainSection
.innerHTML
= '';
370 page
.policyTables
= {};
372 // Create tables and set known policy names for Chrome and extensions.
373 if (names
.hasOwnProperty('chromePolicyNames')) {
374 var table
= page
.appendNewTable('chrome', 'Chrome policies', '');
375 table
.setPolicyNames(names
.chromePolicyNames
);
378 if (names
.hasOwnProperty('extensionPolicyNames')) {
379 for (var ext
in names
.extensionPolicyNames
) {
380 var table
= page
.appendNewTable('extension-' + ext
,
381 names
.extensionPolicyNames
[ext
].name
, 'ID: ' + ext
);
382 table
.setPolicyNames(names
.extensionPolicyNames
[ext
].policyNames
);
388 * Provide a list of the currently set policy values and any errors detected
389 * while parsing these to the UI. Called by the browser on page load and
390 * whenever policy values change.
391 * @param {Object} values Dictionary containing the current policy values.
393 Page
.setPolicyValues = function(values
) {
394 var page
= this.getInstance();
395 if (values
.hasOwnProperty('chromePolicies')) {
396 var table
= page
.policyTables
['chrome'];
397 table
.setPolicyValues(values
.chromePolicies
);
400 if (values
.hasOwnProperty('extensionPolicies')) {
401 for (var extensionId
in values
.extensionPolicies
) {
402 var table
= page
.policyTables
['extension-' + extensionId
];
404 table
.setPolicyValues(values
.extensionPolicies
[extensionId
]);
410 * Provide the current cloud policy status to the UI. Called by the browser on
411 * page load if cloud policy is present and whenever the status changes.
412 * @param {Object} status Dictionary containing the current policy status.
414 Page
.setStatus = function(status
) {
415 this.getInstance().setStatus(status
);
419 * Notify the UI that a request to reload policy values has completed. Called
420 * by the browser after a request to reload policy has been sent by the UI.
422 Page
.reloadPoliciesDone = function() {
423 this.getInstance().reloadPoliciesDone();
428 * Main initialization function. Called by the browser on page load.
430 initialize: function() {
431 uber
.onContentFrameLoaded();
432 cr
.ui
.FocusOutlineManager
.forDocument(document
);
434 this.mainSection
= $('main-section');
435 this.policyTables
= {};
437 // Place the initial focus on the filter input field.
441 $('filter').onsearch = function(event
) {
442 for (policyTable
in self
.policyTables
) {
443 self
.policyTables
[policyTable
].setFilterPattern(this.value
);
446 $('reload-policies').onclick = function(event
) {
447 this.disabled
= true;
448 chrome
.send('reloadPolicies');
451 $('show-unset').onchange = function() {
452 for (policyTable
in self
.policyTables
) {
453 self
.policyTables
[policyTable
].filter();
457 // Notify the browser that the page has loaded, causing it to send the
458 // list of all known policies, the current policy values and the cloud
460 chrome
.send('initialized');
464 * Creates a new policy table section, adds the section to the page,
465 * and returns the new table from that section.
466 * @param {string} id The key for storing the new table in policyTables.
467 * @param {string} label_title Title for this policy table.
468 * @param {string} label_content Description for the policy table.
469 * @return {Element} The newly created table.
471 appendNewTable: function(id
, label_title
, label_content
) {
472 var newSection
= this.createPolicyTableSection(id
, label_title
,
474 this.mainSection
.appendChild(newSection
);
475 return this.policyTables
[id
];
479 * Creates a new section containing a title, description and table of
481 * @param {id} id The key for storing the new table in policyTables.
482 * @param {string} label_title Title for this policy table.
483 * @param {string} label_content Description for the policy table.
484 * @return {Element} The newly created section.
486 createPolicyTableSection: function(id
, label_title
, label_content
) {
487 var section
= document
.createElement('section');
488 section
.setAttribute('class', 'policy-table-section');
490 // Add title and description.
491 var title
= window
.document
.createElement('h3');
492 title
.textContent
= label_title
;
493 section
.appendChild(title
);
496 var description
= window
.document
.createElement('div');
497 description
.classList
.add('table-description');
498 description
.textContent
= label_content
;
499 section
.appendChild(description
);
502 // Add 'No Policies Set' element.
503 var noPolicies
= window
.document
.createElement('div');
504 noPolicies
.classList
.add('no-policies-set');
505 noPolicies
.textContent
= loadTimeData
.getString('noPoliciesSet');
506 section
.appendChild(noPolicies
);
508 // Add table of policies.
509 var newTable
= this.createPolicyTable();
510 this.policyTables
[id
] = newTable
;
511 section
.appendChild(newTable
);
517 * Creates a new table for displaying policies.
518 * @return {Element} The newly created table.
520 createPolicyTable: function() {
521 var newTable
= window
.document
.createElement('table');
522 var tableHead
= window
.document
.createElement('thead');
523 var tableRow
= window
.document
.createElement('tr');
524 var tableHeadings
= ['Scope', 'Level', 'Source', 'Name', 'Value',
526 for (var i
= 0; i
< tableHeadings
.length
; i
++) {
527 var tableHeader
= window
.document
.createElement('th');
528 tableHeader
.classList
.add(tableHeadings
[i
].toLowerCase() + '-column');
529 tableHeader
.textContent
= loadTimeData
.getString('header' +
531 tableRow
.appendChild(tableHeader
);
533 tableHead
.appendChild(tableRow
);
534 newTable
.appendChild(tableHead
);
535 cr
.ui
.decorate(newTable
, PolicyTable
);
540 * Update the status section of the page to show the current cloud policy
542 * @param {Object} status Dictionary containing the current policy status.
544 setStatus: function(status
) {
545 // Remove any existing status boxes.
546 var container
= $('status-box-container');
547 while (container
.firstChild
)
548 container
.removeChild(container
.firstChild
);
549 // Hide the status section.
550 var section
= $('status-section');
551 section
.hidden
= true;
553 // Add a status box for each scope that has a cloud policy status.
554 for (var scope
in status
) {
555 var box
= new StatusBox
;
556 box
.initialize(scope
, status
[scope
]);
557 container
.appendChild(box
);
558 // Show the status section.
559 section
.hidden
= false;
564 * Re-enable the reload policies button when the previous request to reload
565 * policies values has completed.
567 reloadPoliciesDone: function() {
568 $('reload-policies').disabled
= false;
577 // Have the main initialization function be called when the page finishes
579 document
.addEventListener(
581 policy
.Page
.getInstance().initialize
.bind(policy
.Page
.getInstance()));