diff options
Diffstat (limited to 'chromium/ui/webui/resources/cr_components/chromeos')
19 files changed, 2612 insertions, 0 deletions
diff --git a/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html new file mode 100644 index 00000000000..a89ba4d5fde --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.html @@ -0,0 +1,137 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/html/cr.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> + +<dom-module id="bluetooth-dialog"> + <template> + <style include="cr-hidden-style iron-flex"> + #pairing { + margin-bottom: 10px; + } + + #pairing paper-input { + text-align: center; + } + + #pinDiv { + margin-top: 10px; + } + + .dialog-message { + margin-bottom: 10px; + } + + div.contents { + height: 250px; + } + + /* .display indicates a displayed pin code or passkey. */ + span.display { + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 0 0 1px #222; + color: #222; + font-size: 123.08%; /* 16px / 13px */ + height: 38px; + line-height: 38px; + margin: 0 5px; + padding: 0 15px; + text-align: center; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + } + + span.display.next { + background: rgb(77, 144, 254); + border: 2px solid rgb(77, 144, 254); + box-shadow: none; + color: #fff; + } + + span.display.untyped { + border: 1px solid #d4d4d4; + box-shadow: 0 0 0 1px #888; + color: #666; + } + + /* .confirm indicates a confirmation passkey. */ + span.confirm { + color: #999; + font-size: 153.85%; /* 20px / 13px */ + font-weight: 600; /* semibold */ + margin: 0 20px; + } + </style> + <!-- TODO(stevenjb/dschuyler): Find a solution to support i18n{} here --> + <dialog is="cr-dialog" id="dialog" on-cancel="onDialogCanceled_" + close-text="[[i18n('close')]]" on-closed="onDialogCanceled_"> + <div slot="title">[[title]]</div> + <div slot="body"> + <div class="contents layout vertical center center-justified"> + <template is="dom-if" if="[[!errorMessage_]]"> + <div id="pairing" class="layout vertical center center-justified"> + <div class="dialog-message"> + [[getMessage_(pairingDevice, pairingEvent_)]] + </div> + <div hidden$="[[!showEnterPincode_(pairingEvent_)]]"> + <paper-input id="pincode" minlength="1" maxlength="16" + type="text" auto-validate value="{{pinOrPass_}}"> + </paper-input> + </div> + <div hidden$="[[!showEnterPasskey_(pairingEvent_)]]"> + <paper-input id="passkey" minlength="6" maxlength="6" + type="text" auto-validate value="{{pinOrPass_}}"> + </paper-input> + </div> + <div id="pinDiv" class="layout horizontal center center-justified" + hidden="[[!showDisplayPassOrPin_(pairingEvent_)]]"> + <template is="dom-repeat" items="[[digits_]]"> + <span class$="[[getPinClass_(index, pairingEvent_)]]"> + [[getPinDigit_(index, pairingEvent_)]] + </span> + </template> + <span class$="[[getPinClass_(-1, pairingEvent_)]]" + hidden="[[showAcceptReject_(pairingEvent_)]]"> + [[i18n('bluetoothEnterKey')]] + </span> + </div> + </div> + </template> + <template is="dom-if" if="[[errorMessage_]]"> + <div class="layout vertical center center-justified"> + <div class="dialog-message">[[errorMessage_]]</div> + </div> + </template> + </div> + </div> + <div slot="button-container"> + <template is="dom-if" if="[[!errorMessage_]]"> + <paper-button hidden$="[[!showAcceptReject_(pairingEvent_)]]" + on-tap="onAcceptTap_">[[i18n('bluetoothAccept')]]</paper-button> + <paper-button hidden$="[[!showAcceptReject_(pairingEvent_)]]" + on-tap="onRejectTap_">[[i18n('bluetoothReject')]]</paper-button> + <paper-button hidden$="[[!showConnect_(pairingEvent_)]]" + disabled="[[!enableConnect_(pairingEvent_, pinOrPass_)]]" + on-tap="onConnectTap_">[[i18n('bluetoothPair')]]</paper-button> + <paper-button + hidden$="[[!showDismiss_(pairingDevice, pairingEvent_)]]" + on-tap="close">[[i18n('ok')]]</paper-button> + <paper-button hidden$="[[showDismiss_(pairingDevice, pairingEvent_)]]" + on-tap="onCancelTap_"> + [[i18n('cancel')]] + </paper-button> + </template> + <template is="dom-if" if="[[errorMessage_]]"> + <paper-button on-tap="close">[[i18n('ok')]]</paper-button> + </template> + </div> + </dialog> + </template> + <script src="bluetooth_dialog.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js new file mode 100644 index 00000000000..b061349e266 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js @@ -0,0 +1,459 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * Dialog used for pairing a provided |pairing-device|. Set |show-error| to + * show the error results from a pairing event instead of the pairing UI. + * NOTE: This module depends on I18nBehavior which depends on loadTimeData. + */ + +var PairingEventType = chrome.bluetoothPrivate.PairingEventType; + +Polymer({ + is: 'bluetooth-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** + * Interface for bluetooth calls. Set in bluetooth-page. + * @type {Bluetooth} + * @private + */ + bluetooth: { + type: Object, + value: chrome.bluetooth, + }, + + /** + * Interface for bluetoothPrivate calls. + * @type {BluetoothPrivate} + */ + bluetoothPrivate: { + type: Object, + value: chrome.bluetoothPrivate, + }, + + /** Dialog title */ + title: String, + + /** + * Current Pairing device. + * @type {!chrome.bluetooth.Device|undefined} + */ + pairingDevice: Object, + + /** + * Current Pairing event. + * @private {?chrome.bluetoothPrivate.PairingEvent} + */ + pairingEvent_: { + type: Object, + value: null, + }, + + /** + * May be set by the host to show a pairing error result, or may be + * set by the dialog if a pairing or connect error occured. + * @private + */ + errorMessage_: String, + + /** + * Pincode or passkey value, used to trigger connect enabled changes. + * @private + */ + pinOrPass_: String, + + /** + * @const {!Array<number>} + * @private + */ + digits_: { + type: Array, + readOnly: true, + value: [0, 1, 2, 3, 4, 5], + }, + }, + + observers: [ + 'dialogUpdated_(errorMessage_, pairingEvent_)', + 'pairingChanged_(pairingDevice, pairingEvent_)', + ], + + /** + * Listener for chrome.bluetoothPrivate.onPairing events. + * @private {?function(!chrome.bluetoothPrivate.PairingEvent)} + */ + bluetoothPrivateOnPairingListener_: null, + + /** + * Listener for chrome.bluetooth.onBluetoothDeviceChanged events. + * @private {?function(!chrome.bluetooth.Device)} + */ + bluetoothDeviceChangedListener_: null, + + open: function() { + this.startPairing(); + this.pinOrPass_ = ''; + this.getDialog_().showModal(); + this.itemWasFocused_ = false; + }, + + close: function() { + this.endPairing(); + var dialog = this.getDialog_(); + if (dialog.open) + dialog.close(); + }, + + /** + * Updates the dialog after a connect attempt. + * @param {!chrome.bluetooth.Device} device The device connected to + * @param {!{message: string}} lastError chrome.runtime.lastError + * @param {chrome.bluetoothPrivate.ConnectResultType} result The connect + * result + * @return {boolean} + */ + handleError: function(device, lastError, result) { + var error; + if (lastError) { + error = lastError.message; + } else { + switch (result) { + case chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS: + case chrome.bluetoothPrivate.ConnectResultType.ALREADY_CONNECTED: + case chrome.bluetoothPrivate.ConnectResultType.AUTH_CANCELED: + case chrome.bluetoothPrivate.ConnectResultType.SUCCESS: + this.errorMessage_ = ''; + return false; + default: + error = result; + } + } + + var name = device.name || device.address; + var id = 'bluetooth_connect_' + error; + if (this.i18nExists(id)) { + this.errorMessage_ = this.i18n(id, name); + } else { + this.errorMessage_ = error; + console.error('Unexpected error connecting to: ' + name + ': ' + error); + } + return true; + }, + + /** @private */ + dialogUpdated_: function() { + if (this.showEnterPincode_()) + this.$$('#pincode').focus(); + else if (this.showEnterPasskey_()) + this.$$('#passkey').focus(); + }, + + /** + * @return {!CrDialogElement} + * @private + */ + getDialog_: function() { + return /** @type {!CrDialogElement} */ (this.$.dialog); + }, + + /** @private */ + onCancelTap_: function() { + this.getDialog_().cancel(); + }, + + /** @private */ + onDialogCanceled_: function() { + if (!this.errorMessage_) + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CANCEL); + this.endPairing(); + }, + + /** Called when the dialog is opened. Starts listening for pairing events. */ + startPairing: function() { + if (!this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivateOnPairingListener_ = + this.onBluetoothPrivateOnPairing_.bind(this); + this.bluetoothPrivate.onPairing.addListener( + this.bluetoothPrivateOnPairingListener_); + } + if (!this.bluetoothDeviceChangedListener_) { + this.bluetoothDeviceChangedListener_ = + this.onBluetoothDeviceChanged_.bind(this); + this.bluetooth.onDeviceChanged.addListener( + this.bluetoothDeviceChangedListener_); + } + }, + + /** Called when the dialog is closed. */ + endPairing: function() { + if (this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivate.onPairing.removeListener( + this.bluetoothPrivateOnPairingListener_); + this.bluetoothPrivateOnPairingListener_ = null; + } + if (this.bluetoothDeviceChangedListener_) { + this.bluetooth.onDeviceChanged.removeListener( + this.bluetoothDeviceChangedListener_); + this.bluetoothDeviceChangedListener_ = null; + } + this.pairingEvent_ = null; + }, + + /** + * Process bluetoothPrivate.onPairing events. + * @param {!chrome.bluetoothPrivate.PairingEvent} event + * @private + */ + onBluetoothPrivateOnPairing_: function(event) { + if (!this.pairingDevice || + event.device.address != this.pairingDevice.address) { + return; + } + if (event.pairing == PairingEventType.KEYS_ENTERED && + event.passkey === undefined && this.pairingEvent_) { + // 'keysEntered' event might not include the updated passkey so preserve + // the current one. + event.passkey = this.pairingEvent_.passkey; + } + this.pairingEvent_ = event; + }, + + /** + * Process bluetooth.onDeviceChanged events. This ensures that the dialog + * updates when the connection state changes. + * @param {!chrome.bluetooth.Device} device + * @private + */ + onBluetoothDeviceChanged_: function(device) { + if (!this.pairingDevice || device.address != this.pairingDevice.address) + return; + this.pairingDevice = device; + }, + + /** @private */ + pairingChanged_: function() { + // Auto-close the dialog when pairing completes. + if (this.pairingDevice.paired && !this.pairingDevice.connecting && + this.pairingDevice.connected) { + this.close(); + return; + } + this.errorMessage_ = ''; + this.pinOrPass_ = ''; + }, + + /** + * @return {string} + * @private + */ + getMessage_: function() { + var message; + if (!this.pairingEvent_) + message = 'bluetoothStartConnecting'; + else + message = this.getEventDesc_(this.pairingEvent_.pairing); + return this.i18n(message, this.pairingDevice.name); + }, + + /** + * @return {boolean} + * @private + */ + showEnterPincode_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE; + }, + + /** + * @return {boolean} + * @private + */ + showEnterPasskey_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.REQUEST_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + showDisplayPassOrPin_: function() { + if (!this.pairingEvent_) + return false; + var pairing = this.pairingEvent_.pairing; + return ( + pairing == PairingEventType.DISPLAY_PINCODE || + pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.CONFIRM_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED); + }, + + /** + * @return {boolean} + * @private + */ + showAcceptReject_: function() { + return !!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + showConnect_: function() { + if (!this.pairingEvent_) + return false; + var pairing = this.pairingEvent_.pairing; + return pairing == PairingEventType.REQUEST_PINCODE || + pairing == PairingEventType.REQUEST_PASSKEY; + }, + + /** + * @return {boolean} + * @private + */ + enableConnect_: function() { + if (!this.showConnect_()) + return false; + var inputId = + (this.pairingEvent_.pairing == PairingEventType.REQUEST_PINCODE) ? + '#pincode' : + '#passkey'; + var paperInput = /** @type {!PaperInputElement} */ (this.$$(inputId)); + assert(paperInput); + /** @type {string} */ var value = paperInput.value; + return !!value && paperInput.validate(); + }, + + /** + * @return {boolean} + * @private + */ + showDismiss_: function() { + return this.pairingDevice.paired || + (!!this.pairingEvent_ && + this.pairingEvent_.pairing == PairingEventType.COMPLETE); + }, + + /** @private */ + onAcceptTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onConnectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onRejectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.REJECT); + }, + + /** + * @param {!chrome.bluetoothPrivate.PairingResponse} response + * @private + */ + sendResponse_: function(response) { + if (!this.pairingDevice) + return; + var options = + /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ ( + {device: this.pairingDevice, response: response}); + if (response == chrome.bluetoothPrivate.PairingResponse.CONFIRM) { + var pairing = this.pairingEvent_.pairing; + if (pairing == PairingEventType.REQUEST_PINCODE) + options.pincode = this.$$('#pincode').value; + else if (pairing == PairingEventType.REQUEST_PASSKEY) + options.passkey = parseInt(this.$$('#passkey').value, 10); + } + this.bluetoothPrivate.setPairingResponse(options, () => { + if (chrome.runtime.lastError) { + // TODO(stevenjb): Show error. + console.error( + 'Error setting pairing response: ' + options.device.name + + ': Response: ' + options.response + + ': Error: ' + chrome.runtime.lastError.message); + } + this.close(); + }); + + this.fire('response', options); + }, + + /** + * @param {!PairingEventType} eventType + * @return {string} + * @private + */ + getEventDesc_: function(eventType) { + assert(eventType); + if (eventType == PairingEventType.COMPLETE || + eventType == PairingEventType.KEYS_ENTERED || + eventType == PairingEventType.REQUEST_AUTHORIZATION) { + return 'bluetoothStartConnecting'; + } + return 'bluetooth_' + /** @type {string} */ (eventType); + }, + + /** + * @param {number} index + * @return {string} + * @private + */ + getPinDigit_: function(index) { + if (!this.pairingEvent_) + return ''; + var digit = '0'; + var pairing = this.pairingEvent_.pairing; + if (pairing == PairingEventType.DISPLAY_PINCODE && + this.pairingEvent_.pincode && + index < this.pairingEvent_.pincode.length) { + digit = this.pairingEvent_.pincode[index]; + } else if ( + this.pairingEvent_.passkey && + (pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED || + pairing == PairingEventType.CONFIRM_PASSKEY)) { + var passkeyString = String(this.pairingEvent_.passkey); + if (index < passkeyString.length) + digit = passkeyString[index]; + } + return digit; + }, + + /** + * @param {number} index + * @return {string} + * @private + */ + getPinClass_: function(index) { + if (!this.pairingEvent_) + return ''; + if (this.pairingEvent_.pairing == PairingEventType.CONFIRM_PASSKEY) + return 'confirm'; + var cssClass = 'display'; + if (this.pairingEvent_.pairing == PairingEventType.DISPLAY_PASSKEY) { + if (index == 0) + cssClass += ' next'; + else + cssClass += ' untyped'; + } else if ( + this.pairingEvent_.pairing == PairingEventType.KEYS_ENTERED && + this.pairingEvent_.enteredKey) { + var enteredKey = this.pairingEvent_.enteredKey; // 1-7 + var lastKey = this.digits_.length; // 6 + if ((index == -1 && enteredKey > lastKey) || (index + 1 == enteredKey)) + cssClass += ' next'; + else if (index > enteredKey) + cssClass += ' untyped'; + } + return cssClass; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp new file mode 100644 index 00000000000..1e6afe0491a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/compiled_resources2.gyp @@ -0,0 +1,30 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'network_resources', + 'type': 'none', + 'dependencies': [ + 'network/compiled_resources2.gyp:*', + ], + }, + { + 'target_name': 'bluetooth_dialog', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/cr_dialog/compiled_resources2.gyp:cr_dialog', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + '<(DEPTH)/third_party/polymer/v1_0/components-chromium/iron-resizable-behavior/compiled_resources2.gyp:iron-resizable-behavior-extracted', + '<(DEPTH)/third_party/polymer/v1_0/components-chromium/paper-input/compiled_resources2.gyp:paper-input-extracted', + '<(EXTERNS_GYP):bluetooth', + '<(EXTERNS_GYP):bluetooth_private', + '<(INTERFACES_GYP):bluetooth_interface', + '<(INTERFACES_GYP):bluetooth_private_interface', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp b/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp new file mode 100644 index 00000000000..a18b88baaa0 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/compiled_resources2.gyp @@ -0,0 +1,63 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'network_apnlist', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_ip_config', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_nameservers', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_property_list', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_network_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_network_behavior', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy_input', + 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/chromeos/network/compiled_resources2.gyp:cr_onc_types', + '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:i18n_behavior', + ], + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { + 'target_name': 'network_proxy_exclusions', + 'includes': ['../../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + ], +} diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html new file mode 100644 index 00000000000..1a2c716e7ee --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.html @@ -0,0 +1,43 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="network_property_list.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-apnlist"> + <template> + <style include="network-shared md-select"> + paper-button { + margin: 4px 0; + } + </style> + <div class="property-box"> + <div class="start">[[i18n('networkAccessPoint')]]</div> + <div class="md-select-wrapper"> + <select id="selectApn" class="md-select" on-change="onSelectApnChange_" + value="[[selectedApn_]]" + aria-label="[[i18n('networkAccessPoint')]]"> + <template is="dom-repeat" items="[[apnSelectList_]]"> + <option value="[[item.AccessPointName]]">[[apnDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <div class="property-box single-column indented" + hidden$="[[!isOtherSelected_(selectedApn_, networkProperties)]]"> + <network-property-list on-property-change="onOtherApnChange_" + fields="[[otherApnFields_]]" property-dict="[[otherApn_]]" + edit-field-types="[[otherApnEditTypes_]]" prefix="Cellular.APN."> + </network-property-list> + <paper-button class="action-button" on-tap="onSaveOtherTap_"> + [[i18n('save')]] + </paper-button> + </div> + </template> + <script src="network_apnlist.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js new file mode 100644 index 00000000000..c4e5034e13d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_apnlist.js @@ -0,0 +1,271 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying and modifying a list of cellular + * access points. + */ +Polymer({ + is: 'network-apnlist', + + behaviors: [I18nBehavior], + + properties: { + /** + * The current set of properties for the network matching |guid|. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** + * The CrOnc.APNProperties.AccessPointName value of the selected APN. + * @private + */ + selectedApn_: { + type: String, + value: '', + }, + + /** + * Selectable list of APN dictionaries for the UI. Includes an entry + * corresponding to |otherApn| (see below). + * @private {!Array<!CrOnc.APNProperties>} + */ + apnSelectList_: { + type: Array, + value: function() { + return []; + } + }, + + /** + * The user settable properties for a new ('other') APN. The values for + * AccessPointName, Username, and Password will be set to the currently + * active APN if it does not match an existing list entry. + * @private {CrOnc.APNProperties|undefined} + */ + otherApn_: { + type: Object, + }, + + /** + * Array of property names to pass to the Other APN property list. + * @private {!Array<string>} + */ + otherApnFields_: { + type: Array, + value: function() { + return ['AccessPointName', 'Username', 'Password']; + }, + readOnly: true + }, + + /** + * Array of edit types to pass to the Other APN property list. + * @private + */ + otherApnEditTypes_: { + type: Object, + value: function() { + return { + 'AccessPointName': 'String', + 'Username': 'String', + 'Password': 'Password' + }; + }, + readOnly: true + }, + }, + + /** @const */ + DefaultAccessPointName: 'none', + + /** + * Polymer networkProperties changed method. + */ + networkPropertiesChanged_: function() { + if (!this.networkProperties || !this.networkProperties.Cellular) + return; + + /** @type {!CrOnc.APNProperties|undefined} */ var activeApn; + var cellular = this.networkProperties.Cellular; + /** @type {!chrome.networkingPrivate.ManagedAPNProperties|undefined} */ var + apn = cellular.APN; + if (apn && apn.AccessPointName) { + activeApn = /** @type {!CrOnc.APNProperties|undefined} */ ( + CrOnc.getSimpleActiveProperties(apn)); + } else if (cellular.LastGoodAPN && cellular.LastGoodAPN.AccessPointName) { + activeApn = cellular.LastGoodAPN; + } + this.setApnSelectList_(activeApn); + }, + + /** + * Sets the list of selectable APNs for the UI. Appends an 'Other' entry + * (see comments for |otherApn_| above). + * @param {CrOnc.APNProperties|undefined} activeApn The currently active APN + * properties. + * @private + */ + setApnSelectList_: function(activeApn) { + // Copy the list of APNs from this.networkProperties. + var result = this.getApnList_().slice(); + + // Test whether |activeApn| is in the current APN list in networkProperties. + var activeApnInList = activeApn && result.some(function(a) { + return a.AccessPointName == activeApn.AccessPointName; + }); + + // If |activeApn| is specified and not in the list, use the active + // properties for 'other'. Otherwise use any existing 'other' properties. + var otherApnProperties = + (activeApn && !activeApnInList) ? activeApn : this.otherApn_; + var otherApn = this.createApnObject_(otherApnProperties); + + // Always use 'Other' for the name of custom APN entries (the name does + // not get saved). + otherApn.Name = 'Other'; + + // If no 'active' or 'other' AccessPointName was provided, use the default. + otherApn.AccessPointName = + otherApn.AccessPointName || this.DefaultAccessPointName; + + // Save the 'other' properties. + this.otherApn_ = otherApn; + + // Append 'other' to the end of the list of APNs. + result.push(otherApn); + + this.apnSelectList_ = result; + // Set selectedApn_ after dom-repeat has been stamped. + this.async(() => { + this.selectedApn_ = + (activeApn && activeApn.AccessPointName) || otherApn.AccessPointName; + }); + }, + + /** + * @param {!CrOnc.APNProperties|undefined=} apnProperties + * @return {!CrOnc.APNProperties} A new APN object with properties from + * |apnProperties| if provided. + * @private + */ + createApnObject_: function(apnProperties) { + var newApn = {AccessPointName: ''}; + if (apnProperties) + Object.assign(newApn, apnProperties); + return newApn; + }, + + /** + * @return {!Array<!CrOnc.APNProperties>} The list of APN properties in + * |networkProperties| or an empty list if the property is not set. + * @private + */ + getApnList_: function() { + if (!this.networkProperties || !this.networkProperties.Cellular) + return []; + /** @type {!chrome.networkingPrivate.ManagedAPNList|undefined} */ var + apnlist = this.networkProperties.Cellular.APNList; + if (!apnlist) + return []; + return /** @type {!Array<!CrOnc.APNProperties>} */ ( + CrOnc.getActiveValue(apnlist)); + }, + + /** + * Event triggered when the selectApn selection changes. + * @param {!Event} event + * @private + */ + onSelectApnChange_: function(event) { + var target = /** @type {!HTMLSelectElement} */ (event.target); + var accessPointName = target.value; + // When selecting 'Other', don't set a change event unless a valid + // non-default value has been set for Other. + if (this.isOtherSelected_(accessPointName) && + (!this.otherApn_ || !this.otherApn_.AccessPointName || + this.otherApn_.AccessPointName == this.DefaultAccessPointName)) { + this.selectedApn_ = accessPointName; + return; + } + this.sendApnChange_(accessPointName); + }, + + /** + * Event triggered when any 'Other' APN network property changes. + * @param {!{detail: {field: string, value: string}}} event + * @private + */ + onOtherApnChange_: function(event) { + this.set('otherApn_.' + event.detail.field, event.detail.value); + // Don't send a change event for 'Other' until the 'Save' button is tapped. + }, + + /** + * Event triggered when the Other APN 'Save' button is tapped. + * @param {!Event} event + * @private + */ + onSaveOtherTap_: function(event) { + this.sendApnChange_(this.selectedApn_); + }, + + /** + * Send the apn-change event. + * @param {string} accessPointName + * @private + */ + sendApnChange_: function(accessPointName) { + var apnList = this.getApnList_(); + var apn = this.findApnInList(apnList, accessPointName); + if (apn == undefined) { + apn = this.createApnObject_(); + if (this.otherApn_) { + apn.AccessPointName = this.otherApn_.AccessPointName; + apn.Username = this.otherApn_.Username; + apn.Password = this.otherApn_.Password; + } + } + this.fire('apn-change', {field: 'APN', value: apn}); + }, + + /** + * @param {string} accessPointName + * @return {boolean} True if the 'other' APN is currently selected. + * @private + */ + isOtherSelected_: function(accessPointName) { + if (!this.networkProperties || !this.networkProperties.Cellular) + return false; + var apnList = this.getApnList_(); + var apn = this.findApnInList(apnList, accessPointName); + return apn == undefined; + }, + + /** + * @param {!CrOnc.APNProperties} apn + * @return {string} The most descriptive name for the access point. + * @private + */ + apnDesc_: function(apn) { + return apn.LocalizedName || apn.Name || apn.AccessPointName; + }, + + /** + * @param {!Array<!CrOnc.APNProperties>} apnList + * @param {string} accessPointName + * @return {CrOnc.APNProperties|undefined} The entry in |apnList| matching + * |accessPointName| if it exists, or undefined. + * @private + */ + findApnInList: function(apnList, accessPointName) { + return apnList.find(function(a) { + return a.AccessPointName == accessPointName; + }); + } +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html new file mode 100644 index 00000000000..8999e86a290 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.html @@ -0,0 +1,30 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> +<link rel="import" href="network_property_list.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-ip-config"> + <template> + <style include="network-shared iron-flex"></style> + <div class="property-box"> + <div id="autoIPConfigLabel" class="start"> + [[i18n('networkIPConfigAuto')]] + </div> + <paper-toggle-button checked="{{automatic_}}" disabled="[[!editable]]" + aria-labelledby="autoIPConfigLabel"> + </paper-toggle-button> + </div> + <div class="property-box single-column indented stretch" + hidden$="[[!ipConfig_]]"> + <network-property-list + fields="[[ipConfigFields_]]" property-dict="[[ipConfig_]]" + edit-field-types="[[getIPEditFields_(editable, automatic_)]]" + on-property-change="onIPChange_"> + </network-property-list> + </div> + </template> + <script src="network_ip_config.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js new file mode 100644 index 00000000000..519367b120a --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_ip_config.js @@ -0,0 +1,214 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying the IP Config properties for + * a network state. TODO(stevenjb): Allow editing of static IP configurations + * when 'editable' is true. + */ +Polymer({ + is: 'network-ip-config', + + behaviors: [I18nBehavior], + + properties: { + /** + * The network properties dictionary containing the IP Config properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** + * Whether or not the IP Address can be edited. + */ + editable: { + type: Boolean, + value: false, + }, + + /** + * State of 'Configure IP Addresses Automatically'. + * @private + */ + automatic_: { + type: Boolean, + value: true, + observer: 'automaticChanged_', + }, + + /** + * The currently visible IP Config property dictionary. The 'RoutingPrefix' + * property is a human-readable mask instead of a prefix length. + * @private {?{ + * ipv4: !CrOnc.IPConfigUIProperties, + * ipv6: (!CrOnc.IPConfigUIProperties|undefined) + * }} + */ + ipConfig_: { + type: Object, + value: null, + }, + + /** + * Array of properties to pass to the property list. + * @private {!Array<string>} + */ + ipConfigFields_: { + type: Array, + value: function() { + return [ + 'ipv4.IPAddress', + 'ipv4.RoutingPrefix', + 'ipv4.Gateway', + 'ipv6.IPAddress', + ]; + }, + readOnly: true + }, + }, + + /** + * Saved static IP configuration properties when switching to 'automatic'. + * @private {!CrOnc.IPConfigUIProperties|undefined} + */ + savedStaticIp_: undefined, + + /** + * Polymer networkProperties changed method. + */ + networkPropertiesChanged_: function(newValue, oldValue) { + if (!this.networkProperties) + return; + + var properties = this.networkProperties; + if (newValue.GUID != (oldValue && oldValue.GUID)) + this.savedStaticIp_ = undefined; + + // Update the 'automatic' property. + if (properties.IPAddressConfigType) { + var ipConfigType = CrOnc.getActiveValue(properties.IPAddressConfigType); + this.automatic_ = (ipConfigType != CrOnc.IPConfigType.STATIC); + } + + if (properties.IPConfigs || properties.StaticIPConfig) { + // Update the 'ipConfig' property. + var ipv4 = CrOnc.getIPConfigForType(properties, CrOnc.IPType.IPV4); + var ipv6 = CrOnc.getIPConfigForType(properties, CrOnc.IPType.IPV6); + this.ipConfig_ = { + ipv4: this.getIPConfigUIProperties_(ipv4), + ipv6: this.getIPConfigUIProperties_(ipv6) + }; + } else { + this.ipConfig_ = null; + } + }, + + /** @private */ + automaticChanged_: function() { + if (!this.automatic_) { + // Ensure that there is a valid IPConfig object. + this.ipConfig_ = this.ipConfig_ || { + ipv4: { + Gateway: '192.168.1.1', + IPAddress: '192.168.1.1', + RoutingPrefix: '255.255.255.0', + Type: CrOnc.IPType.IPV4, + }, + }; + this.sendStaticIpConfig_(); + return; + } + + // Save the static IP configuration when switching to automatic. + if (this.ipConfig_) + this.savedStaticIp_ = this.ipConfig_.ipv4; + // Send the change. + this.fire('ip-change', { + field: 'IPAddressConfigType', + value: CrOnc.IPConfigType.DHCP, + }); + }, + + /** + * @param {!CrOnc.IPConfigProperties|undefined} ipconfig + * @return {!CrOnc.IPConfigUIProperties} A new IPConfigUIProperties object + * with RoutingPrefix expressed as a string mask instead of a prefix + * length. Returns an empty object if |ipconfig| is undefined. + * @private + */ + getIPConfigUIProperties_: function(ipconfig) { + var result = {}; + if (!ipconfig) + return result; + for (var key in ipconfig) { + var value = ipconfig[key]; + if (key == 'RoutingPrefix') + result.RoutingPrefix = CrOnc.getRoutingPrefixAsNetmask(value); + else + result[key] = value; + } + return result; + }, + + /** + * @param {!CrOnc.IPConfigUIProperties} ipconfig The IP Config UI properties. + * @return {!CrOnc.IPConfigProperties} A new IPConfigProperties object with + * RoutingPrefix expressed as a a prefix length. + * @private + */ + getIPConfigProperties_: function(ipconfig) { + var result = {}; + for (var key in ipconfig) { + var value = ipconfig[key]; + if (key == 'RoutingPrefix') + result.RoutingPrefix = CrOnc.getRoutingPrefixAsLength(value); + else + result[key] = value; + } + return result; + }, + + /** + * @return {Object} An object with the edit type for each editable field. + * @private + */ + getIPEditFields_: function() { + if (!this.editable || this.automatic_) + return {}; + return { + 'ipv4.IPAddress': 'String', + 'ipv4.RoutingPrefix': 'String', + 'ipv4.Gateway': 'String' + }; + }, + + /** + * Event triggered when the network property list changes. + * @param {!{detail: {field: string, value: string}}} event The + * network-property-list change event. + * @private + */ + onIPChange_: function(event) { + if (!this.ipConfig_) + return; + var field = event.detail.field; + var value = event.detail.value; + // Note: |field| includes the 'ipv4.' prefix. + this.set('ipConfig_.' + field, value); + this.sendStaticIpConfig_(); + }, + + /** @private */ + sendStaticIpConfig_: function() { + // This will also set IPAddressConfigType to STATIC. + this.fire('ip-change', { + field: 'StaticIPConfig', + value: this.getIPConfigProperties_(this.ipConfig_.ipv4) + }); + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html new file mode 100644 index 00000000000..a64f6ca853d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.html @@ -0,0 +1,43 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-nameservers"> + <template> + <style include=" network-shared md-select"> + paper-input-container { + -webkit-padding-start: 4px; + } + </style> + + <div class="property-box"> + <div class="start">[[i18n('networkNameservers')]]</div> + <div class="md-select-wrapper"> + <select id="nameserverType" class="md-select" on-change="onTypeChange_" + value="[[nameserversType_]]" + aria-label="[[i18n('networkNameservers')]]"> + <template is="dom-repeat" items="[[nameserverTypeNames_]]"> + <option value="[[item]]">[[nameserverTypeDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <div class="property-box single-column indented" + hidden$="[[!nameservers_.length]]"> + <template is="dom-repeat" items="[[nameservers_]]"> + <paper-input-container no-label-float> + <input id="nameserver[[index]]" is="iron-input" value="[[item]]" + disabled="[[!canEdit_(editable, nameserversType_)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + </div> + </template> + <script src="network_nameservers.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js new file mode 100644 index 00000000000..237614403be --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js @@ -0,0 +1,214 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying network nameserver options. + */ +Polymer({ + is: 'network-nameservers', + + behaviors: [I18nBehavior], + + properties: { + /** + * The network properties dictionary containing the nameserver properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** Whether or not the nameservers can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** + * Array of nameserver addresses stored as strings. + * @private {!Array<string>} + */ + nameservers_: { + type: Array, + value: function() { + return []; + }, + }, + + /** + * The selected nameserver type. + * @private + */ + nameserversType_: { + type: String, + value: 'automatic', + }, + + /** + * Array of nameserver types. + * @private + */ + nameserverTypeNames_: { + type: Array, + value: ['automatic', 'google', 'custom'], + readOnly: true, + }, + }, + + /** @const */ + GOOGLE_NAMESERVERS: [ + '8.8.4.4', + '8.8.8.8', + ], + + /** @const */ + MAX_NAMESERVERS: 4, + + /** + * Saved nameservers when switching to 'automatic'. + * @private {!Array<string>} + */ + savedNameservers_: [], + + /** @private */ + networkPropertiesChanged_: function(newValue, oldValue) { + if (!this.networkProperties) + return; + + if (!oldValue || newValue.GUID != oldValue.GUID) + this.savedNameservers_ = []; + + // Update the 'nameservers' property. + var nameservers = []; + var ipv4 = + CrOnc.getIPConfigForType(this.networkProperties, CrOnc.IPType.IPV4); + if (ipv4 && ipv4.NameServers) + nameservers = ipv4.NameServers; + + // Update the 'nameserversType' property. + var configType = + CrOnc.getActiveValue(this.networkProperties.NameServersConfigType); + var type; + if (configType == CrOnc.IPConfigType.STATIC) { + if (nameservers.join(',') == this.GOOGLE_NAMESERVERS.join(',')) { + type = 'google'; + } else { + type = 'custom'; + } + } else { + type = 'automatic'; + } + this.setNameservers_(type, nameservers); + }, + + /** + * @param {string} nameserversType + * @param {!Array<string>} nameservers + * @private + */ + setNameservers_: function(nameserversType, nameservers) { + if (nameserversType == 'custom') { + // Add empty entries for unset custom nameservers. + for (var i = nameservers.length; i < this.MAX_NAMESERVERS; ++i) + nameservers[i] = ''; + } + this.nameservers_ = nameservers; + // Set nameserversType_ after dom-repeat has been stamped. + this.async(() => { + this.nameserversType_ = nameserversType; + }); + }, + + /** + * @param {string} type The nameservers type. + * @return {string} The description for |type|. + * @private + */ + nameserverTypeDesc_: function(type) { + // TODO(stevenjb): Translate. + if (type == 'custom') + return 'Custom name servers'; + if (type == 'google') + return 'Google name servers'; + return 'Automatic name servers'; + }, + + /** + * @param {boolean} editable + * @param {string} nameserversType + * @return {boolean} True if the nameservers are editable. + * @private + */ + canEdit_: function(editable, nameserversType) { + return editable && nameserversType == 'custom'; + }, + + /** + * Event triggered when the selected type changes. Updates nameservers and + * sends the change value if necessary. + * @param {!Event} event + * @private + */ + onTypeChange_: function(event) { + if (this.nameserversType_ == 'custom') + this.savedNameservers_ = this.nameservers_; + var target = /** @type {!HTMLSelectElement} */ (event.target); + var type = target.value; + this.nameserversType_ = type; + if (type == 'custom') { + // Restore the saved nameservers. + this.setNameservers_(type, this.savedNameservers_); + // Only send custom nameservers if they are not empty. + if (this.savedNameservers_.length == 0) + return; + } + this.sendNameServers_(); + }, + + /** + * Event triggered when a nameserver value changes. + * @private + */ + onValueChange_: function() { + if (this.nameserversType_ != 'custom') { + // If a user inputs Google nameservers in the custom nameservers fields, + // |nameserversType| will change to 'google' so don't send the values. + return; + } + this.sendNameServers_(); + }, + + /** + * Sends the current nameservers type (for automatic) or value. + * @private + */ + sendNameServers_: function() { + var type = this.nameserversType_; + + if (type == 'custom') { + var nameservers = new Array(this.MAX_NAMESERVERS); + for (var i = 0; i < this.MAX_NAMESERVERS; ++i) { + var nameserverInput = this.$$('#nameserver' + i); + nameservers[i] = nameserverInput ? nameserverInput.value : ''; + } + this.fire('nameservers-change', { + field: 'NameServers', + value: nameservers, + }); + } else if (type == 'google') { + this.fire('nameservers-change', { + field: 'NameServers', + value: this.GOOGLE_NAMESERVERS, + }); + } else { + // automatic + this.fire('nameservers-change', { + field: 'NameServersConfigType', + value: CrOnc.IPConfigType.DHCP, + }); + } + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html new file mode 100644 index 00000000000..0d73594c0af --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.html @@ -0,0 +1,71 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-property-list"> + <template> + <style include="network-shared iron-flex"> + paper-input-container { + margin-bottom: -12px; + margin-top: -8px; + } + + /* Property lists are embedded; remove the padding. */ + .property-box { + padding: 0; + } + + .secondary { + color: var(--paper-grey-600); + font-weight: 400; + } + + cr-policy-network-indicator { + -webkit-margin-start: var(--settings-controlled-by-spacing); + } + </style> + <template is="dom-repeat" items="[[fields]]" + filter="[[computeFilter_(prefix, propertyDict, editFieldTypes)]]"> + <div class="property-box single-column stretch"> + <!-- Property label --> + <div>[[getPropertyLabel_(item, prefix)]]</div> + <!-- Uneditable property value --> + <div class="layout horizontal" + hidden="[[isEditable_(item, '', propertyDict, editFieldTypes)]]"> + <div class="secondary"> + [[getPropertyValue_(item, prefix, propertyDict)]] + </div> + <cr-policy-network-indicator + property="[[getProperty_(item, propertyDict)]]"> + </cr-policy-network-indicator> + </div> + <!-- Editable String property value --> + <template is="dom-if" if="[[isEditable_( + item, 'String', propertyDict, editFieldTypes)]]"> + <paper-input-container no-label-float> + <input id="[[item]]" is="iron-input" + value="[[getPropertyValue_(item, prefix, propertyDict)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + <!-- Editable Password property value --> + <template is="dom-if" if="[[isEditable_( + item, 'Password', propertyDict, editFieldTypes)]]"> + <paper-input-container no-label-float> + <input id="[[item]]" is="iron-input" type="password" + value="[[getPropertyValue_(item, prefix, propertyDict)]]" + on-change="onValueChange_"> + </paper-input-container> + </template> + <!-- TODO(stevenjb): Support other types. --> + </div> + </template> + </template> + <script src="network_property_list.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js new file mode 100644 index 00000000000..f333386cb78 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_property_list.js @@ -0,0 +1,214 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying a list of network properties + * in a list. This also supports editing fields inline for fields listed in + * editFieldTypes. + */ +Polymer({ + is: 'network-property-list', + + behaviors: [I18nBehavior, CrPolicyNetworkBehavior], + + properties: { + /** + * The dictionary containing the properties to display. + * @type {!Object|undefined} + */ + propertyDict: {type: Object}, + + /** + * Fields to display. + * @type {!Array<string>} + */ + fields: { + type: Array, + value: function() { + return []; + }, + }, + + /** + * Edit type of editable fields. May contain a property for any field in + * |fields|. Other properties will be ignored. Property values can be: + * 'String' - A text input will be displayed. + * 'Password' - A string with input type = password. + * TODO(stevenjb): Support types with custom validation, e.g. IPAddress. + * TODO(stevenjb): Support 'Number'. + * When a field changes, the 'property-change' event will be fired with + * the field name and the new value provided in the event detail. + */ + editFieldTypes: { + type: Object, + value: function() { + return {}; + }, + }, + + /** Prefix used to look up property key translations. */ + prefix: { + type: String, + value: '', + }, + }, + + /** + * Event triggered when an input field changes. Fires a 'property-change' + * event with the field (property) name set to the target id, and the value + * set to the target input value. + * @param {!Event} event The input change event. + * @private + */ + onValueChange_: function(event) { + if (!this.propertyDict) + return; + var field = event.target.id; + var curValue = this.get(field, this.propertyDict); + if (typeof curValue == 'object') { + // Extract the property from an ONC managed dictionary. + curValue = CrOnc.getActiveValue( + /** @type {!CrOnc.ManagedProperty} */ (curValue)); + } + var newValue = event.target.value; + if (newValue == curValue) + return; + this.fire('property-change', {field: field, value: newValue}); + }, + + /** + * @param {string} key The property key. + * @param {string} prefix + * @return {string} The text to display for the property label. + * @private + */ + getPropertyLabel_: function(key, prefix) { + var oncKey = 'Onc' + prefix + key; + oncKey = oncKey.replace(/\./g, '-'); + if (this.i18nExists(oncKey)) + return this.i18n(oncKey); + // We do not provide translations for every possible network property key. + // For keys specific to a type, strip the type prefix. + var result = prefix + key; + for (var entry in chrome.networkingPrivate.NetworkType) { + var type = chrome.networkingPrivate.NetworkType[entry]; + if (result.startsWith(type + '.')) { + result = result.substr(type.length + 1); + break; + } + } + return result; + }, + + /** + * Generates a filter function dependent on propertyDict and editFieldTypes. + * @param {string} prefix + * @param {!Object} propertyDict + * @param {!Object} editFieldTypes + * @private + */ + computeFilter_: function(prefix, propertyDict, editFieldTypes) { + return key => { + if (editFieldTypes.hasOwnProperty(key)) + return true; + var value = this.getPropertyValue_(key, prefix, propertyDict); + return value !== undefined && value !== ''; + }; + }, + + /** + * @param {string} key The property key. + * @param {string} type The field type. + * @param {!Object} propertyDict + * @param {!Object} editFieldTypes + * @return {boolean} + * @private + */ + isEditable_: function(key, type, propertyDict, editFieldTypes) { + var property = /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get(key, propertyDict)); + if (this.isNetworkPolicyEnforced(property)) + return false; + var editType = editFieldTypes[key]; + return editType !== undefined && (type == '' || editType == type); + }, + + /** + * @param {string} key The property key. + * @param {!Object} propertyDict + * @return {*} The managed property dictionary associated with |key|. + * @private + */ + getProperty_: function(key, propertyDict) { + return this.get(key, propertyDict); + }, + + /** + * @param {string} key The property key. + * @param {string} prefix + * @param {!Object} propertyDict + * @return {string} The text to display for the property value. + * @private + */ + getPropertyValue_: function(key, prefix, propertyDict) { + var value = this.get(key, propertyDict); + if (value === undefined) + return ''; + if (typeof value == 'object') { + // Extract the property from an ONC managed dictionary + value = + CrOnc.getActiveValue(/** @type {!CrOnc.ManagedProperty} */ (value)); + } + var customValue = this.getCustomPropertyValue_(key, value); + if (customValue) + return customValue; + if (typeof value == 'number' || typeof value == 'boolean') + return value.toString(); + assert(typeof value == 'string'); + var valueStr = /** @type {string} */ (value); + var oncKey = 'Onc' + prefix + key; + oncKey = oncKey.replace(/\./g, '-'); + oncKey += '_' + valueStr; + if (this.i18nExists(oncKey)) + return this.i18n(oncKey); + return valueStr; + }, + + /** + * @param {string} key The property key. + * @param {*} value The property value. + * @return {string} The text to display for the property value. If the key + * does not correspond to a custom property, an empty string is returned. + */ + getCustomPropertyValue_: function(key, value) { + if (key == 'Tether.BatteryPercentage') { + assert(typeof value == 'number'); + return this.i18n('OncTether-BatteryPercentage_Value', value.toString()); + } + + if (key == 'Tether.SignalStrength') { + assert(typeof value == 'number'); + // Possible |signalStrength| values should be 0, 25, 50, 75, and 100. Add + // <= checks for robustness. + if (value <= 24) + return this.i18n('OncTether-SignalStrength_Weak'); + if (value <= 49) + return this.i18n('OncTether-SignalStrength_Okay'); + if (value <= 74) + return this.i18n('OncTether-SignalStrength_Good'); + if (value <= 99) + return this.i18n('OncTether-SignalStrength_Strong'); + return this.i18n('OncTether-SignalStrength_VeryStrong'); + } + + if (key == 'Tether.Carrier') { + assert(typeof value == 'string'); + return (!value || value == 'unknown-carrier') ? + this.i18n('tetherUnknownCarrier') : + value; + } + + return ''; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html new file mode 100644 index 00000000000..4b328f9145b --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.html @@ -0,0 +1,154 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior.html"> +<link rel="import" href="chrome://resources/html/assert.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/html/md_select_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys/iron-a11y-keys.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> +<link rel="import" href="network_proxy_exclusions.html"> +<link rel="import" href="network_proxy_input.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy"> + <template> + <style include="network-shared md-select cr-hidden-style iron-flex iron-flex-alignment"> + network-proxy-input { + margin-bottom: 10px; + } + + network-proxy-exclusions { + margin: 10px 0; + } + + #manualProxy { + -webkit-padding-start: var(--cr-section-padding); + } + </style> + + <!-- Proxy type dropdown --> + <div class="property-box"> + <div class="start">[[i18n('networkProxyConnectionType')]]</div> + <div class="md-select-wrapper"> + <select id="proxyType" class="md-select" on-change="onTypeChange_" + value="[[proxy_.Type]]" + disabled="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]" + aria-label="[[i18n('networkProxyConnectionType')]]"> + <template is="dom-repeat" items="[[proxyTypes_]]"> + <option value="[[item]]">[[getProxyTypeDesc_(item)]]</option> + </template> + </select> + <span class="md-select-underline"></span> + </div> + </div> + + <!-- Autoconfiguration (PAC) --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.PAC)]]"> + <div>[[i18n('networkProxyAutoConfig')]]</div> + <paper-input no-label-float class="middle" value="{{proxy_.PAC}}" + disabled="[[!isEditable_('PAC', networkProperties, editable, + useSharedProxies)]]" + on-change="onPACChange_"> + </paper-input> + </div> + + <!-- Web Proxy Auto Discovery (WPAD) --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.WPAD)]]"> + <div>[[i18n('networkProxyWpad')]]</div> + <div class="middle">[[WPAD_]]</div> + </div> + + <!-- Manual --> + <div class="property-box indented" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.MANUAL)]]"> + <div id="networkProxyToggleLabel" class="flex"> + [[i18n('networkProxyUseSame')]] + </div> + <paper-toggle-button checked="{{useSameProxy_}}" + disabled="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]" + aria-labelledby="networkProxyToggleLabel"> + </paper-toggle-button> + </div> + + <div id="manualProxy" class="layout vertical start" + hidden$="[[!matches_(proxy_.Type, ProxySettingsType_.MANUAL)]]"> + <div hidden$="[[!useSameProxy_]]" class="layout vertical"> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.HTTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.HTTPProxy}}" + label="[[i18n('networkProxy')]]"> + </network-proxy-input> + </div> + <div hidden$="[[useSameProxy_]]" class="layout vertical"> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.HTTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.HTTPProxy}}" + label="[[i18n('networkProxyHttp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.SecureHTTPProxy.Host', + networkProperties, editable, useSharedProxies)]]" + value="{{proxy_.Manual.SecureHTTPProxy}}" + label="[[i18n('networkProxyShttp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.FTPProxy.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.FTPProxy}}" + label="[[i18n('networkProxyFtp')]]"> + </network-proxy-input> + <network-proxy-input + on-proxy-change="onProxyInputChange_" + editable="[[isEditable_('Manual.SOCKS.Host', networkProperties, + editable, useSharedProxies)]]" + value="{{proxy_.Manual.SOCKS}}" + label="[[i18n('networkProxySocks')]]"> + </network-proxy-input> + </div> + + <div hidden="[[!isEditable_('Type', networkProperties, editable, + useSharedProxies)]]"> + <div>[[i18n('networkProxyExceptionList')]]</div> + <network-proxy-exclusions on-proxy-change="onProxyExclusionsChange_" + exclusions="{{proxy_.ExcludeDomains}}"> + </network-proxy-exclusions> + <div class="layout horizontal"> + <paper-input-container no-label-float class="flex"> + <input id="proxyExclusion" is="iron-input"> + <iron-a11y-keys keys="enter" + on-keys-pressed="onAddProxyExclusionTap_"> + </iron-a11y-keys> + </paper-input-container> + <paper-button on-tap="onAddProxyExclusionTap_"> + [[i18n('networkProxyAddException')]] + </paper-button> + </div> + </div> + + <paper-button id="saveManualProxy" + on-tap="onSaveProxyTap_" class="action-button" + disabled="[[!isSaveManualProxyEnabled_(networkProperties, + proxyModified_, proxy_.*)]]"> + [[i18n('save')]] + </paper-button> + </div> + + </template> + <script src="network_proxy.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js new file mode 100644 index 00000000000..a1f2056eba8 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy.js @@ -0,0 +1,428 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying and editing network proxy + * values. + */ +Polymer({ + is: 'network-proxy', + + behaviors: [ + CrPolicyNetworkBehavior, + I18nBehavior, + ], + + properties: { + /** + * The network properties dictionary containing the proxy properties to + * display and modify. + * @type {!CrOnc.NetworkProperties|undefined} + */ + networkProperties: { + type: Object, + observer: 'networkPropertiesChanged_', + }, + + /** Whether or not the proxy values can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** Whether shared proxies are allowed. */ + useSharedProxies: { + type: Boolean, + value: false, + observer: 'updateProxy_', + }, + + /** + * UI visible / edited proxy configuration. + * @private {!CrOnc.ProxySettings} + */ + proxy_: { + type: Object, + value: function() { + return this.createDefaultProxySettings_(); + }, + }, + + /** + * The Web Proxy Auto Discovery URL extracted from networkProperties. + * @private + */ + WPAD_: { + type: String, + value: '', + }, + + /** + * Whether or not to use the same manual proxy for all protocols. + * @private + */ + useSameProxy_: { + type: Boolean, + value: false, + observer: 'useSameProxyChanged_', + }, + + /** + * Array of proxy configuration types. + * @private {!Array<string>} + * @const + */ + proxyTypes_: { + type: Array, + value: [ + CrOnc.ProxySettingsType.DIRECT, + CrOnc.ProxySettingsType.PAC, + CrOnc.ProxySettingsType.WPAD, + CrOnc.ProxySettingsType.MANUAL, + ], + readOnly: true + }, + + /** + * Object providing proxy type values for data binding. + * @private {!Object} + * @const + */ + ProxySettingsType_: { + type: Object, + value: { + DIRECT: CrOnc.ProxySettingsType.DIRECT, + PAC: CrOnc.ProxySettingsType.PAC, + MANUAL: CrOnc.ProxySettingsType.MANUAL, + WPAD: CrOnc.ProxySettingsType.WPAD, + }, + readOnly: true, + }, + }, + + /** + * Saved Manual properties so that switching to another type does not loose + * any set properties while the UI is open. + * @private {!CrOnc.ManualProxySettings|undefined} + */ + savedManual_: undefined, + + /** + * Saved ExcludeDomains properties so that switching to a non-Manual type does + * not loose any set exclusions while the UI is open. + * @private {!Array<string>|undefined} + */ + savedExcludeDomains_: undefined, + + /** + * Set to true while modifying proxy values so that an update does not + * override the edited values. + * @private {boolean} + */ + proxyModified_: false, + + /** @override */ + attached: function() { + this.reset(); + }, + + /** + * Called any time the page is refreshed or navigated to so that the proxy + * is updated correctly. + */ + reset: function() { + this.proxyModified_ = false; + this.proxy_ = this.createDefaultProxySettings_(); + this.updateProxy_(); + }, + + /** @private */ + networkPropertiesChanged_: function() { + if (this.proxyModified_) + return; // Ignore update. + this.updateProxy_(); + }, + + /** @private */ + updateProxy_: function() { + if (!this.networkProperties) + return; + + /** @type {!CrOnc.ProxySettings} */ + var proxy = this.createDefaultProxySettings_(); + + // For shared networks with unmanaged proxy settings, ignore any saved + // proxy settings (use the default values). + if (this.isShared_()) { + var property = this.getProxySettingsTypeProperty_(); + if (!this.isControlled(property) && !this.useSharedProxies) { + this.setProxyAsync_(proxy); + return; // Proxy settings will be ignored. + } + } + + /** @type {!chrome.networkingPrivate.ManagedProxySettings|undefined} */ + var proxySettings = this.networkProperties.ProxySettings; + if (proxySettings) { + proxy.Type = /** @type {!CrOnc.ProxySettingsType} */ ( + CrOnc.getActiveValue(proxySettings.Type)); + if (proxySettings.Manual) { + proxy.Manual.HTTPProxy = /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.HTTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.SecureHTTPProxy = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.SecureHTTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.FTPProxy = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties( + proxySettings.Manual.FTPProxy)) || + {Host: '', Port: 80}; + proxy.Manual.SOCKS = + /** @type {!CrOnc.ProxyLocation|undefined} */ ( + CrOnc.getSimpleActiveProperties(proxySettings.Manual.SOCKS)) || + {Host: '', Port: 80}; + var jsonHttp = proxy.Manual.HTTPProxy; + this.useSameProxy_ = + (CrOnc.proxyMatches(jsonHttp, proxy.Manual.SecureHTTPProxy) && + CrOnc.proxyMatches(jsonHttp, proxy.Manual.FTPProxy) && + CrOnc.proxyMatches(jsonHttp, proxy.Manual.SOCKS)) || + (!proxy.Manual.SecureHTTPProxy.Host && + !proxy.Manual.FTPProxy.Host && !proxy.Manual.SOCKS.Host); + } + if (proxySettings.ExcludeDomains) { + proxy.ExcludeDomains = /** @type {!Array<string>|undefined} */ ( + CrOnc.getActiveValue(proxySettings.ExcludeDomains)); + } + proxy.PAC = /** @type {string|undefined} */ ( + CrOnc.getActiveValue(proxySettings.PAC)); + } + // Use saved ExcludeDomains and Manual if not defined. + proxy.ExcludeDomains = proxy.ExcludeDomains || this.savedExcludeDomains_; + proxy.Manual = proxy.Manual || this.savedManual_; + + // Set the Web Proxy Auto Discovery URL. + var ipv4 = + CrOnc.getIPConfigForType(this.networkProperties, CrOnc.IPType.IPV4); + this.WPAD_ = (ipv4 && ipv4.WebProxyAutoDiscoveryUrl) || ''; + + this.setProxyAsync_(proxy); + }, + + /** + * @param {!CrOnc.ProxySettings} proxy + * @private + */ + setProxyAsync_: function(proxy) { + // Set this.proxy_ after dom-repeat has been stamped. + this.async(() => { + this.proxy_ = proxy; + this.proxyModified_ = false; + }); + }, + + /** @private */ + useSameProxyChanged_: function() { + this.proxyModified_ = true; + }, + + /** + * @return {CrOnc.ProxySettings} An empty/default proxy settings object. + * @private + */ + createDefaultProxySettings_: function() { + return { + Type: CrOnc.ProxySettingsType.DIRECT, + ExcludeDomains: [], + Manual: { + HTTPProxy: {Host: '', Port: 80}, + SecureHTTPProxy: {Host: '', Port: 80}, + FTPProxy: {Host: '', Port: 80}, + SOCKS: {Host: '', Port: 1080} + }, + PAC: '' + }; + }, + + /** + * Called when the proxy changes in the UI. + * @private + */ + sendProxyChange_: function() { + var proxy = + /** @type {!CrOnc.ProxySettings} */ (Object.assign({}, this.proxy_)); + if (proxy.Type == CrOnc.ProxySettingsType.MANUAL) { + var manual = proxy.Manual; + var defaultProxy = manual.HTTPProxy || {Host: '', Port: 80}; + if (this.useSameProxy_) { + proxy.Manual.SecureHTTPProxy = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + proxy.Manual.FTPProxy = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + proxy.Manual.SOCKS = /** @type {!CrOnc.ProxyLocation} */ ( + Object.assign({}, defaultProxy)); + } else { + // Remove properties with empty hosts to unset them. + if (manual.HTTPProxy && !manual.HTTPProxy.Host) + delete manual.HTTPProxy; + if (manual.SecureHTTPProxy && !manual.SecureHTTPProxy.Host) + delete manual.SecureHTTPProxy; + if (manual.FTPProxy && !manual.FTPProxy.Host) + delete manual.FTPProxy; + if (manual.SOCKS && !manual.SOCKS.Host) + delete manual.SOCKS; + } + this.savedManual_ = Object.assign({}, manual); + this.savedExcludeDomains_ = proxy.ExcludeDomains; + } else if (proxy.Type == CrOnc.ProxySettingsType.PAC) { + if (!proxy.PAC) + return; + } + this.fire('proxy-change', {field: 'ProxySettings', value: proxy}); + this.proxyModified_ = false; + }, + + /** + * Event triggered when the selected proxy type changes. + * @param {!Event} event + * @private + */ + onTypeChange_: function(event) { + var target = /** @type {!HTMLSelectElement} */ (event.target); + var type = /** @type {chrome.networkingPrivate.ProxySettingsType} */ ( + target.value); + this.set('proxy_.Type', type); + if (type == CrOnc.ProxySettingsType.MANUAL) + this.proxyModified_ = true; + else + this.sendProxyChange_(); + }, + + /** @private */ + onPACChange_: function() { + this.sendProxyChange_(); + }, + + /** @private */ + onProxyInputChange_: function() { + this.proxyModified_ = true; + }, + + /** + * Event triggered when a proxy exclusion is added. + * @param {!Event} event The add proxy exclusion event. + * @private + */ + onAddProxyExclusionTap_: function(event) { + var value = this.$.proxyExclusion.value; + if (!value) + return; + this.push('proxy_.ExcludeDomains', value); + // Clear input. + this.$.proxyExclusion.value = ''; + this.proxyModified_ = true; + }, + + /** + * Event triggered when the proxy exclusion list changes. + * @param {!Event} event The remove proxy exclusions change event. + * @private + */ + onProxyExclusionsChange_: function(event) { + this.proxyModified_ = true; + }, + + /** @private */ + onSaveProxyTap_: function() { + this.sendProxyChange_(); + }, + + /** + * @param {string} proxyType The proxy type. + * @return {string} The description for |proxyType|. + * @private + */ + getProxyTypeDesc_: function(proxyType) { + if (proxyType == CrOnc.ProxySettingsType.MANUAL) + return this.i18n('networkProxyTypeManual'); + if (proxyType == CrOnc.ProxySettingsType.PAC) + return this.i18n('networkProxyTypePac'); + if (proxyType == CrOnc.ProxySettingsType.WPAD) + return this.i18n('networkProxyTypeWpad'); + return this.i18n('networkProxyTypeDirect'); + }, + + /** + * @return {!CrOnc.ManagedProperty|undefined} + * @private + */ + getProxySettingsTypeProperty_: function() { + return /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get('ProxySettings.Type', this.networkProperties)); + }, + + /** + * @param {string} propertyName + * @return {boolean} Whether the named property setting is editable. + * @private + */ + isEditable_: function(propertyName) { + if (!this.editable || (this.isShared_() && !this.useSharedProxies)) + return false; + if (!this.networkProperties.hasOwnProperty('ProxySettings')) + return true; // No proxy settings defined, so not enforced. + var property = /** @type {!CrOnc.ManagedProperty|undefined} */ ( + this.get('ProxySettings.' + propertyName, this.networkProperties)); + if (!property) + return true; + return this.isPropertyEditable_(property); + }, + + /** + * @param {!CrOnc.ManagedProperty|undefined} property + * @return {boolean} Whether |property| is editable. + * @private + */ + isPropertyEditable_: function(property) { + return !this.isNetworkPolicyEnforced(property) && + !this.isExtensionControlled(property); + }, + + /** + * @return {boolean} + * @private + */ + isShared_: function() { + return this.networkProperties.Source == 'Device' || + this.networkProperties.Source == 'DevicePolicy'; + }, + + /** + * @return {boolean} + * @private + */ + isSaveManualProxyEnabled_: function() { + if (!this.proxyModified_) + return false; + var manual = this.proxy_.Manual; + var httpHost = this.get('HTTPProxy.Host', manual); + if (this.useSameProxy_) + return !!httpHost; + return !!httpHost || !!this.get('SecureHTTPProxy.Host', manual) || + !!this.get('FTPProxy.Host', manual) || !!this.get('SOCKS.Host', manual); + }, + + /** + * @param {string} property The property to test + * @param {string} value The value to test against + * @return {boolean} True if property == value + * @private + */ + matches_: function(property, value) { + return property == value; + }, +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html new file mode 100644 index 00000000000..f574252c285 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.html @@ -0,0 +1,37 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> +<link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy-exclusions"> + <template> + <style include="network-shared cr-hidden-style iron-flex"> + iron-icon { + @apply(--cr-actionable); + margin: 5px; + } + + #container { + align-self: stretch; + border: 1px solid lightgrey; + height: 100px; + margin-top: 10px; + overflow-y: auto; + padding: 5px; + } + </style> + <div id="container"> + <template is="dom-repeat" items="[[exclusions]]"> + <div class="layout horizontal center" tabindex="0" > + <div class="flex">[[item]]</div> + <iron-icon class="favicon-image" icon="cr:clear" + on-tap="onRemoveTap_" tabindex="0" hidden="[[!editable]]"> + </iron-icon> + </div> + </template> + </div> + </template> + <script src="network_proxy_exclusions.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js new file mode 100644 index 00000000000..347b22980d3 --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_exclusions.js @@ -0,0 +1,46 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying a list of proxy exclusions. + * Includes UI for adding, changing, and removing entries. + */ + +(function() { + +Polymer({ + is: 'network-proxy-exclusions', + + properties: { + /** Whether or not the proxy values can be edited. */ + editable: { + type: Boolean, + value: false, + }, + + /** + * The list of exclusions. + * @type {!Array<string>} + */ + exclusions: { + type: Array, + value: function() { + return []; + }, + notify: true + } + }, + + /** + * Event triggered when an item is removed. + * @param {!{model: !{index: number}}} event + * @private + */ + onRemoveTap_: function(event) { + var index = event.model.index; + this.splice('exclusions', index, 1); + this.fire('proxy-change'); + } +}); +})(); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html new file mode 100644 index 00000000000..a1104bde59f --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.html @@ -0,0 +1,50 @@ +<link rel="import" href="chrome://resources/html/polymer.html"> + +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-input/iron-input.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input-container.html"> +<link rel="import" href="network_shared_css.html"> + +<dom-module id="network-proxy-input"> + <template> + <style include="network-shared"> + paper-input-container { + -webkit-margin-start: 10px; + margin-bottom: -12px; + margin-top: -8px; + } + + #container { + align-items: center; + display: flex; + flex: 0 1 auto; + flex-direction: row; + } + + #label { + flex: 1; + } + + #host { + width: 200px; + } + + #port { + width: 50px; + } + </style> + <div id="container"> + <div id="label">[[label]]</div> + <paper-input-container id="host" no-label-float> + <input is="iron-input" bind-value="{{value.Host}}" + disabled="[[!editable]]" on-change="onValueChange_"> + </paper-input-container> + <div>[[i18n('networkProxyPort')]]</div> + <paper-input-container id="port" no-label-float> + <input is="iron-input" bind-value="{{value.Port}}" + disabled="[[!editable]]" on-change="onValueChange_"> + </paper-input-container> + </div> + </template> + <script src="network_proxy_input.js"></script> +</dom-module> diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js new file mode 100644 index 00000000000..63609b074ce --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_proxy_input.js @@ -0,0 +1,57 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying and editing a single + * network proxy value. When the URL or port changes, a 'proxy-change' event is + * fired with the combined url and port values passed as a single string, + * url:port. + */ +Polymer({ + is: 'network-proxy-input', + + behaviors: [I18nBehavior], + + properties: { + /** + * Whether or not the proxy value can be edited. + */ + editable: { + type: Boolean, + value: false, + }, + + /** + * A label for the proxy value. + */ + label: { + type: String, + value: 'Proxy', + }, + + /** + * The proxy object. + * @type {!CrOnc.ProxyLocation} + */ + value: { + type: Object, + value: function() { + return {Host: '', Port: 80}; + }, + notify: true, + }, + }, + + /** + * Event triggered when an input value changes. + * @private + */ + onValueChange_: function() { + var port = parseInt(this.value.Port, 10); + if (isNaN(port)) + port = 80; + this.value.Port = port; + this.fire('proxy-change', {value: this.value}); + } +}); diff --git a/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html b/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html new file mode 100644 index 00000000000..c235c50288d --- /dev/null +++ b/chromium/ui/webui/resources/cr_components/chromeos/network/network_shared_css.html @@ -0,0 +1,51 @@ +<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> + + <!-- Common styles for network elements. --> + +<dom-module id="network-shared"> + <template> + <style include="cr-shared-style"> + .property-box { + @apply(--cr-section); + border-top: none; + min-height: var(--cr-section-two-line-min-height); + padding: 0; + } + + .property-box.indented { + -webkit-margin-start: var(--cr-section-padding); + } + + .property-box.single-column { + align-items: flex-start; + flex-direction: column; + justify-content: center; + } + + .property-box.stretch { + align-items: stretch; + } + + .property-box > .start { + align-items: center; + flex: auto; + } + + .property-box > .middle { + -webkit-padding-start: 16px; + align-items: center; + flex: auto; + } + + paper-input-container { + --paper-input-container-input: { + color: var(--paper-grey-600); + font-size: inherit; + font-weight: 400; + }; + margin-bottom: 0; + margin-top: -9px; + } + </style> + </template> +</dom-module> |